diff --git a/AUTHORS b/AUTHORS
index 8f704e3..759394b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -422,6 +422,7 @@
 Jin Yang <jin.a.yang@intel.com>
 Jincheol Jo <jincheol.jo@navercorp.com>
 Jing Zhao <zhaojing7@xiaomi.com>
+Jinglong Zuo <zuojinglong@xiaomi.com>
 Jingwei Liu <kingweiliu@gmail.com>
 Jingyi Wei <wjywbs@gmail.com>
 Jinho Bang <jinho.bang@samsung.com>
diff --git a/DEPS b/DEPS
index dea070f..24b2bd27 100644
--- a/DEPS
+++ b/DEPS
@@ -102,9 +102,14 @@
   # Default to the empty board. Desktop Chrome OS builds don't need cros SDK
   # dependencies. Other Chrome OS builds should always define this explicitly.
   'cros_board': '',
+  # Building for CrOS is only supported on linux currently.
+  'checkout_simplechrome': '(checkout_chromeos and host_os == "linux") and ("{cros_board}" != "")',
   # Surround the board var in quotes so gclient doesn't try parsing the string
   # as an expression.
   'cros_download_vm': '"{cros_board}" == "amd64-generic"',
+  # Should we build and test for public (ie: full) CrOS images, or private
+  # (ie: release) images.
+  'use_public_cros_config': 'not checkout_src_internal',
 
   # ANGLE's deps are relative to the angle_root variable.
   'angle_root': 'src/third_party/angle',
@@ -133,11 +138,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': 'e700738f3c8f3eebb613eb0cb7312a13468a13bf',
+  'skia_revision': 'c19999801df0ca538e8576b353177e76b6892512',
   # 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': '2de3cf380f29310984f58671153d98c4066adb0f',
+  'v8_revision': 'a839a8787787b39289c5d496e15d3514a1ac5e43',
   # 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.
@@ -145,7 +150,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': 'b86e73daf7db3f0b7ee08a49829a96a7217f71c0',
+  'angle_revision': 'fb8e1b25ad7165591b9df6a1190316610bd6476b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -168,7 +173,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling googletest
   # and whatever else without interference from each other.
-  'googletest_revision': '9d4cde44a4a3952cf21861f9370b3bed9265dfd7',
+  'googletest_revision': 'f71fb4f9a912ec945401cc49a287a759b6131026',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling lighttpd
   # and whatever else without interference from each other.
@@ -196,7 +201,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': 'ea6b999d4bba12c46fb69a6b4ddd448cc238213e',
+  'catapult_revision': '535dc1d8e2b7372951dfeb8e57f592bf0c823870',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -802,7 +807,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '21b95024e051a0a0224739b7dcc9268c84b3eadd',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '4a0f5098c4db35c9b4be1fbbf356c37e8e4c296f',
       'condition': 'checkout_linux',
   },
 
@@ -827,7 +832,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd6bf517dd4f5e899d79b865e671353b4ce616f6e',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'aca5b6aca87dd7872c457ae6589902876556c854',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1177,7 +1182,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '945361d2c8963b58a27c2a81ce18ae5cf9d909b7',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '1ef91c971fde3aa0ee292c425e8e8324a9f29228',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1345,7 +1350,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6f0b34abee8dba611c253738d955c59f703c147a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'd703cd022f64ddb6225b08719c2e40d1e1aa997a',
+    Var('webrtc_git') + '/src.git' + '@' + '9c91887c3f47b037a412f045bee1fbf0302dd4ae',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1386,7 +1391,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8b6de23af332aa73e5e921fbedf2f4ac8be0512d',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@56355ce2d52f4caabe893932ee3970ef0fb03ffe',
     'condition': 'checkout_src_internal',
   },
 
@@ -2900,13 +2905,13 @@
     ],
   },
 
-  # Download CrOS simplechrome artifacts. The first hooks is for boards that
-  # support VM images, the second hook for all other boards.
+  # Download public CrOS simplechrome artifacts. The first hooks is for boards
+  # that support VM images, the second hook for all other boards. For internal
+  # boards, see src-internal's DEPS.
   {
     'name': 'cros_simplechrome_artifacts_with_vm',
     'pattern': '.',
-    # Building for CrOS is only supported on linux currently.
-    'condition': '((checkout_chromeos and host_os == "linux") and cros_download_vm) and ("{cros_board}" != "")',
+    'condition': '(checkout_simplechrome and cros_download_vm) and use_public_cros_config',
     'action': [
       'src/third_party/chromite/bin/cros',
       'chrome-sdk',
@@ -2927,8 +2932,7 @@
   {
     'name': 'cros_simplechrome_artifacts_with_no_vm',
     'pattern': '.',
-    # Building for CrOS is only supported on linux currently.
-    'condition': '((checkout_chromeos and host_os == "linux") and not cros_download_vm) and ("{cros_board}" != "")',
+    'condition': '(checkout_simplechrome and not cros_download_vm) and use_public_cros_config',
     'action': [
       'src/third_party/chromite/bin/cros',
       'chrome-sdk',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 7a685ad..5415b00 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -228,6 +228,250 @@
     ),
 )
 
+# Directories that contain deprecated Bind() or Callback types.
+# Find sub-directories from a given directory by running:
+# for i in `find . -maxdepth 1 -type d`; do
+#   echo "-- $i"
+#   (cd $i; git grep -P 'base::(Bind\(|(Callback<|Closure))'|wc -l)
+# done
+#
+# TODO(crbug.com/714018): Remove (or narrow the scope of) paths from this list
+# when they have been converted to modern callback types (OnceCallback,
+# RepeatingCallback, BindOnce, BindRepeating) in order to enable presubmit
+# checks for them and prevent regressions.
+_NOT_CONVERTED_TO_MODERN_BIND_AND_CALLBACK = '|'.join((
+  '^android_webview/browser/',
+  '^apps/',
+  '^ash/',
+  '^base/',
+  '^base/callback.h',  # Intentional.
+  '^chrome/app/',
+  '^chrome/browser/',
+  '^chrome/chrome_elf/',
+  '^chrome/chrome_watcher/',
+  '^chrome/common/',
+  '^chrome/installer/',
+  '^chrome/notification_helper/',
+  '^chrome/renderer/',
+  '^chrome/services/',
+  '^chrome/test/',
+  '^chrome/tools/',
+  '^chrome/utility/',
+  '^chromecast/app/',
+  '^chromecast/browser/',
+  '^chromecast/crash/',
+  '^chromecast/media/',
+  '^chromecast/metrics/',
+  '^chromecast/net/',
+  '^chromeos/attestation/',
+  '^chromeos/audio/',
+  '^chromeos/components/',
+  '^chromeos/cryptohome/',
+  '^chromeos/dbus/',
+  '^chromeos/geolocation/',
+  '^chromeos/login/',
+  '^chromeos/network/',
+  '^chromeos/printing/',
+  '^chromeos/process_proxy/',
+  '^chromeos/services/',
+  '^chromeos/settings/',
+  '^chromeos/timezone/',
+  '^chromeos/tpm/',
+  '^components/arc/',
+  '^components/assist_ranker/',
+  '^components/autofill/',
+  '^components/autofill_assistant/',
+  '^components/bookmarks/',
+  '^components/browser_sync/',
+  '^components/browser_watcher/',
+  '^components/browsing_data/',
+  '^components/cast_channel/',
+  '^components/certificate_transparency/',
+  '^components/chromeos_camera/',
+  '^components/component_updater/',
+  '^components/content_settings/',
+  '^components/crash/',
+  '^components/cronet/',
+  '^components/data_reduction_proxy/',
+  '^components/discardable_memory/',
+  '^components/dom_distiller/',
+  '^components/domain_reliability/',
+  '^components/download/',
+  '^components/drive/',
+  '^components/exo/',
+  '^components/favicon/',
+  '^components/feature_engagement/',
+  '^components/feedback/',
+  '^components/flags_ui/',
+  '^components/gcm_driver/',
+  '^components/google/',
+  '^components/guest_view/',
+  '^components/heap_profiling/',
+  '^components/history/',
+  '^components/image_fetcher/',
+  '^components/invalidation/',
+  '^components/keyed_service/',
+  '^components/login/',
+  '^components/metrics/',
+  '^components/metrics_services_manager/',
+  '^components/nacl/',
+  '^components/navigation_interception/',
+  '^components/net_log/',
+  '^components/network_time/',
+  '^components/ntp_snippets/',
+  '^components/ntp_tiles/',
+  '^components/offline_items_collection/',
+  '^components/offline_pages/',
+  '^components/omnibox/',
+  '^components/ownership/',
+  '^components/pairing/',
+  '^components/password_manager/',
+  '^components/payments/',
+  '^components/plugins/',
+  '^components/policy/',
+  '^components/pref_registry/',
+  '^components/prefs/',
+  '^components/printing/',
+  '^components/proxy_config/',
+  '^components/quirks/',
+  '^components/rappor/',
+  '^components/remote_cocoa/',
+  '^components/renderer_context_menu/',
+  '^components/rlz/',
+  '^components/safe_browsing/',
+  '^components/search_engines/',
+  '^components/search_provider_logos/',
+  '^components/security_interstitials/',
+  '^components/security_state/',
+  '^components/services/',
+  '^components/sessions/',
+  '^components/signin/',
+  '^components/ssl_errors/',
+  '^components/storage_monitor/',
+  '^components/subresource_filter/',
+  '^components/suggestions/',
+  '^components/supervised_user_error_page/',
+  '^components/sync/',
+  '^components/sync_bookmarks/',
+  '^components/sync_device_info/',
+  '^components/sync_preferences/',
+  '^components/sync_sessions/',
+  '^components/test/',
+  '^components/tracing/',
+  '^components/translate/',
+  '^components/ukm/',
+  '^components/update_client/',
+  '^components/upload_list/',
+  '^components/variations/',
+  '^components/visitedlink/',
+  '^components/web_cache/',
+  '^components/web_resource/',
+  '^components/web_restrictions/',
+  '^components/webcrypto/',
+  '^components/webdata/',
+  '^components/webdata_services/',
+  '^components/wifi/',
+  '^components/zoom/',
+  '^content/app/',
+  '^content/browser/',
+  '^content/child/',
+  '^content/common/',
+  '^content/public/',
+  '^content/renderer/android/',
+  '^content/renderer/fetchers/',
+  '^content/renderer/image_downloader/',
+  '^content/renderer/input/',
+  '^content/renderer/java/',
+  '^content/renderer/media/',
+  '^content/renderer/media_capture_from_element/',
+  '^content/renderer/media_recorder/',
+  '^content/renderer/p2p/',
+  '^content/renderer/pepper/',
+  '^content/renderer/service_worker/',
+  '^content/renderer/worker/',
+  '^content/test/',
+  '^content/utility/',
+  '^dbus/',
+  '^device/base/',
+  '^device/bluetooth/',
+  '^device/fido/',
+  '^device/gamepad/',
+  '^device/udev_linux/',
+  '^device/vr/',
+  '^extensions/',
+  '^gin/',
+  '^google_apis/dive/',
+  '^google_apis/gaia/',
+  '^google_apis/gcm/',
+  '^headless/',
+  '^ios/chrome/',
+  '^ios/components/',
+  '^ios/net/',
+  '^ios/web/',
+  '^ios/web_view/',
+  '^ipc/',
+  '^media/audio/',
+  '^media/base/',
+  '^media/capture/',
+  '^media/cast/',
+  '^media/cdm/',
+  '^media/device_monitors/',
+  '^media/ffmpeg/',
+  '^media/filters/',
+  '^media/formats/',
+  '^media/gpu/',
+  '^media/mojo/',
+  '^media/muxers/',
+  '^media/remoting/',
+  '^media/renderers/',
+  '^media/test/',
+  '^mojo/core/',
+  '^mojo/public/',
+  '^net/',
+  '^ppapi/proxy/',
+  '^ppapi/shared_impl/',
+  '^ppapi/tests/',
+  '^ppapi/thunk/',
+  '^remoting/base/',
+  '^remoting/client/',
+  '^remoting/codec/',
+  '^remoting/host/',
+  '^remoting/internal/',
+  '^remoting/ios/',
+  '^remoting/protocol/',
+  '^remoting/signaling/',
+  '^remoting/test/',
+  '^sandbox/linux/',
+  '^sandbox/win/',
+  '^services/',
+  '^storage/browser/',
+  '^testing/gmock_mutant.h',
+  '^testing/libfuzzer/',
+  '^third_party/blink/',
+  '^third_party/crashpad/crashpad/test/gtest_main.cc',
+  '^third_party/leveldatabase/leveldb_chrome.cc',
+  '^third_party/boringssl/gtest_main_chromium.cc',
+  '^third_party/cacheinvalidation/overrides/' +
+     'google/cacheinvalidation/deps/callback.h',
+  '^third_party/libaddressinput/chromium/chrome_address_validator.cc',
+  '^third_party/zlib/google/',
+  '^tools/android/',
+  '^tools/clang/base_bind_rewriters/',  # Intentional.
+  '^tools/gdb/gdb_chrome.py',  # Intentional.
+  '^ui/accelerated_widget_mac/',
+  '^ui/android/',
+  '^ui/aura/',
+  '^ui/base/',
+  '^ui/compositor/',
+  '^ui/display/',
+  '^ui/events/',
+  '^ui/gfx/',
+  '^ui/message_center/',
+  '^ui/ozone/',
+  '^ui/snapshot/',
+  '^ui/views_content_client/',
+  '^ui/wm/',
+))
 
 # Format: Sequence of tuples containing:
 # * String pattern or, if starting with a slash, a regular expression.
@@ -563,6 +807,33 @@
       (),
     ),
     (
+      r'/\bbase::Bind\(',
+      (
+          'Please use base::Bind{Once,Repeating} instead',
+          'of base::Bind. (crbug.com/714018)',
+      ),
+      False,
+      _NOT_CONVERTED_TO_MODERN_BIND_AND_CALLBACK,
+    ),
+    (
+      r'/\bbase::Callback[<:]',
+      (
+          'Please use base::{Once,Repeating}Callback instead',
+          'of base::Callback. (crbug.com/714018)',
+      ),
+      False,
+      _NOT_CONVERTED_TO_MODERN_BIND_AND_CALLBACK,
+    ),
+    (
+      r'/\bbase::Closure\b',
+      (
+          'Please use base::{Once,Repeating}Closure instead',
+          'of base::Closure. (crbug.com/714018)',
+      ),
+      False,
+      _NOT_CONVERTED_TO_MODERN_BIND_AND_CALLBACK,
+    ),
+    (
       r'/base::SharedMemory(|Handle)',
       (
           'base::SharedMemory is deprecated. Please use',
diff --git a/android_webview/browser/aw_autofill_client.cc b/android_webview/browser/aw_autofill_client.cc
index 2253222..67eef93 100644
--- a/android_webview/browser/aw_autofill_client.cc
+++ b/android_webview/browser/aw_autofill_client.cc
@@ -78,10 +78,6 @@
   return nullptr;
 }
 
-autofill::LegacyStrikeDatabase* AwAutofillClient::GetLegacyStrikeDatabase() {
-  return nullptr;
-}
-
 autofill::StrikeDatabase* AwAutofillClient::GetStrikeDatabase() {
   return nullptr;
 }
diff --git a/android_webview/browser/aw_autofill_client.h b/android_webview/browser/aw_autofill_client.h
index 08f8c4e..f9f45552 100644
--- a/android_webview/browser/aw_autofill_client.h
+++ b/android_webview/browser/aw_autofill_client.h
@@ -22,7 +22,6 @@
 class CardUnmaskDelegate;
 class CreditCard;
 class FormStructure;
-class LegacyStrikeDatabase;
 class MigratableCreditCard;
 class PersonalDataManager;
 class StrikeDatabase;
@@ -69,7 +68,6 @@
   identity::IdentityManager* GetIdentityManager() override;
   autofill::FormDataImporter* GetFormDataImporter() override;
   autofill::payments::PaymentsClient* GetPaymentsClient() override;
-  autofill::LegacyStrikeDatabase* GetLegacyStrikeDatabase() override;
   autofill::StrikeDatabase* GetStrikeDatabase() override;
   ukm::UkmRecorder* GetUkmRecorder() override;
   ukm::SourceId GetUkmSourceId() override;
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 6b4cca1..639f17b 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -50,6 +50,7 @@
     "public/cpp/arc_custom_tab.h",
     "public/cpp/ash_prefs.h",
     "public/cpp/docked_magnifier_controller.h",
+    "public/cpp/first_run_helper.h",
     "public/cpp/multi_user_window_manager.h",
     "public/cpp/multi_user_window_manager_delegate.h",
     "public/cpp/multi_user_window_manager_observer.h",
@@ -291,8 +292,8 @@
     "events/switch_access_event_handler.h",
     "first_run/desktop_cleaner.cc",
     "first_run/desktop_cleaner.h",
-    "first_run/first_run_helper.cc",
-    "first_run/first_run_helper.h",
+    "first_run/first_run_helper_impl.cc",
+    "first_run/first_run_helper_impl.h",
     "focus_cycler.cc",
     "frame/header_view.cc",
     "frame/non_client_frame_view_ash.cc",
@@ -1632,7 +1633,7 @@
     "events/spoken_feedback_event_rewriter_unittest.cc",
     "events/switch_access_event_handler_unittest.cc",
     "extended_desktop_unittest.cc",
-    "first_run/first_run_helper_unittest.cc",
+    "first_run/first_run_helper_impl_unittest.cc",
     "focus_cycler_unittest.cc",
     "frame/caption_buttons/frame_caption_button_container_view_unittest.cc",
     "frame/caption_buttons/frame_size_button_unittest.cc",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 483a7b6..301bf53 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -250,7 +250,7 @@
 }
 
 void AppListControllerImpl::PublishSearchResults(
-    std::vector<SearchResultMetadataPtr> results) {
+    std::vector<std::unique_ptr<ash::SearchResultMetadata>> results) {
   std::vector<std::unique_ptr<app_list::SearchResult>> new_results;
   for (auto& result_metadata : results) {
     std::unique_ptr<app_list::SearchResult> result =
@@ -342,7 +342,7 @@
 }
 
 void AppListControllerImpl::SetSearchResultMetadata(
-    SearchResultMetadataPtr metadata) {
+    std::unique_ptr<ash::SearchResultMetadata> metadata) {
   app_list::SearchResult* result = search_model_.FindSearchResult(metadata->id);
   if (result)
     result->SetMetadata(std::move(metadata));
@@ -855,7 +855,7 @@
     base::Optional<mojom::AppListViewState> recorded_app_list_view_state,
     base::Optional<bool> recorded_home_launcher_shown) {
   app_list::RecordAppListAppLaunched(
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       recorded_app_list_view_state.value_or(GetAppListViewState()),
       IsTabletMode(),
       recorded_home_launcher_shown.value_or(presenter_.home_launcher_shown()));
@@ -886,24 +886,23 @@
   }
 }
 
-void AppListControllerImpl::OpenSearchResult(
-    const std::string& result_id,
-    int event_flags,
-    ash::mojom::AppListLaunchedFrom launched_from,
-    ash::mojom::AppListLaunchType launch_type,
-    int suggestion_index) {
+void AppListControllerImpl::OpenSearchResult(const std::string& result_id,
+                                             int event_flags,
+                                             AppListLaunchedFrom launched_from,
+                                             AppListLaunchType launch_type,
+                                             int suggestion_index) {
   app_list::SearchResult* result = search_model_.FindSearchResult(result_id);
   if (!result)
     return;
 
-  if (launch_type == mojom::AppListLaunchType::kAppSearchResult) {
+  if (launch_type == AppListLaunchType::kAppSearchResult) {
     switch (launched_from) {
-      case mojom::AppListLaunchedFrom::kLaunchedFromSearchBox:
-      case mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip:
+      case AppListLaunchedFrom::kLaunchedFromSearchBox:
+      case AppListLaunchedFrom::kLaunchedFromSuggestionChip:
         RecordAppLaunched(launched_from);
         break;
-      case mojom::AppListLaunchedFrom::kLaunchedFromGrid:
-      case mojom::AppListLaunchedFrom::kLaunchedFromShelf:
+      case AppListLaunchedFrom::kLaunchedFromGrid:
+      case AppListLaunchedFrom::kLaunchedFromShelf:
         break;
     }
   }
@@ -914,8 +913,7 @@
 
   // Suggestion chips are not represented to the user as search results, so do
   // not record search result metrics for them.
-  if (launched_from !=
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip) {
+  if (launched_from != AppListLaunchedFrom::kLaunchedFromSuggestionChip) {
     base::RecordAction(base::UserMetricsAction("AppList_OpenSearchResult"));
 
     UMA_HISTOGRAM_COUNTS_100(app_list::kSearchQueryLength,
@@ -939,8 +937,7 @@
       app_list_features::IsEmbeddedAssistantUIEnabled()) {
     // Record the assistant result. Other types of results are recorded in
     // |client_| where there is richer data on SearchResultType.
-    DCHECK_EQ(ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
-              launched_from)
+    DCHECK_EQ(AppListLaunchedFrom::kLaunchedFromSearchBox, launched_from)
         << "Only log search results which are represented to the user as "
            "search results (ie. search results in the search result page) not "
            "chips.";
@@ -1028,10 +1025,9 @@
   Shell::Get()->wallpaper_controller()->GetWallpaperColors(std::move(callback));
 }
 
-void AppListControllerImpl::ActivateItem(
-    const std::string& id,
-    int event_flags,
-    mojom::AppListLaunchedFrom launched_from) {
+void AppListControllerImpl::ActivateItem(const std::string& id,
+                                         int event_flags,
+                                         AppListLaunchedFrom launched_from) {
   RecordAppLaunched(launched_from);
 
   if (client_)
@@ -1176,7 +1172,7 @@
 }
 
 void AppListControllerImpl::RecordAppLaunched(
-    mojom::AppListLaunchedFrom launched_from) {
+    AppListLaunchedFrom launched_from) {
   app_list::RecordAppListAppLaunched(launched_from, GetAppListViewState(),
                                      IsTabletMode(),
                                      presenter_.home_launcher_shown());
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index c289b26..207b0f5 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -25,7 +25,6 @@
 #include "ash/public/cpp/app_list/app_list_controller.h"
 #include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
 #include "ash/public/cpp/shelf_types.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "ash/public/interfaces/app_list_view.mojom.h"
 #include "ash/public/interfaces/voice_interaction_controller.mojom.h"
 #include "ash/session/session_observer.h"
@@ -95,7 +94,7 @@
   void UpdateSearchBox(const base::string16& text,
                        bool initiated_by_user) override;
   void PublishSearchResults(
-      std::vector<SearchResultMetadataPtr> results) override;
+      std::vector<std::unique_ptr<ash::SearchResultMetadata>> results) override;
   void SetItemMetadata(const std::string& id,
                        std::unique_ptr<ash::AppListItemMetadata> data) override;
   void SetItemIcon(const std::string& id, const gfx::ImageSkia& icon) override;
@@ -106,7 +105,8 @@
                     std::vector<std::unique_ptr<ash::AppListItemMetadata>> apps,
                     bool is_search_engine_google) override;
 
-  void SetSearchResultMetadata(SearchResultMetadataPtr metadata) override;
+  void SetSearchResultMetadata(
+      std::unique_ptr<ash::SearchResultMetadata> metadata) override;
   void SetSearchResultIsInstalling(const std::string& id,
                                    bool is_installing) override;
   void SetSearchResultPercentDownloaded(const std::string& id,
@@ -159,8 +159,8 @@
   void StartSearch(const base::string16& raw_query) override;
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
-                        ash::mojom::AppListLaunchedFrom launched_from,
-                        ash::mojom::AppListLaunchType launch_type,
+                        AppListLaunchedFrom launched_from,
+                        AppListLaunchType launch_type,
                         int suggestion_index) override;
   void LogResultLaunchHistogram(
       app_list::SearchResultLaunchLocation launch_location,
@@ -181,7 +181,7 @@
       GetWallpaperProminentColorsCallback callback) override;
   void ActivateItem(const std::string& id,
                     int event_flags,
-                    mojom::AppListLaunchedFrom launched_from) override;
+                    AppListLaunchedFrom launched_from) override;
   void GetContextMenuModel(const std::string& id,
                            GetContextMenuModelCallback callback) override;
   void ShowWallpaperContextMenu(const gfx::Point& onscreen_location,
@@ -336,7 +336,7 @@
   void Shutdown();
 
   // Record the app launch for AppListAppLaunchedV2 metric.
-  void RecordAppLaunched(mojom::AppListLaunchedFrom launched_from);
+  void RecordAppLaunched(AppListLaunchedFrom launched_from);
 
   app_list::AppListClient* client_ = nullptr;
 
diff --git a/ash/app_list/app_list_metrics.cc b/ash/app_list/app_list_metrics.cc
index 1567ef71..24529ba 100644
--- a/ash/app_list/app_list_metrics.cc
+++ b/ash/app_list/app_list_metrics.cc
@@ -230,7 +230,7 @@
                             removal_decision);
 }
 
-void RecordAppListAppLaunched(ash::mojom::AppListLaunchedFrom launched_from,
+void RecordAppListAppLaunched(ash::AppListLaunchedFrom launched_from,
                               ash::mojom::AppListViewState app_list_state,
                               bool is_tablet_mode,
                               bool home_launcher_shown) {
diff --git a/ash/app_list/app_list_metrics.h b/ash/app_list/app_list_metrics.h
index 67cf6ec..19595dd 100644
--- a/ash/app_list/app_list_metrics.h
+++ b/ash/app_list/app_list_metrics.h
@@ -6,7 +6,7 @@
 #define ASH_APP_LIST_APP_LIST_METRICS_H_
 
 #include "ash/app_list/app_list_export.h"
-#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/interfaces/app_list_view.mojom.h"
 #include "ui/events/event.h"
 
@@ -257,10 +257,10 @@
 // Parameters to call RecordAppListAppLaunched. Passed to code that does not
 // directly have access to them, such ash AppListMenuModelAdapter.
 struct AppLaunchedMetricParams {
-  ash::mojom::AppListLaunchedFrom launched_from =
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromGrid;
-  ash::mojom::AppListLaunchType search_launch_type =
-      ash::mojom::AppListLaunchType::kSearchResult;
+  ash::AppListLaunchedFrom launched_from =
+      ash::AppListLaunchedFrom::kLaunchedFromGrid;
+  ash::AppListLaunchType search_launch_type =
+      ash::AppListLaunchType::kSearchResult;
   ash::mojom::AppListViewState app_list_view_state =
       ash::mojom::AppListViewState::kClosed;
   bool is_tablet_mode = false;
@@ -302,7 +302,7 @@
     int suggestion_index);
 
 APP_LIST_EXPORT void RecordAppListAppLaunched(
-    ash::mojom::AppListLaunchedFrom launched_from,
+    ash::AppListLaunchedFrom launched_from,
     ash::mojom::AppListViewState app_list_state,
     bool is_tablet_mode,
     bool home_launcher_shown);
diff --git a/ash/app_list/app_list_view_delegate.h b/ash/app_list/app_list_view_delegate.h
index ad1e530..97b19c1 100644
--- a/ash/app_list/app_list_view_delegate.h
+++ b/ash/app_list/app_list_view_delegate.h
@@ -11,7 +11,6 @@
 #include "ash/app_list/app_list_metrics.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/public/cpp/ash_public_export.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "ash/public/interfaces/app_list_view.mojom.h"
 #include "base/callback_forward.h"
 #include "base/strings/string16.h"
@@ -61,8 +60,8 @@
   // histograms to log to.
   virtual void OpenSearchResult(const std::string& result_id,
                                 int event_flags,
-                                ash::mojom::AppListLaunchedFrom launched_from,
-                                ash::mojom::AppListLaunchType launch_type,
+                                ash::AppListLaunchedFrom launched_from,
+                                ash::AppListLaunchType launch_type,
                                 int suggestion_index) = 0;
 
   // Called to log UMA metrics for the launch of an item either in the app tile
@@ -116,7 +115,7 @@
   // Activates (opens) the item.
   virtual void ActivateItem(const std::string& id,
                             int event_flags,
-                            ash::mojom::AppListLaunchedFrom launched_from) = 0;
+                            ash::AppListLaunchedFrom launched_from) = 0;
 
   // Returns the context menu model for a ChromeAppListItem with |id|, or NULL
   // if there is currently no menu for the item (e.g. during install).
diff --git a/ash/app_list/model/app_list_item.h b/ash/app_list/model/app_list_item.h
index c1a55a8..fe700c1 100644
--- a/ash/app_list/model/app_list_item.h
+++ b/ash/app_list/model/app_list_item.h
@@ -13,7 +13,6 @@
 
 #include "ash/app_list/model/app_list_model_export.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "components/sync/model/string_ordinal.h"
diff --git a/ash/app_list/model/app_list_model.h b/ash/app_list/model/app_list_model.h
index 2bb9184a..9a74ae2 100644
--- a/ash/app_list/model/app_list_model.h
+++ b/ash/app_list/model/app_list_model.h
@@ -14,7 +14,7 @@
 #include "ash/app_list/model/app_list_item_list.h"
 #include "ash/app_list/model/app_list_item_list_observer.h"
 #include "ash/app_list/model/app_list_model_export.h"
-#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/interfaces/app_list_view.mojom.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
diff --git a/ash/app_list/model/search/search_result.cc b/ash/app_list/model/search/search_result.cc
index 45c5c3c..36a5885 100644
--- a/ash/app_list/model/search/search_result.cc
+++ b/ash/app_list/model/search/search_result.cc
@@ -14,14 +14,15 @@
 namespace app_list {
 
 SearchResult::SearchResult()
-    : metadata_(ash::mojom::SearchResultMetadata::New()) {}
+    : metadata_(std::make_unique<ash::SearchResultMetadata>()) {}
 
 SearchResult::~SearchResult() {
   for (auto& observer : observers_)
     observer.OnResultDestroying();
 }
 
-void SearchResult::SetMetadata(ash::mojom::SearchResultMetadataPtr metadata) {
+void SearchResult::SetMetadata(
+    std::unique_ptr<ash::SearchResultMetadata> metadata) {
   metadata_ = std::move(metadata);
   for (auto& observer : observers_)
     observer.OnMetadataChanged();
diff --git a/ash/app_list/model/search/search_result.h b/ash/app_list/model/search/search_result.h
index 0e0ed94e..0a411b6 100644
--- a/ash/app_list/model/search/search_result.h
+++ b/ash/app_list/model/search/search_result.h
@@ -14,7 +14,6 @@
 
 #include "ash/app_list/model/app_list_model_export.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
@@ -145,12 +144,12 @@
   // Invokes a custom action on the result. It does nothing by default.
   virtual void InvokeAction(int action_index, int event_flags);
 
-  void SetMetadata(ash::mojom::SearchResultMetadataPtr metadata);
-  ash::mojom::SearchResultMetadataPtr TakeMetadata() {
+  void SetMetadata(std::unique_ptr<ash::SearchResultMetadata> metadata);
+  std::unique_ptr<ash::SearchResultMetadata> TakeMetadata() {
     return std::move(metadata_);
   }
-  ash::mojom::SearchResultMetadataPtr CloneMetadata() const {
-    return metadata_->Clone();
+  std::unique_ptr<ash::SearchResultMetadata> CloneMetadata() const {
+    return std::make_unique<ash::SearchResultMetadata>(*metadata_);
   }
 
  protected:
@@ -170,7 +169,7 @@
   int percent_downloaded_ = 0;
   bool is_visible_ = true;
 
-  ash::mojom::SearchResultMetadataPtr metadata_;
+  std::unique_ptr<ash::SearchResultMetadata> metadata_;
 
   base::ObserverList<SearchResultObserver>::Unchecked observers_;
 
diff --git a/ash/app_list/test/app_list_test_view_delegate.cc b/ash/app_list/test/app_list_test_view_delegate.cc
index f03b6b1..90d1ca67 100644
--- a/ash/app_list/test/app_list_test_view_delegate.cc
+++ b/ash/app_list/test/app_list_test_view_delegate.cc
@@ -40,8 +40,8 @@
 void AppListTestViewDelegate::OpenSearchResult(
     const std::string& result_id,
     int event_flags,
-    ash::mojom::AppListLaunchedFrom launched_from,
-    ash::mojom::AppListLaunchType launch_type,
+    ash::AppListLaunchedFrom launched_from,
+    ash::AppListLaunchType launch_type,
     int suggestion_index) {
   const SearchModel::SearchResults* results = search_model_->results();
   for (size_t i = 0; i < results->item_count(); ++i) {
@@ -56,14 +56,14 @@
   }
   ++open_search_result_count_;
 
-  if (launch_type == ash::mojom::AppListLaunchType::kAppSearchResult) {
+  if (launch_type == ash::AppListLaunchType::kAppSearchResult) {
     switch (launched_from) {
-      case ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox:
-      case ash::mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip:
+      case ash::AppListLaunchedFrom::kLaunchedFromSearchBox:
+      case ash::AppListLaunchedFrom::kLaunchedFromSuggestionChip:
         RecordAppLaunched(launched_from);
         return;
-      case ash::mojom::AppListLaunchedFrom::kLaunchedFromGrid:
-      case ash::mojom::AppListLaunchedFrom::kLaunchedFromShelf:
+      case ash::AppListLaunchedFrom::kLaunchedFromGrid:
+      case ash::AppListLaunchedFrom::kLaunchedFromShelf:
         return;
     }
   }
@@ -86,7 +86,7 @@
 void AppListTestViewDelegate::ActivateItem(
     const std::string& id,
     int event_flags,
-    ash::mojom::AppListLaunchedFrom launched_from) {
+    ash::AppListLaunchedFrom launched_from) {
   app_list::AppListItem* item = model_->FindItem(id);
   if (!item)
     return;
@@ -168,7 +168,7 @@
     app_list::AppLaunchedMetricParams* metric_params) {}
 
 void AppListTestViewDelegate::RecordAppLaunched(
-    ash::mojom::AppListLaunchedFrom launched_from) {
+    ash::AppListLaunchedFrom launched_from) {
   app_list::RecordAppListAppLaunched(launched_from, model_->state_fullscreen(),
                                      false /*tablet mode*/,
                                      false /*home launcher shown*/);
diff --git a/ash/app_list/test/app_list_test_view_delegate.h b/ash/app_list/test/app_list_test_view_delegate.h
index 16d4116f..f809372 100644
--- a/ash/app_list/test/app_list_test_view_delegate.h
+++ b/ash/app_list/test/app_list_test_view_delegate.h
@@ -15,7 +15,7 @@
 #include "ash/app_list/app_list_view_delegate.h"
 #include "ash/app_list/model/search/search_model.h"
 #include "ash/app_list/test/app_list_test_model.h"
-#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/callback_forward.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
@@ -63,8 +63,8 @@
   void StartSearch(const base::string16& raw_query) override {}
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
-                        ash::mojom::AppListLaunchedFrom launched_from,
-                        ash::mojom::AppListLaunchType launch_type,
+                        ash::AppListLaunchedFrom launched_from,
+                        ash::AppListLaunchType launch_type,
                         int suggestion_index) override;
   void LogResultLaunchHistogram(
       app_list::SearchResultLaunchLocation launch_location,
@@ -84,7 +84,7 @@
       GetWallpaperProminentColorsCallback callback) override {}
   void ActivateItem(const std::string& id,
                     int event_flags,
-                    ash::mojom::AppListLaunchedFrom launched_from) override;
+                    ash::AppListLaunchedFrom launched_from) override;
   void GetContextMenuModel(const std::string& id,
                            GetContextMenuModelCallback callback) override;
   void ShowWallpaperContextMenu(const gfx::Point& onscreen_location,
@@ -114,7 +114,7 @@
   AppListTestModel* GetTestModel() { return model_.get(); }
 
  private:
-  void RecordAppLaunched(ash::mojom::AppListLaunchedFrom launched_from);
+  void RecordAppLaunched(ash::AppListLaunchedFrom launched_from);
 
   // ui::SimpleMenuModel::Delegate overrides:
   bool IsCommandIdChecked(int command_id) const override;
diff --git a/ash/app_list/test/test_app_list_client.h b/ash/app_list/test/test_app_list_client.h
index edd843e..4463ee4 100644
--- a/ash/app_list/test/test_app_list_client.h
+++ b/ash/app_list/test/test_app_list_client.h
@@ -24,8 +24,8 @@
   void StartSearch(const base::string16& trimmed_query) override {}
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
-                        ash::mojom::AppListLaunchedFrom launched_from,
-                        ash::mojom::AppListLaunchType launch_type,
+                        ash::AppListLaunchedFrom launched_from,
+                        ash::AppListLaunchType launch_type,
                         int suggestion_index) override {}
   void InvokeSearchResultAction(const std::string& result_id,
                                 int action_index,
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index 0a1242b..ef8bc0b9 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -16,7 +16,7 @@
 #include "ash/app_list/views/apps_grid_view.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
-#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/auto_reset.h"
 #include "base/bind.h"
 #include "base/strings/utf_string_conversions.h"
@@ -481,7 +481,7 @@
   views::View::ConvertRectToScreen(apps_grid_view_, &anchor_rect);
 
   AppLaunchedMetricParams metric_params = {
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromGrid};
+      ash::AppListLaunchedFrom::kLaunchedFromGrid};
   delegate_->GetAppLaunchedMetricParams(&metric_params);
 
   context_menu_ = std::make_unique<AppListMenuModelAdapter>(
diff --git a/ash/app_list/views/app_list_main_view.cc b/ash/app_list/views/app_list_main_view.cc
index 617450b..28e5135 100644
--- a/ash/app_list/views/app_list_main_view.cc
+++ b/ash/app_list/views/app_list_main_view.cc
@@ -165,7 +165,7 @@
   } else {
     base::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
     delegate_->ActivateItem(item->id(), event_flags,
-                            ash::mojom::AppListLaunchedFrom::kLaunchedFromGrid);
+                            ash::AppListLaunchedFrom::kLaunchedFromGrid);
   }
 }
 
diff --git a/ash/app_list/views/app_list_menu_model_adapter.cc b/ash/app_list/views/app_list_menu_model_adapter.cc
index 1439264..d3d822fa 100644
--- a/ash/app_list/views/app_list_menu_model_adapter.cc
+++ b/ash/app_list/views/app_list_menu_model_adapter.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "ash/app_list/app_list_metrics.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/app_menu_constants.h"
 #include "base/metrics/histogram_macros.h"
 #include "ui/views/controls/menu/menu_runner.h"
@@ -167,9 +168,9 @@
   // kLaunchedFromSearchBox. Early out if it is not launched as an app search
   // result.
   if (metric_params_.launched_from ==
-          ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox &&
+          ash::AppListLaunchedFrom::kLaunchedFromSearchBox &&
       metric_params_.search_launch_type !=
-          ash::mojom::AppListLaunchType::kAppSearchResult) {
+          ash::AppListLaunchType::kAppSearchResult) {
     return;
   }
 
diff --git a/ash/app_list/views/search_result_answer_card_view.cc b/ash/app_list/views/search_result_answer_card_view.cc
index 397f5f2..e46fdfe9 100644
--- a/ash/app_list/views/search_result_answer_card_view.cc
+++ b/ash/app_list/views/search_result_answer_card_view.cc
@@ -222,9 +222,8 @@
                                    view_delegate_->GetSearchModel());
       view_delegate_->OpenSearchResult(
           result()->id(), event.flags(),
-          ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
-          ash::mojom::AppListLaunchType::kSearchResult,
-          -1 /* suggestion_index */);
+          ash::AppListLaunchedFrom::kLaunchedFromSearchBox,
+          ash::AppListLaunchType::kSearchResult, -1 /* suggestion_index */);
     }
   }
 
diff --git a/ash/app_list/views/search_result_list_view.cc b/ash/app_list/views/search_result_list_view.cc
index 74d663b..d6c9a428 100644
--- a/ash/app_list/views/search_result_list_view.cc
+++ b/ash/app_list/views/search_result_list_view.cc
@@ -226,9 +226,8 @@
         SearchResultLaunchLocation::kResultList, view->index_in_container());
     view_delegate_->OpenSearchResult(
         view->result()->id(), event_flags,
-        ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
-        ash::mojom::AppListLaunchType::kSearchResult,
-        -1 /* suggestion_index */);
+        ash::AppListLaunchedFrom::kLaunchedFromSearchBox,
+        ash::AppListLaunchType::kSearchResult, -1 /* suggestion_index */);
   }
 }
 
diff --git a/ash/app_list/views/search_result_suggestion_chip_view.cc b/ash/app_list/views/search_result_suggestion_chip_view.cc
index 87a879d6..b66039f 100644
--- a/ash/app_list/views/search_result_suggestion_chip_view.cc
+++ b/ash/app_list/views/search_result_suggestion_chip_view.cc
@@ -10,8 +10,8 @@
 #include "ash/app_list/app_list_view_delegate.h"
 #include "ash/app_list/model/search/search_result.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/app_list/internal_app_id_constants.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
@@ -107,8 +107,8 @@
                                view_delegate_->GetSearchModel());
   view_delegate_->OpenSearchResult(
       result()->id(), event.flags(),
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip,
-      ash::mojom::AppListLaunchType::kAppSearchResult, index_in_container());
+      ash::AppListLaunchedFrom::kLaunchedFromSuggestionChip,
+      ash::AppListLaunchType::kAppSearchResult, index_in_container());
 }
 
 const char* SearchResultSuggestionChipView::GetClassName() const {
diff --git a/ash/app_list/views/search_result_tile_item_view.cc b/ash/app_list/views/search_result_tile_item_view.cc
index 290aa9e..c4c6f9d 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -14,9 +14,9 @@
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
 #include "ash/public/cpp/pagination/pagination_model.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "ash/public/interfaces/app_list_view.mojom.h"
 #include "base/bind.h"
 #include "base/i18n/number_formatting.h"
@@ -363,8 +363,8 @@
   anchor_rect.ClampToCenteredSize(AppListConfig::instance().grid_focus_size());
 
   AppLaunchedMetricParams metric_params = {
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
-      ash::mojom::AppListLaunchType::kAppSearchResult};
+      ash::AppListLaunchedFrom::kLaunchedFromSearchBox,
+      ash::AppListLaunchType::kAppSearchResult};
   view_delegate_->GetAppLaunchedMetricParams(&metric_params);
 
   context_menu_ = std::make_unique<AppListMenuModelAdapter>(
@@ -392,8 +392,8 @@
                                view_delegate_->GetSearchModel());
   view_delegate_->OpenSearchResult(
       result()->id(), event_flags,
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
-      ash::mojom::AppListLaunchType::kAppSearchResult, index_in_container());
+      ash::AppListLaunchedFrom::kLaunchedFromSearchBox,
+      ash::AppListLaunchType::kAppSearchResult, index_in_container());
   view_delegate_->LogResultLaunchHistogram(
       SearchResultLaunchLocation::kTileList, index_in_container());
 }
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index 0b8e84f3..8fe2b9b2 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -17,7 +17,7 @@
 #include "ash/app_list/views/search_result_list_view.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
-#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/bind.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_palette.h"
@@ -459,8 +459,8 @@
     return;
 
   AppLaunchedMetricParams metric_params = {
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
-      ash::mojom::AppListLaunchType::kSearchResult};
+      ash::AppListLaunchedFrom::kLaunchedFromSearchBox,
+      ash::AppListLaunchType::kSearchResult};
   view_delegate_->GetAppLaunchedMetricParams(&metric_params);
 
   context_menu_ = std::make_unique<AppListMenuModelAdapter>(
diff --git a/ash/autoclick/autoclick_controller.cc b/ash/autoclick/autoclick_controller.cc
index f6c7662..24a64d5 100644
--- a/ash/autoclick/autoclick_controller.cc
+++ b/ash/autoclick/autoclick_controller.cc
@@ -13,6 +13,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/accessibility/accessibility_feature_disable_dialog.h"
 #include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
+#include "ash/wm/fullscreen_window_finder.h"
 #include "ash/wm/root_window_finder.h"
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -60,6 +61,7 @@
       drag_event_rewriter_(std::make_unique<AutoclickDragEventRewriter>()) {
   Shell::GetPrimaryRootWindow()->GetHost()->GetEventSource()->AddEventRewriter(
       drag_event_rewriter_.get());
+  Shell::Get()->cursor_manager()->AddObserver(this);
   InitClickTimers();
   UpdateRingSize();
 }
@@ -69,6 +71,7 @@
   menu_bubble_controller_ = nullptr;
   CancelAutoclickAction();
 
+  Shell::Get()->cursor_manager()->RemoveObserver(this);
   Shell::Get()->RemovePreTargetHandler(this);
   SetTapDownTarget(nullptr);
   Shell::GetPrimaryRootWindow()
@@ -440,14 +443,14 @@
   DCHECK(event->target());
   if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED)
     return;
-  gfx::Point point_in_screen = event->target()->GetScreenLocation(*event);
+  last_mouse_location_ = event->target()->GetScreenLocation(*event);
   if (!(event->flags() & ui::EF_IS_SYNTHESIZED) &&
       (event->type() == ui::ET_MOUSE_MOVED ||
        (event->type() == ui::ET_MOUSE_DRAGGED &&
         drag_event_rewriter_->IsEnabled()))) {
     mouse_event_flags_ = event->flags();
     // Update the point even if the animation is not currently being shown.
-    UpdateRingWidget(point_in_screen);
+    UpdateRingWidget(last_mouse_location_);
 
     // The distance between the mouse location and the anchor location
     // must exceed a certain threshold to initiate a new autoclick countdown.
@@ -455,10 +458,10 @@
     // 1. initiate an unwanted autoclick from rest
     // 2. prevent the autoclick from ever occurring when the mouse
     //    arrives at the target.
-    gfx::Vector2d delta = point_in_screen - anchor_location_;
+    gfx::Vector2d delta = last_mouse_location_ - anchor_location_;
     if (delta.LengthSquared() >= movement_threshold_ * movement_threshold_) {
-      anchor_location_ = point_in_screen;
-      gesture_anchor_location_ = point_in_screen;
+      anchor_location_ = last_mouse_location_;
+      gesture_anchor_location_ = last_mouse_location_;
       // Stop all the timers, restarting the gesture timer only. This keeps
       // the animation from being drawn while the user is still moving quickly.
       start_gesture_timer_->Reset();
@@ -468,12 +471,13 @@
       autoclick_ring_handler_->StopGesture();
     } else if (start_gesture_timer_->IsRunning()) {
       // Keep track of where the gesture will be anchored.
-      gesture_anchor_location_ = point_in_screen;
+      gesture_anchor_location_ = last_mouse_location_;
     } else if (autoclick_timer_->IsRunning() && !stabilize_click_position_) {
       // If we are not stabilizing the click position, update the gesture
       // center with each mouse move event.
-      gesture_anchor_location_ = point_in_screen;
-      autoclick_ring_handler_->SetGestureCenter(point_in_screen, widget_.get());
+      gesture_anchor_location_ = last_mouse_location_;
+      autoclick_ring_handler_->SetGestureCenter(last_mouse_location_,
+                                                widget_.get());
     }
   } else if (event->type() == ui::ET_MOUSE_PRESSED ||
              event->type() == ui::ET_MOUSE_RELEASED ||
@@ -516,4 +520,20 @@
   CancelAutoclickAction();
 }
 
+void AutoclickController::OnCursorVisibilityChanged(bool is_visible) {
+  if (!menu_bubble_controller_)
+    return;
+  // TODO(katie): Check that the display which is fullscreen is the same as the
+  // one containing the bubble, to determine whether to hide the bubble.
+  // Currently just checking if the display under the mouse is fullscreen.
+  aura::Window* window = wm::GetWindowForFullscreenModeInRoot(
+      wm::GetRootWindowAt(last_mouse_location_));
+  bool is_fullscreen = window != nullptr;
+
+  // Hide the bubble when the cursor is gone in fullscreen mode.
+  // Always show it otherwise.
+  menu_bubble_controller_->SetBubbleVisibility(is_fullscreen ? is_visible
+                                                             : true);
+}
+
 }  // namespace ash
diff --git a/ash/autoclick/autoclick_controller.h b/ash/autoclick/autoclick_controller.h
index 6df031d..8c60988c 100644
--- a/ash/autoclick/autoclick_controller.h
+++ b/ash/autoclick/autoclick_controller.h
@@ -10,6 +10,7 @@
 #include "ash/public/interfaces/accessibility_controller_enums.mojom.h"
 #include "base/macros.h"
 #include "base/time/time.h"
+#include "ui/aura/client/cursor_client_observer.h"
 #include "ui/aura/window_observer.h"
 #include "ui/events/event_handler.h"
 #include "ui/gfx/geometry/point.h"
@@ -33,8 +34,10 @@
 // animate at the mouse event location and an automatic mouse event event will
 // happen after a certain amount of time at that location. The event type is
 // determined by SetAutoclickEventType.
-class ASH_EXPORT AutoclickController : public ui::EventHandler,
-                                       public aura::WindowObserver {
+class ASH_EXPORT AutoclickController
+    : public ui::EventHandler,
+      public aura::WindowObserver,
+      public aura::client::CursorClientObserver {
  public:
   AutoclickController();
   ~AutoclickController() override;
@@ -119,6 +122,11 @@
   // aura::WindowObserver overrides:
   void OnWindowDestroying(aura::Window* window) override;
 
+  // aura::client::CursorClientObserver overrides:
+  void OnCursorVisibilityChanged(bool is_visible) override;
+  // TODO(katie): Override OnCursorDisplayChanged to move the autoclick
+  // bubble menu to the same display as the cursor.
+
   // Whether Autoclick is currently enabled.
   bool enabled_ = false;
   mojom::AutoclickEventType event_type_ = kDefaultAutoclickEventType;
@@ -136,20 +144,23 @@
   // The target window is observed by AutoclickController for the duration
   // of a autoclick gesture.
   aura::Window* tap_down_target_ = nullptr;
+  // The most recent mouse location.
+  gfx::Point last_mouse_location_{-kDefaultAutoclickMovementThreshold,
+                                  -kDefaultAutoclickMovementThreshold};
   // The position in screen coordinates used to determine the distance the
   // mouse has moved since dwell began. It is used to determine
   // if move events should cancel the gesture.
-  gfx::Point anchor_location_ = gfx::Point(-kDefaultAutoclickMovementThreshold,
-                                           -kDefaultAutoclickMovementThreshold);
+  gfx::Point anchor_location_{-kDefaultAutoclickMovementThreshold,
+                              -kDefaultAutoclickMovementThreshold};
   // The position in screen coodinates tracking where the autoclick gesture
   // should be anchored. While the |start_gesture_timer_| is running and before
   // the animation is drawn, subtle mouse movements will update the
   // |gesture_anchor_location_|, so that once animation begins it can focus on
   // the most recent mose point.
-  gfx::Point gesture_anchor_location_ =
-      gfx::Point(-kDefaultAutoclickMovementThreshold,
-                 -kDefaultAutoclickMovementThreshold);
+  gfx::Point gesture_anchor_location_{-kDefaultAutoclickMovementThreshold,
+                                      -kDefaultAutoclickMovementThreshold};
 
+  // The widget containing the autoclick ring.
   std::unique_ptr<views::Widget> widget_;
   base::TimeDelta delay_;
   // The timer that counts down from the beginning of a gesture until a click.
diff --git a/ash/autoclick/autoclick_unittest.cc b/ash/autoclick/autoclick_unittest.cc
index fb7521a..3b592d8b 100644
--- a/ash/autoclick/autoclick_unittest.cc
+++ b/ash/autoclick/autoclick_unittest.cc
@@ -12,6 +12,8 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/collision_detection/collision_detection_utils.h"
 #include "ash/wm/desks/desks_util.h"
+#include "ash/wm/window_state.h"
+#include "ash/wm/wm_event.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
@@ -148,6 +150,12 @@
         ->menu_view_;
   }
 
+  views::Widget* GetAutoclickBubbleWidget() {
+    return GetAutoclickController()
+        ->GetMenuBubbleControllerForTesting()
+        ->bubble_widget_;
+  }
+
   views::View* GetMenuButton(AutoclickMenuView::ButtonId view_id) {
     AutoclickMenuView* menu_view = GetAutoclickMenuView();
     if (!menu_view)
@@ -944,4 +952,112 @@
   EXPECT_FALSE(GetAutoclickController()->IsEnabled());
 }
 
+TEST_F(AutoclickTest, HidesBubbleInFullscreenWhenCursorHides) {
+  Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true);
+  ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager();
+  cursor_manager->SetCursor(ui::CursorType::kPointer);
+
+  const struct {
+    const std::string display_spec;
+    gfx::Rect widget_position;
+  } kTestCases[] = {
+      {"800x600", gfx::Rect(0, 0, 200, 200)},
+      {"800x600,800x600", gfx::Rect(0, 0, 200, 200)},
+      {"800x600,800x600", gfx::Rect(1000, 0, 200, 200)},
+  };
+  for (const auto& test : kTestCases) {
+    SCOPED_TRACE(test.display_spec);
+    UpdateDisplay(test.display_spec);
+
+    std::unique_ptr<views::Widget> widget =
+        CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(),
+                         test.widget_position, /*show=*/true);
+    EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+
+    // Move the mouse over the widget, so it's on the same screen as the widget.
+    GetEventGenerator()->MoveMouseTo(test.widget_position.origin());
+
+    // When the widget is not fullscreen, hiding the cursor does not cause
+    // the bubble to be hidden.
+    cursor_manager->HideCursor();
+    EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+    cursor_manager->ShowCursor();
+    EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+
+    // Bubble is visible in fullscreen mode because the mouse cursor is visible.
+    widget->SetFullscreen(true);
+    EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+
+    // Bubble is hidden when the cursor is hidden in fullscreen mode, and shown
+    // when the cursor is shown.
+    cursor_manager->HideCursor();
+    EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible());
+    cursor_manager->ShowCursor();
+    EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+
+    // Changing the type to another visible type doesn't cause the bubble to
+    // hide.
+    cursor_manager->SetCursor(ui::CursorType::kHand);
+    EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+
+    // Changing the type to an kNone causes the bubble to hide.
+    cursor_manager->SetCursor(ui::CursorType::kNone);
+    EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible());
+
+    // Hiding and showing don't re-show the bubble because the type is still
+    // kNone.
+    cursor_manager->HideCursor();
+    EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible());
+    cursor_manager->ShowCursor();
+    EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible());
+
+    // The bubble is shown when the cursor is a visible type again.
+    cursor_manager->SetCursor(ui::CursorType::kPointer);
+    EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+  }
+}
+
+TEST_F(AutoclickTest, DoesNotHideBubbleWhenNotOverFullscreenWindow) {
+  UpdateDisplay("800x600,800x600");
+  Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true);
+  ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager();
+  cursor_manager->SetCursor(ui::CursorType::kPointer);
+
+  std::unique_ptr<views::Widget> widget =
+      CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(),
+                       gfx::Rect(1000, 0, 200, 200), true);
+
+  EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+
+  // Move the mouse over the other display.
+  GetEventGenerator()->MoveMouseTo(gfx::Point(10, 10));
+
+  // When the widget is fullscreen, hiding the cursor does not hide the bubble
+  // because the cursor is on a different display.
+  widget->SetFullscreen(true);
+  cursor_manager->HideCursor();
+  EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+}
+
+TEST_F(AutoclickTest, DoesNotHideBubbleWhenOverInactiveFullscreenWindow) {
+  Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true);
+  ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager();
+  cursor_manager->SetCursor(ui::CursorType::kPointer);
+
+  std::unique_ptr<views::Widget> widget =
+      CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(),
+                       gfx::Rect(0, 0, 200, 200), true);
+  GetEventGenerator()->MoveMouseTo(gfx::Point(10, 10));
+  widget->SetFullscreen(true);
+  EXPECT_TRUE(widget->IsActive());
+  views::Widget* popup_widget = views::Widget::CreateWindowWithContextAndBounds(
+      nullptr, CurrentContext(), gfx::Rect(200, 200, 200, 200));
+  popup_widget->Show();
+
+  cursor_manager->HideCursor();
+  EXPECT_FALSE(widget->IsActive());
+  EXPECT_TRUE(popup_widget->IsActive());
+  EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible());
+}
+
 }  // namespace ash
diff --git a/ash/first_run/first_run_helper.cc b/ash/first_run/first_run_helper.cc
deleted file mode 100644
index ac62094..0000000
--- a/ash/first_run/first_run_helper.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/first_run/first_run_helper.h"
-
-#include <memory>
-#include <utility>
-
-#include "ash/app_list/views/app_list_view.h"
-#include "ash/first_run/desktop_cleaner.h"
-#include "ash/root_window_controller.h"
-#include "ash/session/session_controller_impl.h"
-#include "ash/shelf/app_list_button.h"
-#include "ash/shelf/shelf.h"
-#include "ash/shelf/shelf_widget.h"
-#include "ash/shell.h"
-#include "ash/system/status_area_widget.h"
-#include "ash/system/unified/unified_system_tray.h"
-#include "base/logging.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/views/view.h"
-
-namespace ash {
-namespace {
-
-}  // namespace
-
-FirstRunHelper::FirstRunHelper() = default;
-
-FirstRunHelper::~FirstRunHelper() = default;
-
-void FirstRunHelper::BindRequest(mojom::FirstRunHelperRequest request) {
-  bindings_.AddBinding(this, std::move(request));
-}
-
-void FirstRunHelper::Start(mojom::FirstRunHelperClientPtr client) {
-  client_ = std::move(client);
-  cleaner_ = std::make_unique<DesktopCleaner>();
-  Shell::Get()->session_controller()->AddObserver(this);
-}
-
-void FirstRunHelper::Stop() {
-  Shell::Get()->session_controller()->RemoveObserver(this);
-  // Ensure the tray is closed.
-  CloseTrayBubble();
-  cleaner_.reset();
-}
-
-void FirstRunHelper::GetAppListButtonBounds(GetAppListButtonBoundsCallback cb) {
-  Shelf* shelf = Shelf::ForWindow(Shell::GetPrimaryRootWindow());
-  AppListButton* app_button = shelf->shelf_widget()->GetAppListButton();
-  std::move(cb).Run(app_button->GetBoundsInScreen());
-}
-
-void FirstRunHelper::OpenTrayBubble(OpenTrayBubbleCallback cb) {
-  UnifiedSystemTray* tray = Shell::Get()
-                                ->GetPrimaryRootWindowController()
-                                ->GetStatusAreaWidget()
-                                ->unified_system_tray();
-  tray->ShowBubble(false /* show_by_click */);
-  std::move(cb).Run(tray->GetBubbleBoundsInScreen());
-}
-
-void FirstRunHelper::CloseTrayBubble() {
-  Shell::Get()
-      ->GetPrimaryRootWindowController()
-      ->GetStatusAreaWidget()
-      ->unified_system_tray()
-      ->CloseBubble();
-}
-
-void FirstRunHelper::OnLockStateChanged(bool locked) {
-  Cancel();
-}
-
-void FirstRunHelper::OnChromeTerminating() {
-  Cancel();
-}
-
-void FirstRunHelper::FlushForTesting() {
-  client_.FlushForTesting();
-}
-
-void FirstRunHelper::Cancel() {
-  if (client_)
-    client_->OnCancelled();
-}
-
-}  // namespace ash
diff --git a/ash/first_run/first_run_helper.h b/ash/first_run/first_run_helper.h
deleted file mode 100644
index ca1f0da7..0000000
--- a/ash/first_run/first_run_helper.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_FIRST_RUN_FIRST_RUN_HELPER_H_
-#define ASH_FIRST_RUN_FIRST_RUN_HELPER_H_
-
-#include "ash/ash_export.h"
-#include "ash/public/interfaces/first_run_helper.mojom.h"
-#include "ash/session/session_observer.h"
-#include "base/macros.h"
-#include "base/observer_list.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-
-namespace ash {
-
-class DesktopCleaner;
-
-// Interface used by first-run tutorial to manipulate and retrieve information
-// about shell elements.
-class ASH_EXPORT FirstRunHelper : public mojom::FirstRunHelper,
-                                  public SessionObserver {
- public:
-  FirstRunHelper();
-  ~FirstRunHelper() override;
-
-  void BindRequest(mojom::FirstRunHelperRequest request);
-
-  // mojom::FirstRunHelper:
-  void Start(mojom::FirstRunHelperClientPtr client) override;
-  void Stop() override;
-  void GetAppListButtonBounds(GetAppListButtonBoundsCallback cb) override;
-  void OpenTrayBubble(OpenTrayBubbleCallback cb) override;
-  void CloseTrayBubble() override;
-
-  // SessionObserver:
-  void OnLockStateChanged(bool locked) override;
-  void OnChromeTerminating() override;
-
-  void FlushForTesting();
-
- private:
-  // Notifies the client to cancel the tutorial.
-  void Cancel();
-
-  // Bindings for clients of the mojo interface.
-  mojo::BindingSet<mojom::FirstRunHelper> bindings_;
-
-  // Client interface (e.g. chrome).
-  mojom::FirstRunHelperClientPtr client_;
-
-  std::unique_ptr<DesktopCleaner> cleaner_;
-
-  DISALLOW_COPY_AND_ASSIGN(FirstRunHelper);
-};
-
-}  // namespace ash
-
-#endif  // ASH_FIRST_RUN_FIRST_RUN_HELPER_H_
diff --git a/ash/first_run/first_run_helper_impl.cc b/ash/first_run/first_run_helper_impl.cc
new file mode 100644
index 0000000..223e0cfc
--- /dev/null
+++ b/ash/first_run/first_run_helper_impl.cc
@@ -0,0 +1,80 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/first_run/first_run_helper_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "ash/app_list/views/app_list_view.h"
+#include "ash/first_run/desktop_cleaner.h"
+#include "ash/root_window_controller.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shelf/app_list_button.h"
+#include "ash/shelf/shelf.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "base/logging.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+// static
+std::unique_ptr<FirstRunHelper> FirstRunHelper::Start(
+    base::OnceClosure on_cancelled) {
+  return std::make_unique<FirstRunHelperImpl>(std::move(on_cancelled));
+}
+
+FirstRunHelperImpl::FirstRunHelperImpl(base::OnceClosure on_cancelled)
+    : on_cancelled_(std::move(on_cancelled)),
+      cleaner_(std::make_unique<DesktopCleaner>()) {
+  Shell::Get()->session_controller()->AddObserver(this);
+}
+
+FirstRunHelperImpl::~FirstRunHelperImpl() {
+  Shell::Get()->session_controller()->RemoveObserver(this);
+  // Ensure the tray is closed.
+  CloseTrayBubble();
+}
+
+gfx::Rect FirstRunHelperImpl::GetAppListButtonBounds() {
+  Shelf* shelf = Shelf::ForWindow(Shell::GetPrimaryRootWindow());
+  AppListButton* app_button = shelf->shelf_widget()->GetAppListButton();
+  return app_button->GetBoundsInScreen();
+}
+
+gfx::Rect FirstRunHelperImpl::OpenTrayBubble() {
+  UnifiedSystemTray* tray = Shell::Get()
+                                ->GetPrimaryRootWindowController()
+                                ->GetStatusAreaWidget()
+                                ->unified_system_tray();
+  tray->ShowBubble(false /* show_by_click */);
+  return tray->GetBubbleBoundsInScreen();
+}
+
+void FirstRunHelperImpl::CloseTrayBubble() {
+  Shell::Get()
+      ->GetPrimaryRootWindowController()
+      ->GetStatusAreaWidget()
+      ->unified_system_tray()
+      ->CloseBubble();
+}
+
+void FirstRunHelperImpl::OnLockStateChanged(bool locked) {
+  Cancel();
+}
+
+void FirstRunHelperImpl::OnChromeTerminating() {
+  Cancel();
+}
+
+void FirstRunHelperImpl::Cancel() {
+  if (!on_cancelled_.is_null())
+    std::move(on_cancelled_).Run();
+}
+
+}  // namespace ash
diff --git a/ash/first_run/first_run_helper_impl.h b/ash/first_run/first_run_helper_impl.h
new file mode 100644
index 0000000..d8c7f27
--- /dev/null
+++ b/ash/first_run/first_run_helper_impl.h
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_FIRST_RUN_FIRST_RUN_HELPER_IMPL_H_
+#define ASH_FIRST_RUN_FIRST_RUN_HELPER_IMPL_H_
+
+#include "ash/ash_export.h"
+#include "ash/public/cpp/first_run_helper.h"
+#include "ash/session/session_observer.h"
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace ash {
+
+class DesktopCleaner;
+
+// Interface used by first-run tutorial to manipulate and retrieve information
+// about shell elements.
+class ASH_EXPORT FirstRunHelperImpl : public FirstRunHelper,
+                                      public SessionObserver {
+ public:
+  explicit FirstRunHelperImpl(base::OnceClosure on_cancelled);
+  ~FirstRunHelperImpl() override;
+
+  // FirstRunHelper:
+  gfx::Rect GetAppListButtonBounds() override;
+  gfx::Rect OpenTrayBubble() override;
+  void CloseTrayBubble() override;
+
+  // SessionObserver:
+  void OnLockStateChanged(bool locked) override;
+  void OnChromeTerminating() override;
+
+  void FlushForTesting();
+
+ private:
+  // Notifies the client to cancel the tutorial.
+  void Cancel();
+
+  base::OnceClosure on_cancelled_;
+
+  std::unique_ptr<DesktopCleaner> cleaner_;
+
+  DISALLOW_COPY_AND_ASSIGN(FirstRunHelperImpl);
+};
+
+}  // namespace ash
+
+#endif  // ASH_FIRST_RUN_FIRST_RUN_HELPER_IMPL_H_
diff --git a/ash/first_run/first_run_helper_unittest.cc b/ash/first_run/first_run_helper_impl_unittest.cc
similarity index 74%
rename from ash/first_run/first_run_helper_unittest.cc
rename to ash/first_run/first_run_helper_impl_unittest.cc
index 070519c..4c8d020 100644
--- a/ash/first_run/first_run_helper_unittest.cc
+++ b/ash/first_run/first_run_helper_impl_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/first_run/first_run_helper.h"
+#include "ash/first_run/first_run_helper_impl.h"
 
 #include "ash/first_run/desktop_cleaner.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -12,25 +12,20 @@
 
 namespace ash {
 
-class FirstRunHelperTest : public AshTestBase,
-                           public mojom::FirstRunHelperClient {
+class FirstRunHelperTest : public AshTestBase {
  public:
-  FirstRunHelperTest() : cancelled_times_(0) {}
-
+  FirstRunHelperTest() = default;
   ~FirstRunHelperTest() override = default;
 
   void SetUp() override {
     AshTestBase::SetUp();
     CheckContainersAreVisible();
-    helper_ = Shell::Get()->first_run_helper();
-    mojom::FirstRunHelperClientPtr client_ptr;
-    binding_.Bind(mojo::MakeRequest(&client_ptr));
-    helper_->Start(std::move(client_ptr));
+    helper_ = FirstRunHelper::Start(base::BindOnce(
+        &FirstRunHelperTest::OnCancelled, base::Unretained(this)));
   }
 
   void TearDown() override {
-    helper_->Stop();
-    helper_ = nullptr;
+    helper_.reset();
     CheckContainersAreVisible();
     AshTestBase::TearDown();
   }
@@ -57,17 +52,15 @@
     }
   }
 
-  FirstRunHelper* helper() { return helper_; }
+  FirstRunHelper* helper() { return helper_.get(); }
 
   int cancelled_times() const { return cancelled_times_; }
 
  private:
-  // mojom::FirstRunHelperClient:
-  void OnCancelled() override { ++cancelled_times_; }
+  void OnCancelled() { ++cancelled_times_; }
 
-  FirstRunHelper* helper_;
-  mojo::Binding<mojom::FirstRunHelperClient> binding_{this};
-  int cancelled_times_;
+  std::unique_ptr<FirstRunHelper> helper_;
+  int cancelled_times_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(FirstRunHelperTest);
 };
@@ -81,14 +74,12 @@
 // Tests that screen lock cancels the tutorial.
 TEST_F(FirstRunHelperTest, ScreenLock) {
   GetSessionControllerClient()->LockScreen();
-  helper()->FlushForTesting();
   EXPECT_EQ(cancelled_times(), 1);
 }
 
 // Tests that shutdown cancels the tutorial.
 TEST_F(FirstRunHelperTest, ChromeTerminating) {
   Shell::Get()->session_controller()->NotifyChromeTerminating();
-  helper()->FlushForTesting();
   EXPECT_EQ(cancelled_times(), 1);
 }
 
diff --git a/ash/mojo_interface_factory.cc b/ash/mojo_interface_factory.cc
index fa7e940..adbf95f 100644
--- a/ash/mojo_interface_factory.cc
+++ b/ash/mojo_interface_factory.cc
@@ -17,7 +17,6 @@
 #include "ash/autotest/shelf_integration_test_api.h"
 #include "ash/display/cros_display_config.h"
 #include "ash/events/event_rewriter_controller.h"
-#include "ash/first_run/first_run_helper.h"
 #include "ash/highlighter/highlighter_controller.h"
 #include "ash/ime/ime_controller.h"
 #include "ash/ime/ime_engine_factory_registry.h"
@@ -117,11 +116,6 @@
   Shell::Get()->event_rewriter_controller()->BindRequest(std::move(request));
 }
 
-void BindFirstRunHelperRequestOnMainThread(
-    mojom::FirstRunHelperRequest request) {
-  Shell::Get()->first_run_helper()->BindRequest(std::move(request));
-}
-
 void BindHighlighterControllerRequestOnMainThread(
     mojom::HighlighterControllerRequest request) {
   Shell::Get()->highlighter_controller()->BindRequest(std::move(request));
@@ -262,9 +256,6 @@
       base::BindRepeating(&BindEventRewriterControllerRequestOnMainThread),
       main_thread_task_runner);
   registry->AddInterface(
-      base::BindRepeating(&BindFirstRunHelperRequestOnMainThread),
-      main_thread_task_runner);
-  registry->AddInterface(
       base::BindRepeating(&BindHighlighterControllerRequestOnMainThread),
       main_thread_task_runner);
   registry->AddInterface(
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 3edda99..b35b9af 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -20,6 +20,8 @@
     "app_list/app_list_metrics.h",
     "app_list/app_list_switches.cc",
     "app_list/app_list_switches.h",
+    "app_list/app_list_types.cc",
+    "app_list/app_list_types.h",
     "app_list/internal_app_id_constants.h",
     "app_list/term_break_iterator.cc",
     "app_list/term_break_iterator.h",
@@ -36,6 +38,7 @@
     "ash_features.h",
     "ash_pref_names.cc",
     "ash_pref_names.h",
+    "ash_public_export.h",
     "ash_switches.cc",
     "ash_switches.h",
     "ash_typography.cc",
@@ -148,6 +151,7 @@
     "//chromeos/constants",
     "//chromeos/dbus/power:power_manager_proto",
     "//components/prefs",
+    "//components/sync:rest_of_sync",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
     "//services/ws/public/mojom",
@@ -165,7 +169,6 @@
   ]
 
   public_deps = [
-    ":base",
     "//ash/public/interfaces:interfaces_internal",
     "//base",
     "//components/session_manager:base",
@@ -178,24 +181,6 @@
   output_name = "ash_public_cpp"
 }
 
-# This is listed separately because app_list.mojom type mapping depends on it
-# but //ash/public/interfaces could not depend on //ash/public/cpp.
-# TODO(crbug.com/958134): Move this back when app_list.mojom is gone.
-source_set("base") {
-  sources = [
-    "app_list/app_list_types.cc",
-    "app_list/app_list_types.h",
-    "ash_public_export.h",
-  ]
-
-  defines = [ "ASH_PUBLIC_IMPLEMENTATION" ]
-
-  public_deps = [
-    "//components/sync:rest_of_sync",
-    "//ui/gfx",
-  ]
-}
-
 source_set("manifest") {
   sources = [
     "manifest.cc",
diff --git a/ash/public/cpp/app_list/app_list_client.h b/ash/public/cpp/app_list/app_list_client.h
index 739c746..669792a 100644
--- a/ash/public/cpp/app_list/app_list_client.h
+++ b/ash/public/cpp/app_list/app_list_client.h
@@ -10,21 +10,19 @@
 #include <memory>
 #include <string>
 
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/ash_public_export.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "base/callback_forward.h"
 #include "base/strings/string16.h"
 #include "components/sync/model/string_ordinal.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "services/content/public/mojom/navigable_contents_factory.mojom.h"
 #include "ui/base/models/simple_menu_model.h"
 
 namespace app_list {
 
 class AppListController;
 
-// TODO(crbug.com/958134): Remove the alias and app_list.mojom.h include.
-using AppListLaunchedFrom = ash::mojom::AppListLaunchedFrom;
-using AppListLaunchType = ash::mojom::AppListLaunchType;
-
 // A client interface implemented in Chrome to handle calls from Ash.
 // These include:
 // - When Chrome components are needed to get involved in the user's actions on
@@ -55,8 +53,8 @@
   // page.
   virtual void OpenSearchResult(const std::string& result_id,
                                 int event_flags,
-                                AppListLaunchedFrom launched_from,
-                                AppListLaunchType launch_type,
+                                ash::AppListLaunchedFrom launched_from,
+                                ash::AppListLaunchType launch_type,
                                 int suggestion_index) = 0;
   // Invokes a custom action on a result with |result_id|.
   // |action_index| corresponds to the index of an action on the search result,
@@ -131,7 +129,7 @@
           receiver) = 0;
 
  protected:
-  virtual ~AppListClient() {}
+  virtual ~AppListClient() = default;
 };
 
 }  // namespace app_list
diff --git a/ash/public/cpp/app_list/app_list_controller.h b/ash/public/cpp/app_list/app_list_controller.h
index ede93977..3763b02 100644
--- a/ash/public/cpp/app_list/app_list_controller.h
+++ b/ash/public/cpp/app_list/app_list_controller.h
@@ -7,10 +7,10 @@
 
 #include <memory>
 
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/ash_public_export.h"
-// TODO(crbug.com/958134): Remove.
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
 #include "base/strings/string16.h"
 
 namespace app_list {
@@ -27,8 +27,6 @@
 //   happen while installing/uninstalling apps and the app list gets toggled.
 class ASH_PUBLIC_EXPORT AppListController {
  public:
-  using SearchResultMetadataPtr = ash::mojom::SearchResultMetadataPtr;
-
   // Gets the instance.
   static AppListController* Get();
 
@@ -84,7 +82,7 @@
 
   // Publishes search results to Ash to render them.
   virtual void PublishSearchResults(
-      std::vector<SearchResultMetadataPtr> results) = 0;
+      std::vector<std::unique_ptr<ash::SearchResultMetadata>> results) = 0;
 
   // Updates an item's metadata (e.g. name, position, etc).
   virtual void SetItemMetadata(
@@ -110,7 +108,8 @@
       bool is_search_engine_google) = 0;
 
   // Updates a search rresult's metadata.
-  virtual void SetSearchResultMetadata(SearchResultMetadataPtr metadata) = 0;
+  virtual void SetSearchResultMetadata(
+      std::unique_ptr<ash::SearchResultMetadata> metadata) = 0;
 
   // Updates whether a search result is being installed.
   virtual void SetSearchResultIsInstalling(const std::string& id,
diff --git a/ash/public/cpp/app_list/app_list_metrics.cc b/ash/public/cpp/app_list/app_list_metrics.cc
index 583bb4a..3535cb5d 100644
--- a/ash/public/cpp/app_list/app_list_metrics.cc
+++ b/ash/public/cpp/app_list/app_list_metrics.cc
@@ -4,6 +4,7 @@
 
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/metrics/histogram_macros.h"
 
 namespace {
@@ -24,7 +25,7 @@
 namespace app_list {
 
 void RecordSearchResultOpenTypeHistogram(
-    ash::mojom::AppListLaunchedFrom launch_location,
+    ash::AppListLaunchedFrom launch_location,
     SearchResultType type,
     bool is_tablet_mode) {
   if (type == SEARCH_RESULT_TYPE_BOUNDARY) {
@@ -33,7 +34,7 @@
   }
 
   switch (launch_location) {
-    case ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox:
+    case ash::AppListLaunchedFrom::kLaunchedFromSearchBox:
       UMA_HISTOGRAM_ENUMERATION(kAppListSearchResultOpenTypeHistogram, type,
                                 SEARCH_RESULT_TYPE_BOUNDARY);
       if (is_tablet_mode) {
@@ -45,7 +46,7 @@
             SEARCH_RESULT_TYPE_BOUNDARY);
       }
       break;
-    case ash::mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip:
+    case ash::AppListLaunchedFrom::kLaunchedFromSuggestionChip:
       if (is_tablet_mode) {
         UMA_HISTOGRAM_ENUMERATION(
             kAppListSuggestionChipOpenTypeHistogramInTablet, type,
@@ -56,8 +57,8 @@
             SEARCH_RESULT_TYPE_BOUNDARY);
       }
       break;
-    case ash::mojom::AppListLaunchedFrom::kLaunchedFromShelf:
-    case ash::mojom::AppListLaunchedFrom::kLaunchedFromGrid:
+    case ash::AppListLaunchedFrom::kLaunchedFromShelf:
+    case ash::AppListLaunchedFrom::kLaunchedFromGrid:
       // Search results don't live in the shelf or the app grid.
       NOTREACHED();
       break;
diff --git a/ash/public/cpp/app_list/app_list_metrics.h b/ash/public/cpp/app_list/app_list_metrics.h
index 03ac37c..d5d2f1b 100644
--- a/ash/public/cpp/app_list/app_list_metrics.h
+++ b/ash/public/cpp/app_list/app_list_metrics.h
@@ -6,7 +6,10 @@
 #define ASH_PUBLIC_CPP_APP_LIST_APP_LIST_METRICS_H_
 
 #include "ash/public/cpp/ash_public_export.h"
-#include "ash/public/interfaces/app_list.mojom.h"
+
+namespace ash {
+enum class AppListLaunchedFrom;
+}
 
 namespace app_list {
 // The type of the ChromeSearchResult. This is used for logging so do not
@@ -72,7 +75,7 @@
 };
 
 ASH_PUBLIC_EXPORT void RecordSearchResultOpenTypeHistogram(
-    ash::mojom::AppListLaunchedFrom launch_location,
+    ash::AppListLaunchedFrom launch_location,
     SearchResultType type,
     bool is_tablet_mode);
 
diff --git a/ash/public/cpp/app_list/app_list_types.cc b/ash/public/cpp/app_list/app_list_types.cc
index b873b59..0403449 100644
--- a/ash/public/cpp/app_list/app_list_types.cc
+++ b/ash/public/cpp/app_list/app_list_types.cc
@@ -51,4 +51,12 @@
 
 SearchResultAction::~SearchResultAction() = default;
 
+////////////////////////////////////////////////////////////////////////////////
+// SearchResultMetadata:
+
+SearchResultMetadata::SearchResultMetadata() = default;
+SearchResultMetadata::SearchResultMetadata(const SearchResultMetadata& rhs) =
+    default;
+SearchResultMetadata::~SearchResultMetadata() = default;
+
 }  // namespace ash
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index 65e576a..3e01216a 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -9,9 +9,12 @@
 #include <vector>
 
 #include "ash/public/cpp/ash_public_export.h"
+#include "base/optional.h"
+#include "base/strings/string16.h"
 #include "components/sync/model/string_ordinal.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/range/range.h"
+#include "url/gurl.h"
 
 namespace ash {
 
@@ -64,6 +67,28 @@
   kStatusSyncing,  // Syncing apps or installing synced apps.
 };
 
+// The UI component the user launched the search result from. Must match
+// chrome/browser/ui/app_list/app_launch_event_logger.proto.
+// This enum is used in a histogram, do not remove/renumber entries. If you're
+// adding to this enum with the intention that it will be logged, update the
+// AppListLaunchedFrom enum listing in tools/metrics/histograms/enums.xml.
+enum class AppListLaunchedFrom {
+  kLaunchedFromGrid = 1,
+  kLaunchedFromSuggestionChip = 2,
+  kLaunchedFromShelf = 3,
+  kLaunchedFromSearchBox = 4,
+  kMaxValue = kLaunchedFromSearchBox,
+};
+
+// The UI representation of the search result. Currently all search results
+// that are not apps (OminboxResult, LauncherSearcResult, etc.) are grouped
+// into kSearchResult. Meanwhile SearchResultTileItemView (shown in zero state)
+// and suggested chips are considered kAppSearchResult.
+enum class AppListLaunchType {
+  kSearchResult = 0,
+  kAppSearchResult,
+};
+
 // Type of the search result, which is set in Chrome.
 enum class SearchResultType {
   kUnknown,         // Unknown type. Don't use over IPC
@@ -144,6 +169,84 @@
 };
 using SearchResultActions = std::vector<SearchResultAction>;
 
+// A structure holding the common information which is sent from chrome to ash,
+// representing a search result.
+struct ASH_PUBLIC_EXPORT SearchResultMetadata {
+  SearchResultMetadata();
+  SearchResultMetadata(const SearchResultMetadata& rhs);
+  ~SearchResultMetadata();
+
+  // The id of the result.
+  std::string id;
+
+  // The title of the result, e.g. an app's name, an autocomplete query, etc.
+  base::string16 title;
+
+  // A detail string of this result.
+  base::string16 details;
+
+  // An text to be announced by a screen reader app.
+  base::string16 accessible_name;
+
+  // How the title matches the query. See the SearchResultTag section for more
+  // details.
+  std::vector<SearchResultTag> title_tags;
+
+  // How the details match the query. See the SearchResultTag section for more
+  // details.
+  std::vector<SearchResultTag> details_tags;
+
+  // Actions that can be performed on this result. See the SearchResultAction
+  // section for more details.
+  std::vector<SearchResultAction> actions;
+
+  // The average rating score of the app corresponding to this result, ranged
+  // from 0 to 5. It's negative if there's no rating for the result.
+  float rating = -1.0;
+
+  // A formatted price string, e.g. "$7.09", "HK$3.94", etc.
+  base::string16 formatted_price;
+
+  // The type of this result.
+  SearchResultType result_type = SearchResultType::kUnknown;
+
+  // How this result is displayed.
+  SearchResultDisplayType display_type = SearchResultDisplayType::kList;
+
+  // A score to determine the result display order.
+  double display_score = 0;
+
+  // Whether this is searched from Omnibox.
+  bool is_omnibox_search = false;
+
+  // Whether this result is installing.
+  bool is_installing = false;
+
+  // A query URL associated with this result. The meaning and treatment of the
+  // URL (e.g. displaying inline web contents) is dependent on the result type.
+  base::Optional<GURL> query_url;
+
+  // An optional id that identifies an equivalent result to this result. Answer
+  // card result has this set to remove the equivalent omnibox
+  // search-what-you-typed result when there is an answer card for the query.
+  base::Optional<std::string> equivalent_result_id;
+
+  // The icon of this result.
+  gfx::ImageSkia icon;
+
+  // The icon of this result in a smaller dimension to be rendered in suggestion
+  // chip view.
+  gfx::ImageSkia chip_icon;
+
+  // The badge icon of this result that indicates its type, e.g. installable
+  // from PlayStore, installable from WebStore, etc.
+  gfx::ImageSkia badge_icon;
+
+  // If set to true, whether or not to send visibility updates through to to
+  // the chrome side when this result is set visible/invisible.
+  bool notify_visibility_change = false;
+};
+
 }  // namespace ash
 
 #endif  // ASH_PUBLIC_CPP_APP_LIST_APP_LIST_TYPES_H_
diff --git a/ash/public/cpp/first_run_helper.h b/ash/public/cpp/first_run_helper.h
new file mode 100644
index 0000000..35b57f8b
--- /dev/null
+++ b/ash/public/cpp/first_run_helper.h
@@ -0,0 +1,49 @@
+// 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 ASH_PUBLIC_CPP_FIRST_RUN_HELPER_H_
+#define ASH_PUBLIC_CPP_FIRST_RUN_HELPER_H_
+
+#include <memory>
+
+#include "ash/ash_export.h"
+#include "base/callback_forward.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ash {
+
+// Allows clients to control pieces of the UI used in first-run tutorials.
+// Methods exist here instead of on the Shelf or SystemTray interfaces due to
+// small behavior differences (all methods only affect the primary display,
+// opening the system tray bubble is persistent, etc.).
+class ASH_EXPORT FirstRunHelper {
+ public:
+  virtual ~FirstRunHelper() = default;
+
+  // Cleans up the ash UI on tutorial start. Returns the instance for further
+  // method calls. Destroying the instance restores the ash UI on tutorial.
+  // |on_cancelled| will be called when something happened inside ash that
+  // should cancel the tutorial (e.g. the device is shutting down).
+  static std::unique_ptr<FirstRunHelper> Start(base::OnceClosure on_cancelled);
+
+  // Returns the bounds of the app list button on the primary display in screen
+  // coordinates.
+  virtual gfx::Rect GetAppListButtonBounds() = 0;
+
+  // Opens the system tray bubble menu to show the default view. Does nothing if
+  // the bubble is already open. The bubble stays open until explicitly closed.
+  // Returns bubble bounds in screen coordinates.
+  virtual gfx::Rect OpenTrayBubble() = 0;
+
+  // Closes the system tray bubble menu. Does nothing if the bubble is already
+  // closed.
+  virtual void CloseTrayBubble() = 0;
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_FIRST_RUN_HELPER_H_
diff --git a/ash/public/cpp/manifest.cc b/ash/public/cpp/manifest.cc
index 48433a3..d830c8f 100644
--- a/ash/public/cpp/manifest.cc
+++ b/ash/public/cpp/manifest.cc
@@ -13,7 +13,6 @@
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/cros_display_config.mojom.h"
 #include "ash/public/interfaces/event_rewriter_controller.mojom.h"
-#include "ash/public/interfaces/first_run_helper.mojom.h"
 #include "ash/public/interfaces/highlighter_controller.mojom.h"
 #include "ash/public/interfaces/ime_controller.mojom.h"
 #include "ash/public/interfaces/keyboard_controller.mojom.h"
@@ -75,9 +74,8 @@
                   mojom::AssistantVolumeControl,
                   mojom::KioskNextShellController,
                   mojom::CrosDisplayConfigController,
-                  mojom::EventRewriterController, mojom::FirstRunHelper,
-                  mojom::HighlighterController, mojom::ImeController,
-                  ime::mojom::ImeEngineFactoryRegistry,
+                  mojom::EventRewriterController, mojom::HighlighterController,
+                  mojom::ImeController, ime::mojom::ImeEngineFactoryRegistry,
                   mojom::KeyboardController, mojom::LocaleUpdateController,
                   mojom::LoginScreen, mojom::MediaController,
                   mojom::NewWindowController, mojom::NightLightController,
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index 35411d6f..b8f99bf 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -19,7 +19,6 @@
     "accessibility_controller.mojom",
     "accessibility_controller_enums.mojom",
     "accessibility_focus_ring_controller.mojom",
-    "app_list.mojom",
     "app_list_view.mojom",
     "ash_message_center_controller.mojom",
     "assistant_controller.mojom",
@@ -29,7 +28,6 @@
     "constants.mojom",
     "cros_display_config.mojom",
     "event_rewriter_controller.mojom",
-    "first_run_helper.mojom",
     "highlighter_controller.mojom",
     "ime_controller.mojom",
     "ime_info.mojom",
diff --git a/ash/public/interfaces/app_list.mojom b/ash/public/interfaces/app_list.mojom
deleted file mode 100644
index 9f5fb1b..0000000
--- a/ash/public/interfaces/app_list.mojom
+++ /dev/null
@@ -1,148 +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.
-
-module ash.mojom;
-
-import "components/sync/mojo/syncer.mojom";
-import "mojo/public/mojom/base/string16.mojom";
-import "mojo/public/mojom/base/unguessable_token.mojom";
-import "services/content/public/mojom/navigable_contents_factory.mojom";
-import "ui/gfx/geometry/mojo/geometry.mojom";
-import "ui/gfx/image/mojo/image.mojom";
-import "ui/gfx/range/mojo/range.mojom";
-import "url/mojom/url.mojom";
-
-// A structure holding the common information which is sent from chrome to ash,
-// representing a search result.
-struct SearchResultMetadata {
-  string id;                  // The id of the result.
-  mojo_base.mojom.String16 title;    // The title of the result, e.g. an app's
-                                     // name, an autocomplete query, etc.
-  mojo_base.mojom.String16 details;  // A detail string of this result.
-  mojo_base.mojom.String16 accessible_name;
-                                     // An text to be announced by a screen
-                                     // reader app.
-  array<SearchResultTag> title_tags;    // How the title matches the query. See
-                                        // the SearchResultTag section for
-                                        // more details.
-  array<SearchResultTag> details_tags;  // How the details match the query. See
-                                        // the SearchResultTag section for
-                                        // more details.
-  array<SearchResultAction> actions;    // Actions that can be performed on this
-                                        // result. See the SearchResultAction
-                                        // section for more details.
-  float rating = -1.0;        // The average rating score of the app
-                              // corresponding to this result, ranged from 0 to
-                              // 5. It's negative if there's no rating for the
-                              // result.
-  mojo_base.mojom.String16 formatted_price;  // A formatted price string, e.g.
-                                             // "$7.09", "HK$3.94", etc.
-  SearchResultType result_type;              // The type of this result.
-  SearchResultDisplayType display_type = kList;
-                                             // How this result is displayed.
-  double display_score;       // A score to determine the result display order.
-  bool is_omnibox_search;     // Whether this result is searched from Omnibox.
-  bool is_installing;         // Whether this result is installing.
-
-  url.mojom.Url? query_url;  // A query URL associated with this result. The
-                             // meaning and treatment of the URL
-                             // (e.g. displaying inline web contents) is
-                             // dependent on the result type.
-
-  string? equivalent_result_id;  // An optional id that identifies an equivalent
-                                 // result to this result. Answer card result
-                                 // has this set to remove the equivalent
-                                 // omnibox search-what-you-typed result when
-                                 // there is an answer card for the query.
-
-  gfx.mojom.ImageSkia? icon;  // The icon of this result.
-  gfx.mojom.ImageSkia? chip_icon;  // The icon of this result in a smaller
-                                   // dimension to be rendered in suggestion
-                                   // chip view.
-  gfx.mojom.ImageSkia? badge_icon;  // The badge icon of this result that
-                                    // indicates its type, e.g. installable from
-                                    // PlayStore, installable from WebStore,
-                                    // etc.
-  // If set to true, whether or not to send visibility updates through to to
-  // the chrome side when this result is set visible/invisible.
-  bool notify_visibility_change;
-};
-
-// All possible states of the app list.
-enum AppListState {
-  kStateApps = 0,
-  kStateSearchResults,
-  kStateStart_DEPRECATED,
-  kStateEmbeddedAssistant,
-};
-
-// The status of the app list model.
-enum AppListModelStatus {
-  kStatusNormal,
-  kStatusSyncing,  // Syncing apps or installing synced apps.
-};
-
-// The UI component the user launched the search result from. Must match
-// chrome/browser/ui/app_list/app_launch_event_logger.proto.
-// This enum is used in a histogram, do not remove/renumber entries. If you're
-// adding to this enum with the intention that it will be logged, update the
-// AppListLaunchedFrom enum listing in tools/metrics/histograms/enums.xml.
-enum AppListLaunchedFrom {
-  kLaunchedFromGrid = 1,
-  kLaunchedFromSuggestionChip = 2,
-  kLaunchedFromShelf = 3,
-  kLaunchedFromSearchBox = 4,
-};
-
-// The UI representation of the search result. Currently all search results
-// that are not apps (OminboxResult, LauncherSearcResult, etc.) are grouped
-// into kSearchResult. Meanwhile SearchResultTileItemView (shown in zero state)
-// and suggested chips are considered kAppSearchResult.
-enum AppListLaunchType {
- kSearchResult = 0,
- kAppSearchResult,
-};
-
-// How the result should be displayed. Do not change the order of these as
-// they are used for metrics.
-enum SearchResultDisplayType {
-  kNone = 0,
-  kList,
-  kTile,
-  kRecommendation,
-  kCard,
-  // Add new values here.
-};
-
-// Type of the search result, which is set in Chrome.
-enum SearchResultType {
-  kInstalledApp,    // Installed apps.
-  kPlayStoreApp,    // Installable apps from PlayStore.
-  kInstantApp,      // Instant apps.
-  kInternalApp,     // Chrome OS apps.
-  kOmnibox,         // Results from Omninbox.
-  kLauncher,        // Results from launcher search (currently only from Files).
-  kAnswerCard,      // WebContents based answer card.
-  kPlayStoreReinstallApp, // Reinstall recommendations from PlayStore.
-  kArcAppShortcut,  // ARC++ app shortcuts.
-  // Add new values here.
-};
-
-// A tagged range in search result text.
-struct SearchResultTag {
-  // Similar to ACMatchClassification::Style, the style values are not
-  // mutually exclusive.
-  int16 styles;
-  gfx.mojom.Range range;
-};
-
-// Data representing an action that can be performed on a search result.
-// An action could be represented as an image button, it will be always visibe
-// if |visible_on_hover| is false; otherwise how it is visible only when
-// when its associated search result is hovered.
-struct SearchResultAction {
-  mojo_base.mojom.String16 tooltip_text;
-  gfx.mojom.ImageSkia image;
-  bool visible_on_hover;
-};
diff --git a/ash/public/interfaces/app_list.typemap b/ash/public/interfaces/app_list.typemap
deleted file mode 100644
index 672d5ce..0000000
--- a/ash/public/interfaces/app_list.typemap
+++ /dev/null
@@ -1,21 +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.
-
-mojom = "//ash/public/interfaces/app_list.mojom"
-public_headers = [ "//ash/public/cpp/app_list/app_list_types.h" ]
-traits_headers = [ "//ash/public/cpp/app_list/app_list_struct_traits.h" ]
-sources = [
-  "//ash/public/cpp/app_list/app_list_struct_traits.cc",
-]
-deps = [
-  "//ash/public/cpp:base",
-]
-type_mappings = [
-  "ash.mojom.AppListState=ash::AppListState",
-  "ash.mojom.AppListModelStatus=ash::AppListModelStatus",
-  "ash.mojom.SearchResultType=ash::SearchResultType",
-  "ash.mojom.SearchResultDisplayType=ash::SearchResultDisplayType",
-  "ash.mojom.SearchResultTag=ash::SearchResultTag",
-  "ash.mojom.SearchResultAction=ash::SearchResultAction",
-]
diff --git a/ash/public/interfaces/first_run_helper.mojom b/ash/public/interfaces/first_run_helper.mojom
deleted file mode 100644
index 5d7791b..0000000
--- a/ash/public/interfaces/first_run_helper.mojom
+++ /dev/null
@@ -1,40 +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.
-
-module ash.mojom;
-
-import "ui/gfx/geometry/mojo/geometry.mojom";
-
-// Allows clients to control pieces of the UI used in first-run tutorials.
-// Exists because the first-run tutorial is a component extension run by
-// Chrome. Methods exist here instead of on the Shelf or SystemTray interfaces
-// due to small behavior differences (all methods only affect the primary
-// display, opening the system tray bubble is persistent, etc.).
-interface FirstRunHelper {
-  // Cleans up the ash UI on tutorial start.
-  Start(FirstRunHelperClient client);
-
-  // Restores the ash UI on tutorial stop.
-  Stop();
-
-  // Returns the bounds of the app list button on the primary display in screen
-  // coordinates.
-  GetAppListButtonBounds() => (gfx.mojom.Rect screen_bounds);
-
-  // Opens the system tray bubble menu to show the default view. Does nothing if
-  // the bubble is already open. The bubble stays open until explicitly closed.
-  // Returns bubble bounds in screen coordinates.
-  OpenTrayBubble() => (gfx.mojom.Rect screen_bounds);
-
-  // Closes the system tray bubble menu. Does nothing if the bubble is already
-  // closed.
-  CloseTrayBubble();
-};
-
-// The client for the FirstRunHelper interface, e.g. chrome.
-interface FirstRunHelperClient {
-  // Informs the client that something happened inside ash that should cancel
-  // the tutorial (e.g. the device is shutting down).
-  OnCancelled();
-};
diff --git a/ash/public/interfaces/typemaps.gni b/ash/public/interfaces/typemaps.gni
index a250f1c1..a21e3662 100644
--- a/ash/public/interfaces/typemaps.gni
+++ b/ash/public/interfaces/typemaps.gni
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 typemaps = [
-  "//ash/public/interfaces/app_list.typemap",
   "//ash/public/interfaces/shelf_integration_test_api.typemap",
   "//ash/public/interfaces/user_info.typemap",
   "//ash/public/interfaces/wallpaper.typemap",
diff --git a/ash/shelf/app_list_button.cc b/ash/shelf/app_list_button.cc
index 2997874..22b2192 100644
--- a/ash/shelf/app_list_button.cc
+++ b/ash/shelf/app_list_button.cc
@@ -36,6 +36,8 @@
       l10n_util::GetStringUTF16(IDS_ASH_SHELF_APP_LIST_LAUNCHER_TITLE));
   set_notify_action(Button::NOTIFY_ON_PRESS);
   set_has_ink_drop_action_on_click(false);
+
+  SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
 }
 
 AppListButton::~AppListButton() = default;
@@ -101,4 +103,17 @@
   }
 }
 
+bool AppListButton::DoesIntersectRect(const views::View* target,
+                                      const gfx::Rect& rect) const {
+  DCHECK_EQ(target, this);
+  gfx::Rect button_bounds = target->GetLocalBounds();
+  // Increase clickable area for the button from
+  // (kShelfControlSize x kShelfButtonSize) to
+  // (kShelfButtonSize x kShelfButtonSize).
+  int left_offset = button_bounds.width() - kShelfButtonSize;
+  int bottom_offset = button_bounds.height() - kShelfButtonSize;
+  button_bounds.Inset(gfx::Insets(0, left_offset, bottom_offset, 0));
+  return button_bounds.Intersects(rect);
+}
+
 }  // namespace ash
diff --git a/ash/shelf/app_list_button.h b/ash/shelf/app_list_button.h
index 86c6bfc..37bed766 100644
--- a/ash/shelf/app_list_button.h
+++ b/ash/shelf/app_list_button.h
@@ -11,6 +11,7 @@
 #include "ash/shelf/app_list_button_controller.h"
 #include "ash/shelf/shelf_control_button.h"
 #include "base/macros.h"
+#include "ui/views/view_targeter_delegate.h"
 
 namespace ash {
 
@@ -24,7 +25,8 @@
 //
 // If Assistant is enabled, the button is filled in; long-pressing it will
 // launch Assistant.
-class ASH_EXPORT AppListButton : public ShelfControlButton {
+class ASH_EXPORT AppListButton : public ShelfControlButton,
+                                 public views::ViewTargeterDelegate {
  public:
   static const char kViewClassName[];
 
@@ -47,6 +49,10 @@
   void PaintButtonContents(gfx::Canvas* canvas) override;
 
  private:
+  // views::ViewTargeterDelegate:
+  bool DoesIntersectRect(const views::View* target,
+                         const gfx::Rect& rect) const override;
+
   // The controller used to determine the button's behavior.
   AppListButtonController controller_;
 
diff --git a/ash/shell.cc b/ash/shell.cc
index 291d5fb..98bf6a06 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -45,7 +45,6 @@
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/drag_drop/drag_drop_controller.h"
 #include "ash/events/event_rewriter_controller.h"
-#include "ash/first_run/first_run_helper.h"
 #include "ash/focus_cycler.h"
 #include "ash/frame/non_client_frame_view_ash.h"
 #include "ash/frame/snap_controller_impl.h"
@@ -551,7 +550,6 @@
     : brightness_control_delegate_(
           std::make_unique<system::BrightnessControllerChromeos>()),
       connector_(connector),
-      first_run_helper_(std::make_unique<FirstRunHelper>()),
       focus_cycler_(std::make_unique<FocusCycler>()),
       ime_controller_(std::make_unique<ImeController>()),
       ime_engine_factory_registry_(
diff --git a/ash/shell.h b/ash/shell.h
index 995c109..6ecc61a 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -119,7 +119,6 @@
 class EventClientImpl;
 class EventRewriterController;
 class EventTransformationHandler;
-class FirstRunHelper;
 class FocusCycler;
 class HighContrastController;
 class HighlighterController;
@@ -366,7 +365,6 @@
   HomeScreenController* home_screen_controller() {
     return home_screen_controller_.get();
   }
-  FirstRunHelper* first_run_helper() { return first_run_helper_.get(); }
   ::wm::FocusController* focus_controller() { return focus_controller_.get(); }
   AshFocusRules* focus_rules() { return focus_rules_; }
   FocusCycler* focus_cycler() { return focus_cycler_.get(); }
@@ -672,7 +670,6 @@
       detachable_base_notification_controller_;
   std::unique_ptr<DisplaySpeakerController> display_speaker_controller_;
   std::unique_ptr<DragDropController> drag_drop_controller_;
-  std::unique_ptr<FirstRunHelper> first_run_helper_;
   std::unique_ptr<FocusCycler> focus_cycler_;
   std::unique_ptr<HomeScreenController> home_screen_controller_;
   std::unique_ptr<ImeController> ime_controller_;
diff --git a/ash/shell/example_app_list_client.cc b/ash/shell/example_app_list_client.cc
index 06741cd2..63e0e5c 100644
--- a/ash/shell/example_app_list_client.cc
+++ b/ash/shell/example_app_list_client.cc
@@ -214,7 +214,7 @@
   query = base::i18n::ToLower(trimmed_query);
 
   search_results_.clear();
-  std::vector<ash::mojom::SearchResultMetadataPtr> result_data;
+  std::vector<std::unique_ptr<ash::SearchResultMetadata>> result_data;
   for (int i = 0; i < static_cast<int>(WindowTypeShelfItem::LAST_TYPE); ++i) {
     WindowTypeShelfItem::Type type = static_cast<WindowTypeShelfItem::Type>(i);
 
@@ -233,8 +233,8 @@
 void ExampleAppListClient::OpenSearchResult(
     const std::string& result_id,
     int event_flags,
-    ash::mojom::AppListLaunchedFrom launched_from,
-    ash::mojom::AppListLaunchType launch_type,
+    ash::AppListLaunchedFrom launched_from,
+    ash::AppListLaunchType launch_type,
     int suggestion_index) {
   auto it = std::find_if(
       search_results_.begin(), search_results_.end(),
diff --git a/ash/shell/example_app_list_client.h b/ash/shell/example_app_list_client.h
index 3952128..4696bf6 100644
--- a/ash/shell/example_app_list_client.h
+++ b/ash/shell/example_app_list_client.h
@@ -34,8 +34,8 @@
   void StartSearch(const base::string16& trimmed_query) override;
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
-                        ash::mojom::AppListLaunchedFrom launched_from,
-                        ash::mojom::AppListLaunchType launch_type,
+                        ash::AppListLaunchedFrom launched_from,
+                        ash::AppListLaunchType launch_type,
                         int suggestion_index) override;
   void ActivateItem(int profile_id,
                     const std::string& id,
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller.cc b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
index a7ce3950..5a60f21 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller.cc
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
@@ -152,6 +152,16 @@
   bubble_widget_->Close();
 }
 
+void AutoclickMenuBubbleController::SetBubbleVisibility(bool is_visible) {
+  if (!bubble_widget_)
+    return;
+
+  if (is_visible)
+    bubble_widget_->Show();
+  else
+    bubble_widget_->Hide();
+}
+
 void AutoclickMenuBubbleController::ClickOnBubble(gfx::Point location_in_dips,
                                                   int mouse_event_flags) {
   if (!bubble_widget_)
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller.h b/ash/system/accessibility/autoclick_menu_bubble_controller.h
index 0f4f28f..0a21190 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller.h
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller.h
@@ -30,6 +30,9 @@
 
   void CloseBubble();
 
+  // Shows or hides the bubble.
+  void SetBubbleVisibility(bool is_visible);
+
   // Performs the mouse events on the bubble. at the given location in DIPs.
   void ClickOnBubble(gfx::Point location_in_dips, int mouse_event_flags);
 
diff --git a/ash/system/ime/tray_ime_chromeos.cc b/ash/system/ime/tray_ime_chromeos.cc
index b21673a..f7162a9 100644
--- a/ash/system/ime/tray_ime_chromeos.cc
+++ b/ash/system/ime/tray_ime_chromeos.cc
@@ -82,6 +82,10 @@
   CloseBubble();
 }
 
+const char* IMEDetailedView::GetClassName() const {
+  return "IMEDetailedView";
+}
+
 }  // namespace tray
 
 }  // namespace ash
diff --git a/ash/system/ime/tray_ime_chromeos.h b/ash/system/ime/tray_ime_chromeos.h
index fe20120..82b3e927 100644
--- a/ash/system/ime/tray_ime_chromeos.h
+++ b/ash/system/ime/tray_ime_chromeos.h
@@ -46,6 +46,7 @@
                            const ui::Event& event) override;
   void CreateExtraTitleRowButtons() override;
   void ShowSettings();
+  const char* GetClassName() const override;
 
   ImeController* const ime_controller_;
 
diff --git a/ash/system/ime_menu/ime_list_view.cc b/ash/system/ime_menu/ime_list_view.cc
index d621640..9974b4c 100644
--- a/ash/system/ime_menu/ime_list_view.cc
+++ b/ash/system/ime_menu/ime_list_view.cc
@@ -178,6 +178,9 @@
     tri_view->AddView(TriView::Container::END, toggle_);
   }
 
+  // views::View:
+  const char* GetClassName() const override { return "KeyboardStatusRow"; }
+
  private:
   // ToggleButton to toggle keyboard on or off.
   views::ToggleButton* toggle_ = nullptr;
@@ -346,6 +349,10 @@
   ScrollItemToVisible(current_ime_view_);
 }
 
+const char* ImeListView::GetClassName() const {
+  return "ImeListView";
+}
+
 void ImeListView::FocusCurrentImeIfNeeded() {
   views::FocusManager* manager = GetFocusManager();
   if (!manager || manager->GetFocusedView() || last_selected_item_id_.empty())
diff --git a/ash/system/ime_menu/ime_list_view.h b/ash/system/ime_menu/ime_list_view.h
index 2b62b34..e611efd 100644
--- a/ash/system/ime_menu/ime_list_view.h
+++ b/ash/system/ime_menu/ime_list_view.h
@@ -78,6 +78,7 @@
 
   // views::View:
   void VisibilityChanged(View* starting_from, bool is_visible) override;
+  const char* GetClassName() const override;
 
  private:
   friend class ImeListViewTestApi;
diff --git a/ash/system/ime_menu/ime_menu_tray.cc b/ash/system/ime_menu/ime_menu_tray.cc
index 5b25232d..9e4543cf 100644
--- a/ash/system/ime_menu/ime_menu_tray.cc
+++ b/ash/system/ime_menu/ime_menu_tray.cc
@@ -101,6 +101,7 @@
     return gfx::Size(kTrayItemSize, kTrayItemSize);
   }
   int GetHeightForWidth(int width) const override { return kTrayItemSize; }
+  const char* GetClassName() const override { return "ImeMenuLabel"; }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ImeMenuLabel);
@@ -111,6 +112,9 @@
   ImeMenuImageView() { SetBorder(views::CreateEmptyBorder(gfx::Insets(0, 6))); }
   ~ImeMenuImageView() override = default;
 
+  // views::View:
+  const char* GetClassName() const override { return "ImeMenuImageView"; }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ImeMenuImageView);
 };
@@ -163,6 +167,9 @@
 
   ~ImeTitleView() override = default;
 
+  // views::View:
+  const char* GetClassName() const override { return "ImeTitleView"; }
+
  private:
   // Settings button that is only used if the emoji, handwriting and voice
   // buttons are not available.
@@ -217,6 +224,9 @@
     ime_menu_tray_->ShowKeyboardWithKeyset(keyset);
   }
 
+  // views::View:
+  const char* GetClassName() const override { return "ImeButtonsView"; }
+
  private:
   void Init(bool show_emoji, bool show_handwriting, bool show_voice) {
     auto box_layout =
@@ -275,6 +285,9 @@
   ImeMenuListView() : ImeMenuListView(std::make_unique<Delegate>()) {}
   ~ImeMenuListView() override = default;
 
+  // views::View:
+  const char* GetClassName() const override { return "ImeMenuListView"; }
+
  private:
   class Delegate : public DetailedViewDelegate {
    public:
@@ -465,6 +478,10 @@
   return bubble_ ? bubble_->bubble_view() : nullptr;
 }
 
+const char* ImeMenuTray::GetClassName() const {
+  return "ImeMenuTray";
+}
+
 void ImeMenuTray::OnIMERefresh() {
   UpdateTrayLabel();
   if (bubble_ && ime_list_view_) {
diff --git a/ash/system/ime_menu/ime_menu_tray.h b/ash/system/ime_menu/ime_menu_tray.h
index 868dbf1..d2c9f90 100644
--- a/ash/system/ime_menu/ime_menu_tray.h
+++ b/ash/system/ime_menu/ime_menu_tray.h
@@ -59,6 +59,7 @@
   void CloseBubble() override;
   void ShowBubble(bool show_by_click) override;
   TrayBubbleView* GetBubbleView() override;
+  const char* GetClassName() const override;
 
   // IMEObserver:
   void OnIMERefresh() override;
diff --git a/ash/system/message_center/notifier_settings_view.cc b/ash/system/message_center/notifier_settings_view.cc
index 1af99dc..b82f467 100644
--- a/ash/system/message_center/notifier_settings_view.cc
+++ b/ash/system/message_center/notifier_settings_view.cc
@@ -117,6 +117,7 @@
   bool OnKeyReleased(const ui::KeyEvent& event) override;
   void OnPaint(gfx::Canvas* canvas) override;
   void OnBlur() override;
+  const char* GetClassName() const override;
 
  private:
   // Initialize |disabled_filter_|. Should be called once.
@@ -201,6 +202,10 @@
   SchedulePaint();
 }
 
+const char* NotifierButtonWrapperView::GetClassName() const {
+  return "NotifierButtonWrapperView";
+}
+
 void NotifierButtonWrapperView::CreateDisabledFilter() {
   DCHECK(!disabled_filter_);
   disabled_filter_ = new views::View;
@@ -216,6 +221,9 @@
  public:
   ScrollContentsView() = default;
 
+  // views::View:
+  const char* GetClassName() const override { return "ScrollContentsView"; }
+
  private:
   void PaintChildren(const views::PaintInfo& paint_info) override {
     views::View::PaintChildren(paint_info);
@@ -277,6 +285,9 @@
     AddChildView(label);
   }
 
+  // views::View:
+  const char* GetClassName() const override { return "EmptyNotifierView"; }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(EmptyNotifierView);
 };
@@ -336,6 +347,10 @@
   return checkbox_->GetChecked();
 }
 
+const char* NotifierSettingsView::NotifierButton::GetClassName() const {
+  return "NotifierButton";
+}
+
 void NotifierSettingsView::NotifierButton::ButtonPressed(
     views::Button* button,
     const ui::Event& event) {
@@ -504,6 +519,10 @@
       IDS_ASH_MESSAGE_CENTER_SETTINGS_DIALOG_DESCRIPTION));
 }
 
+const char* NotifierSettingsView::GetClassName() const {
+  return "NotifierSettingsView";
+}
+
 void NotifierSettingsView::OnNotifierListUpdated(
     const std::vector<mojom::NotifierUiDataPtr>& ui_data) {
   // TODO(tetsui): currently notifier settings list doesn't update after once
diff --git a/ash/system/message_center/notifier_settings_view.h b/ash/system/message_center/notifier_settings_view.h
index 464bc5b..538bb58 100644
--- a/ash/system/message_center/notifier_settings_view.h
+++ b/ash/system/message_center/notifier_settings_view.h
@@ -45,8 +45,9 @@
   void UpdateNotifierIcon(const message_center::NotifierId& notifier_id,
                           const gfx::ImageSkia& icon) override;
 
-  // Overridden from views::View:
+  // views::View:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  const char* GetClassName() const override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(NotifierSettingsViewTest, TestLearnMoreButton);
@@ -65,6 +66,8 @@
     const message_center::NotifierId& notifier_id() const {
       return notifier_id_;
     }
+    // views::Button:
+    const char* GetClassName() const override;
 
    private:
     // Overridden from views::ButtonListener:
diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc
index cd728184..5ecd365 100644
--- a/ash/system/message_center/unified_message_center_view.cc
+++ b/ash/system/message_center/unified_message_center_view.cc
@@ -88,6 +88,8 @@
     PreferredSizeChanged();
   }
 
+  const char* GetClassName() const override { return "ScrollerContentsView"; }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ScrollerContentsView);
 };
@@ -118,6 +120,10 @@
                          kStackingNotificationClearAllButtonPadding.height());
   }
 
+  const char* GetClassName() const override {
+    return "StackingBarClearAllButton";
+  }
+
   int GetHeightForWidth(int width) const override {
     return label()->GetPreferredSize().height() +
            kStackingNotificationClearAllButtonPadding.height();
@@ -281,6 +287,10 @@
   views::View::OnPaint(canvas);
 }
 
+const char* StackingNotificationCounterView::GetClassName() const {
+  return "StackingNotificationCounterView";
+}
+
 void StackingNotificationCounterView::UpdateVisibility() {
   switch (animation_state_) {
     case UnifiedMessageCenterAnimationState::IDLE:
@@ -422,6 +432,10 @@
   return preferred_size;
 }
 
+const char* UnifiedMessageCenterView::GetClassName() const {
+  return "UnifiedMessageCenterView";
+}
+
 void UnifiedMessageCenterView::OnMessageCenterScrolled() {
   last_scroll_position_from_bottom_ =
       scroll_bar_->GetMaxPosition() - scroller_->GetVisibleRect().y();
diff --git a/ash/system/message_center/unified_message_center_view.h b/ash/system/message_center/unified_message_center_view.h
index fa5918b0..a27c7c4 100644
--- a/ash/system/message_center/unified_message_center_view.h
+++ b/ash/system/message_center/unified_message_center_view.h
@@ -65,6 +65,7 @@
 
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
+  const char* GetClassName() const override;
 
  private:
   friend class UnifiedMessageCenterViewTest;
@@ -125,6 +126,7 @@
   void RemovedFromWidget() override;
   void Layout() override;
   gfx::Size CalculatePreferredSize() const override;
+  const char* GetClassName() const override;
 
   // MessageCenterScrollBar::Observer:
   void OnMessageCenterScrolled() override;
diff --git a/ash/system/message_center/unified_message_list_view.cc b/ash/system/message_center/unified_message_list_view.cc
index 463ba3d..ded2a3a 100644
--- a/ash/system/message_center/unified_message_list_view.cc
+++ b/ash/system/message_center/unified_message_list_view.cc
@@ -152,6 +152,8 @@
     PreferredSizeChanged();
   }
 
+  const char* GetClassName() const override { return "UnifiedMessageListView"; }
+
   // MessageView::SlideObserver:
   void OnSlideChanged(const std::string& notification_id) override {
     control_view_->UpdateButtonsVisibility();
@@ -314,6 +316,10 @@
                                                ideal_height_));
 }
 
+const char* UnifiedMessageListView::GetClassName() const {
+  return "UnifiedMessageListView";
+}
+
 void UnifiedMessageListView::OnNotificationAdded(const std::string& id) {
   auto* notification = MessageCenter::Get()->FindVisibleNotificationById(id);
   if (!notification)
diff --git a/ash/system/message_center/unified_message_list_view.h b/ash/system/message_center/unified_message_list_view.h
index 3745b7f3..5de0cdb 100644
--- a/ash/system/message_center/unified_message_list_view.h
+++ b/ash/system/message_center/unified_message_list_view.h
@@ -73,6 +73,7 @@
   void PreferredSizeChanged() override;
   void Layout() override;
   gfx::Size CalculatePreferredSize() const override;
+  const char* GetClassName() const override;
 
   // message_center::MessageCenterObserver:
   void OnNotificationAdded(const std::string& id) override;
diff --git a/ash/system/model/system_tray_model.cc b/ash/system/model/system_tray_model.cc
index e5e5488..72d2b059 100644
--- a/ash/system/model/system_tray_model.cc
+++ b/ash/system/model/system_tray_model.cc
@@ -29,9 +29,10 @@
       tracing_(std::make_unique<TracingModel>()),
       update_model_(std::make_unique<UpdateModel>()),
       virtual_keyboard_(std::make_unique<VirtualKeyboardModel>()),
-      active_network_icon_(std::make_unique<ActiveNetworkIcon>(connector)),
-      network_observer_(std::make_unique<TrayNetworkStateObserver>(connector)) {
-}
+      network_observer_(std::make_unique<TrayNetworkStateObserver>(connector)),
+      active_network_icon_(
+          std::make_unique<ActiveNetworkIcon>(connector,
+                                              network_observer_.get())) {}
 
 SystemTrayModel::~SystemTrayModel() = default;
 
diff --git a/ash/system/model/system_tray_model.h b/ash/system/model/system_tray_model.h
index acf1cd0..6051bd0 100644
--- a/ash/system/model/system_tray_model.h
+++ b/ash/system/model/system_tray_model.h
@@ -68,12 +68,12 @@
   TracingModel* tracing() { return tracing_.get(); }
   UpdateModel* update_model() { return update_model_.get(); }
   VirtualKeyboardModel* virtual_keyboard() { return virtual_keyboard_.get(); }
-  ActiveNetworkIcon* active_network_icon() {
-    return active_network_icon_.get();
-  }
   TrayNetworkStateObserver* network_observer() {
     return network_observer_.get();
   }
+  ActiveNetworkIcon* active_network_icon() {
+    return active_network_icon_.get();
+  }
   const mojom::SystemTrayClientPtr& client_ptr() { return client_ptr_; }
 
  private:
@@ -84,8 +84,8 @@
   std::unique_ptr<TracingModel> tracing_;
   std::unique_ptr<UpdateModel> update_model_;
   std::unique_ptr<VirtualKeyboardModel> virtual_keyboard_;
-  std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
   std::unique_ptr<TrayNetworkStateObserver> network_observer_;
+  std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
 
   // TODO(tetsui): Add following as a sub-model of SystemTrayModel:
   // * BluetoothModel
diff --git a/ash/system/network/active_network_icon.cc b/ash/system/network/active_network_icon.cc
index 1b40ac0..de2a9e73 100644
--- a/ash/system/network/active_network_icon.cc
+++ b/ash/system/network/active_network_icon.cc
@@ -5,7 +5,9 @@
 #include "ash/system/network/active_network_icon.h"
 
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/system/network/network_icon.h"
 #include "ash/system/tray/tray_constants.h"
 #include "base/stl_util.h"
@@ -44,42 +46,27 @@
   return kUnifiedMenuIconColor;
 }
 
-NetworkStatePropertiesPtr GetConnectingOrConnected(
-    const NetworkStatePropertiesPtr* connecting_network,
-    const NetworkStatePropertiesPtr* connected_network) {
-  if (connecting_network &&
-      (!connected_network || connecting_network->get()->connect_requested)) {
-    // If connecting to a network, and there is either no connected network or
-    // the connection was user requested, use the connecting network.
-    return connecting_network->Clone();
-  }
-  if (connected_network)
-    return connected_network->Clone();
-  return nullptr;
-}
-
 }  // namespace
 
-ActiveNetworkIcon::ActiveNetworkIcon(service_manager::Connector* connector)
-    : weak_ptr_factory_(this) {
+ActiveNetworkIcon::ActiveNetworkIcon(service_manager::Connector* connector,
+                                     TrayNetworkStateObserver* model)
+    : model_(model), weak_ptr_factory_(this) {
   if (connector)  // May be null in tests.
     BindCrosNetworkConfig(connector);
+  model_->AddObserver(this);
 }
 
-ActiveNetworkIcon::~ActiveNetworkIcon() = default;
+ActiveNetworkIcon::~ActiveNetworkIcon() {
+  model_->RemoveObserver(this);
+}
 
 void ActiveNetworkIcon::BindCrosNetworkConfig(
     service_manager::Connector* connector) {
-  // Ensure bindings are reset in case this is called after a failure.
-  cros_network_config_observer_binding_.Close();
+  // Ensure binding is reset in case this is called after a failure.
   cros_network_config_ptr_.reset();
 
   connector->BindInterface(chromeos::network_config::mojom::kServiceName,
                            &cros_network_config_ptr_);
-  chromeos::network_config::mojom::CrosNetworkConfigObserverPtr observer_ptr;
-  cros_network_config_observer_binding_.Bind(mojo::MakeRequest(&observer_ptr));
-  cros_network_config_ptr_->AddObserver(std::move(observer_ptr));
-  UpdateActiveNetworks();
 
   // If the connection is lost (e.g. due to a crash), attempt to rebind it.
   cros_network_config_ptr_.set_connection_error_handler(
@@ -94,14 +81,14 @@
   const NetworkStateProperties* network = nullptr;
   switch (type) {
     case Type::kSingle:
-      network = default_network_.get();
+      network = model_->default_network();
       break;
     case Type::kPrimary:
       // TODO(902409): Provide strings for technology or connecting.
-      network = default_network_.get();
+      network = model_->default_network();
       break;
     case Type::kCellular:
-      network = active_cellular_.get();
+      network = model_->active_cellular();
       break;
   }
   if (network &&
@@ -176,21 +163,23 @@
     network_icon::IconType icon_type,
     bool* animating) {
   // If no network, check for cellular initializing.
-  if (!default_network_ && cellular_uninitialized_msg_ != 0) {
+  NetworkStateProperties* default_network = model_->default_network();
+  if (!default_network && cellular_uninitialized_msg_ != 0) {
     if (animating)
       *animating = true;
     return network_icon::GetConnectingImageForNetworkType(
         NetworkType::kCellular, icon_type);
   }
-  return GetDefaultImageImpl(default_network_.get(), icon_type, animating);
+  return GetDefaultImageImpl(default_network, icon_type, animating);
 }
 
 gfx::ImageSkia ActiveNetworkIcon::GetDualImagePrimary(
     network_icon::IconType icon_type,
     bool* animating) {
-  if (default_network_ && default_network_->type == NetworkType::kCellular) {
+  NetworkStateProperties* default_network = model_->default_network();
+  if (default_network && default_network->type == NetworkType::kCellular) {
     if (chromeos::network_config::StateIsConnected(
-            default_network_->connection_state)) {
+            default_network->connection_state)) {
       // TODO(902409): Show proper technology badges.
       if (animating)
         *animating = false;
@@ -198,16 +187,17 @@
                                    GetDefaultColorForIconType(icon_type));
     }
     // If Cellular is connecting, use the active non cellular network.
-    return GetDefaultImageImpl(active_non_cellular_.get(), icon_type,
+    return GetDefaultImageImpl(model_->active_non_cellular(), icon_type,
                                animating);
   }
-  return GetDefaultImageImpl(default_network_.get(), icon_type, animating);
+  return GetDefaultImageImpl(default_network, icon_type, animating);
 }
 
 gfx::ImageSkia ActiveNetworkIcon::GetDualImageCellular(
     network_icon::IconType icon_type,
     bool* animating) {
-  if (!base::ContainsKey(devices_, NetworkType::kCellular)) {
+  if (model_->GetDeviceState(NetworkType::kCellular) ==
+      DeviceStateType::kUnavailable) {
     if (animating)
       *animating = false;
     return gfx::ImageSkia();
@@ -220,7 +210,8 @@
         NetworkType::kCellular, icon_type);
   }
 
-  if (!active_cellular_) {
+  NetworkStateProperties* active_cellular = model_->active_cellular();
+  if (!active_cellular) {
     if (animating)
       *animating = false;
     return network_icon::GetDisconnectedImageForNetworkType(
@@ -228,7 +219,7 @@
   }
 
   return network_icon::GetImageForNonVirtualNetwork(
-      active_cellular_.get(), icon_type, false /* show_vpn_badge */, animating);
+      active_cellular, icon_type, false /* show_vpn_badge */, animating);
 }
 
 gfx::ImageSkia ActiveNetworkIcon::GetDefaultImageImpl(
@@ -240,8 +231,9 @@
     return GetDefaultImageForNoNetwork(icon_type, animating);
   }
   // Don't show connected Ethernet in the tray unless a VPN is present.
+  NetworkStateProperties* active_vpn = model_->active_vpn();
   if (network->type == NetworkType::kEthernet && IsTrayIcon(icon_type) &&
-      !active_vpn_) {
+      !active_vpn) {
     if (animating)
       *animating = false;
     VLOG(1) << __func__ << ": Ethernet: No icon";
@@ -250,8 +242,8 @@
 
   // Connected network with a connecting VPN.
   if (chromeos::network_config::StateIsConnected(network->connection_state) &&
-      active_vpn_ &&
-      active_vpn_->connection_state == ConnectionStateType::kConnecting) {
+      active_vpn &&
+      active_vpn->connection_state == ConnectionStateType::kConnecting) {
     if (animating)
       *animating = true;
     VLOG(1) << __func__ << ": Connected with connecting VPN";
@@ -260,7 +252,7 @@
   }
 
   // Default behavior: connected or connecting network, possibly with VPN badge.
-  bool show_vpn_badge = !!active_vpn_;
+  bool show_vpn_badge = !!active_vpn;
   VLOG(1) << __func__ << ": Network: " << network->name;
   return network_icon::GetImageForNonVirtualNetwork(network, icon_type,
                                                     show_vpn_badge, animating);
@@ -271,8 +263,7 @@
     bool* animating) {
   if (animating)
     *animating = false;
-  DeviceStateProperties* wifi = GetDevice(NetworkType::kWiFi);
-  if (wifi && wifi->state == DeviceStateType::kEnabled) {
+  if (model_->GetDeviceState(NetworkType::kWiFi) == DeviceStateType::kEnabled) {
     // WiFi is enabled but disconnected, show an empty wedge.
     return network_icon::GetBasicImage(icon_type, NetworkType::kWiFi,
                                        false /* connected */);
@@ -282,17 +273,8 @@
                                                    icon_type);
 }
 
-void ActiveNetworkIcon::UpdateActiveNetworks() {
-  DCHECK(cros_network_config_ptr_);
-  cros_network_config_ptr_->GetNetworkStateList(
-      NetworkFilter::New(FilterType::kActive, NetworkType::kAll,
-                         /*limit=*/0),
-      base::BindOnce(&ActiveNetworkIcon::OnActiveNetworksChanged,
-                     base::Unretained(this)));
-}
-
 void ActiveNetworkIcon::SetCellularUninitializedMsg() {
-  DeviceStateProperties* cellular = GetDevice(NetworkType::kCellular);
+  DeviceStateProperties* cellular = model_->GetDevice(NetworkType::kCellular);
   if (cellular && cellular->state == DeviceStateType::kUninitialized) {
     cellular_uninitialized_msg_ = IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
     uninitialized_state_time_ = base::Time::Now();
@@ -314,66 +296,13 @@
     cellular_uninitialized_msg_ = 0;
 }
 
-// CrosNetworkConfigObserver
+// TrayNetworkStateObserver::Observer
 
-void ActiveNetworkIcon::OnActiveNetworksChanged(
-    std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
-        networks) {
-  active_cellular_.reset();
-  active_vpn_.reset();
-
-  const NetworkStatePropertiesPtr* connected_network = nullptr;
-  const NetworkStatePropertiesPtr* connected_non_cellular = nullptr;
-  const NetworkStatePropertiesPtr* connecting_network = nullptr;
-  const NetworkStatePropertiesPtr* connecting_non_cellular = nullptr;
-  for (const NetworkStatePropertiesPtr& network : networks) {
-    if (network->type == NetworkType::kVPN) {
-      if (!active_vpn_)
-        active_vpn_ = network.Clone();
-      continue;
-    }
-    if (network->type == NetworkType::kCellular) {
-      if (!active_cellular_)
-        active_cellular_ = network.Clone();
-    }
-    if (chromeos::network_config::StateIsConnected(network->connection_state)) {
-      if (!connected_network)
-        connected_network = &network;
-      if (!connected_non_cellular && network->type != NetworkType::kCellular) {
-        connected_non_cellular = &network;
-      }
-      continue;
-    }
-    // Active non connected networks are connecting.
-    if (chromeos::network_config::NetworkStateMatchesType(
-            network.get(), NetworkType::kWireless)) {
-      if (!connecting_network)
-        connecting_network = &network;
-      if (!connecting_non_cellular && network->type != NetworkType::kCellular) {
-        connecting_non_cellular = &network;
-      }
-    }
-  }
-
-  VLOG_IF(2, connected_network)
-      << __func__ << ": Connected network: " << connected_network->get()->name
-      << " State: " << connected_network->get()->connection_state;
-  VLOG_IF(2, connecting_network)
-      << __func__ << ": Connecting network: " << connecting_network->get()->name
-      << " State: " << connecting_network->get()->connection_state;
-
-  default_network_ =
-      GetConnectingOrConnected(connecting_network, connected_network);
-  VLOG_IF(2, default_network_)
-      << __func__ << ": Default network: " << default_network_->name;
-
-  active_non_cellular_ =
-      GetConnectingOrConnected(connecting_non_cellular, connected_non_cellular);
-
+void ActiveNetworkIcon::ActiveNetworkStateChanged() {
   SetCellularUninitializedMsg();
 }
 
-void ActiveNetworkIcon::OnNetworkStateListChanged() {
+void ActiveNetworkIcon::NetworkListChanged() {
   if (purge_timer_.IsRunning())
     return;
   purge_timer_.Start(FROM_HERE,
@@ -382,32 +311,6 @@
                                     weak_ptr_factory_.GetWeakPtr()));
 }
 
-void ActiveNetworkIcon::OnDeviceStateListChanged() {
-  cros_network_config_ptr_->GetDeviceStateList(base::BindOnce(
-      &ActiveNetworkIcon::OnGetDeviceStateList, base::Unretained(this)));
-}
-
-void ActiveNetworkIcon::OnGetDeviceStateList(
-    std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
-        devices) {
-  devices_.clear();
-  for (auto& device : devices) {
-    NetworkType type = device->type;
-    if (base::ContainsKey(devices_, type))
-      continue;  // Ignore multiple entries with the same type.
-    devices_.emplace(std::make_pair(type, std::move(device)));
-  }
-  UpdateActiveNetworks();
-  SetCellularUninitializedMsg();
-}
-
-DeviceStateProperties* ActiveNetworkIcon::GetDevice(NetworkType type) {
-  auto iter = devices_.find(type);
-  if (iter == devices_.end())
-    return nullptr;
-  return iter->second.get();
-}
-
 void ActiveNetworkIcon::PurgeNetworkIconCache() {
   cros_network_config_ptr_->GetNetworkStateList(
       NetworkFilter::New(FilterType::kVisible, NetworkType::kAll,
diff --git a/ash/system/network/active_network_icon.h b/ash/system/network/active_network_icon.h
index 2371ae7..77abb4e4 100644
--- a/ash/system/network/active_network_icon.h
+++ b/ash/system/network/active_network_icon.h
@@ -10,13 +10,12 @@
 
 #include "ash/ash_export.h"
 #include "ash/system/network/network_icon.h"
-#include "base/containers/flat_map.h"
+#include "ash/system/network/tray_network_state_observer.h"
 #include "base/macros.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
-#include "mojo/public/cpp/bindings/binding.h"
 
 namespace gfx {
 class ImageSkia;
@@ -28,8 +27,8 @@
 
 namespace ash {
 
-// Tracks changes to the active networks and provides an interface to
-// network_icon for the default network. This class supports two interfaces:
+// Provides an interface to network_icon for the default network. This class
+// supports two interfaces:
 // * Single: A single icon is shown to represent the active network state.
 // * Dual: One or two icons are shown to represent the active network state:
 // ** Primary: The state of the primary active network. If Cellular, a
@@ -41,8 +40,7 @@
 // TODO(stevenjb): Move all test coverage to active_network_icon_unittest.cc and
 // test Dual icon methods.
 // This class is also responsible for periodically purging the icon cache.
-class ASH_EXPORT ActiveNetworkIcon
-    : public chromeos::network_config::mojom::CrosNetworkConfigObserver {
+class ASH_EXPORT ActiveNetworkIcon : public TrayNetworkStateObserver::Observer {
  public:
   enum class Type {
     kSingle,    // A single network icon in the tray.
@@ -50,7 +48,8 @@
     kCellular,  // Multiple network icons: cellular icon.
   };
 
-  explicit ActiveNetworkIcon(service_manager::Connector* connector);
+  ActiveNetworkIcon(service_manager::Connector* connector,
+                    TrayNetworkStateObserver* model);
   ~ActiveNetworkIcon() override;
 
   // Provides the a11y and tooltip strings for |type|.
@@ -85,36 +84,19 @@
   gfx::ImageSkia GetDefaultImageForNoNetwork(network_icon::IconType icon_type,
                                              bool* animating);
 
-  void UpdateActiveNetworks();
   void SetCellularUninitializedMsg();
 
-  // CrosNetworkConfigObserver
-  void OnActiveNetworksChanged(
-      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
-          networks) override;
-  void OnNetworkStateListChanged() override;
-  void OnDeviceStateListChanged() override;
+  // TrayNetworkStateObserver::Observer
+  void ActiveNetworkStateChanged() override;
+  void NetworkListChanged() override;
 
-  void OnGetDeviceStateList(
-      std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
-          devices);
-  chromeos::network_config::mojom::DeviceStateProperties* GetDevice(
-      chromeos::network_config::mojom::NetworkType type);
   void PurgeNetworkIconCache();
 
+  TrayNetworkStateObserver* model_;
+
   chromeos::network_config::mojom::CrosNetworkConfigPtr
       cros_network_config_ptr_;
-  mojo::Binding<chromeos::network_config::mojom::CrosNetworkConfigObserver>
-      cros_network_config_observer_binding_{this};
 
-  base::flat_map<chromeos::network_config::mojom::NetworkType,
-                 chromeos::network_config::mojom::DeviceStatePropertiesPtr>
-      devices_;
-  chromeos::network_config::mojom::NetworkStatePropertiesPtr default_network_;
-  chromeos::network_config::mojom::NetworkStatePropertiesPtr
-      active_non_cellular_;
-  chromeos::network_config::mojom::NetworkStatePropertiesPtr active_cellular_;
-  chromeos::network_config::mojom::NetworkStatePropertiesPtr active_vpn_;
   int cellular_uninitialized_msg_ = 0;
   base::Time uninitialized_state_time_;
   base::OneShotTimer purge_timer_;
diff --git a/ash/system/network/active_network_icon_unittest.cc b/ash/system/network/active_network_icon_unittest.cc
index 4a2c6c6..6ac0e9a 100644
--- a/ash/system/network/active_network_icon_unittest.cc
+++ b/ash/system/network/active_network_icon_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/network/network_icon.h"
+#include "ash/system/network/tray_network_state_observer.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -40,8 +41,10 @@
   ~ActiveNetworkIconTest() override = default;
 
   void SetUp() override {
-    active_network_icon_ =
-        std::make_unique<ActiveNetworkIcon>(network_config_helper_.connector());
+    network_state_observer_ = std::make_unique<TrayNetworkStateObserver>(
+        network_config_helper_.connector());
+    active_network_icon_ = std::make_unique<ActiveNetworkIcon>(
+        network_config_helper_.connector(), network_state_observer_.get());
   }
 
   void TearDown() override { active_network_icon_.reset(); }
@@ -154,6 +157,7 @@
  private:
   const base::MessageLoop message_loop_;
   chromeos::network_config::CrosNetworkConfigTestHelper network_config_helper_;
+  std::unique_ptr<TrayNetworkStateObserver> network_state_observer_;
   std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
 
   std::string eth_path_;
diff --git a/ash/system/network/network_icon_unittest.cc b/ash/system/network/network_icon_unittest.cc
index b60d3bb..fec94c5 100644
--- a/ash/system/network/network_icon_unittest.cc
+++ b/ash/system/network/network_icon_unittest.cc
@@ -41,8 +41,10 @@
 
   void SetUp() override {
     SetUpDefaultNetworkState();
-    active_network_icon_ =
-        std::make_unique<ActiveNetworkIcon>(network_config_helper_.connector());
+    network_state_observer_ = std::make_unique<TrayNetworkStateObserver>(
+        network_config_helper_.connector());
+    active_network_icon_ = std::make_unique<ActiveNetworkIcon>(
+        network_config_helper_.connector(), network_state_observer_.get());
   }
 
   void TearDown() override {
@@ -151,14 +153,14 @@
  private:
   const base::MessageLoop message_loop_;
   chromeos::network_config::CrosNetworkConfigTestHelper network_config_helper_;
+  std::unique_ptr<TrayNetworkStateObserver> network_state_observer_;
+  std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
 
   // Preconfigured service paths:
   std::string wifi1_path_;
   std::string wifi2_path_;
   std::string cellular_path_;
 
-  std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
-
   DISALLOW_COPY_AND_ASSIGN(NetworkIconTest);
 };
 
diff --git a/ash/system/network/tray_network_state_observer.cc b/ash/system/network/tray_network_state_observer.cc
index 3614467f..b11eeb5 100644
--- a/ash/system/network/tray_network_state_observer.cc
+++ b/ash/system/network/tray_network_state_observer.cc
@@ -15,13 +15,34 @@
 #include "third_party/cros_system_api/dbus/service_constants.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 
+using chromeos::network_config::mojom::ConnectionStateType;
+using chromeos::network_config::mojom::DeviceStateProperties;
+using chromeos::network_config::mojom::DeviceStatePropertiesPtr;
 using chromeos::network_config::mojom::DeviceStateType;
+using chromeos::network_config::mojom::FilterType;
+using chromeos::network_config::mojom::NetworkFilter;
+using chromeos::network_config::mojom::NetworkStateProperties;
+using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
 using chromeos::network_config::mojom::NetworkType;
 
 namespace {
 
 const int kUpdateFrequencyMs = 1000;
 
+NetworkStatePropertiesPtr GetConnectingOrConnected(
+    const NetworkStatePropertiesPtr* connecting_network,
+    const NetworkStatePropertiesPtr* connected_network) {
+  if (connecting_network &&
+      (!connected_network || connecting_network->get()->connect_requested)) {
+    // If connecting to a network, and there is either no connected network or
+    // the connection was user requested, use the connecting network.
+    return connecting_network->Clone();
+  }
+  if (connected_network)
+    return connected_network->Clone();
+  return nullptr;
+}
+
 }  // namespace
 
 namespace ash {
@@ -47,8 +68,23 @@
   observer_list_.RemoveObserver(observer);
 }
 
+DeviceStateProperties* TrayNetworkStateObserver::GetDevice(NetworkType type) {
+  auto iter = devices_.find(type);
+  if (iter == devices_.end())
+    return nullptr;
+  return iter->second.get();
+}
+
+DeviceStateType TrayNetworkStateObserver::GetDeviceState(NetworkType type) {
+  DeviceStateProperties* device = GetDevice(type);
+  return device ? device->state : DeviceStateType::kUnavailable;
+}
+
+// CrosNetworkConfigObserver
+
 void TrayNetworkStateObserver::OnActiveNetworksChanged(
-    std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>) {
+    std::vector<NetworkStatePropertiesPtr> networks) {
+  UpdateActiveNetworks(std::move(networks));
   SendActiveNetworkStateChanged();
 }
 
@@ -57,10 +93,7 @@
 }
 
 void TrayNetworkStateObserver::OnDeviceStateListChanged() {
-  // This observer is triggered when any device state changes. Depending on the
-  // actual state changes, an immediate or delated update may be triggered.
-  // See OnGetDeviceStateList for details.
-  UpdateDeviceEnabledStates();
+  GetDeviceStateList();
 }
 
 void TrayNetworkStateObserver::BindCrosNetworkConfig(
@@ -74,7 +107,7 @@
   chromeos::network_config::mojom::CrosNetworkConfigObserverPtr observer_ptr;
   cros_network_config_observer_binding_.Bind(mojo::MakeRequest(&observer_ptr));
   cros_network_config_ptr_->AddObserver(std::move(observer_ptr));
-  UpdateDeviceEnabledStates();
+  GetDeviceStateList();
 
   // If the connection is lost (e.g. due to a crash), attempt to rebind it.
   cros_network_config_ptr_.set_connection_error_handler(
@@ -82,33 +115,85 @@
                      base::Unretained(this), connector));
 }
 
-void TrayNetworkStateObserver::UpdateDeviceEnabledStates() {
+void TrayNetworkStateObserver::GetDeviceStateList() {
   cros_network_config_ptr_->GetDeviceStateList(base::BindOnce(
       &TrayNetworkStateObserver::OnGetDeviceStateList, base::Unretained(this)));
 }
 
 void TrayNetworkStateObserver::OnGetDeviceStateList(
-    std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
-        devices) {
-  bool wifi_was_enabled = wifi_enabled_;
-  bool mobile_was_enabled = mobile_enabled_;
-  wifi_enabled_ = false;
-  mobile_enabled_ = false;
+    std::vector<DeviceStatePropertiesPtr> devices) {
+  devices_.clear();
   for (auto& device : devices) {
     NetworkType type = device->type;
-    if (type == NetworkType::kWiFi &&
-        device->state == DeviceStateType::kEnabled) {
-      wifi_enabled_ = true;
+    if (base::ContainsKey(devices_, type))
+      continue;  // Ignore multiple entries with the same type.
+    devices_.emplace(std::make_pair(type, std::move(device)));
+  }
+
+  GetActiveNetworks();  // Will trigger an observer event.
+}
+
+void TrayNetworkStateObserver::GetActiveNetworks() {
+  DCHECK(cros_network_config_ptr_);
+  cros_network_config_ptr_->GetNetworkStateList(
+      NetworkFilter::New(FilterType::kActive, NetworkType::kAll,
+                         /*limit=*/0),
+      base::BindOnce(&TrayNetworkStateObserver::OnActiveNetworksChanged,
+                     base::Unretained(this)));
+}
+
+void TrayNetworkStateObserver::UpdateActiveNetworks(
+    std::vector<NetworkStatePropertiesPtr> networks) {
+  active_cellular_.reset();
+  active_vpn_.reset();
+
+  const NetworkStatePropertiesPtr* connected_network = nullptr;
+  const NetworkStatePropertiesPtr* connected_non_cellular = nullptr;
+  const NetworkStatePropertiesPtr* connecting_network = nullptr;
+  const NetworkStatePropertiesPtr* connecting_non_cellular = nullptr;
+  for (const NetworkStatePropertiesPtr& network : networks) {
+    if (network->type == NetworkType::kVPN) {
+      if (!active_vpn_)
+        active_vpn_ = network.Clone();
+      continue;
     }
-    if ((type == NetworkType::kCellular || type == NetworkType::kTether) &&
-        device->state == DeviceStateType::kEnabled) {
-      mobile_enabled_ = true;
+    if (network->type == NetworkType::kCellular) {
+      if (!active_cellular_)
+        active_cellular_ = network.Clone();
+    }
+    if (chromeos::network_config::StateIsConnected(network->connection_state)) {
+      if (!connected_network)
+        connected_network = &network;
+      if (!connected_non_cellular && network->type != NetworkType::kCellular) {
+        connected_non_cellular = &network;
+      }
+      continue;
+    }
+    // Active non connected networks are connecting.
+    if (chromeos::network_config::NetworkStateMatchesType(
+            network.get(), NetworkType::kWireless)) {
+      if (!connecting_network)
+        connecting_network = &network;
+      if (!connecting_non_cellular && network->type != NetworkType::kCellular) {
+        connecting_non_cellular = &network;
+      }
     }
   }
-  if (wifi_was_enabled != wifi_enabled_ ||
-      mobile_was_enabled != mobile_enabled_) {
-    SendActiveNetworkStateChanged();
-  }
+
+  VLOG_IF(2, connected_network)
+      << __func__ << ": Connected network: " << connected_network->get()->name
+      << " State: " << connected_network->get()->connection_state;
+  VLOG_IF(2, connecting_network)
+      << __func__ << ": Connecting network: " << connecting_network->get()->name
+      << " State: " << connecting_network->get()->connection_state;
+
+  default_network_ =
+      GetConnectingOrConnected(connecting_network, connected_network);
+  VLOG_IF(2, default_network_)
+      << __func__ << ": Default network: " << default_network_->name;
+
+  active_non_cellular_ =
+      GetConnectingOrConnected(connecting_non_cellular, connected_non_cellular);
 }
 
 void TrayNetworkStateObserver::NotifyNetworkListChanged() {
diff --git a/ash/system/network/tray_network_state_observer.h b/ash/system/network/tray_network_state_observer.h
index f295f13..1cacf7b 100644
--- a/ash/system/network/tray_network_state_observer.h
+++ b/ash/system/network/tray_network_state_observer.h
@@ -7,6 +7,8 @@
 
 #include <vector>
 
+#include "ash/ash_export.h"
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "base/timer/timer.h"
@@ -19,11 +21,12 @@
 
 namespace ash {
 
-class TrayNetworkStateObserver
+// TrayNetworkStateObserver observes the mojo interface and tracks the devices
+// and active networks. It has UI observers that are informed when important
+// changes occur.
+class ASH_EXPORT TrayNetworkStateObserver
     : public chromeos::network_config::mojom::CrosNetworkConfigObserver {
  public:
-  // TrayNetworkStateObserver observes the mojo interface, and in turn has UI
-  // observers that only need to be informed when the UI should be refreshed.
   class Observer : public base::CheckedObserver {
    public:
     // The active networks changed or a device enabled state changed.
@@ -39,6 +42,29 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
+  // Returns DeviceStateProperties for |type| if it exists or null.
+  chromeos::network_config::mojom::DeviceStateProperties* GetDevice(
+      chromeos::network_config::mojom::NetworkType type);
+
+  // Returns the DeviceStateType for |type| if a device exists or kUnavailable.
+  chromeos::network_config::mojom::DeviceStateType GetDeviceState(
+      chromeos::network_config::mojom::NetworkType type);
+
+  chromeos::network_config::mojom::NetworkStateProperties* default_network() {
+    return default_network_.get();
+  }
+  chromeos::network_config::mojom::NetworkStateProperties*
+  active_non_cellular() {
+    return active_non_cellular_.get();
+  }
+  chromeos::network_config::mojom::NetworkStateProperties* active_cellular() {
+    return active_cellular_.get();
+  }
+  chromeos::network_config::mojom::NetworkStateProperties* active_vpn() {
+    return active_vpn_.get();
+  }
+
+ private:
   // CrosNetworkConfigObserver
   void OnActiveNetworksChanged(
       std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>)
@@ -46,12 +72,17 @@
   void OnNetworkStateListChanged() override;
   void OnDeviceStateListChanged() override;
 
- private:
   void BindCrosNetworkConfig(service_manager::Connector* connector);
-  void UpdateDeviceEnabledStates();
+  void GetDeviceStateList();
   void OnGetDeviceStateList(
       std::vector<chromeos::network_config::mojom::DeviceStatePropertiesPtr>
           devices);
+
+  void GetActiveNetworks();
+  void UpdateActiveNetworks(
+      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
+          networks);
+
   void NotifyNetworkListChanged();
   void SendActiveNetworkStateChanged();
   void SendNetworkListChanged();
@@ -71,12 +102,14 @@
   // Timer used to limit the frequency of NetworkListChanged updates.
   base::OneShotTimer timer_;
 
-  // The cached states of whether Wi-Fi and Mobile are enabled. The tray
-  // includes expanding network lists of these types, so these cached values
-  // are used to determine when to prioritize updating the tray when they
-  // change.
-  bool wifi_enabled_ = false;
-  bool mobile_enabled_ = false;
+  base::flat_map<chromeos::network_config::mojom::NetworkType,
+                 chromeos::network_config::mojom::DeviceStatePropertiesPtr>
+      devices_;
+  chromeos::network_config::mojom::NetworkStatePropertiesPtr default_network_;
+  chromeos::network_config::mojom::NetworkStatePropertiesPtr
+      active_non_cellular_;
+  chromeos::network_config::mojom::NetworkStatePropertiesPtr active_cellular_;
+  chromeos::network_config::mojom::NetworkStatePropertiesPtr active_vpn_;
 
   DISALLOW_COPY_AND_ASSIGN(TrayNetworkStateObserver);
 };
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index 3dc4914..bbd911d8 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -118,6 +118,9 @@
 
   ~TitleView() override = default;
 
+  // views::View:
+  const char* GetClassName() const override { return "TitleView"; }
+
  private:
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override {
@@ -519,6 +522,10 @@
   return bubble_ ? bubble_->bubble_view() : nullptr;
 }
 
+const char* PaletteTray::GetClassName() const {
+  return "PaletteTray";
+}
+
 void PaletteTray::InitializeWithLocalState() {
   DCHECK(!local_state_);
   local_state_ = Shell::Get()->local_state();
diff --git a/ash/system/palette/palette_tray.h b/ash/system/palette/palette_tray.h
index f2e1588..22d0b87 100644
--- a/ash/system/palette/palette_tray.h
+++ b/ash/system/palette/palette_tray.h
@@ -80,6 +80,7 @@
   void CloseBubble() override;
   void ShowBubble(bool show_by_click) override;
   TrayBubbleView* GetBubbleView() override;
+  const char* GetClassName() const override;
 
   // PaletteToolManager::Delegate:
   void HidePalette() override;
diff --git a/ash/system/palette/palette_welcome_bubble.cc b/ash/system/palette/palette_welcome_bubble.cc
index 59f4b1c..83cf026 100644
--- a/ash/system/palette/palette_welcome_bubble.cc
+++ b/ash/system/palette/palette_welcome_bubble.cc
@@ -70,6 +70,9 @@
 
   int GetDialogButtons() const override { return ui::DIALOG_BUTTON_NONE; }
 
+  // views::View:
+  const char* GetClassName() const override { return "WelcomeBubbleView"; }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(WelcomeBubbleView);
 };
diff --git a/ash/wm/window_resizer.cc b/ash/wm/window_resizer.cc
index cb73eb2..c9ea3c8 100644
--- a/ash/wm/window_resizer.cc
+++ b/ash/wm/window_resizer.cc
@@ -28,20 +28,6 @@
 namespace ash {
 namespace {
 
-void OnFramePresented(base::TimeTicks start_time,
-                      const gfx::PresentationFeedback& feedback) {
-  UMA_HISTOGRAM_TIMES("Ash.InteractiveWindowResize.TimeToPresent",
-                      feedback.timestamp - start_time);
-}
-
-void RecordMetricsForResize(base::TimeTicks start_time, aura::Window* window) {
-  DCHECK(window);
-  ui::Compositor* compositor = window->GetHost()->compositor();
-  DCHECK(compositor);
-  compositor->RequestPresentationTimeForNextFrame(
-      base::BindOnce(&OnFramePresented, start_time));
-}
-
 // Returns true for resize components along the right edge, where a drag in
 // positive x will make the window larger.
 bool IsRightEdge(int window_component) {
@@ -92,6 +78,10 @@
 
 WindowResizer::WindowResizer(wm::WindowState* window_state)
     : window_state_(window_state) {
+  recorder_ = ash::CreatePresentationTimeHistogramRecorder(
+      GetTarget()->layer()->GetCompositor(),
+      "Ash.InteractiveWindowResize.TimeToPresent",
+      "Ash.InteractiveWindowResize.TimeToPresent.MaxLatency");
   DCHECK(window_state_->drag_details());
 }
 
@@ -279,8 +269,6 @@
 void WindowResizer::SetBoundsDuringResize(const gfx::Rect& bounds) {
   aura::Window* window = GetTarget();
   DCHECK(window);
-  // Consider having this time come from the event.
-  base::TimeTicks start = base::TimeTicks::Now();
   const gfx::Rect original_bounds = window->bounds();
   window->SetBounds(bounds);
   aura::WindowTracker tracker;
@@ -289,7 +277,7 @@
     return;  // Assume we've been destroyed.
   if (bounds.size() == original_bounds.size())
     return;
-  RecordMetricsForResize(start, window);
+  recorder_->RequestNext();
 }
 
 void WindowResizer::AdjustDeltaForTouchResize(int* delta_x, int* delta_y) {
diff --git a/ash/wm/window_resizer.h b/ash/wm/window_resizer.h
index ae689505..35e9899 100644
--- a/ash/wm/window_resizer.h
+++ b/ash/wm/window_resizer.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/presentation_time_recorder.h"
 #include "ash/wm/drag_details.h"
 #include "ash/wm/window_state.h"
 #include "base/macros.h"
@@ -110,6 +111,8 @@
   void CalculateBoundsWithAspectRatio(float aspect_ratio,
                                       gfx::Rect* new_bounds);
 
+  std::unique_ptr<PresentationTimeRecorder> recorder_;
+
   DISALLOW_COPY_AND_ASSIGN(WindowResizer);
 };
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index c9aafba..e0810ff 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -755,9 +755,13 @@
     "task/promise/abstract_promise.h",
     "task/promise/dependent_list.cc",
     "task/promise/dependent_list.h",
+    "task/promise/helpers.h",
     "task/promise/no_op_promise_executor.cc",
     "task/promise/no_op_promise_executor.h",
-    "task/promise/small_unique_object.h",
+    "task/promise/promise.h",
+    "task/promise/promise_result.h",
+    "task/promise/then_and_catch_executor.cc",
+    "task/promise/then_and_catch_executor.h",
     "task/scoped_set_task_priority_for_current_thread.cc",
     "task/scoped_set_task_priority_for_current_thread.h",
     "task/sequence_manager/associated_thread_id.cc",
@@ -2631,6 +2635,8 @@
     "task/post_task_unittest.cc",
     "task/promise/abstract_promise_unittest.cc",
     "task/promise/dependent_list_unittest.cc",
+    "task/promise/helpers_unittest.cc",
+    "task/promise/promise_unittest.cc",
     "task/scoped_set_task_priority_for_current_thread_unittest.cc",
     "task/sequence_manager/atomic_flag_set_unittest.cc",
     "task/sequence_manager/lazily_deallocated_deque_unittest.cc",
@@ -3034,6 +3040,7 @@
       "observer_list_unittest.nc",
       "optional_unittest.nc",
       "strings/string16_unittest.nc",
+      "task/promise/promise_unittest.nc",
       "task/task_traits_extension_unittest.nc",
       "task/task_traits_unittest.nc",
       "thread_annotations_unittest.nc",
@@ -3313,17 +3320,17 @@
     java_files = [
       "test/android/javatests/src/org/chromium/base/test/ReachedCodeProfiler.java",
       "test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java",
-      "test/android/javatests/src/org/chromium/base/test/BaseJUnit4TestRule.java",
       "test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java",
       "test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java",
       "test/android/javatests/src/org/chromium/base/test/BaseTestResult.java",
-      "test/android/javatests/src/org/chromium/base/test/DestroyActivitiesRule.java",
       "test/android/javatests/src/org/chromium/base/test/LifetimeAssertRule.java",
+      "test/android/javatests/src/org/chromium/base/test/DestroyActivitiesRule.java",
       "test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java",
       "test/android/javatests/src/org/chromium/base/test/SetUpTestRule.java",
       "test/android/javatests/src/org/chromium/base/test/SetUpStatement.java",
       "test/android/javatests/src/org/chromium/base/test/TestListInstrumentationRunListener.java",
       "test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java",
+      "test/android/javatests/src/org/chromium/base/test/CommitSharedPreferencesTestRule.java",
       "test/android/javatests/src/org/chromium/base/test/params/ParameterizedRunner.java",
       "test/android/javatests/src/org/chromium/base/test/params/BlockJUnit4RunnerDelegate.java",
       "test/android/javatests/src/org/chromium/base/test/params/BaseJUnit4RunnerDelegate.java",
diff --git a/base/allocator/allocator_shim_default_dispatch_to_glibc.cc b/base/allocator/allocator_shim_default_dispatch_to_glibc.cc
index 67d456b..aa19b7a 100644
--- a/base/allocator/allocator_shim_default_dispatch_to_glibc.cc
+++ b/base/allocator/allocator_shim_default_dispatch_to_glibc.cc
@@ -4,6 +4,7 @@
 
 #include "base/allocator/allocator_shim.h"
 
+#include <dlfcn.h>
 #include <malloc.h>
 
 // This translation unit defines a default dispatch for the allocator shim which
@@ -54,9 +55,16 @@
 size_t GlibcGetSizeEstimate(const AllocatorDispatch*,
                             void* address,
                             void* context) {
-  // TODO(siggi, primiano): malloc_usable_size may need redirection in the
-  //     presence of interposing shims that divert allocations.
-  return malloc_usable_size(address);
+  // glibc does not expose an alias to resolve malloc_usable_size. Dynamically
+  // resolve it instead. This should be safe because glibc (and hence dlfcn)
+  // does not use malloc_size internally and so there should not be a risk of
+  // recursion.
+  using MallocUsableSizeFunction = decltype(malloc_usable_size)*;
+  static MallocUsableSizeFunction fn_ptr =
+      reinterpret_cast<MallocUsableSizeFunction>(
+          dlsym(RTLD_NEXT, "malloc_usable_size"));
+
+  return fn_ptr(address);
 }
 
 }  // namespace
diff --git a/base/allocator/allocator_shim_default_dispatch_to_tcmalloc.cc b/base/allocator/allocator_shim_default_dispatch_to_tcmalloc.cc
index 550c825..1cddbf4 100644
--- a/base/allocator/allocator_shim_default_dispatch_to_tcmalloc.cc
+++ b/base/allocator/allocator_shim_default_dispatch_to_tcmalloc.cc
@@ -87,16 +87,4 @@
 }
 #endif
 
-SHIM_ALWAYS_EXPORT size_t malloc_size(void* address) __THROW {
-  return tc_malloc_size(address);
-}
-
-#if defined(__ANDROID__)
-SHIM_ALWAYS_EXPORT size_t malloc_usable_size(const void* address) __THROW {
-#else
-SHIM_ALWAYS_EXPORT size_t malloc_usable_size(void* address) __THROW {
-#endif
-  return tc_malloc_size(address);
-}
-
 }  // extern "C"
diff --git a/base/allocator/allocator_shim_override_libc_symbols.h b/base/allocator/allocator_shim_override_libc_symbols.h
index b77cbb1..cb9eeed 100644
--- a/base/allocator/allocator_shim_override_libc_symbols.h
+++ b/base/allocator/allocator_shim_override_libc_symbols.h
@@ -52,12 +52,18 @@
   return ShimPosixMemalign(r, a, s);
 }
 
+SHIM_ALWAYS_EXPORT size_t malloc_size(void* address) __THROW {
+  return ShimGetSizeEstimate(address, nullptr);
+}
+
+SHIM_ALWAYS_EXPORT size_t malloc_usable_size(void* address) __THROW {
+  return ShimGetSizeEstimate(address, nullptr);
+}
+
 // The default dispatch translation unit has to define also the following
 // symbols (unless they are ultimately routed to the system symbols):
 //   void malloc_stats(void);
 //   int mallopt(int, int);
 //   struct mallinfo mallinfo(void);
-//   size_t malloc_size(void*);
-//   size_t malloc_usable_size(const void*);
 
 }  // extern "C"
diff --git a/base/allocator/allocator_shim_unittest.cc b/base/allocator/allocator_shim_unittest.cc
index 02b084b..be42d81 100644
--- a/base/allocator/allocator_shim_unittest.cc
+++ b/base/allocator/allocator_shim_unittest.cc
@@ -550,6 +550,10 @@
 static size_t GetAllocatedSize(void* ptr) {
   return malloc_size(ptr);
 }
+#elif defined(OS_LINUX)
+static size_t GetAllocatedSize(void* ptr) {
+  return malloc_usable_size(ptr);
+}
 #else
 #define NO_MALLOC_SIZE
 #endif
diff --git a/base/android/application_status_listener_unittest.cc b/base/android/application_status_listener_unittest.cc
index 31e1cde3..3d580d8 100644
--- a/base/android/application_status_listener_unittest.cc
+++ b/base/android/application_status_listener_unittest.cc
@@ -106,7 +106,7 @@
   // Create a new listener that stores the new state into |result| on every
   // state change.
   auto listener = ApplicationStatusListener::New(
-      base::Bind(&StoreStateTo, base::Unretained(&result)));
+      base::BindRepeating(&StoreStateTo, base::Unretained(&result)));
 
   EXPECT_EQ(kInvalidApplicationState, result);
 
diff --git a/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java b/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java
index a11f22c..20c626d 100644
--- a/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java
+++ b/base/android/javatests/src/org/chromium/base/AdvancedMockContextTest.java
@@ -52,7 +52,7 @@
     @SmallTest
     public void testComponentCallbacksForTargetContext() {
         Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        Application targetApplication = BaseJUnit4ClassRunner.getApplication();
+        Application targetApplication = (Application) targetContext.getApplicationContext();
         AdvancedMockContext context = new AdvancedMockContext(targetContext);
         Callback1 callback1 = new Callback1();
         Callback2 callback2 = new Callback2();
diff --git a/base/barrier_closure_unittest.cc b/base/barrier_closure_unittest.cc
index 819f6ac..05104c1 100644
--- a/base/barrier_closure_unittest.cc
+++ b/base/barrier_closure_unittest.cc
@@ -13,17 +13,19 @@
 
 TEST(BarrierClosureTest, RunImmediatelyForZeroClosures) {
   int count = 0;
-  base::Closure done_closure(base::Bind(&Increment, base::Unretained(&count)));
+  auto done_closure = base::BindOnce(&Increment, base::Unretained(&count));
 
-  base::Closure barrier_closure = base::BarrierClosure(0, done_closure);
+  base::RepeatingClosure barrier_closure =
+      base::BarrierClosure(0, std::move(done_closure));
   EXPECT_EQ(1, count);
 }
 
 TEST(BarrierClosureTest, RunAfterNumClosures) {
   int count = 0;
-  base::Closure done_closure(base::Bind(&Increment, base::Unretained(&count)));
+  auto done_closure = base::BindOnce(&Increment, base::Unretained(&count));
 
-  base::Closure barrier_closure = base::BarrierClosure(2, done_closure);
+  base::RepeatingClosure barrier_closure =
+      base::BarrierClosure(2, std::move(done_closure));
   EXPECT_EQ(0, count);
 
   barrier_closure.Run();
@@ -50,7 +52,7 @@
 
 TEST(BarrierClosureTest, ReleasesDoneClosureWhenDone) {
   bool done_destructed = false;
-  base::Closure barrier_closure = base::BarrierClosure(
+  base::RepeatingClosure barrier_closure = base::BarrierClosure(
       1,
       base::BindOnce(&DestructionIndicator::DoNothing,
                      base::Owned(new DestructionIndicator(&done_destructed))));
@@ -59,22 +61,21 @@
   EXPECT_TRUE(done_destructed);
 }
 
-void ResetBarrierClosure(base::Closure* closure) {
-  *closure = base::Closure();
+void ResetBarrierClosure(base::RepeatingClosure* closure) {
+  *closure = base::RepeatingClosure();
 }
 
 // Tests a case when |done_closure| resets a |barrier_closure|.
-// |barrier_closure| is a Closure holding the |done_closure|. |done_closure|
-// holds a pointer back to the |barrier_closure|. When |barrier_closure| is
-// Run() it calls ResetBarrierClosure() which erases the |barrier_closure| while
-// still inside of its Run(). The Run() implementation (in base::BarrierClosure)
-// must not try use itself after executing ResetBarrierClosure() or this test
-// would crash inside Run().
+// |barrier_closure| is a RepeatingClosure holding the |done_closure|.
+// |done_closure| holds a pointer back to the |barrier_closure|. When
+// |barrier_closure| is Run() it calls ResetBarrierClosure() which erases the
+// |barrier_closure| while still inside of its Run(). The Run() implementation
+// (in base::BarrierClosure) must not try use itself after executing
+// ResetBarrierClosure() or this test would crash inside Run().
 TEST(BarrierClosureTest, KeepingClosureAliveUntilDone) {
-  base::Closure barrier_closure;
-  base::Closure done_closure =
-      base::Bind(ResetBarrierClosure, &barrier_closure);
-  barrier_closure = base::BarrierClosure(1, done_closure);
+  base::RepeatingClosure barrier_closure;
+  auto done_closure = base::BindOnce(ResetBarrierClosure, &barrier_closure);
+  barrier_closure = base::BarrierClosure(1, std::move(done_closure));
   barrier_closure.Run();
 }
 
diff --git a/base/callback_internal.h b/base/callback_internal.h
index 07c3dc49..7e49a8b 100644
--- a/base/callback_internal.h
+++ b/base/callback_internal.h
@@ -19,14 +19,15 @@
 
 namespace internal {
 
-class CallbackBase;
-class CallbackBaseCopyable;
-
+class ThenAndCatchExecutorCommon;
 class BindStateBase;
 
 template <typename Functor, typename... BoundArgs>
 struct BindState;
 
+class CallbackBase;
+class CallbackBaseCopyable;
+
 struct BindStateBaseRefCountTraits {
   static void Destruct(const BindStateBase*);
 };
@@ -135,6 +136,8 @@
   void Reset();
 
  protected:
+  friend class ThenAndCatchExecutorCommon;
+
   using InvokeFuncStorage = BindStateBase::InvokeFuncStorage;
 
   // Returns true if this callback equals |other|. |other| may be null.
diff --git a/base/critical_closure.h b/base/critical_closure.h
index 94c618d..5017d19 100644
--- a/base/critical_closure.h
+++ b/base/critical_closure.h
@@ -51,7 +51,7 @@
 // Example:
 //   file_task_runner_->PostTask(
 //       FROM_HERE,
-//       MakeCriticalClosure(base::Bind(&WriteToDiskTask, path_, data)));
+//       MakeCriticalClosure(base::BindOnce(&WriteToDiskTask, path_, data)));
 //
 // Note new closures might be posted in this closure. If the new closures need
 // background running time, |MakeCriticalClosure| should be applied on them
diff --git a/base/deferred_sequenced_task_runner_unittest.cc b/base/deferred_sequenced_task_runner_unittest.cc
index ce92392..4eabf969 100644
--- a/base/deferred_sequenced_task_runner_unittest.cc
+++ b/base/deferred_sequenced_task_runner_unittest.cc
@@ -201,7 +201,7 @@
   base::RunLoop run_loop;
   runner->PostTask(FROM_HERE,
                    BindOnce(
-                       [](bool* run_called, base::Closure quit_closure) {
+                       [](bool* run_called, base::OnceClosure quit_closure) {
                          *run_called = true;
                          std::move(quit_closure).Run();
                        },
diff --git a/base/files/important_file_writer.cc b/base/files/important_file_writer.cc
index 2a9ef4f93..d751f654 100644
--- a/base/files/important_file_writer.cc
+++ b/base/files/important_file_writer.cc
@@ -255,7 +255,7 @@
     return;
   }
 
-  Closure task = AdaptCallbackForRepeating(
+  RepeatingClosure task = AdaptCallbackForRepeating(
       BindOnce(&WriteScopedStringToFileAtomically, path_, std::move(data),
                std::move(before_next_write_callback_),
                std::move(after_next_write_callback_), histogram_suffix_));
@@ -266,7 +266,7 @@
     // on the current thread.
     NOTREACHED();
 
-    task.Run();
+    std::move(task).Run();
   }
   ClearPendingWrite();
 }
diff --git a/base/profiler/stack_sampling_profiler_test_util.cc b/base/profiler/stack_sampling_profiler_test_util.cc
index 04e6df6..e560e0c 100644
--- a/base/profiler/stack_sampling_profiler_test_util.cc
+++ b/base/profiler/stack_sampling_profiler_test_util.cc
@@ -56,13 +56,13 @@
 
 }  // namespace
 
-TargetThread::TargetThread(const Closure& to_run) : to_run_(to_run) {}
+TargetThread::TargetThread(OnceClosure to_run) : to_run_(std::move(to_run)) {}
 
 TargetThread::~TargetThread() = default;
 
 void TargetThread::ThreadMain() {
   id_ = PlatformThread::CurrentId();
-  to_run_.Run();
+  std::move(to_run_).Run();
 }
 
 UnwindScenario::UnwindScenario(const SetupFunction& setup_function)
@@ -75,7 +75,7 @@
 }
 
 FunctionAddressRange UnwindScenario::GetSetupFunctionAddressRange() const {
-  return setup_function_.Run(Closure());
+  return setup_function_.Run(OnceClosure());
 }
 
 FunctionAddressRange UnwindScenario::GetOuterFunctionAddressRange() const {
@@ -122,11 +122,11 @@
 
 // Disable inlining for this function so that it gets its own stack frame.
 NOINLINE FunctionAddressRange
-CallWithPlainFunction(const Closure& wait_for_sample) {
+CallWithPlainFunction(OnceClosure wait_for_sample) {
   const void* start_program_counter = GetProgramCounter();
 
   if (!wait_for_sample.is_null())
-    wait_for_sample.Run();
+    std::move(wait_for_sample).Run();
 
   // Volatile to prevent a tail call to GetProgramCounter().
   const void* volatile end_program_counter = GetProgramCounter();
diff --git a/base/profiler/stack_sampling_profiler_test_util.h b/base/profiler/stack_sampling_profiler_test_util.h
index 8151a80..6f4d3579 100644
--- a/base/profiler/stack_sampling_profiler_test_util.h
+++ b/base/profiler/stack_sampling_profiler_test_util.h
@@ -20,7 +20,7 @@
 // A thread to target for profiling that will run the supplied closure.
 class TargetThread : public PlatformThread::Delegate {
  public:
-  TargetThread(const Closure& to_run);
+  TargetThread(OnceClosure to_run);
   ~TargetThread() override;
 
   // PlatformThread::Delegate:
@@ -30,7 +30,7 @@
 
  private:
   PlatformThreadId id_ = 0;
-  Closure to_run_;
+  OnceClosure to_run_;
 
   DISALLOW_COPY_AND_ASSIGN(TargetThread);
 };
@@ -49,7 +49,7 @@
   // calls into the passed closure to wait for a sample to be taken. Returns the
   // address range of the function that sets up the unwind scenario. The passed
   // closure will be null when invoked solely to obtain the address range.
-  using SetupFunction = RepeatingCallback<FunctionAddressRange(const Closure&)>;
+  using SetupFunction = RepeatingCallback<FunctionAddressRange(OnceClosure)>;
 
   // Events to coordinate the sampling.
   struct SampleEvents {
@@ -88,7 +88,7 @@
 
 // UnwindScenario setup function that calls into |wait_for_sample| without doing
 // any special unwinding setup, to exercise the "normal" unwind scenario.
-FunctionAddressRange CallWithPlainFunction(const Closure& wait_for_sample);
+FunctionAddressRange CallWithPlainFunction(OnceClosure wait_for_sample);
 
 // The callback to perform profiling on the provided thread.
 using ProfileCallback = OnceCallback<void(PlatformThreadId)>;
diff --git a/base/profiler/stack_sampling_profiler_unittest.cc b/base/profiler/stack_sampling_profiler_unittest.cc
index fd94290..c52fcc2d 100644
--- a/base/profiler/stack_sampling_profiler_unittest.cc
+++ b/base/profiler/stack_sampling_profiler_unittest.cc
@@ -67,7 +67,7 @@
 // Calls into |wait_for_sample| after using alloca(), to test unwinding with a
 // frame pointer.
 // Disable inlining for this function so that it gets its own stack frame.
-NOINLINE FunctionAddressRange CallWithAlloca(const Closure& wait_for_sample) {
+NOINLINE FunctionAddressRange CallWithAlloca(OnceClosure wait_for_sample) {
   const void* start_program_counter = GetProgramCounter();
 
   // Volatile to force a dynamic stack allocation.
@@ -80,7 +80,7 @@
     *p = '\0';
 
   if (!wait_for_sample.is_null())
-    wait_for_sample.Run();
+    std::move(wait_for_sample).Run();
 
   // Volatile to prevent a tail call to GetProgramCounter().
   const void* volatile end_program_counter = GetProgramCounter();
@@ -89,9 +89,9 @@
 
 // The function to be executed by the code in the other library.
 void OtherLibraryCallback(void* arg) {
-  const Closure* wait_for_sample = static_cast<const Closure*>(arg);
+  OnceClosure* wait_for_sample = static_cast<OnceClosure*>(arg);
 
-  wait_for_sample->Run();
+  std::move(*wait_for_sample).Run();
 
   // Prevent tail call.
   volatile int i = 0;
@@ -103,7 +103,7 @@
 // modules.
 // Disable inlining for this function so that it gets its own stack frame.
 NOINLINE FunctionAddressRange
-CallThroughOtherLibrary(NativeLibrary library, const Closure& wait_for_sample) {
+CallThroughOtherLibrary(NativeLibrary library, OnceClosure wait_for_sample) {
   const void* start_program_counter = GetProgramCounter();
 
   if (!wait_for_sample.is_null()) {
@@ -114,9 +114,7 @@
         GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction"));
     EXPECT_TRUE(function);
 
-    // This copy avoids the need for a const_cast.
-    Closure wait_for_sample_copy = wait_for_sample;
-    (*function)(&OtherLibraryCallback, &wait_for_sample_copy);
+    (*function)(&OtherLibraryCallback, &wait_for_sample);
   }
 
   // Volatile to prevent a tail call to GetProgramCounter().
@@ -162,13 +160,13 @@
 // The callback type used to collect a profile. The passed Profile is move-only.
 // Other threads, including the UI thread, may block on callback completion so
 // this should run as quickly as possible.
-using ProfileCompletedCallback = Callback<void(Profile)>;
+using ProfileCompletedCallback = OnceCallback<void(Profile)>;
 
 // TestProfileBuilder collects samples produced by the profiler.
 class TestProfileBuilder : public ProfileBuilder {
  public:
   TestProfileBuilder(ModuleCache* module_cache,
-                     const ProfileCompletedCallback& callback);
+                     ProfileCompletedCallback callback);
 
   ~TestProfileBuilder() override;
 
@@ -189,14 +187,14 @@
   int metadata_count_ = 0;
 
   // Callback made when sampling a profile completes.
-  const ProfileCompletedCallback callback_;
+  ProfileCompletedCallback callback_;
 
   DISALLOW_COPY_AND_ASSIGN(TestProfileBuilder);
 };
 
 TestProfileBuilder::TestProfileBuilder(ModuleCache* module_cache,
-                                       const ProfileCompletedCallback& callback)
-    : module_cache_(module_cache), callback_(callback) {}
+                                       ProfileCompletedCallback callback)
+    : module_cache_(module_cache), callback_(std::move(callback)) {}
 
 TestProfileBuilder::~TestProfileBuilder() = default;
 
@@ -214,7 +212,7 @@
 
 void TestProfileBuilder::OnProfileCompleted(TimeDelta profile_duration,
                                             TimeDelta sampling_period) {
-  callback_.Run(
+  std::move(callback_).Run(
       Profile(samples_, metadata_count_, profile_duration, sampling_period));
 }
 
diff --git a/base/profiler/thread_delegate_win.cc b/base/profiler/thread_delegate_win.cc
index 12f9d88..71ba5aa 100644
--- a/base/profiler/thread_delegate_win.cc
+++ b/base/profiler/thread_delegate_win.cc
@@ -7,6 +7,7 @@
 #include <windows.h>
 #include <winternl.h>
 
+#include "base/debug/alias.h"
 #include "base/logging.h"
 #include "base/profiler/native_unwinder_win.h"
 #include "build/build_config.h"
@@ -29,9 +30,22 @@
 };
 
 win::ScopedHandle GetThreadHandle(PlatformThreadId thread_id) {
-  win::ScopedHandle handle(::OpenThread(
-      THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION,
-      FALSE, thread_id));
+  // TODO(http://crbug.com/947459): Remove the test_handle* CHECKs once we
+  // understand which flag is triggering the failure.
+
+  DWORD flags = 0;
+  base::debug::Alias(&flags);
+
+  flags |= THREAD_GET_CONTEXT;
+  win::ScopedHandle test_handle1(::OpenThread(flags, FALSE, thread_id));
+  CHECK(test_handle1.IsValid());
+
+  flags |= THREAD_QUERY_INFORMATION;
+  win::ScopedHandle test_handle2(::OpenThread(flags, FALSE, thread_id));
+  CHECK(test_handle2.IsValid());
+
+  flags |= THREAD_SUSPEND_RESUME;
+  win::ScopedHandle handle(::OpenThread(flags, FALSE, thread_id));
   CHECK(handle.IsValid());
   return handle;
 }
diff --git a/base/run_loop_unittest.cc b/base/run_loop_unittest.cc
index 8c023306..d9832e4 100644
--- a/base/run_loop_unittest.cc
+++ b/base/run_loop_unittest.cc
@@ -612,7 +612,7 @@
 
   RunLoop::AddNestingObserverOnCurrentThread(&nesting_observer);
 
-  const RepeatingClosure run_nested_loop = Bind([]() {
+  const RepeatingClosure run_nested_loop = BindRepeating([]() {
     RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
     ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                             nested_run_loop.QuitClosure());
diff --git a/base/sequence_checker_unittest.cc b/base/sequence_checker_unittest.cc
index 8d44f3e..164c4f9b7 100644
--- a/base/sequence_checker_unittest.cc
+++ b/base/sequence_checker_unittest.cc
@@ -26,17 +26,17 @@
 // Runs a callback on another thread.
 class RunCallbackThread : public SimpleThread {
  public:
-  explicit RunCallbackThread(const Closure& callback)
-      : SimpleThread("RunCallbackThread"), callback_(callback) {
+  explicit RunCallbackThread(OnceClosure callback)
+      : SimpleThread("RunCallbackThread"), callback_(std::move(callback)) {
     Start();
     Join();
   }
 
  private:
   // SimpleThread:
-  void Run() override { callback_.Run(); }
+  void Run() override { std::move(callback_).Run(); }
 
-  const Closure callback_;
+  OnceClosure callback_;
 
   DISALLOW_COPY_AND_ASSIGN(RunCallbackThread);
 };
@@ -83,7 +83,7 @@
 TEST(SequenceCheckerTest, CallsDisallowedOnDifferentThreadsNoSequenceToken) {
   SequenceCheckerImpl sequence_checker;
   RunCallbackThread thread(
-      Bind(&ExpectNotCalledOnValidSequence, Unretained(&sequence_checker)));
+      BindOnce(&ExpectNotCalledOnValidSequence, Unretained(&sequence_checker)));
 }
 
 TEST(SequenceCheckerTest, CallsAllowedOnDifferentThreadsSameSequenceToken) {
@@ -94,8 +94,9 @@
   SequenceCheckerImpl sequence_checker;
   EXPECT_TRUE(sequence_checker.CalledOnValidSequence());
 
-  RunCallbackThread thread(Bind(&ExpectCalledOnValidSequenceWithSequenceToken,
-                                Unretained(&sequence_checker), sequence_token));
+  RunCallbackThread thread(
+      BindOnce(&ExpectCalledOnValidSequenceWithSequenceToken,
+               Unretained(&sequence_checker), sequence_token));
 }
 
 TEST(SequenceCheckerTest, CallsDisallowedOnSameThreadDifferentSequenceToken) {
@@ -145,7 +146,7 @@
   // Verify that CalledOnValidSequence() returns true when called on a
   // different thread after a call to DetachFromSequence().
   RunCallbackThread thread(
-      Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)));
+      BindOnce(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)));
 
   EXPECT_FALSE(sequence_checker.CalledOnValidSequence());
 }
diff --git a/base/task/cancelable_task_tracker.cc b/base/task/cancelable_task_tracker.cc
index 057c515..96f4558 100644
--- a/base/task/cancelable_task_tracker.cc
+++ b/base/task/cancelable_task_tracker.cc
@@ -103,7 +103,7 @@
                std::move(untrack_closure))));
 
   *is_canceled_cb =
-      Bind(&IsCanceled, RetainedRef(flag), std::move(untrack_runner));
+      BindRepeating(&IsCanceled, RetainedRef(flag), std::move(untrack_runner));
 
   Track(id, std::move(flag));
   return id;
diff --git a/base/task/cancelable_task_tracker.h b/base/task/cancelable_task_tracker.h
index f2fc970..dd43735 100644
--- a/base/task/cancelable_task_tracker.h
+++ b/base/task/cancelable_task_tracker.h
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// CancelableTaskTracker posts tasks (in the form of a Closure) to a
+// CancelableTaskTracker posts tasks (in the form of a OnceClosure) to a
 // TaskRunner, and is able to cancel the task later if it's not needed
 // anymore.  On destruction, CancelableTaskTracker will cancel all
 // tracked tasks.
 //
-// Each cancelable task can be associated with a reply (also a Closure). After
-// the task is run on the TaskRunner, |reply| will be posted back to
+// Each cancelable task can be associated with a reply (also a OnceClosure).
+// After the task is run on the TaskRunner, |reply| will be posted back to
 // originating TaskRunner.
 //
 // NOTE:
@@ -64,7 +64,7 @@
   typedef int64_t TaskId;
   static const TaskId kBadTaskId;
 
-  typedef Callback<bool()> IsCanceledCallback;
+  using IsCanceledCallback = RepeatingCallback<bool()>;
 
   CancelableTaskTracker();
 
diff --git a/base/task/cancelable_task_tracker_unittest.cc b/base/task/cancelable_task_tracker_unittest.cc
index dae5431..52e0a903 100644
--- a/base/task/cancelable_task_tracker_unittest.cc
+++ b/base/task/cancelable_task_tracker_unittest.cc
@@ -316,8 +316,8 @@
 // Runs |fn| with |task_tracker|, expecting it to crash in debug mode.
 void MaybeRunDeadlyTaskTrackerMemberFunction(
     CancelableTaskTracker* task_tracker,
-    const Callback<void(CancelableTaskTracker*)>& fn) {
-  EXPECT_DCHECK_DEATH(fn.Run(task_tracker));
+    OnceCallback<void(CancelableTaskTracker*)> fn) {
+  EXPECT_DCHECK_DEATH(std::move(fn).Run(task_tracker));
 }
 
 void PostDoNothingTask(CancelableTaskTracker* task_tracker) {
@@ -333,7 +333,7 @@
   bad_thread.task_runner()->PostTask(
       FROM_HERE,
       BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
-               Unretained(&task_tracker_), Bind(&PostDoNothingTask)));
+               Unretained(&task_tracker_), BindOnce(&PostDoNothingTask)));
 }
 
 void TryCancel(CancelableTaskTracker::TaskId task_id,
@@ -355,7 +355,7 @@
   bad_thread.task_runner()->PostTask(
       FROM_HERE,
       BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
-               Unretained(&task_tracker_), Bind(&TryCancel, task_id)));
+               Unretained(&task_tracker_), BindOnce(&TryCancel, task_id)));
 
   test_task_runner->RunUntilIdle();
 }
@@ -374,7 +374,7 @@
   bad_thread.task_runner()->PostTask(
       FROM_HERE, BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
                           Unretained(&task_tracker_),
-                          Bind(&CancelableTaskTracker::TryCancelAll)));
+                          BindOnce(&CancelableTaskTracker::TryCancelAll)));
 
   test_task_runner->RunUntilIdle();
 }
diff --git a/base/task/promise/abstract_promise.cc b/base/task/promise/abstract_promise.cc
index 0bd5f01..db58f74 100644
--- a/base/task/promise/abstract_promise.cc
+++ b/base/task/promise/abstract_promise.cc
@@ -47,6 +47,11 @@
 void AbstractPromise::AddAsDependentForAllPrerequisites() {
   DCHECK(prerequisites_);
 
+  // Note a curried promise will eventually get to all its children and pass
+  // them catch responsibility through AddAsDependentForAllPrerequisites,
+  // although that'll be done lazily (only once they resolve/reject, so there
+  // is a possibility the DCHECKs might be racy.
+
   for (AdjacencyListNode& node : prerequisites_->prerequisite_list) {
     node.dependent_node.dependent = this;
 
@@ -196,20 +201,6 @@
   prerequisite->passed_catch_responsibility_ = true;
 }
 
-void AbstractPromise::PassCatchResponsibilityOntoDependentsForCurriedPromise(
-    DependentList::Node* dependent_list) {
-  CheckedAutoLock lock(GetCheckedLock());
-  if (!dependent_list)
-    return;
-
-  if (IsResolvedWithPromise()) {
-    for (DependentList::Node* node = dependent_list; node;
-         node = node->next.load(std::memory_order_relaxed)) {
-      node->dependent->MaybeInheritChecks(this);
-    }
-  }
-}
-
 AbstractPromise::LocationRef::LocationRef(const Location& from_here)
     : from_here_(from_here) {}
 
@@ -225,11 +216,7 @@
 #endif
 
 const AbstractPromise::Executor* AbstractPromise::GetExecutor() const {
-  const SmallUniqueObject<Executor>* executor =
-      base::unique_any_cast<SmallUniqueObject<Executor>>(&value_);
-  if (!executor)
-    return nullptr;
-  return executor->get();
+  return base::unique_any_cast<Executor>(&value_);
 }
 
 AbstractPromise::Executor::PrerequisitePolicy
@@ -353,10 +340,6 @@
   DependentList::Node* dependent_list = dependents_.ConsumeOnceForResolve();
   dependent_list = NonThreadSafeReverseList(dependent_list);
 
-#if DCHECK_IS_ON()
-  PassCatchResponsibilityOntoDependentsForCurriedPromise(dependent_list);
-#endif
-
   // Propagate resolve to dependents.
   DependentList::Node* next;
   for (DependentList::Node* node = dependent_list; node; node = next) {
@@ -374,10 +357,6 @@
   DependentList::Node* dependent_list = dependents_.ConsumeOnceForReject();
   dependent_list = NonThreadSafeReverseList(dependent_list);
 
-#if DCHECK_IS_ON()
-  PassCatchResponsibilityOntoDependentsForCurriedPromise(dependent_list);
-#endif
-
   // Propagate rejection to dependents. We always propagate rejection
   // immediately.
   DependentList::Node* next;
@@ -429,7 +408,8 @@
 
 void AbstractPromise::OnResolved() {
 #if DCHECK_IS_ON()
-  DCHECK(executor_can_resolve_) << from_here_.ToString();
+  DCHECK(executor_can_resolve_ || IsResolvedWithPromise())
+      << from_here_.ToString();
 #endif
   if (IsResolvedWithPromise()) {
     scoped_refptr<AbstractPromise> curried_promise =
@@ -516,5 +496,42 @@
   action_prerequisite_count = 1;
 }
 
+AbstractPromise::Executor::~Executor() {
+  vtable_->destructor(storage_);
+}
+
+AbstractPromise::Executor::PrerequisitePolicy
+AbstractPromise::Executor::GetPrerequisitePolicy() const {
+  return vtable_->get_prerequsite_policy(storage_);
+}
+
+bool AbstractPromise::Executor::IsCancelled() const {
+  return vtable_->is_cancelled(storage_);
+}
+
+#if DCHECK_IS_ON()
+AbstractPromise::Executor::ArgumentPassingType
+AbstractPromise::Executor::ResolveArgumentPassingType() const {
+  return vtable_->resolve_argument_passing_type(storage_);
+}
+
+AbstractPromise::Executor::ArgumentPassingType
+AbstractPromise::Executor::RejectArgumentPassingType() const {
+  return vtable_->reject_argument_passing_type(storage_);
+}
+
+bool AbstractPromise::Executor::CanResolve() const {
+  return vtable_->can_resolve(storage_);
+}
+
+bool AbstractPromise::Executor::CanReject() const {
+  return vtable_->can_reject(storage_);
+}
+#endif
+
+void AbstractPromise::Executor::Execute(AbstractPromise* promise) {
+  return vtable_->execute(storage_, promise);
+}
+
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/promise/abstract_promise.h b/base/task/promise/abstract_promise.h
index 33dde53..1ae8231 100644
--- a/base/task/promise/abstract_promise.h
+++ b/base/task/promise/abstract_promise.h
@@ -14,7 +14,6 @@
 #include "base/no_destructor.h"
 #include "base/task/common/checked_lock.h"
 #include "base/task/promise/dependent_list.h"
-#include "base/task/promise/small_unique_object.h"
 #include "base/thread_annotations.h"
 
 namespace base {
@@ -46,7 +45,7 @@
 // resolved. This lets us disambiguate promises with the same resolve and reject
 // type.
 template <typename T>
-struct BASE_EXPORT Resolved {
+struct Resolved {
   using Type = T;
 
   static_assert(!std::is_same<T, NoReject>::value,
@@ -64,7 +63,7 @@
 };
 
 template <>
-struct BASE_EXPORT Resolved<void> {
+struct Resolved<void> {
   using Type = void;
   Void value;
 };
@@ -73,7 +72,7 @@
 // rejected. This lets us disambiguate promises with the same resolve and reject
 // type.
 template <typename T>
-struct BASE_EXPORT Rejected {
+struct Rejected {
   using Type = T;
   T value;
 
@@ -93,7 +92,7 @@
 };
 
 template <>
-struct BASE_EXPORT Rejected<void> {
+struct Rejected<void> {
   using Type = void;
   Void value;
 };
@@ -124,7 +123,7 @@
                   ExecutorArgs&&... executor_args) noexcept
       : task_runner_(std::move(task_runner)),
         from_here_(std::move(from_here)),
-        value_(in_place_type_t<SmallUniqueObject<Executor>>(),
+        value_(in_place_type_t<Executor>(),
                in_place_type_t<DerivedExecutorType>(),
                std::forward<ExecutorArgs>(executor_args)...),
 #if DCHECK_IS_ON()
@@ -205,9 +204,27 @@
   // Unresolved promises have an executor which invokes one of the callbacks
   // associated with the promise. Once the callback has been invoked the
   // Executor is destroyed.
+  //
+  // Ideally Executor would be a pure virtual class, but we want to store these
+  // inline to reduce the number of memory allocations (small object
+  // optimization). The problem is even though placement new returns the same
+  // address it was allocated at, you have to use the returned pointer.  Casting
+  // the buffer to the derived class is undefined behavior. STL implementations
+  // usually store an extra pointer, but there we have opted for implementing
+  // our own VTable to save a little bit of memory.
   class BASE_EXPORT Executor {
    public:
-    virtual ~Executor() {}
+    // Constructs |Derived| in place.
+    template <typename Derived, typename... Args>
+    explicit Executor(in_place_type_t<Derived>, Args&&... args) {
+      static_assert(sizeof(Derived) <= MaxSize, "Derived is too big");
+      static_assert(sizeof(Executor) <= sizeof(AnyInternal::InlineAlloc),
+                    "Executor is too big");
+      vtable_ = &VTableHelper<Derived>::vtable_;
+      new (storage_) Derived(std::forward<Args>(args)...);
+    }
+
+    ~Executor();
 
     // Controls whether or not a promise should wait for its prerequisites
     // before becoming eligible for execution.
@@ -227,11 +244,11 @@
     };
 
     // Returns the associated PrerequisitePolicy.
-    virtual PrerequisitePolicy GetPrerequisitePolicy() = 0;
+    PrerequisitePolicy GetPrerequisitePolicy() const;
 
     // NB if there is both a resolve and a reject executor we require them to
     // be both canceled at the same time.
-    virtual bool IsCancelled() const = 0;
+    bool IsCancelled() const;
 
     // Describes an executor callback.
     enum class ArgumentPassingType : uint8_t {
@@ -249,10 +266,10 @@
 #if DCHECK_IS_ON()
     // Returns details of the resolve and reject executor callbacks if any. This
     // data is used to diagnose double moves and missing catches.
-    virtual ArgumentPassingType ResolveArgumentPassingType() const = 0;
-    virtual ArgumentPassingType RejectArgumentPassingType() const = 0;
-    virtual bool CanResolve() const = 0;
-    virtual bool CanReject() const = 0;
+    ArgumentPassingType ResolveArgumentPassingType() const;
+    ArgumentPassingType RejectArgumentPassingType() const;
+    bool CanResolve() const;
+    bool CanReject() const;
 #endif
 
     // Invokes the associate callback for |promise|. If the callback was
@@ -263,7 +280,84 @@
     // |promise->OnResolved()|.
     // Caution the Executor will be destructed when |promise->state()| is
     // written to.
-    virtual void Execute(AbstractPromise* promise) = 0;
+    void Execute(AbstractPromise* promise);
+
+   private:
+    static constexpr size_t MaxSize = sizeof(void*) * 2;
+
+    struct VTable {
+      void (*destructor)(void* self);
+      PrerequisitePolicy (*get_prerequsite_policy)(const void* self);
+      bool (*is_cancelled)(const void* self);
+#if DCHECK_IS_ON()
+      ArgumentPassingType (*resolve_argument_passing_type)(const void* self);
+      ArgumentPassingType (*reject_argument_passing_type)(const void* self);
+      bool (*can_resolve)(const void* self);
+      bool (*can_reject)(const void* self);
+#endif
+      void (*execute)(void* self, AbstractPromise* promise);
+
+     private:
+      DISALLOW_COPY_AND_ASSIGN(VTable);
+    };
+
+    template <typename DerivedType>
+    struct VTableHelper {
+      VTableHelper(const VTableHelper& other) = delete;
+      VTableHelper& operator=(const VTableHelper& other) = delete;
+
+      static void Destructor(void* self) {
+        static_cast<DerivedType*>(self)->~DerivedType();
+      }
+
+      static PrerequisitePolicy GetPrerequisitePolicy(const void* self) {
+        return static_cast<const DerivedType*>(self)->GetPrerequisitePolicy();
+      }
+
+      static bool IsCancelled(const void* self) {
+        return static_cast<const DerivedType*>(self)->IsCancelled();
+      }
+
+#if DCHECK_IS_ON()
+      static ArgumentPassingType ResolveArgumentPassingType(const void* self) {
+        return static_cast<const DerivedType*>(self)
+            ->ResolveArgumentPassingType();
+      }
+
+      static ArgumentPassingType RejectArgumentPassingType(const void* self) {
+        return static_cast<const DerivedType*>(self)
+            ->RejectArgumentPassingType();
+      }
+
+      static bool CanResolve(const void* self) {
+        return static_cast<const DerivedType*>(self)->CanResolve();
+      }
+
+      static bool CanReject(const void* self) {
+        return static_cast<const DerivedType*>(self)->CanReject();
+      }
+#endif
+
+      static void Execute(void* self, AbstractPromise* promise) {
+        return static_cast<DerivedType*>(self)->Execute(promise);
+      }
+
+      static constexpr VTable vtable_ = {
+        &VTableHelper::Destructor,
+        &VTableHelper::GetPrerequisitePolicy,
+        &VTableHelper::IsCancelled,
+#if DCHECK_IS_ON()
+        &VTableHelper::ResolveArgumentPassingType,
+        &VTableHelper::RejectArgumentPassingType,
+        &VTableHelper::CanResolve,
+        &VTableHelper::CanReject,
+#endif
+        &VTableHelper::Execute,
+      };
+    };
+
+    const VTable* vtable_;
+    char storage_[MaxSize];
   };
 
   // Signals that this promise was cancelled. If executor hasn't run yet, this
@@ -387,8 +481,8 @@
 
   const Location from_here_;
 
-  // To save memory |value_| contains SmallUniqueObject<Executor> (which is
-  // stored inline) before it has run and afterwards it contains one of:
+  // To save memory |value_| contains Executor (which is stored inline) before
+  // it has run and afterwards it contains one of:
   // * Resolved<T>
   // * Rejected<T>
   // * scoped_refptr<AbstractPromise> (for curried promises - i.e. a promise
@@ -399,10 +493,6 @@
   void MaybeInheritChecks(AbstractPromise* source)
       EXCLUSIVE_LOCKS_REQUIRED(GetCheckedLock());
 
-  // Does nothing if this promise wasn't resolved by a promise.
-  void PassCatchResponsibilityOntoDependentsForCurriedPromise(
-      DependentList::Node* dependent_list);
-
   // Controls how we deal with unhandled rejection.
   const RejectPolicy reject_policy_;
 
@@ -487,6 +577,11 @@
   std::unique_ptr<AdjacencyList> prerequisites_;
 };
 
+// static
+template <typename T>
+const AbstractPromise::Executor::VTable
+    AbstractPromise::Executor::VTableHelper<T>::vtable_;
+
 }  // namespace internal
 }  // namespace base
 
diff --git a/base/task/promise/abstract_promise_unittest.cc b/base/task/promise/abstract_promise_unittest.cc
index 387eb235..77b84bf 100644
--- a/base/task/promise/abstract_promise_unittest.cc
+++ b/base/task/promise/abstract_promise_unittest.cc
@@ -33,7 +33,7 @@
 namespace base {
 namespace internal {
 
-class TestExecutor : public AbstractPromise::Executor {
+class TestExecutor {
  public:
   TestExecutor(PrerequisitePolicy policy,
 #if DCHECK_IS_ON()
@@ -53,24 +53,24 @@
   }
 
 #if DCHECK_IS_ON()
-  ArgumentPassingType ResolveArgumentPassingType() const override {
+  ArgumentPassingType ResolveArgumentPassingType() const {
     return resolve_argument_passing_type_;
   }
 
-  ArgumentPassingType RejectArgumentPassingType() const override {
+  ArgumentPassingType RejectArgumentPassingType() const {
     return reject_argument_passing_type_;
   }
 
-  bool CanResolve() const override { return resolve_flags_ & 1; }
+  bool CanResolve() const { return resolve_flags_ & 1; }
 
-  bool CanReject() const override { return resolve_flags_ & 2; }
+  bool CanReject() const { return resolve_flags_ & 2; }
 #endif
 
-  PrerequisitePolicy GetPrerequisitePolicy() override { return policy_; }
+  PrerequisitePolicy GetPrerequisitePolicy() const { return policy_; }
 
-  bool IsCancelled() const override { return false; }
+  bool IsCancelled() const { return false; }
 
-  void Execute(AbstractPromise* p) override { std::move(callback_).Run(p); }
+  void Execute(AbstractPromise* p) { std::move(callback_).Run(p); }
 
  private:
   base::OnceCallback<void(AbstractPromise*)> callback_;
diff --git a/base/task/promise/helpers.h b/base/task/promise/helpers.h
new file mode 100644
index 0000000..8a1104d
--- /dev/null
+++ b/base/task/promise/helpers.h
@@ -0,0 +1,519 @@
+// 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_PROMISE_HELPERS_H_
+#define BASE_TASK_PROMISE_HELPERS_H_
+
+#include <type_traits>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/task/promise/abstract_promise.h"
+#include "base/task/promise/promise_result.h"
+
+namespace base {
+namespace internal {
+
+// PromiseCallbackTraits computes the resolve and reject types of a Promise
+// from the return type of a resolve or reject callback.
+//
+// Usage example:
+//   using Traits = PromiseCallbackTraits<int>;
+//
+//   Traits::
+//       ResolveType is int
+//       RejectType is NoReject
+//       could_resolve is true
+//       could_reject is false
+template <typename T>
+struct PromiseCallbackTraits {
+  using ResolveType = T;
+  using RejectType = NoReject;
+  static constexpr bool could_resolve = true;
+  static constexpr bool could_reject = false;
+};
+
+template <typename T>
+struct PromiseCallbackTraits<Resolved<T>> {
+  using ResolveType = T;
+  using RejectType = NoReject;
+  static constexpr bool could_resolve = true;
+  static constexpr bool could_reject = false;
+};
+
+template <typename T>
+struct PromiseCallbackTraits<Rejected<T>> {
+  using ResolveType = NoResolve;
+  using RejectType = T;
+  static constexpr bool could_resolve = false;
+  static constexpr bool could_reject = true;
+};
+
+template <typename Reject>
+struct PromiseCallbackTraits<Promise<NoResolve, Reject>> {
+  using ResolveType = NoResolve;
+  using RejectType = Reject;
+  static constexpr bool could_resolve = false;
+  static constexpr bool could_reject = true;
+};
+
+template <typename Resolve>
+struct PromiseCallbackTraits<Promise<Resolve, NoReject>> {
+  using ResolveType = Resolve;
+  using RejectType = NoReject;
+  static constexpr bool could_resolve = true;
+  static constexpr bool could_reject = false;
+};
+
+template <typename Resolve, typename Reject>
+struct PromiseCallbackTraits<Promise<Resolve, Reject>> {
+  using ResolveType = Resolve;
+  using RejectType = Reject;
+  static constexpr bool could_resolve = true;
+  static constexpr bool could_reject = true;
+};
+
+template <typename Reject>
+struct PromiseCallbackTraits<PromiseResult<NoResolve, Reject>> {
+  using ResolveType = NoResolve;
+  using RejectType = Reject;
+  static constexpr bool could_resolve = false;
+  static constexpr bool could_reject = true;
+};
+
+template <typename Resolve>
+struct PromiseCallbackTraits<PromiseResult<Resolve, NoReject>> {
+  using ResolveType = Resolve;
+  using RejectType = NoReject;
+  static constexpr bool could_resolve = true;
+  static constexpr bool could_reject = false;
+};
+
+template <typename Resolve, typename Reject>
+struct PromiseCallbackTraits<PromiseResult<Resolve, Reject>> {
+  using ResolveType = Resolve;
+  using RejectType = Reject;
+  static constexpr bool could_resolve = true;
+  static constexpr bool could_reject = true;
+};
+
+template <typename T>
+struct IsScopedRefPtr {
+  static constexpr bool value = false;
+};
+
+template <typename T>
+struct IsScopedRefPtr<scoped_refptr<T>> {
+  static constexpr bool value = true;
+};
+
+// UseMoveSemantics determines whether move semantics should be used to
+// pass |T| as a function parameter.
+//
+// Usage example:
+//
+//   UseMoveSemantics<std::unique_ptr<int>>::value;  // is true
+//   UseMoveSemantics<int>::value; // is false
+//   UseMoveSemantics<scoped_refptr<Dummy>>::value; // is false
+//
+// Will give false positives for some copyable types, but that should be
+// harmless.
+template <typename T,
+          bool use_move = !std::is_reference<T>::value &&
+                          !std::is_pointer<T>::value &&
+                          !std::is_fundamental<std::decay_t<T>>::value &&
+                          !IsScopedRefPtr<T>::value>
+struct UseMoveSemantics : public std::integral_constant<bool, use_move> {
+  static_assert(!std::is_rvalue_reference<T>::value,
+                "Promise<T&&> not supported");
+
+  static constexpr AbstractPromise::Executor::ArgumentPassingType
+      argument_passing_type =
+          use_move ? AbstractPromise::Executor::ArgumentPassingType::kMove
+                   : AbstractPromise::Executor::ArgumentPassingType::kNormal;
+};
+
+// CallbackTraits extracts properties relevant to Promises from a callback.
+//
+// Usage example:
+//
+//   using Traits = CallbackTraits<
+//       base::OnceCallback<PromiseResult<int, std::string>(float)>;
+//
+//   Traits::
+//     ResolveType is int
+//     RejectType is std::string
+//     ArgType is float
+//     ReturnType is PromiseResult<int, std::string>
+//     SignatureType is PromiseResult<int, std::string>(float)
+//     argument_passing_type is kNormal
+template <typename T>
+struct CallbackTraits;
+
+template <typename T>
+struct CallbackTraits<base::OnceCallback<T()>> {
+  using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
+  using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
+  using ArgType = void;
+  using ReturnType = T;
+  using SignatureType = T();
+  static constexpr AbstractPromise::Executor::ArgumentPassingType
+      argument_passing_type =
+          AbstractPromise::Executor::ArgumentPassingType::kNormal;
+};
+
+template <typename T>
+struct CallbackTraits<base::RepeatingCallback<T()>> {
+  using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
+  using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
+  using ArgType = void;
+  using ReturnType = T;
+  using SignatureType = T();
+  static constexpr AbstractPromise::Executor::ArgumentPassingType
+      argument_passing_type =
+          AbstractPromise::Executor::ArgumentPassingType::kNormal;
+};
+
+template <typename T, typename Arg>
+struct CallbackTraits<base::OnceCallback<T(Arg)>> {
+  using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
+  using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
+  using ArgType = Arg;
+  using ReturnType = T;
+  using SignatureType = T(Arg);
+  static constexpr AbstractPromise::Executor::ArgumentPassingType
+      argument_passing_type = UseMoveSemantics<Arg>::argument_passing_type;
+};
+
+template <typename T, typename Arg>
+struct CallbackTraits<base::RepeatingCallback<T(Arg)>> {
+  using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
+  using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
+  using ArgType = Arg;
+  using ReturnType = T;
+  using SignatureType = T(Arg);
+  static constexpr AbstractPromise::Executor::ArgumentPassingType
+      argument_passing_type = UseMoveSemantics<Arg>::argument_passing_type;
+};
+
+// Helper for combining the resolve types of two promises.
+template <typename A, typename B>
+struct ResolveCombinerHelper {
+  using Type = A;
+  static constexpr bool valid = false;
+};
+
+template <typename A>
+struct ResolveCombinerHelper<A, A> {
+  using Type = A;
+  static constexpr bool valid = true;
+};
+
+template <typename B>
+struct ResolveCombinerHelper<NoResolve, B> {
+  using Type = B;
+  static constexpr bool valid = true;
+};
+
+template <typename A>
+struct ResolveCombinerHelper<A, NoResolve> {
+  using Type = A;
+  static constexpr bool valid = true;
+};
+
+template <>
+struct ResolveCombinerHelper<NoResolve, NoResolve> {
+  using Type = NoResolve;
+  static constexpr bool valid = true;
+};
+
+// Helper for combining the reject types of two promises.
+template <typename A, typename B>
+struct RejectCombinerHelper {
+  using Type = A;
+  static constexpr bool valid = false;
+};
+
+template <typename A>
+struct RejectCombinerHelper<A, A> {
+  using Type = A;
+  static constexpr bool valid = true;
+};
+
+template <typename B>
+struct RejectCombinerHelper<NoReject, B> {
+  using Type = B;
+  static constexpr bool valid = true;
+};
+
+template <typename A>
+struct RejectCombinerHelper<A, NoReject> {
+  using Type = A;
+  static constexpr bool valid = true;
+};
+
+template <>
+struct RejectCombinerHelper<NoReject, NoReject> {
+  using Type = NoReject;
+  static constexpr bool valid = true;
+};
+
+// Helper that computes and validates the return type for combining promises.
+// Essentially the promise types have to match unless there's NoResolve or
+// or NoReject in which case they can be combined.
+template <typename ThenReturnResolveT,
+          typename ThenReturnRejectT,
+          typename CatchReturnResolveT,
+          typename CatchReturnRejectT>
+struct PromiseCombiner {
+  using ResolveHelper =
+      ResolveCombinerHelper<ThenReturnResolveT, CatchReturnResolveT>;
+  using RejectHelper =
+      RejectCombinerHelper<ThenReturnRejectT, CatchReturnRejectT>;
+  using ResolveType = typename ResolveHelper::Type;
+  using RejectType = typename RejectHelper::Type;
+  static constexpr bool valid = ResolveHelper::valid && RejectHelper::valid;
+};
+
+// TODO(alexclarke): Specialize |CallbackTraits| for callbacks with more than
+// one argument to support Promises::All.
+
+template <typename RejectStorage>
+struct EmplaceInnerHelper {
+  template <typename Resolve, typename Reject>
+  static void Emplace(AbstractPromise* promise,
+                      PromiseResult<Resolve, Reject> result) {
+    promise->emplace(std::move(result.value()));
+  }
+};
+
+// TODO(alexclarke): Specialize |EmplaceInnerHelper| where RejectStorage is
+// base::Variant to support Promises::All.
+
+template <typename ResolveStorage, typename RejectStorage>
+struct EmplaceHelper {
+  template <typename Resolve, typename Reject>
+  static void Emplace(AbstractPromise* promise,
+                      PromiseResult<Resolve, Reject>&& result) {
+    static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value ||
+                      std::is_same<NoResolve, Resolve>::value,
+                  "Resolve should match ResolveStorage");
+    static_assert(std::is_same<typename RejectStorage::Type, Reject>::value ||
+                      std::is_same<NoReject, Reject>::value,
+                  "Reject should match RejectStorage");
+    EmplaceInnerHelper<RejectStorage>::Emplace(promise, std::move(result));
+  }
+
+  template <typename Resolve, typename Reject>
+  static void Emplace(AbstractPromise* promise,
+                      Promise<Resolve, Reject>&& result) {
+    static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value ||
+                      std::is_same<NoResolve, Resolve>::value,
+                  "Resolve should match ResolveStorage");
+    static_assert(std::is_same<typename RejectStorage::Type, Reject>::value ||
+                      std::is_same<NoReject, Reject>::value,
+                  "Reject should match RejectStorage");
+    promise->emplace(std::move(result.abstract_promise_));
+  }
+
+  template <typename Result>
+  static void Emplace(AbstractPromise* promise, Result&& result) {
+    static_assert(std::is_same<typename ResolveStorage::Type, Result>::value,
+                  "Result should match ResolveStorage");
+    promise->emplace(Resolved<Result>{std::forward<Result>(result)});
+  }
+
+  template <typename Resolve>
+  static void Emplace(AbstractPromise* promise, Resolved<Resolve>&& resolved) {
+    static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value,
+                  "Resolve should match ResolveStorage");
+    promise->emplace(std::move(resolved));
+  }
+
+  template <typename Reject>
+  static void Emplace(AbstractPromise* promise, Rejected<Reject>&& rejected) {
+    static_assert(std::is_same<typename RejectStorage::Type, Reject>::value,
+                  "Reject should match RejectStorage");
+    promise->emplace(std::move(rejected));
+  }
+};
+
+// Helper that decides whether or not to std::move arguments for a callback
+// based on the type the callback specifies (i.e. we don't need to move if the
+// callback requests a reference).
+template <typename CbArg, typename ArgStorageType>
+class ArgMoveSemanticsHelper {
+ public:
+  static CbArg Get(AbstractPromise* arg) {
+    return GetImpl(arg, UseMoveSemantics<CbArg>());
+  }
+
+ private:
+  static CbArg GetImpl(AbstractPromise* arg, std::true_type should_move) {
+    return arg->TakeInnerValue<ArgStorageType>();
+  }
+
+  static CbArg GetImpl(AbstractPromise* arg, std::false_type should_move) {
+    return unique_any_cast<ArgStorageType>(&arg->value())->value;
+  }
+};
+
+// Helper for running a promise callback and storing the result if any.
+//
+// Callback = signature of the callback to execute,
+// ArgStorageType = type of the callback parameter (pr void if none)
+// ResolveStorage = type to use for resolve, usually Resolved<T>.
+// RejectStorage = type to use for reject, usually Rejected<T>.
+// TODO(alexclarke): Add support for Rejected<Variant<...>>.
+template <typename Callback,
+          typename ArgStorageType,
+          typename ResolveStorage,
+          typename RejectStorage>
+struct RunHelper;
+
+// Run helper for callbacks with a single argument.
+template <typename CbResult,
+          typename CbArg,
+          typename ArgStorageType,
+          typename ResolveStorage,
+          typename RejectStorage>
+struct RunHelper<OnceCallback<CbResult(CbArg)>,
+                 ArgStorageType,
+                 ResolveStorage,
+                 RejectStorage> {
+  using Callback = OnceCallback<CbResult(CbArg)>;
+
+  static void Run(Callback executor,
+                  AbstractPromise* arg,
+                  AbstractPromise* result) {
+    EmplaceHelper<ResolveStorage, RejectStorage>::Emplace(
+        result, std::move(executor).Run(
+                    ArgMoveSemanticsHelper<CbArg, ArgStorageType>::Get(arg)));
+  }
+};
+
+// Run helper for callbacks with a single argument and void return value.
+template <typename CbArg,
+          typename ArgStorageType,
+          typename ResolveStorage,
+          typename RejectStorage>
+struct RunHelper<OnceCallback<void(CbArg)>,
+                 ArgStorageType,
+                 ResolveStorage,
+                 RejectStorage> {
+  using Callback = OnceCallback<void(CbArg)>;
+
+  static void Run(Callback executor,
+                  AbstractPromise* arg,
+                  AbstractPromise* result) {
+    static_assert(std::is_void<typename ResolveStorage::Type>::value, "");
+    std::move(executor).Run(
+        ArgMoveSemanticsHelper<CbArg, ArgStorageType>::Get(arg));
+    result->emplace(Resolved<void>());
+  }
+};
+
+// Run helper for callbacks with no arguments.
+template <typename CbResult,
+          typename ArgStorageType,
+          typename ResolveStorage,
+          typename RejectStorage>
+struct RunHelper<OnceCallback<CbResult()>,
+                 ArgStorageType,
+                 ResolveStorage,
+                 RejectStorage> {
+  using Callback = OnceCallback<CbResult()>;
+
+  static void Run(Callback executor,
+                  AbstractPromise* arg,
+                  AbstractPromise* result) {
+    EmplaceHelper<ResolveStorage, RejectStorage>::Emplace(
+        result, std::move(executor).Run());
+  }
+};
+
+// Run helper for callbacks with no arguments and void return type.
+template <typename ArgStorageType,
+          typename ResolveStorage,
+          typename RejectStorage>
+struct RunHelper<OnceCallback<void()>,
+                 ArgStorageType,
+                 ResolveStorage,
+                 RejectStorage> {
+  static void Run(OnceCallback<void()> executor,
+                  AbstractPromise* arg,
+                  AbstractPromise* result) {
+    static_assert(std::is_void<typename ResolveStorage::Type>::value, "");
+    std::move(executor).Run();
+    result->emplace(Resolved<void>());
+  }
+};
+
+// TODO(alexclarke): Specialize RunHelper for callbacks unpacked from a tuple.
+
+// Used by ManualPromiseResolver<> to generate callbacks.
+template <typename T, typename... Args>
+class PromiseCallbackHelper {
+ public:
+  using Callback = base::OnceCallback<void(Args&&...)>;
+  using RepeatingCallback = base::RepeatingCallback<void(Args&&...)>;
+
+  static Callback GetResolveCallback(scoped_refptr<AbstractPromise>& promise) {
+    return base::BindOnce(
+        [](scoped_refptr<AbstractPromise> promise, Args&&... args) {
+          promise->emplace(Resolved<T>{std::forward<Args>(args)...});
+          promise->OnResolved();
+        },
+        promise);
+  }
+
+  static RepeatingCallback GetRepeatingResolveCallback(
+      scoped_refptr<AbstractPromise>& promise) {
+    return base::BindRepeating(
+        [](scoped_refptr<AbstractPromise> promise, Args&&... args) {
+          promise->emplace(Resolved<T>{std::forward<Args>(args)...});
+          promise->OnResolved();
+        },
+        promise);
+  }
+
+  static Callback GetRejectCallback(scoped_refptr<AbstractPromise>& promise) {
+    return base::BindOnce(
+        [](scoped_refptr<AbstractPromise> promise, Args&&... args) {
+          promise->emplace(Rejected<T>{std::forward<Args>(args)...});
+          promise->OnRejected();
+        },
+        promise);
+  }
+
+  static RepeatingCallback GetRepeatingRejectCallback(
+      scoped_refptr<AbstractPromise>& promise) {
+    return base::BindRepeating(
+        [](scoped_refptr<AbstractPromise> promise, Args&&... args) {
+          promise->emplace(Rejected<T>{std::forward<Args>(args)...});
+          promise->OnRejected();
+        },
+        promise);
+  }
+};
+
+// Validates that the argument type |CallbackArgType| of a resolve or
+// reject callback is compatible with the resolve or reject type
+// |PromiseType| of this Promise.
+template <typename PromiseType, typename CallbackArgType>
+struct IsValidPromiseArg {
+  static constexpr bool value =
+      std::is_same<PromiseType, std::decay_t<CallbackArgType>>::value;
+};
+
+template <typename PromiseType, typename CallbackArgType>
+struct IsValidPromiseArg<PromiseType&, CallbackArgType> {
+  static constexpr bool value =
+      std::is_same<PromiseType&, CallbackArgType>::value;
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_TASK_PROMISE_HELPERS_H_
diff --git a/base/task/promise/helpers_unittest.cc b/base/task/promise/helpers_unittest.cc
new file mode 100644
index 0000000..edaecce
--- /dev/null
+++ b/base/task/promise/helpers_unittest.cc
@@ -0,0 +1,284 @@
+// 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/promise/helpers.h"
+
+#include "base/bind.h"
+#include "base/task/promise/promise.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/do_nothing_promise.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+TEST(UseMoveSemantics, GeneralTypes) {
+  static_assert(!UseMoveSemantics<int>::value,
+                "Integral types don't need to be moved");
+
+  static_assert(UseMoveSemantics<std::unique_ptr<int>>::value,
+                "Move only types should be moved");
+
+  static_assert(
+      !UseMoveSemantics<scoped_refptr<AbstractPromise>>::value,
+      "To support multiple callbacks scoped_refptr doesn't need to be moved.");
+}
+
+TEST(CallbackTraits, CallbackReferenceTypes) {
+  static_assert(
+      std::is_same<int&,
+                   CallbackTraits<Callback<int&(int&)>>::ResolveType>::value,
+      "");
+
+  static_assert(
+      std::is_same<int&, CallbackTraits<Callback<int&(int&)>>::ArgType>::value,
+      "");
+}
+
+TEST(CallbackTraits, RepeatingCallbackReferenceTypes) {
+  static_assert(
+      std::is_same<
+          int&,
+          CallbackTraits<RepeatingCallback<int&(int&)>>::ResolveType>::value,
+      "");
+
+  static_assert(
+      std::is_same<
+          int&, CallbackTraits<RepeatingCallback<int&(int&)>>::ArgType>::value,
+      "");
+}
+
+TEST(PromiseCombiner, InvalidCombos) {
+  static_assert(!PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<int>,
+                                 Rejected<bool>>::valid,
+                "Invalid, reject types differ");
+  static_assert(!PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<void>,
+                                 Rejected<float>>::valid,
+                "Invalid, resolve types differ");
+}
+
+TEST(PromiseCombiner, TypesMatch) {
+  using Result = PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<int>,
+                                 Rejected<float>>;
+  static_assert(Result::valid, "Types are the same, should match");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(PromiseCombiner, NoResolve) {
+  using Result = PromiseCombiner<NoResolve, Rejected<float>, Resolved<int>,
+                                 Rejected<float>>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(PromiseCombiner, NoResolve2) {
+  using Result = PromiseCombiner<Resolved<int>, Rejected<float>, NoResolve,
+                                 Rejected<float>>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(PromiseCombiner, BothNoResolve) {
+  using Result =
+      PromiseCombiner<NoResolve, Rejected<float>, NoResolve, Rejected<float>>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, NoResolve>::value,
+                "Resolve type should be NoResolve");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(PromiseCombiner, NoReject) {
+  using Result =
+      PromiseCombiner<Resolved<int>, NoReject, Resolved<int>, Rejected<float>>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(PromiseCombiner, NoReject2) {
+  using Result =
+      PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<int>, NoReject>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(PromiseCombiner, BothNoReject) {
+  using Result =
+      PromiseCombiner<Resolved<int>, NoReject, Resolved<int>, NoReject>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, NoReject>::value,
+                "Resolve type should be NoReject");
+}
+
+TEST(PromiseCombiner, NoResolveAndNoReject) {
+  using Result =
+      PromiseCombiner<Resolved<int>, NoReject, NoResolve, Rejected<float>>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(PromiseCombiner, NoResolveAndNoReject2) {
+  using Result =
+      PromiseCombiner<NoResolve, Rejected<float>, Resolved<int>, NoReject>;
+  static_assert(Result::valid, "Valid combination");
+
+  static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
+                "Resolve type should be int");
+
+  static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
+                "Resolve type should be float");
+}
+
+TEST(EmplaceHelper, EmplacePromiseResult) {
+  scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
+  scoped_refptr<AbstractPromise> reject = DoNothingPromiseBuilder(FROM_HERE);
+
+  EmplaceHelper<Resolved<int>, Rejected<std::string>>::Emplace(
+      resolve.get(), PromiseResult<int, std::string>(123));
+  EmplaceHelper<Resolved<int>, Rejected<std::string>>::Emplace(
+      reject.get(), PromiseResult<int, std::string>("Oh no!"));
+
+  EXPECT_EQ(unique_any_cast<Resolved<int>>(resolve->value()).value, 123);
+  EXPECT_EQ(unique_any_cast<Rejected<std::string>>(reject->value()).value,
+            "Oh no!");
+}
+
+TEST(EmplaceHelper, EmplacePromise) {
+  scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
+  scoped_refptr<AbstractPromise> curried = DoNothingPromiseBuilder(FROM_HERE);
+
+  EmplaceHelper<Resolved<int>, Rejected<NoReject>>::Emplace(
+      resolve.get(), Promise<int>(std::move(curried)));
+
+  EXPECT_TRUE(resolve->IsResolvedWithPromise());
+}
+
+TEST(EmplaceHelper, NakedType) {
+  scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
+
+  EmplaceHelper<Resolved<int>, Rejected<NoReject>>::Emplace(resolve.get(), 123);
+
+  EXPECT_EQ(unique_any_cast<Resolved<int>>(resolve->value()).value, 123);
+}
+
+TEST(EmplaceHelper, ReferenceType) {
+  scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
+
+  int a = 12345;
+
+  EmplaceHelper<Resolved<int&>, Rejected<NoReject>>::Emplace<int&>(
+      resolve.get(), a);
+
+  EXPECT_EQ(unique_any_cast<Resolved<int&>>(resolve->value()).value, 12345);
+}
+
+TEST(EmplaceHelper, ResolvedInt) {
+  scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
+
+  EmplaceHelper<Resolved<int>, Rejected<NoReject>>::Emplace(resolve.get(),
+                                                            Resolved<int>(123));
+
+  EXPECT_EQ(unique_any_cast<Resolved<int>>(resolve->value()).value, 123);
+}
+
+TEST(EmplaceHelper, RejectedString) {
+  scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
+
+  EmplaceHelper<Resolved<void>, Rejected<std::string>>::Emplace(
+      resolve.get(), Rejected<std::string>("Whoops!"));
+
+  EXPECT_EQ(unique_any_cast<Rejected<std::string>>(resolve->value()).value,
+            "Whoops!");
+}
+
+TEST(RunHelper, CallbackVoidArgumentIntResult) {
+  scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
+  scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
+
+  RunHelper<OnceCallback<int()>, Resolved<void>, Resolved<int>,
+            Rejected<std::string>>::Run(BindOnce([]() { return 123; }),
+                                        arg.get(), result.get());
+
+  EXPECT_EQ(unique_any_cast<Resolved<int>>(result->value()).value, 123);
+}
+
+TEST(RunHelper, CallbackVoidArgumentVoidResult) {
+  scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
+  scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
+
+  RunHelper<OnceCallback<void()>, Resolved<void>, Resolved<void>,
+            Rejected<std::string>>::Run(BindOnce([]() {}), arg.get(),
+                                        result.get());
+
+  EXPECT_EQ(result->value().type(), TypeId::From<Resolved<void>>());
+}
+
+TEST(RunHelper, CallbackIntArgumentIntResult) {
+  scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
+  scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
+  arg->emplace(Resolved<int>(123));
+
+  RunHelper<OnceCallback<int(int)>, Resolved<int>, Resolved<int>,
+            Rejected<std::string>>::Run(BindOnce([](int value) {
+                                          return value + 1;
+                                        }),
+                                        arg.get(), result.get());
+
+  EXPECT_EQ(unique_any_cast<Resolved<int>>(result->value()).value, 124);
+}
+
+TEST(RunHelper, CallbackIntArgumentArgumentVoidResult) {
+  scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
+  scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
+  arg->emplace(Resolved<int>(123));
+
+  int value;
+  RunHelper<OnceCallback<void(int)>, Resolved<int>, Resolved<void>,
+            Rejected<std::string>>::Run(BindLambdaForTesting([&](int arg) {
+                                          value = arg;
+                                        }),
+                                        arg.get(), result.get());
+
+  EXPECT_EQ(value, 123);
+  EXPECT_EQ(result->value().type(), TypeId::From<Resolved<void>>());
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/base/task/promise/no_op_promise_executor.cc b/base/task/promise/no_op_promise_executor.cc
index 17cffac..ef58fb9 100644
--- a/base/task/promise/no_op_promise_executor.cc
+++ b/base/task/promise/no_op_promise_executor.cc
@@ -19,8 +19,8 @@
 NoOpPromiseExecutor::~NoOpPromiseExecutor() {}
 
 AbstractPromise::Executor::PrerequisitePolicy
-NoOpPromiseExecutor::GetPrerequisitePolicy() {
-  return PrerequisitePolicy::kNever;
+NoOpPromiseExecutor::GetPrerequisitePolicy() const {
+  return AbstractPromise::Executor::PrerequisitePolicy::kNever;
 }
 
 bool NoOpPromiseExecutor::IsCancelled() const {
@@ -30,12 +30,12 @@
 #if DCHECK_IS_ON()
 AbstractPromise::Executor::ArgumentPassingType
 NoOpPromiseExecutor::ResolveArgumentPassingType() const {
-  return ArgumentPassingType::kNoCallback;
+  return AbstractPromise::Executor::ArgumentPassingType::kNoCallback;
 }
 
 AbstractPromise::Executor::ArgumentPassingType
 NoOpPromiseExecutor::RejectArgumentPassingType() const {
-  return ArgumentPassingType::kNoCallback;
+  return AbstractPromise::Executor::ArgumentPassingType::kNoCallback;
 }
 
 bool NoOpPromiseExecutor::CanResolve() const {
diff --git a/base/task/promise/no_op_promise_executor.h b/base/task/promise/no_op_promise_executor.h
index 9984249..d65dbc3 100644
--- a/base/task/promise/no_op_promise_executor.h
+++ b/base/task/promise/no_op_promise_executor.h
@@ -12,11 +12,11 @@
 namespace internal {
 
 // An Executor that doesn't do anything.
-class BASE_EXPORT NoOpPromiseExecutor : public AbstractPromise::Executor {
+class BASE_EXPORT NoOpPromiseExecutor {
  public:
   NoOpPromiseExecutor(bool can_resolve, bool can_reject);
 
-  ~NoOpPromiseExecutor() override;
+  ~NoOpPromiseExecutor();
 
   static scoped_refptr<internal::AbstractPromise> Create(
       Location from_here,
@@ -24,16 +24,18 @@
       bool can_reject,
       RejectPolicy reject_policy);
 
-  PrerequisitePolicy GetPrerequisitePolicy() override;
-  bool IsCancelled() const override;
+  AbstractPromise::Executor::PrerequisitePolicy GetPrerequisitePolicy() const;
+  bool IsCancelled() const;
 
 #if DCHECK_IS_ON()
-  ArgumentPassingType ResolveArgumentPassingType() const override;
-  ArgumentPassingType RejectArgumentPassingType() const override;
-  bool CanResolve() const override;
-  bool CanReject() const override;
+  AbstractPromise::Executor::ArgumentPassingType ResolveArgumentPassingType()
+      const;
+  AbstractPromise::Executor::ArgumentPassingType RejectArgumentPassingType()
+      const;
+  bool CanResolve() const;
+  bool CanReject() const;
 #endif
-  void Execute(AbstractPromise* promise) override;
+  void Execute(AbstractPromise* promise);
 
  private:
 #if DCHECK_IS_ON()
diff --git a/base/task/promise/promise.h b/base/task/promise/promise.h
new file mode 100644
index 0000000..cb70a2f6
--- /dev/null
+++ b/base/task/promise/promise.h
@@ -0,0 +1,425 @@
+// 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_PROMISE_PROMISE_H_
+#define BASE_TASK_PROMISE_PROMISE_H_
+
+#include "base/task/post_task.h"
+#include "base/task/promise/helpers.h"
+#include "base/task/promise/no_op_promise_executor.h"
+#include "base/task/promise/promise_result.h"
+#include "base/task/promise/then_and_catch_executor.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+
+namespace base {
+
+// Inspired by ES6 promises, Promise<> is a PostTask based callback system for
+// asynchronous operations. An operation can resolve (succeed) with a value and
+// optionally reject (fail) with a different result. Interested parties can be
+// notified using ThenOn() and CatchOn() which schedule callbacks to run as
+// appropriate on the specified task runner or task traits. If a promise is
+// settled when a ThenOn() / CatchOn() / FinallyOn() statement is added, the
+// callback will be posted immediately, otherwise it has to wait.
+//
+// Promise<> is copyable, moveable and thread safe. Under the hood
+// internal::AbstractPromise is refcounted so retaining multiple Promises<> will
+// prevent that part of the promise graph from being released.
+template <typename ResolveType, typename RejectType = NoReject>
+class Promise {
+ public:
+  Promise() : abstract_promise_(nullptr) {}
+
+  explicit Promise(
+      scoped_refptr<internal::AbstractPromise> abstract_promise) noexcept
+      : abstract_promise_(std::move(abstract_promise)) {}
+
+  // Constructs an unresolved promise for use by a ManualPromiseResolver<> and
+  // TaskRunner::PostPromise.
+  Promise(scoped_refptr<TaskRunner> task_runner,
+          const Location& location,
+          RejectPolicy reject_policy)
+      : abstract_promise_(MakeRefCounted<internal::AbstractPromise>(
+            std::move(task_runner),
+            location,
+            nullptr,
+            reject_policy,
+            internal::AbstractPromise::ConstructWith<
+                internal::DependentList::ConstructUnresolved,
+                internal::NoOpPromiseExecutor>(),
+            /* can_resolve */ !std::is_same<ResolveType, NoResolve>::value,
+            /* can_reject */ !std::is_same<RejectType, NoReject>::value)) {}
+
+  NOINLINE ~Promise() = default;
+
+  operator bool() const { return !!abstract_promise_; }
+
+  bool IsCancelledForTesting() const {
+    DCHECK(abstract_promise_);
+    return abstract_promise_->IsCanceled();
+  }
+
+  // A task to execute |on_reject| is posted on |task_runner| as soon as this
+  // promise (or an uncaught ancestor) is rejected. A Promise<> for the return
+  // value of |on_reject| is returned.
+  //
+  // The following callback return types have special meanings:
+  // 1. PromiseResult<Resolve, Reject> lets the callback resolve, reject or
+  //    curry a Promise<Resolve, Reject>
+  // 2. Promise<Resolve, Reject> where the result is a curried promise.
+  //
+  // If a promise has multiple Catches they will be run in order of creation.
+  template <typename RejectCb>
+  NOINLINE auto CatchOn(scoped_refptr<TaskRunner> task_runner,
+                        const Location& from_here,
+                        RejectCb&& on_reject) noexcept {
+    DCHECK(abstract_promise_);
+
+    // Extract properties from the |on_reject| callback.
+    using RejectCallbackTraits = internal::CallbackTraits<RejectCb>;
+    using RejectCallbackArgT = typename RejectCallbackTraits::ArgType;
+
+    // Compute the resolve and reject types of the returned Promise.
+    using ReturnedPromiseTraits =
+        internal::PromiseCombiner<ResolveType,
+                                  NoReject,  // We've caught the reject case.
+                                  typename RejectCallbackTraits::ResolveType,
+                                  typename RejectCallbackTraits::RejectType>;
+    using ReturnedPromiseResolveT = typename ReturnedPromiseTraits::ResolveType;
+    using ReturnedPromiseRejectT = typename ReturnedPromiseTraits::RejectType;
+
+    static_assert(!std::is_same<NoReject, RejectType>::value,
+                  "Can't catch a NoReject promise.");
+
+    // Check we wouldn't need to return Promise<Variant<...>, ...>
+    static_assert(ReturnedPromiseTraits::valid,
+                  "Ambiguous promise resolve type");
+    static_assert(
+        internal::IsValidPromiseArg<RejectType, RejectCallbackArgT>::value ||
+            std::is_void<RejectCallbackArgT>::value,
+        "|on_reject| callback must accept Promise::RejectType or void.");
+
+    return Promise<ReturnedPromiseResolveT, ReturnedPromiseRejectT>(
+        MakeRefCounted<internal::AbstractPromise>(
+            std::move(task_runner), from_here,
+            std::make_unique<internal::AbstractPromise::AdjacencyList>(
+                abstract_promise_),
+            RejectPolicy::kMustCatchRejection,
+            internal::AbstractPromise::ConstructWith<
+                internal::DependentList::ConstructUnresolved,
+                internal::ThenAndCatchExecutor<
+                    OnceClosure,  // Never called.
+                    OnceCallback<typename RejectCallbackTraits::SignatureType>,
+                    internal::NoCallback, RejectType,
+                    Resolved<ReturnedPromiseResolveT>,
+                    Rejected<ReturnedPromiseRejectT>>>(),
+            OnceClosure(),
+            static_cast<
+                OnceCallback<typename RejectCallbackTraits::SignatureType>>(
+                std::forward<RejectCb>(on_reject))));
+  }
+
+  template <typename RejectCb>
+  auto CatchOn(const TaskTraits& traits,
+               const Location& from_here,
+               RejectCb&& on_reject) noexcept {
+    return CatchOn(CreateTaskRunnerWithTraits(traits), from_here,
+                   std::forward<RejectCb>(on_reject));
+  }
+
+  template <typename RejectCb>
+  auto CatchOnCurrent(const Location& from_here,
+                      RejectCb&& on_reject) noexcept {
+    return CatchOn(SequencedTaskRunnerHandle::Get(), from_here,
+                   std::forward<RejectCb>(on_reject));
+  }
+
+  // A task to execute |on_resolve| is posted on |task_runner| as soon as this
+  // promise (or an unhandled ancestor) is resolved. A Promise<> for the return
+  // value of |on_resolve| is returned.
+  //
+  // The following callback return types have special meanings:
+  // 1. PromiseResult<Resolve, Reject> lets the callback resolve, reject or
+  //    curry a Promise<Resolve, Reject>
+  // 2. Promise<Resolve, Reject> where the result is a curried promise.
+  //
+  // If a promise has multiple Thens they will be run in order of creation.
+  template <typename ResolveCb>
+  NOINLINE auto ThenOn(scoped_refptr<TaskRunner> task_runner,
+                       const Location& from_here,
+                       ResolveCb&& on_resolve) noexcept {
+    DCHECK(abstract_promise_);
+
+    // Extract properties from the |on_resolve| callback.
+    using ResolveCallbackTraits =
+        internal::CallbackTraits<std::decay_t<ResolveCb>>;
+    using ResolveCallbackArgT = typename ResolveCallbackTraits::ArgType;
+
+    // Compute the resolve and reject types of the returned Promise.
+    using ReturnedPromiseTraits =
+        internal::PromiseCombiner<NoResolve,  // We've caught the resolve case.
+                                  RejectType,
+                                  typename ResolveCallbackTraits::ResolveType,
+                                  typename ResolveCallbackTraits::RejectType>;
+    using ReturnedPromiseResolveT = typename ReturnedPromiseTraits::ResolveType;
+    using ReturnedPromiseRejectT = typename ReturnedPromiseTraits::RejectType;
+
+    // Check we wouldn't need to return Promise<..., Variant<...>>
+    static_assert(ReturnedPromiseTraits::valid,
+                  "Ambiguous promise reject type");
+
+    static_assert(
+        internal::IsValidPromiseArg<ResolveType, ResolveCallbackArgT>::value ||
+            std::is_void<ResolveCallbackArgT>::value,
+        "|on_resolve| callback must accept Promise::ResolveType or void.");
+
+    return Promise<ReturnedPromiseResolveT, ReturnedPromiseRejectT>(
+        MakeRefCounted<internal::AbstractPromise>(
+            std::move(task_runner), from_here,
+            std::make_unique<internal::AbstractPromise::AdjacencyList>(
+                abstract_promise_),
+            RejectPolicy::kMustCatchRejection,
+            internal::AbstractPromise::ConstructWith<
+                internal::DependentList::ConstructUnresolved,
+                internal::ThenAndCatchExecutor<
+                    OnceCallback<typename ResolveCallbackTraits::SignatureType>,
+                    OnceClosure, ResolveType, internal::NoCallback,
+                    Resolved<ReturnedPromiseResolveT>,
+                    Rejected<ReturnedPromiseRejectT>>>(),
+            std::forward<ResolveCb>(on_resolve), OnceClosure()));
+  }
+
+  template <typename ResolveCb>
+  auto ThenOn(const TaskTraits& traits,
+              const Location& from_here,
+              ResolveCb&& on_resolve) noexcept {
+    return ThenOn(CreateTaskRunnerWithTraits(traits), from_here,
+                  std::forward<ResolveCb>(on_resolve));
+  }
+
+  template <typename ResolveCb>
+  auto ThenOnCurrent(const Location& from_here,
+                     ResolveCb&& on_resolve) noexcept {
+    return ThenOn(SequencedTaskRunnerHandle::Get(), from_here,
+                  std::forward<ResolveCb>(on_resolve));
+  }
+
+  // A task to execute |on_reject| is posted on |task_runner| as soon as this
+  // promise (or an uncaught ancestor) is rejected. Likewise a task to execute
+  // |on_resolve| is posted on |task_runner| as soon as this promise (or an
+  // unhandled ancestor) is resolved. A Promise<> for the return value of
+  // |on_resolve| or |on_reject| is returned.
+  //
+  // The following callback return types have special meanings:
+  // 1. PromiseResult<Resolve, Reject> lets the callback resolve, reject or
+  //    curry a Promise<Resolve, Reject>
+  // 2. Promise<Resolve, Reject> where the result is a curried promise.
+  //
+  // If a promise has multiple Catches/ Thens, they will be run in order of
+  // creation.
+  //
+  // Note if either |on_resolve| or |on_reject| are canceled (due to weak
+  // pointer invalidation), then the other must be canceled at the same time as
+  // well. This restriction only applies to this form of ThenOn/ThenOnCurrent.
+  template <typename ResolveCb, typename RejectCb>
+  NOINLINE auto ThenOn(scoped_refptr<TaskRunner> task_runner,
+                       const Location& from_here,
+                       ResolveCb&& on_resolve,
+                       RejectCb&& on_reject) noexcept {
+    DCHECK(abstract_promise_);
+
+    // Extract properties from the |on_resolve| and |on_reject| callbacks.
+    using ResolveCallbackTraits = internal::CallbackTraits<ResolveCb>;
+    using RejectCallbackTraits = internal::CallbackTraits<RejectCb>;
+    using ResolveCallbackArgT = typename ResolveCallbackTraits::ArgType;
+    using RejectCallbackArgT = typename RejectCallbackTraits::ArgType;
+
+    // Compute the resolve and reject types of the returned Promise.
+    using ReturnedPromiseTraits =
+        internal::PromiseCombiner<typename ResolveCallbackTraits::ResolveType,
+                                  typename ResolveCallbackTraits::RejectType,
+                                  typename RejectCallbackTraits::ResolveType,
+                                  typename RejectCallbackTraits::RejectType>;
+    using ReturnedPromiseResolveT = typename ReturnedPromiseTraits::ResolveType;
+    using ReturnedPromiseRejectT = typename ReturnedPromiseTraits::RejectType;
+
+    static_assert(!std::is_same<NoReject, RejectType>::value,
+                  "Can't catch a NoReject promise.");
+
+    static_assert(ReturnedPromiseTraits::valid,
+                  "|on_resolve| callback and |on_resolve| callback must return "
+                  "compatible types.");
+
+    static_assert(
+        internal::IsValidPromiseArg<ResolveType, ResolveCallbackArgT>::value ||
+            std::is_void<ResolveCallbackArgT>::value,
+        "|on_resolve| callback must accept Promise::ResolveType or void.");
+
+    static_assert(
+        internal::IsValidPromiseArg<RejectType, RejectCallbackArgT>::value ||
+            std::is_void<RejectCallbackArgT>::value,
+        "|on_reject| callback must accept Promise::RejectType or void.");
+
+    return Promise<ReturnedPromiseResolveT, ReturnedPromiseRejectT>(
+        MakeRefCounted<internal::AbstractPromise>(
+            std::move(task_runner), from_here,
+            std::make_unique<internal::AbstractPromise::AdjacencyList>(
+                abstract_promise_),
+            RejectPolicy::kMustCatchRejection,
+            internal::AbstractPromise::ConstructWith<
+                internal::DependentList::ConstructUnresolved,
+                internal::ThenAndCatchExecutor<
+                    OnceCallback<typename ResolveCallbackTraits::SignatureType>,
+                    OnceCallback<typename RejectCallbackTraits::SignatureType>,
+                    ResolveType, RejectType, Resolved<ReturnedPromiseResolveT>,
+                    Rejected<ReturnedPromiseRejectT>>>(),
+            static_cast<
+                OnceCallback<typename ResolveCallbackTraits::SignatureType>>(
+                std::forward<ResolveCb>(on_resolve)),
+            static_cast<
+                OnceCallback<typename RejectCallbackTraits::SignatureType>>(
+                std::forward<RejectCb>(on_reject))));
+  }
+
+  template <typename ResolveCb, typename RejectCb>
+  auto ThenOn(const TaskTraits& traits,
+              const Location& from_here,
+              ResolveCb&& on_resolve,
+              RejectCb&& on_reject) noexcept {
+    return ThenOn(CreateTaskRunnerWithTraits(traits), from_here,
+                  std::forward<ResolveCb>(on_resolve),
+                  std::forward<RejectCb>(on_reject));
+  }
+
+  template <typename ResolveCb, typename RejectCb>
+  auto ThenOnCurrent(const Location& from_here,
+                     ResolveCb&& on_resolve,
+                     RejectCb&& on_reject) noexcept {
+    return ThenOn(SequencedTaskRunnerHandle::Get(), from_here,
+                  std::forward<ResolveCb>(on_resolve),
+                  std::forward<RejectCb>(on_reject));
+  }
+
+  template <typename... Args>
+  NOINLINE static Promise<ResolveType, RejectType> CreateResolved(
+      const Location& from_here,
+      Args&&... args) noexcept {
+    scoped_refptr<internal::AbstractPromise> promise(
+        MakeRefCounted<internal::AbstractPromise>(
+            nullptr, from_here, nullptr, RejectPolicy::kMustCatchRejection,
+            internal::AbstractPromise::ConstructWith<
+                internal::DependentList::ConstructResolved,
+                internal::NoOpPromiseExecutor>(),
+            /* can_resolve */ true,
+            /* can_reject */ false));
+    promise->emplace(Resolved<ResolveType>{std::forward<Args>(args)...});
+    return Promise<ResolveType, RejectType>(std::move(promise));
+  }
+
+  template <typename... Args>
+  NOINLINE static Promise<ResolveType, RejectType> CreateRejected(
+      const Location& from_here,
+      Args&&... args) noexcept {
+    scoped_refptr<internal::AbstractPromise> promise(
+        MakeRefCounted<internal::AbstractPromise>(
+            nullptr, from_here, nullptr, RejectPolicy::kMustCatchRejection,
+            internal::AbstractPromise::ConstructWith<
+                internal::DependentList::ConstructRejected,
+                internal::NoOpPromiseExecutor>(),
+            /* can_resolve */ false,
+            /* can_reject */ true));
+    promise->emplace(Rejected<RejectType>{std::forward<Args>(args)...});
+    return Promise<ResolveType, RejectType>(std::move(promise));
+  }
+
+  using ResolveT = ResolveType;
+  using RejectT = RejectType;
+
+ private:
+  template <typename A, typename B>
+  friend class Promise;
+
+  template <typename A, typename B>
+  friend class PromiseResult;
+
+  template <typename RejectStorage, typename ResultStorage>
+  friend struct internal::EmplaceHelper;
+
+  template <typename A, typename B>
+  friend class ManualPromiseResolver;
+
+  scoped_refptr<internal::AbstractPromise> abstract_promise_;
+};
+
+// Used for manually resolving and rejecting a Promise. This is for
+// compatibility with old code and will eventually be removed.
+template <typename ResolveType, typename RejectType = NoReject>
+class ManualPromiseResolver {
+ public:
+  using ResolveHelper = std::conditional_t<
+      std::is_void<ResolveType>::value,
+      internal::PromiseCallbackHelper<void>,
+      internal::PromiseCallbackHelper<ResolveType, ResolveType>>;
+
+  using RejectHelper = std::conditional_t<
+      std::is_void<RejectType>::value,
+      internal::PromiseCallbackHelper<void>,
+      internal::PromiseCallbackHelper<RejectType, RejectType>>;
+
+  ManualPromiseResolver(
+      const Location& from_here,
+      RejectPolicy reject_policy = RejectPolicy::kMustCatchRejection)
+      : promise_(SequencedTaskRunnerHandle::Get(), from_here, reject_policy) {}
+
+  template <typename... Args>
+  void Resolve(Args&&... arg) noexcept {
+    DCHECK(!promise_.abstract_promise_->IsResolved());
+    DCHECK(!promise_.abstract_promise_->IsRejected());
+    static_assert(!std::is_same<NoResolve, ResolveType>::value,
+                  "Can't resolve a NoResolve promise.");
+    promise_.abstract_promise_->emplace(
+        Resolved<ResolveType>{std::forward<Args>(arg)...});
+    promise_.abstract_promise_->OnResolved();
+  }
+
+  template <typename... Args>
+  void Reject(Args&&... arg) noexcept {
+    DCHECK(!promise_.abstract_promise_->IsResolved());
+    DCHECK(!promise_.abstract_promise_->IsRejected());
+    static_assert(!std::is_same<NoReject, RejectType>::value,
+                  "Can't reject a NoReject promise.");
+    promise_.abstract_promise_->emplace(
+        Rejected<RejectType>{std::forward<Args>(arg)...});
+    promise_.abstract_promise_->OnRejected();
+  }
+
+  typename ResolveHelper::Callback GetResolveCallback() {
+    return ResolveHelper::GetResolveCallback(promise_.abstract_promise_);
+  }
+
+  typename ResolveHelper::RepeatingCallback GetRepeatingResolveCallback() {
+    return ResolveHelper::GetRepeatingResolveCallback(
+        promise_.abstract_promise_);
+  }
+
+  typename RejectHelper::Callback GetRejectCallback() {
+    static_assert(!std::is_same<NoReject, RejectType>::value,
+                  "Can't reject a NoReject promise.");
+    return RejectHelper::GetRejectCallback(promise_.abstract_promise_);
+  }
+
+  typename RejectHelper::RepeatingCallback GetRepeatingRejectCallback() {
+    static_assert(!std::is_same<NoReject, RejectType>::value,
+                  "Can't reject a NoReject promise.");
+    return RejectHelper::GetRepeatingRejectCallback(promise_.abstract_promise_);
+  }
+
+  Promise<ResolveType, RejectType>& promise() { return promise_; }
+
+ private:
+  Promise<ResolveType, RejectType> promise_;
+};
+
+}  // namespace base
+
+#endif  // BASE_TASK_PROMISE_PROMISE_H_
diff --git a/base/task/promise/promise_result.h b/base/task/promise/promise_result.h
new file mode 100644
index 0000000..50949c9
--- /dev/null
+++ b/base/task/promise/promise_result.h
@@ -0,0 +1,145 @@
+// 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_PROMISE_PROMISE_RESULT_H_
+#define BASE_TASK_PROMISE_PROMISE_RESULT_H_
+
+#include <type_traits>
+
+#include "base/containers/unique_any.h"
+
+namespace base {
+
+// An optional return type from a promise callback, which allows the callback to
+// decide whether or not it should reject.  E.g.
+//
+// enum class Error { kReason };
+//
+// PromiseResult<int, Error> MyFn() {
+//   int result;
+//   ...
+//   if (error)
+//     return Error::kReason;
+//   return result;
+// }
+//
+// If ResolveType and RejectType are distinct PromiseResult's constructor will
+// accept them.  E.g.
+//
+// PromiseResult<int, std::string> pr(123);       // Resolve
+// PromiseResult<int, std::string> pr("whoops");  // Reject
+//
+// If ResolveType and RejectType are the same you need to use Resolved<> and
+// Rejected<> to disambiguate.  E.g.
+//
+// PromiseResult<int, int> pr(Resolved{123});     // Resolve
+// PromiseResult<int, int> pr(Rejected{123});     // Reject
+//
+// PromiseResult<ResolveType, RejectType> has a constructor that accepts
+// Promise<ResolveType, RejectType>.
+template <typename ResolveType, typename RejectType = NoReject>
+class PromiseResult {
+ public:
+  template <typename ResolveT = ResolveType,
+            typename RejectT = RejectType,
+            class Enable = std::enable_if<std::is_void<ResolveT>::value ^
+                                          std::is_void<RejectT>::value>>
+  PromiseResult() : PromiseResult(typename Analyze<void>::TagType()) {}
+
+  template <typename T>
+  PromiseResult(T&& t)
+      : PromiseResult(typename Analyze<std::decay_t<T>>::TagType(),
+                      std::forward<T>(t)) {}
+
+  PromiseResult(PromiseResult&& other) noexcept = default;
+
+  PromiseResult(const PromiseResult&) = delete;
+  PromiseResult& operator=(const PromiseResult&) = delete;
+
+  unique_any& value() { return value_; }
+
+ private:
+  struct IsWrapped {};
+  struct IsResolved {};
+  struct IsRejected {};
+  struct IsPromise {};
+
+  // Helper that assigns one of the above tags for |T|.
+  template <typename T>
+  struct Analyze {
+    using DecayedT = std::decay_t<T>;
+
+    static constexpr bool is_resolve =
+        std::is_convertible<DecayedT, std::decay_t<ResolveType>>::value;
+
+    static constexpr bool is_reject =
+        std::is_convertible<DecayedT, std::decay_t<RejectType>>::value;
+
+    static_assert(!is_reject || !std::is_same<DecayedT, NoReject>::value,
+                  "A NoReject promise can't reject");
+
+    static_assert(!std::is_same<std::decay_t<ResolveType>,
+                                std::decay_t<RejectType>>::value,
+                  "Ambiguous because ResolveType and RejectType are the same");
+
+    static_assert(is_resolve || is_reject,
+                  "Argument matches neither resolve nor reject type.");
+
+    static_assert(
+        is_resolve != is_reject && (is_resolve || is_reject),
+        "Ambiguous because argument matches both ResolveType and RejectType");
+
+    using TagType = std::conditional_t<is_resolve, IsResolved, IsRejected>;
+  };
+
+  template <typename T>
+  struct Analyze<Resolved<T>> {
+    using TagType = IsWrapped;
+    static_assert(std::is_same<ResolveType, T>::value,
+                  "T in Resolved<T> is not ResolveType");
+  };
+
+  template <typename T>
+  struct Analyze<Rejected<T>> {
+    static_assert(std::is_same<RejectType, T>::value,
+                  "T in Rejected<T> is not RejectType");
+
+    static_assert(!std::is_same<RejectType, NoReject>::value,
+                  "A NoReject promise can't reject");
+    using TagType = IsWrapped;
+  };
+
+  template <typename ResolveT, typename RejectT>
+  struct Analyze<Promise<ResolveT, RejectT>> {
+    using TagType = IsPromise;
+
+    static_assert(std::is_same<ResolveT, ResolveType>::value,
+                  "Promise resolve types don't match");
+
+    static_assert(std::is_same<RejectT, RejectType>::value,
+                  "Promise reject types don't match");
+  };
+
+  template <typename... Args>
+  PromiseResult(IsResolved, Args&&... args)
+      : value_(in_place_type_t<Resolved<ResolveType>>(),
+               std::forward<Args>(args)...) {}
+
+  template <typename... Args>
+  PromiseResult(IsRejected, Args&&... args)
+      : value_(in_place_type_t<Rejected<RejectType>>(),
+               std::forward<Args>(args)...) {}
+
+  PromiseResult(IsPromise, const Promise<ResolveType, RejectType>& promise)
+      : value_(promise.abstract_promise_) {}
+
+  template <typename T>
+  PromiseResult(IsWrapped, T&& t) : value_(std::forward<T>(t)) {}
+
+  unique_any value_;
+};
+
+}  // namespace base
+
+#endif  // BASE_TASK_PROMISE_PROMISE_RESULT_H_
diff --git a/base/task/promise/promise_unittest.cc b/base/task/promise/promise_unittest.cc
new file mode 100644
index 0000000..c041c1f
--- /dev/null
+++ b/base/task/promise/promise_unittest.cc
@@ -0,0 +1,1409 @@
+// 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 <memory>
+#include <string>
+
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/task/promise/promise.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/do_nothing_promise.h"
+#include "base/test/gtest_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/values.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::ElementsAre;
+
+namespace base {
+namespace {
+
+void RecordOrder(std::vector<int>* run_order, int order) {
+  run_order->push_back(order);
+}
+
+class ObjectToDelete : public RefCounted<ObjectToDelete> {
+ public:
+  // |delete_flag| is set to true when this object is deleted
+  ObjectToDelete(bool* delete_flag) : delete_flag_(delete_flag) {
+    EXPECT_FALSE(*delete_flag_);
+  }
+
+ private:
+  friend class RefCounted<ObjectToDelete>;
+  ~ObjectToDelete() { *delete_flag_ = true; }
+
+  bool* const delete_flag_;
+
+  DISALLOW_COPY_AND_ASSIGN(ObjectToDelete);
+};
+
+class MockObject {
+ public:
+  MockObject() = default;
+
+  void Task(scoped_refptr<ObjectToDelete>) {}
+  void Reply(scoped_refptr<ObjectToDelete>) {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockObject);
+};
+
+struct DummyError {};
+
+}  // namespace
+
+class PromiseTest : public testing::Test {
+ public:
+  test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+TEST(PromiseMemoryLeakTest, TargetTaskRunnerClearsTasks) {
+  scoped_refptr<TestMockTimeTaskRunner> post_runner =
+      MakeRefCounted<TestMockTimeTaskRunner>();
+  scoped_refptr<TestMockTimeTaskRunner> reply_runner =
+      MakeRefCounted<TestMockTimeTaskRunner>(
+          TestMockTimeTaskRunner::Type::kBoundToThread);
+  MockObject mock_object;
+  bool delete_task_flag = false;
+  bool delete_reply_flag = false;
+
+  Promise<int>::CreateResolved(FROM_HERE, 42)
+      .ThenOn(post_runner, FROM_HERE,
+              BindOnce(&MockObject::Task, Unretained(&mock_object),
+                       MakeRefCounted<ObjectToDelete>(&delete_task_flag)))
+      .ThenOnCurrent(
+          FROM_HERE,
+          BindOnce(&MockObject::Reply, Unretained(&mock_object),
+                   MakeRefCounted<ObjectToDelete>(&delete_reply_flag)));
+
+  post_runner->ClearPendingTasks();
+
+  post_runner = nullptr;
+  reply_runner = nullptr;
+
+  EXPECT_TRUE(delete_task_flag);
+  EXPECT_TRUE(delete_reply_flag);
+}
+
+TEST_F(PromiseTest, GetResolveCallbackThen) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+  p.GetResolveCallback().Run(123);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                              EXPECT_EQ(123, result);
+                              run_loop.Quit();
+                            }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, GetRejectCallbackCatch) {
+  ManualPromiseResolver<int, std::string> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(
+      FROM_HERE, BindLambdaForTesting([&](int result) {
+        run_loop.Quit();
+        FAIL() << "We shouldn't get here, the promise was rejected!";
+      }),
+      BindLambdaForTesting([&](const std::string& err) {
+        run_loop.Quit();
+        EXPECT_EQ("Oh no!", err);
+      }));
+
+  p.GetRejectCallback().Run(std::string("Oh no!"));
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, GetRepeatingResolveCallbackThen) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+  p.GetRepeatingResolveCallback().Run(123);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                              EXPECT_EQ(123, result);
+                              run_loop.Quit();
+                            }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, GetRepeatingRejectCallbackCatch) {
+  ManualPromiseResolver<int, std::string> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(
+      FROM_HERE, BindLambdaForTesting([&](int result) {
+        run_loop.Quit();
+        FAIL() << "We shouldn't get here, the promise was rejected!";
+      }),
+      BindLambdaForTesting([&](const std::string& err) {
+        run_loop.Quit();
+        EXPECT_EQ("Oh no!", err);
+      }));
+
+  p.GetRepeatingRejectCallback().Run(std::string("Oh no!"));
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, CreateResolvedThen) {
+  RunLoop run_loop;
+  Promise<int>::CreateResolved(FROM_HERE, 123)
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                       EXPECT_EQ(123, result);
+                       run_loop.Quit();
+                     }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, CatchOnCurrentReturnTypes) {
+  ManualPromiseResolver<int, void> p1(FROM_HERE);
+
+  // Check CatchOnCurrent returns the expected return types for various
+  // return types.
+  Promise<int> r1 =
+      p1.promise().CatchOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
+  Promise<int> r2 = p1.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
+  Promise<int, int> r3 = p1.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return Rejected<int>(123); }));
+
+  Promise<int, void> r4 = p1.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return PromiseResult<int, void>(123.0); }));
+  Promise<int> r5 = p1.promise().CatchOnCurrent(
+      FROM_HERE,
+      BindOnce([]() { return PromiseResult<int, NoReject>(123.0); }));
+  Promise<int, int> r6 = p1.promise().CatchOnCurrent(
+      FROM_HERE,
+      BindOnce([]() { return PromiseResult<NoResolve, int>(123.0); }));
+
+  Promise<int, void> r7 = p1.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return Promise<int, void>(); }));
+  Promise<int> r8 = p1.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return Promise<int, NoReject>(); }));
+  Promise<int, int> r9 = p1.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return Promise<NoResolve, int>(); }));
+
+  ManualPromiseResolver<NoResolve, void> p2(FROM_HERE);
+  Promise<int> r10 =
+      p2.promise().CatchOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
+  Promise<int> r11 = p2.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
+  Promise<NoResolve, int> r12 = p2.promise().CatchOnCurrent(
+      FROM_HERE, BindOnce([]() { return Rejected<int>(123); }));
+}
+
+TEST_F(PromiseTest, ThenOnCurrentReturnTypes) {
+  ManualPromiseResolver<std::string, void> p1(FROM_HERE);
+
+  // Check ThenOnCurrent returns the expected return types for various
+  // return types.
+  Promise<int, void> r1 =
+      p1.promise().ThenOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
+  Promise<int, void> r2 = p1.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
+  Promise<NoResolve, void> r3 = p1.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return Rejected<void>(); }));
+
+  Promise<int, void> r4 = p1.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return PromiseResult<int, void>(123.0); }));
+  Promise<int, void> r5 = p1.promise().ThenOnCurrent(
+      FROM_HERE,
+      BindOnce([]() { return PromiseResult<int, NoReject>(123.0); }));
+  Promise<NoResolve, void> r6 = p1.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return PromiseResult<NoResolve, void>(); }));
+
+  Promise<int, void> r7 = p1.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return Promise<int, void>(); }));
+  Promise<int, void> r8 = p1.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return Promise<int, NoReject>(); }));
+  Promise<NoResolve, void> r9 = p1.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return Promise<NoResolve, void>(); }));
+
+  ManualPromiseResolver<std::string> p2(FROM_HERE);
+  Promise<int> r10 =
+      p2.promise().ThenOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
+  Promise<int> r11 = p2.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
+  Promise<NoResolve, int> r12 = p2.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([]() { return Rejected<int>(123); }));
+}
+
+TEST_F(PromiseTest, ThenAndCatchOnCurrentReturnTypes) {
+  struct A {};
+  struct B {};
+  struct C {};
+  struct D {};
+
+  Promise<B, NoReject> p1 =
+      ManualPromiseResolver<A, NoReject>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Resolved<B>(); }));
+  Promise<NoResolve, B> p2 =
+      ManualPromiseResolver<A, NoReject>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Rejected<B>(); }));
+  Promise<B, C> p3 =
+      ManualPromiseResolver<A, NoReject>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() -> PromiseResult<B, C> { return B{}; }));
+
+  Promise<B, NoReject> p4 =
+      ManualPromiseResolver<NoResolve, A>(FROM_HERE).promise().CatchOnCurrent(
+          FROM_HERE, BindOnce([]() { return Resolved<B>(); }));
+  Promise<NoResolve, B> p5 =
+      ManualPromiseResolver<NoResolve, A>(FROM_HERE).promise().CatchOnCurrent(
+          FROM_HERE, BindOnce([]() { return Rejected<B>(); }));
+  Promise<B, C> p6 =
+      ManualPromiseResolver<NoResolve, A>(FROM_HERE).promise().CatchOnCurrent(
+          FROM_HERE, BindOnce([]() -> PromiseResult<B, C> { return B{}; }));
+
+  Promise<B, C> p7 =
+      ManualPromiseResolver<A, C>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Resolved<B>(); }));
+  Promise<NoResolve, C> p8 =
+      ManualPromiseResolver<A, C>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Rejected<C>(); }));
+  Promise<B, C> p9 =
+      ManualPromiseResolver<A, C>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() -> PromiseResult<B, C> { return B{}; }));
+
+  Promise<A, NoReject> p10 =
+      ManualPromiseResolver<A, C>(FROM_HERE).promise().CatchOnCurrent(
+          FROM_HERE, BindOnce([]() { return Resolved<A>(); }));
+  Promise<A, B> p11 =
+      ManualPromiseResolver<A, C>(FROM_HERE).promise().CatchOnCurrent(
+          FROM_HERE, BindOnce([]() { return Rejected<B>(); }));
+  Promise<A, B> p12 =
+      ManualPromiseResolver<A, C>(FROM_HERE).promise().CatchOnCurrent(
+          FROM_HERE, BindOnce([]() -> PromiseResult<A, B> { return B{}; }));
+
+  Promise<C, NoReject> p13 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Resolved<C>(); }),
+          BindOnce([]() { return Resolved<C>(); }));
+  Promise<NoResolve, D> p14 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Rejected<D>(); }),
+          BindOnce([]() { return Rejected<D>(); }));
+  Promise<C, D> p15 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Resolved<C>(); }),
+          BindOnce([]() { return Rejected<D>(); }));
+  Promise<C, D> p16 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Rejected<D>(); }),
+          BindOnce([]() { return Resolved<C>(); }));
+
+  Promise<C, D> p17 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() -> PromiseResult<C, D> { return C{}; }),
+          BindOnce([]() { return Resolved<C>(); }));
+  Promise<C, D> p18 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() -> PromiseResult<C, D> { return C{}; }),
+          BindOnce([]() { return Rejected<D>(); }));
+  Promise<C, D> p19 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Resolved<C>(); }),
+          BindOnce([]() -> PromiseResult<C, D> { return C{}; }));
+  Promise<C, D> p20 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() { return Rejected<D>(); }),
+          BindOnce([]() -> PromiseResult<C, D> { return C{}; }));
+
+  Promise<C, D> p21 =
+      ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
+          FROM_HERE, BindOnce([]() -> PromiseResult<C, D> { return C{}; }),
+          BindOnce([]() -> PromiseResult<C, D> { return C{}; }));
+}
+
+TEST_F(PromiseTest, RejectAndReReject) {
+  ManualPromiseResolver<int, std::string> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .CatchOnCurrent(
+          FROM_HERE,
+          BindOnce([](const std::string& err) -> PromiseResult<int, int> {
+            EXPECT_EQ("Oh no!", err);
+            // Re-Reject with -1 this time.
+            return Rejected<int>(-1);
+          }))
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](int err) {
+                        EXPECT_EQ(-1, err);
+                        run_loop.Quit();
+                        return -1;
+                      }));
+
+  p.GetRejectCallback().Run("Oh no!");
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectAndReRejectThenCatch) {
+  ManualPromiseResolver<int, std::string> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting([](std::string) {
+                        return Rejected<int>(-1);
+                      }))
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting(
+                                     [&](int) { return Resolved<int>(1000); }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int value) {
+                       EXPECT_EQ(1000, value);
+                       return Rejected<DummyError>();
+                     }))
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting(
+                                     [&](DummyError) { run_loop.Quit(); }));
+
+  p.GetRejectCallback().Run("Oh no!");
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThenWhichAlwayResolves) {
+  ManualPromiseResolver<void> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([]() -> Resolved<int> {
+                       // Resolve
+                       return 123;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int value) {
+                       EXPECT_EQ(123, value);
+                       run_loop.Quit();
+                     }));
+
+  p.GetResolveCallback().Run();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThenWhichAlwayRejects) {
+  ManualPromiseResolver<void, int> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([]() -> Rejected<int> {
+                       // Reject
+                       return -1;
+                     }))
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](int err) {
+                        EXPECT_EQ(-1, err);
+                        run_loop.Quit();
+                      }));
+
+  p.GetResolveCallback().Run();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThenWhichAlwayRejectsTypeTwo) {
+  ManualPromiseResolver<void> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([]() -> Rejected<int> {
+                       // Reject
+                       return -1;
+                     }))
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](int err) {
+                        EXPECT_EQ(-1, err);
+                        run_loop.Quit();
+                      }));
+
+  p.GetResolveCallback().Run();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThenWhichAlwayRejectsTypeThree) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+
+  base::RunLoop run_loop;
+
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                       return Rejected<std::string>(std::string("reject"));
+                     }))
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](std::string result) {
+                        run_loop.Quit();
+                      }));
+
+  p.GetResolveCallback().Run(123);
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ReferenceType) {
+  int a = 123;
+  int b = 456;
+  ManualPromiseResolver<int&> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int& value) -> int& {
+                       EXPECT_EQ(123, value);
+                       return b;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int& value) {
+                       EXPECT_EQ(456, value);
+                       run_loop.Quit();
+                     }));
+
+  p.GetResolveCallback().Run(a);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, PromiseResultVoid) {
+  ManualPromiseResolver<void> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
+                                    [&]() { return PromiseResult<void>(); }))
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&]() { run_loop.Quit(); }));
+
+  p.Resolve();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RefcountedType) {
+  scoped_refptr<internal::AbstractPromise> a =
+      DoNothingPromiseBuilder(FROM_HERE);
+  scoped_refptr<internal::AbstractPromise> b =
+      DoNothingPromiseBuilder(FROM_HERE);
+  ManualPromiseResolver<scoped_refptr<internal::AbstractPromise>> p(FROM_HERE);
+  RunLoop run_loop;
+
+  p.promise()
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting(
+                         [&](scoped_refptr<internal::AbstractPromise> value) {
+                           EXPECT_EQ(a, value);
+                           return b;
+                         }))
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting(
+                         [&](scoped_refptr<internal::AbstractPromise> value) {
+                           EXPECT_EQ(b, value);
+                           run_loop.Quit();
+                         }));
+
+  p.Resolve(a);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ResolveThenVoidFunction) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+  p.Resolve(123);
+
+  // You don't have to use the resolve (or reject) arguments from the
+  // previous promise.
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(FROM_HERE,
+                            BindLambdaForTesting([&]() { run_loop.Quit(); }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ResolveAfterThen) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                              EXPECT_EQ(123, result);
+                              run_loop.Quit();
+                            }));
+
+  p.Resolve(123);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectOutsidePromiseAfterThen) {
+  ManualPromiseResolver<int, void> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(
+      FROM_HERE, BindLambdaForTesting([&](int result) {
+        run_loop.Quit();
+        FAIL() << "We shouldn't get here, the promise was rejected!";
+      }),
+      run_loop.QuitClosure());
+
+  p.Reject();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThenChainMoveOnlyType) {
+  ManualPromiseResolver<std::unique_ptr<int>> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> result) {
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> result) {
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&](std::unique_ptr<int> result) {
+                       EXPECT_THAT(123, *result);
+                       run_loop.Quit();
+                     }));
+
+  p.Resolve(std::make_unique<int>(123));
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, MultipleMovesNotAllowed) {
+  ManualPromiseResolver<std::unique_ptr<int>> p(FROM_HERE);
+
+  // The executor argument will be called with move semantics.
+  p.promise().ThenOnCurrent(FROM_HERE,
+                            BindOnce([](std::unique_ptr<int> result) {}));
+
+  // It's an error to do that twice.
+  EXPECT_DCHECK_DEATH({
+    p.promise().ThenOnCurrent(FROM_HERE,
+                              BindOnce([](std::unique_ptr<int> result) {}));
+  });
+}
+
+TEST_F(PromiseTest, ThenChain) {
+  ManualPromiseResolver<std::vector<size_t>> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
+                       result.push_back(1);
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
+                       result.push_back(2);
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
+                       result.push_back(3);
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&](std::vector<size_t> result) {
+                       EXPECT_THAT(result, ElementsAre(0u, 1u, 2u, 3u));
+                       run_loop.Quit();
+                     }));
+
+  p.Resolve(std::vector<size_t>{0});
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectionInThenChainDefaultVoid) {
+  ManualPromiseResolver<std::vector<size_t>> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
+                       result.push_back(result.size());
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
+                       result.push_back(result.size());
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE,
+                     BindOnce([](std::vector<size_t> result)
+                                  -> PromiseResult<std::vector<size_t>, void> {
+                       return Rejected<void>();
+                     }))
+      .ThenOnCurrent(
+          FROM_HERE, BindLambdaForTesting([&](std::vector<size_t> result) {
+            FAIL() << "We shouldn't get here, the promise was rejected!";
+          }),
+          BindLambdaForTesting([&]() { run_loop.Quit(); }));
+
+  p.Resolve(std::vector<size_t>{0});
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectPropagation) {
+  ManualPromiseResolver<int, bool> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
+      .ThenOnCurrent(
+          FROM_HERE,
+          BindOnce([](int result) -> PromiseResult<int, std::string> {
+            return std::string("Fail shouldn't get here");
+          }),
+          BindOnce([](bool value) -> PromiseResult<int, std::string> {
+            EXPECT_FALSE(value);
+            return std::string("Oh no!");
+          }))
+      .ThenOnCurrent(
+          FROM_HERE, BindLambdaForTesting([&](int result) {
+            FAIL() << "We shouldn't get here, the promise was rejected!";
+            run_loop.Quit();
+          }),
+          BindLambdaForTesting([&](const std::string& err) {
+            EXPECT_EQ("Oh no!", err);
+            run_loop.Quit();
+          }));
+
+  p.Reject(false);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectPropagationThensAfterRejectSkipped) {
+  ManualPromiseResolver<int, bool> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
+      .ThenOnCurrent(
+          FROM_HERE,
+          BindOnce([](int result) -> PromiseResult<int, std::string> {
+            return std::string("Fail shouldn't get here");
+          }),
+          BindOnce([](bool value) -> PromiseResult<int, std::string> {
+            EXPECT_FALSE(value);
+            return std::string("Oh no!");  // Reject
+          }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
+                       CHECK(false) << "Shouldn't get here";
+                       return result + 1;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
+                       CHECK(false) << "Shouldn't get here";
+                       return result + 1;
+                     }))
+      .ThenOnCurrent(
+          FROM_HERE, BindLambdaForTesting([&](int result) {
+            FAIL() << "We shouldn't get here, the promise was rejected!";
+            run_loop.Quit();
+          }),
+          BindLambdaForTesting([&](const std::string& err) {
+            EXPECT_EQ("Oh no!", err);
+            run_loop.Quit();
+          }));
+
+  p.Reject(false);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThenOnWithHetrogenousButCompatibleReturnTypes) {
+  ManualPromiseResolver<void, int> p(FROM_HERE);
+
+  // Make sure ThenOnCurrent returns the expected type.
+  Promise<int, std::string> p2 = p.promise().ThenOnCurrent(
+      FROM_HERE,
+      BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
+      BindOnce([](int err) -> Resolved<int> { return 123; }));
+}
+
+TEST_F(PromiseTest, ThenOnWithHetrogenousButCompatibleReturnTypes2) {
+  ManualPromiseResolver<void, int> p(FROM_HERE);
+
+  // Make sure ThenOnCurrent returns the expected type.
+  Promise<int, std::string> p2 = p.promise().ThenOnCurrent(
+      FROM_HERE,
+      BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
+      BindOnce([](int err) -> Rejected<std::string> { return "123"; }));
+}
+
+TEST_F(PromiseTest, ThenOnWithHetrogenousButCompatibleReturnTypes3) {
+  ManualPromiseResolver<int, std::string> p(FROM_HERE);
+
+  // Make sure ThenOnCurrent returns the expected type.
+  Promise<void, bool> p2 = p.promise().ThenOnCurrent(
+      FROM_HERE, BindOnce([](int value) -> PromiseResult<void, bool> {
+        if (value % 2) {
+          return Resolved<void>();
+        } else {
+          return true;
+        }
+      }),
+      BindOnce([](const std::string& err) -> Rejected<bool> { return false; }));
+}
+
+TEST_F(PromiseTest, ThenOnAfterNoResolvePromiseResult) {
+  ManualPromiseResolver<std::unique_ptr<int>, int> p1(FROM_HERE);
+
+  RunLoop run_loop;
+  p1.promise()
+      .CatchOnCurrent(FROM_HERE, BindLambdaForTesting(
+                                     [&](int) -> PromiseResult<NoResolve, int> {
+                                       return Rejected<int>();
+                                     }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](std::unique_ptr<int>) {
+                       run_loop.Quit();
+                       return std::make_unique<int>(42);
+                     }),
+                     BindLambdaForTesting([&](int err) {
+                       CHECK(false) << "Shouldn't get here";
+                       return std::make_unique<int>(42);
+                     }));
+
+  p1.GetResolveCallback().Run(std::make_unique<int>(42));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, CatchCreatesNoRejectPromise) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+
+  // Make sure CatchOnCurrent returns the expected type.
+  Promise<int> p2 =
+      p.promise()
+          .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int) {
+                           return Rejected<std::string>();
+                         }))
+          .CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](std::string) {
+                            return Resolved<int>();
+                          }));
+}
+
+TEST_F(PromiseTest, ResolveSkipsCatches) {
+  ManualPromiseResolver<int, void> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
+      .CatchOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, void> {
+                        CHECK(false) << "Shouldn't get here";
+                        return -1;
+                      }))
+      .CatchOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, void> {
+                        CHECK(false) << "Shouldn't get here";
+                        return -1;
+                      }))
+      .CatchOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, void> {
+                        CHECK(false) << "Shouldn't get here";
+                        return -1;
+                      }))
+      .ThenOnCurrent(
+          FROM_HERE, BindLambdaForTesting([&](int result) {
+            EXPECT_EQ(2, result);
+            run_loop.Quit();
+          }),
+          BindLambdaForTesting([&]() {
+            FAIL() << "We shouldn't get here, the promise was resolved!";
+            run_loop.Quit();
+          }));
+
+  p.Resolve(1);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThenChainVariousReturnTypes) {
+  ManualPromiseResolver<void> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([]() { return 5; }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
+                       EXPECT_EQ(5, result);
+                       return std::string("Hello");
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](std::string result) {
+                       EXPECT_EQ("Hello", result);
+                       return true;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](bool result) {
+                       EXPECT_TRUE(result);
+                       run_loop.Quit();
+                     }));
+
+  p.GetResolveCallback().Run();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, CurriedVoidPromise) {
+  Promise<void> p = Promise<void>::CreateResolved(FROM_HERE);
+  ManualPromiseResolver<void> promise_resolver(FROM_HERE);
+
+  RunLoop run_loop;
+  p.ThenOnCurrent(FROM_HERE,
+                  BindOnce(
+                      [](ManualPromiseResolver<void>* promise_resolver) {
+                        return promise_resolver->promise();
+                      },
+                      &promise_resolver))
+      .ThenOnCurrent(FROM_HERE, run_loop.QuitClosure());
+  RunLoop().RunUntilIdle();
+
+  promise_resolver.Resolve();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, CurriedIntPromise) {
+  Promise<int> p = Promise<int>::CreateResolved(FROM_HERE, 1000);
+  ManualPromiseResolver<int> promise_resolver(FROM_HERE);
+
+  RunLoop run_loop;
+  p.ThenOnCurrent(
+       FROM_HERE,
+       BindOnce(
+           [](ManualPromiseResolver<int>* promise_resolver, int result) {
+             EXPECT_EQ(1000, result);
+             return promise_resolver->promise();
+           },
+           &promise_resolver))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                       EXPECT_EQ(123, result);
+                       run_loop.Quit();
+                     }));
+  RunLoop().RunUntilIdle();
+
+  promise_resolver.Resolve(123);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, PromiseResultReturningAPromise) {
+  Promise<int> p = Promise<int>::CreateResolved(FROM_HERE, 1000);
+  ManualPromiseResolver<int> promise_resolver(FROM_HERE);
+
+  RunLoop run_loop;
+  p.ThenOnCurrent(FROM_HERE,
+                  BindLambdaForTesting([&](int result) -> PromiseResult<int> {
+                    EXPECT_EQ(1000, result);
+                    return promise_resolver.promise();
+                  }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                       EXPECT_EQ(123, result);
+                       run_loop.Quit();
+                     }));
+  RunLoop().RunUntilIdle();
+
+  promise_resolver.Resolve(123);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ResolveToDisambiguateThenReturnValue) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE,
+                     BindOnce([](int i) -> PromiseResult<Value, Value> {
+                       if ((i % 2) == 1)
+                         return Resolved<Value>("Success it was odd.");
+                       return Rejected<Value>("Failure it was even.");
+                     }))
+      .ThenOnCurrent(
+          FROM_HERE, BindLambdaForTesting([&](Value result) {
+            EXPECT_EQ("Success it was odd.", result.GetString());
+            run_loop.Quit();
+          }),
+          BindLambdaForTesting([&](Value err) {
+            run_loop.Quit();
+            FAIL() << "We shouldn't get here, the promise was resolved!";
+          }));
+
+  p.Resolve(1);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectedToDisambiguateThenReturnValue) {
+  ManualPromiseResolver<int, int> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, int> {
+                       return Rejected<int>(123);
+                     }))
+      .ThenOnCurrent(
+          FROM_HERE, BindLambdaForTesting([&](int result) {
+            run_loop.Quit();
+            FAIL() << "We shouldn't get here, the promise was rejected!";
+          }),
+          BindLambdaForTesting([&](int err) {
+            run_loop.Quit();
+            EXPECT_EQ(123, err);
+          }));
+
+  p.Resolve();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, NestedPromises) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+  p.Resolve(100);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
+                       ManualPromiseResolver<int> p2(FROM_HERE);
+                       p2.Resolve(200);
+                       return p2.promise().ThenOnCurrent(
+                           FROM_HERE, BindOnce([](int result) {
+                             ManualPromiseResolver<int> p3(FROM_HERE);
+                             p3.Resolve(300);
+                             return p3.promise().ThenOnCurrent(
+                                 FROM_HERE,
+                                 BindOnce([](int result) { return result; }));
+                           }));
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                       EXPECT_EQ(300, result);
+                       run_loop.Quit();
+                     }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, Catch) {
+  ManualPromiseResolver<int, std::string> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result; }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result; }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result; }))
+      .CatchOnCurrent(FROM_HERE,
+                      BindLambdaForTesting([&](const std::string& err) {
+                        EXPECT_EQ("Whoops!", err);
+                        run_loop.Quit();
+                        return -1;
+                      }));
+
+  p.Reject("Whoops!");
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, BranchedThenChainExecutionOrder) {
+  std::vector<int> run_order;
+
+  ManualPromiseResolver<void> promise_a(FROM_HERE);
+  Promise<void> promise_b =
+      promise_a.promise()
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 0))
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 1));
+
+  Promise<void> promise_c =
+      promise_a.promise()
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 2))
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 3));
+
+  Promise<void> promise_d =
+      promise_a.promise()
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 4))
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 5));
+
+  promise_a.Resolve();
+  RunLoop().RunUntilIdle();
+
+  EXPECT_THAT(run_order, ElementsAre(0, 2, 4, 1, 3, 5));
+}
+
+TEST_F(PromiseTest, BranchedThenChainWithCatchExecutionOrder) {
+  std::vector<int> run_order;
+
+  ManualPromiseResolver<void, void> promise_a(FROM_HERE);
+  Promise<void> promise_b =
+      promise_a.promise()
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 0))
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 1))
+          .CatchOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 2));
+
+  Promise<void> promise_c =
+      promise_a.promise()
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 3))
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 4))
+          .CatchOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 5));
+
+  Promise<void> promise_d =
+      promise_a.promise()
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 6))
+          .ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 7))
+          .CatchOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 8));
+
+  promise_a.Reject();
+  RunLoop().RunUntilIdle();
+
+  EXPECT_THAT(run_order, ElementsAre(2, 5, 8));
+}
+
+TEST_F(PromiseTest, CatchRejectInThenChain) {
+  ManualPromiseResolver<int> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(
+          FROM_HERE,
+          BindOnce([](int result) -> PromiseResult<int, std::string> {
+            return std::string("Whoops!");
+          }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
+                       CHECK(false) << "Shouldn't get here";
+                       return result;
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
+                       CHECK(false) << "Shouldn't get here";
+                       return result;
+                     }))
+      .CatchOnCurrent(FROM_HERE,
+                      BindLambdaForTesting([&](const std::string& err) {
+                        EXPECT_EQ("Whoops!", err);
+                        run_loop.Quit();
+                        return -1;
+                      }));
+
+  p.Resolve(123);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, CatchThenVoid) {
+  ManualPromiseResolver<int, void> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .CatchOnCurrent(FROM_HERE, BindOnce([]() { return 123; }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                       EXPECT_EQ(123, result);
+                       run_loop.Quit();
+                     }));
+
+  p.Reject();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, CatchThenInt) {
+  ManualPromiseResolver<int, int> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .CatchOnCurrent(FROM_HERE, BindOnce([](int err) { return err + 1; }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
+                       EXPECT_EQ(124, result);
+                       run_loop.Quit();
+                     }));
+
+  p.Reject(123);
+  run_loop.Run();
+}
+
+namespace {
+struct Cancelable {
+  Cancelable() : weak_ptr_factory(this) {}
+
+  void LogTask(std::vector<std::string>* log, std::string value) {
+    log->push_back(value);
+  }
+
+  void NopTask() {}
+
+  WeakPtrFactory<Cancelable> weak_ptr_factory;
+};
+}  // namespace
+
+TEST_F(PromiseTest, CancelViaWeakPtr) {
+  std::vector<std::string> log;
+  ManualPromiseResolver<void, std::string> mpr(FROM_HERE,
+                                               RejectPolicy::kCatchNotRequired);
+  Promise<void, std::string> p1 = mpr.promise();
+  {
+    Cancelable cancelable;
+    Promise<void, std::string> p2 = p1.ThenOnCurrent(
+        FROM_HERE,
+        BindOnce(&Cancelable::LogTask, cancelable.weak_ptr_factory.GetWeakPtr(),
+                 &log, "Then #1"));
+    p2.ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
+                                    [&]() -> PromiseResult<void, std::string> {
+                                      log.push_back("Then #2 (reject)");
+                                      return std::string("Whoops!");
+                                    }))
+        .ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
+                                      [&]() { log.push_back("Then #3"); }))
+        .ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
+                                      [&]() { log.push_back("Then #4"); }))
+        .CatchOnCurrent(FROM_HERE,
+                        BindLambdaForTesting([&](const std::string& err) {
+                          log.push_back("Caught " + err);
+                        }));
+
+    p2.ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&]() { log.push_back("Then #5"); }));
+    p2.ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&]() { log.push_back("Then #6"); }));
+  }
+
+  mpr.Resolve();
+  RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(log.empty());
+}
+
+TEST_F(PromiseTest, CatchNotRequired) {
+  ManualPromiseResolver<bool, int> p(FROM_HERE,
+                                     RejectPolicy::kCatchNotRequired);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(FROM_HERE, run_loop.QuitClosure());
+
+  // Note this doesn't DCHECK even though we haven't specified a Catch.
+  p.Resolve();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, MoveOnlyTypeMultipleThensNotAllowed) {
+#if DCHECK_IS_ON()
+  Promise<std::unique_ptr<int>> p =
+      Promise<std::unique_ptr<int>>::CreateResolved(FROM_HERE,
+                                                    std::make_unique<int>(123));
+
+  p.ThenOnCurrent(FROM_HERE,
+                  BindOnce([](std::unique_ptr<int> i) { EXPECT_EQ(123, *i); }));
+
+  EXPECT_DCHECK_DEATH({
+    p.ThenOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> i) {
+                      EXPECT_EQ(123, *i);
+                    }));
+  });
+#endif
+}
+
+TEST_F(PromiseTest, MoveOnlyTypeMultipleCatchesNotAllowed) {
+#if DCHECK_IS_ON()
+  auto p = Promise<void, std::unique_ptr<int>>::CreateRejected(
+      FROM_HERE, std::make_unique<int>(123));
+
+  p.CatchOnCurrent(
+      FROM_HERE, BindOnce([](std::unique_ptr<int> i) { EXPECT_EQ(123, *i); }));
+
+  EXPECT_DCHECK_DEATH({
+    p.CatchOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> i) {
+                       EXPECT_EQ(123, *i);
+                     }));
+  });
+#endif
+}
+
+TEST_F(PromiseTest, PostTaskUnhandledRejection) {
+#if DCHECK_IS_ON()
+  Promise<void, int> p =
+      Promise<void, int>::CreateRejected(FROM_HERE).ThenOnCurrent(
+          FROM_HERE, BindOnce([]() {}));
+
+  RunLoop().RunUntilIdle();
+
+  Promise<void, int> null_promise;
+  EXPECT_DCHECK_DEATH({ p = null_promise; });
+
+  // EXPECT_DCHECK_DEATH uses fork under the hood so we still have to tidy up.
+  p.CatchOnCurrent(FROM_HERE, BindOnce([]() {}));
+#endif
+}
+
+TEST_F(PromiseTest, ManualPromiseResolverPotentialUnhandledRejection) {
+#if DCHECK_IS_ON()
+  ManualPromiseResolver<void, void> promise_resolver(FROM_HERE);
+
+  // |promise_resolver| could reject but there's no catch.
+  Promise<void, void> p =
+      promise_resolver.promise().ThenOnCurrent(FROM_HERE, BindOnce([]() {}));
+
+  promise_resolver.Resolve();
+  RunLoop().RunUntilIdle();
+
+  Promise<void, void> null_promise;
+  EXPECT_DCHECK_DEATH({ p = null_promise; });
+
+  // EXPECT_DCHECK_DEATH uses fork under the hood so we still have to tidy up.
+  p.CatchOnCurrent(FROM_HERE, BindOnce([]() {}));
+#endif
+}
+
+TEST_F(PromiseTest, ManualPromiseResolverResolveCalledTwice) {
+#if DCHECK_IS_ON()
+  ManualPromiseResolver<void> promise_resolver(FROM_HERE);
+
+  promise_resolver.Resolve();
+
+  EXPECT_DCHECK_DEATH({ promise_resolver.Resolve(); });
+#endif
+}
+
+TEST_F(PromiseTest, ManualPromiseResolverRejectCalledTwice) {
+#if DCHECK_IS_ON()
+  ManualPromiseResolver<void, void> promise_resolver(
+      FROM_HERE, RejectPolicy::kCatchNotRequired);
+
+  promise_resolver.Reject();
+
+  EXPECT_DCHECK_DEATH({ promise_resolver.Reject(); });
+#endif
+}
+
+TEST_F(PromiseTest, ManualPromiseResolverResolveCalledAfterReject) {
+#if DCHECK_IS_ON()
+  ManualPromiseResolver<void, void> promise_resolver(
+      FROM_HERE, RejectPolicy::kCatchNotRequired);
+
+  promise_resolver.Reject();
+
+  EXPECT_DCHECK_DEATH({ promise_resolver.Resolve(); });
+#endif
+}
+
+TEST_F(PromiseTest, ManualPromiseResolverRepeatingResolveCallbackCalledTwice) {
+#if DCHECK_IS_ON()
+  ManualPromiseResolver<void, void> promise_resolver(
+      FROM_HERE, RejectPolicy::kCatchNotRequired);
+  RepeatingCallback<void(void)> resolve =
+      promise_resolver.GetRepeatingResolveCallback();
+
+  resolve.Run();
+
+  EXPECT_DCHECK_DEATH({ resolve.Run(); });
+#endif
+}
+
+TEST_F(PromiseTest, ManualPromiseResolverRepeatingRejectCallbackCalledTwice) {
+#if DCHECK_IS_ON()
+  ManualPromiseResolver<void, void> promise_resolver(
+      FROM_HERE, RejectPolicy::kCatchNotRequired);
+  RepeatingCallback<void(void)> resolve =
+      promise_resolver.GetRepeatingRejectCallback();
+
+  resolve.Run();
+
+  EXPECT_DCHECK_DEATH({ resolve.Run(); });
+#endif
+}
+
+class MultiThreadedPromiseTest : public PromiseTest {
+ public:
+  void SetUp() override {
+    thread_a_.reset(new Thread("MultiThreadPromiseTest_Thread_A"));
+    thread_b_.reset(new Thread("MultiThreadPromiseTest_Thread_B"));
+    thread_c_.reset(new Thread("MultiThreadPromiseTest_Thread_C"));
+    thread_a_->Start();
+    thread_b_->Start();
+    thread_c_->Start();
+  }
+
+  void TearDown() override {
+    thread_a_->Stop();
+    thread_b_->Stop();
+    thread_c_->Stop();
+  }
+
+  std::unique_ptr<Thread> thread_a_;
+  std::unique_ptr<Thread> thread_b_;
+  std::unique_ptr<Thread> thread_c_;
+};
+
+TEST_F(MultiThreadedPromiseTest, SimpleThreadHopping) {
+  ManualPromiseResolver<void> promise_resolver(FROM_HERE);
+
+  RunLoop run_loop;
+  promise_resolver.promise()
+      .ThenOn(
+          thread_a_->task_runner(), FROM_HERE, BindLambdaForTesting([&]() {
+            EXPECT_TRUE(thread_a_->task_runner()->RunsTasksInCurrentSequence());
+          }))
+      .ThenOn(
+          thread_b_->task_runner(), FROM_HERE, BindLambdaForTesting([&]() {
+            EXPECT_TRUE(thread_b_->task_runner()->RunsTasksInCurrentSequence());
+          }))
+      .ThenOn(
+          thread_c_->task_runner(), FROM_HERE, BindLambdaForTesting([&]() {
+            EXPECT_TRUE(thread_c_->task_runner()->RunsTasksInCurrentSequence());
+          }))
+      .ThenOnCurrent(
+          FROM_HERE, BindLambdaForTesting([&]() {
+            EXPECT_FALSE(
+                thread_a_->task_runner()->RunsTasksInCurrentSequence());
+            EXPECT_FALSE(
+                thread_b_->task_runner()->RunsTasksInCurrentSequence());
+            EXPECT_FALSE(
+                thread_c_->task_runner()->RunsTasksInCurrentSequence());
+            run_loop.Quit();
+          }));
+
+  promise_resolver.Resolve();
+  run_loop.Run();
+}
+
+TEST_F(MultiThreadedPromiseTest, CrossThreadThens) {
+  ManualPromiseResolver<void> promise_resolver(FROM_HERE);
+
+  auto resolve_task =
+      BindLambdaForTesting([&]() { promise_resolver.Resolve(); });
+
+  RunLoop run_loop;
+
+  // Rolling our own thread-unsafe BarrierClosure to ensure atomics aren't
+  // necessary for this test to resolve all Thens on |thread_c_|.
+  int thens_remaining = 1000;
+  auto then_task = BindLambdaForTesting([&]() {
+    --thens_remaining;
+    if (!thens_remaining)
+      run_loop.Quit();
+  });
+
+  thread_a_->task_runner()->PostTask(
+      FROM_HERE, BindLambdaForTesting([&]() {
+        // Post 500 thens.
+        for (int i = 0; i < 500; i++) {
+          promise_resolver.promise().ThenOn(thread_c_->task_runner(), FROM_HERE,
+                                            then_task);
+        }
+        // Post a task onto the main thread to resolve |promise_resolver|.
+        // This should run at an undefined time yet all the thens should run.
+        thread_b_->task_runner()->PostTask(FROM_HERE, resolve_task);
+
+        // Post another 500 thens.
+        for (int i = 0; i < 500; i++) {
+          promise_resolver.promise().ThenOn(thread_c_->task_runner(), FROM_HERE,
+                                            then_task);
+        }
+      }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ThreadPoolThenChain) {
+  ManualPromiseResolver<std::vector<size_t>> p(FROM_HERE);
+  auto main_sequence = SequencedTaskRunnerHandle::Get();
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOn({TaskPriority::USER_BLOCKING}, FROM_HERE,
+              BindLambdaForTesting([&](std::vector<size_t> result) {
+                EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());
+                result.push_back(1);
+                return result;
+              }))
+      .ThenOn({TaskPriority::USER_BLOCKING}, FROM_HERE,
+              BindLambdaForTesting([&](std::vector<size_t> result) {
+                EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());
+                result.push_back(2);
+                return result;
+              }))
+      .ThenOn({TaskPriority::USER_BLOCKING}, FROM_HERE,
+              BindLambdaForTesting([&](std::vector<size_t> result) {
+                EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());
+                result.push_back(3);
+                return result;
+              }))
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&](std::vector<size_t> result) {
+                       EXPECT_TRUE(main_sequence->RunsTasksInCurrentSequence());
+                       EXPECT_THAT(result, ElementsAre(0u, 1u, 2u, 3u));
+                       run_loop.Quit();
+                     }));
+
+  p.Resolve(std::vector<size_t>{0});
+  run_loop.Run();
+}
+
+}  // namespace base
diff --git a/base/task/promise/promise_unittest.nc b/base/task/promise/promise_unittest.nc
new file mode 100644
index 0000000..078530a7
--- /dev/null
+++ b/base/task/promise/promise_unittest.nc
@@ -0,0 +1,105 @@
+// 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.
+//
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/task/promise/promise.h"
+#include "base/task/promise/promise_result.h"
+
+namespace base {
+
+#if defined(NCTEST_METHOD_CANT_CATCH_NOREJECT_PROMISE) // [r"fatal error: static_assert failed .*\"Can't catch a NoReject promise\."]
+void WontCompile() {
+  Promise<int> p;
+  p.CatchOnCurrent(FROM_HERE, BindOnce([]() {}));
+}
+#elif defined(NCTEST_METHOD_CANT_CATCH_NOREJECT_PROMISE_TYPE_TWO) // [r"fatal error: static_assert failed .*\"Can't catch a NoReject promise\."]
+void WontCompile() {
+  Promise<int> p;
+  p.ThenOnCurrent(FROM_HERE, BindOnce([](int) {}), BindOnce([]() {}));
+}
+#elif defined(NCTEST_METHOD_RESOLVE_CALLBACK_TYPE_MISSMATCH) // [r"fatal error: static_assert failed .*\"|on_resolve| callback must accept Promise::ResolveType or void\."]
+void WontCompile() {
+  Promise<int, void> p;
+  p.ThenOnCurrent(FROM_HERE, BindOnce([](bool) { }));
+}
+#elif defined(NCTEST_METHOD_REJECT_CALLBACK_TYPE_MISSMATCH) // [r"fatal error: static_assert failed .*\"|on_reject| callback must accept Promise::ResolveType or void\."]
+void WontCompile() {
+  Promise<int, void> p;
+  p.CatchOnCurrent(FROM_HERE, BindOnce([](bool) { }));
+}
+#elif defined(NCTEST_METHOD_REJECT_CALLBACK_TYPE_MISSMATCH_TYPE_TWO) // [r"fatal error: static_assert failed .*\"|on_reject| callback must accept Promise::ResolveType or void\."]
+void WontCompile() {
+  Promise<int, void> p;
+  p.ThenOnCurrent(FROM_HERE, BindOnce([](int) { }), BindOnce([](bool) { }));
+}
+#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
+void WontCompile() {
+  Promise<void> p;
+  p.ThenOnCurrent(
+      FROM_HERE,
+      BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
+      BindOnce([](int err) -> Rejected<bool> { return "123"; }));
+}
+#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES2) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
+void WontCompile() {
+  Promise<void> p;
+  p.ThenOnCurrent(
+      FROM_HERE,
+      BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
+      BindOnce([](int err) -> Resolved<std::string> { return "123"; }));
+}
+#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES3) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
+void WontCompile() {
+  Promise<int, void> p;
+  p.ThenOnCurrent(FROM_HERE, BindOnce([](int) { return true; }),
+                             BindOnce([](int) { return 123.0; }));
+}
+#elif defined(NCTEST_METHOD_AMBIGUOUS_CONSTRUCTOR) // [r"fatal error: static_assert failed .*\"Ambiguous because ResolveType and RejectType are the same"]
+void WontCompile() {
+  PromiseResult<void, void> pr;
+}
+#elif defined(NCTEST_METHOD_AMBIGUOUS_CONSTRUCTOR2) // [r"fatal error: static_assert failed .*\"Ambiguous because ResolveType and RejectType are the same"]
+void WontCompile() {
+  PromiseResult<int, int> pr(123);
+}
+#elif defined(NCTEST_METHOD_REJECTED_NOREJECT) // [r"fatal error: static_assert failed .*\"Can't have Rejected<NoReject>"]
+void WontCompile() {
+  Rejected<NoReject>();
+}
+#elif defined(NCTEST_METHOD_ARGUMENT_DOESNT_MATCH) // [r"fatal error: static_assert failed .*\"Argument matches neither resolve nor reject type\."]
+void WontCompile() {
+  PromiseResult<int, float> pr("invalid");
+}
+#elif defined(NCTEST_METHOD_ARGUMENT_DOESNT_MATCH2) // [r"fatal error: static_assert failed .*\"Promise resolve types don't match"]
+void WontCompile() {
+  Promise<void> p;
+  PromiseResult<int, float> pr(p);
+}
+#elif defined(NCTEST_METHOD_UNRELATED_RESOLVE) // [r"fatal error: static_assert failed .*\"T in Resolved<T> is not ResolveType"]
+void WontCompile() {
+  struct Unrelated{};
+  PromiseResult<int, void> pr(Resolved<Unrelated>{});
+}
+#elif defined(NCTEST_METHOD_UNRELATED_REJECT) // [r"fatal error: static_assert failed .*\"T in Rejected<T> is not RejectType"]
+void WontCompile() {
+  struct Unrelated{};
+  PromiseResult<int, void> pr(Rejected<Unrelated>{});
+}
+#elif defined(NCTEST_METHOD_AMBIGUOUS_REJECT_TYPE) // [r"fatal error: static_assert failed .*\"Ambiguous promise reject type"]
+void WontCompile() {
+  Promise<int, void> p1;
+  // If supported |p2| would have type Promise<NoReject, variant<void, bool>>.
+  auto p2 = p1.ThenOnCurrent(FROM_HERE, BindOnce([]() { return Rejected<bool>(true); }));
+}
+#elif defined(NCTEST_METHOD_AMBIGUOUS_RESOLVE_TYPE) // [r"fatal error: static_assert failed .*\"Ambiguous promise resolve type"]
+void WontCompile() {
+  Promise<int, void> p1;
+  // If supported |p2| would have type Promise<variant<int, bool>, NoReject>.
+  auto p2 = p1.CatchOnCurrent(FROM_HERE, BindOnce([](int) { return Resolved<bool>(true); }));
+}
+#endif
+
+}  // namespace base
diff --git a/base/task/promise/small_unique_object.h b/base/task/promise/small_unique_object.h
deleted file mode 100644
index d446d035..0000000
--- a/base/task/promise/small_unique_object.h
+++ /dev/null
@@ -1,98 +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 BASE_TASK_PROMISE_SMALL_UNIQUE_OBJECT_H_
-#define BASE_TASK_PROMISE_SMALL_UNIQUE_OBJECT_H_
-
-#include <cstring>
-
-#include "base/macros.h"
-#include "base/template_util.h"
-
-namespace base {
-namespace internal {
-
-// A container intended for inline storage of a derived virtual object up to a
-// maximum size. This is useful to avoid unnecessary heap allocations.
-template <typename T, size_t MaxSize = sizeof(int*) * 3>
-class BASE_EXPORT SmallUniqueObject {
- public:
-  SmallUniqueObject() { memset(&union_, 0, sizeof(union_)); }
-
-  template <typename Derived, typename... Args>
-  SmallUniqueObject(in_place_type_t<Derived>, Args&&... args) noexcept {
-    static_assert(std::is_base_of<T, Derived>::value,
-                  "T is not a base of Derived");
-    static_assert(sizeof(Derived) <= MaxSize,
-                  "Derived is too big to be held by SmallUniqueObject");
-    static_assert(sizeof(T) <= MaxSize,
-                  "T is too big to be held by SmallUniqueObject");
-    new (union_.storage) Derived(std::forward<Args>(args)...);
-  }
-
-  ~SmallUniqueObject() { reset(); }
-
-  explicit operator bool() const { return !Empty(); }
-
-  bool Empty() const {
-    for (size_t i = 0; i < kMaxInts; i++) {
-      if (union_.flag[i])
-        return false;
-    }
-    return true;
-  }
-
-  void reset() {
-    if (!Empty()) {
-      get()->~T();
-      memset(&union_, 0, sizeof(union_));
-    }
-  }
-
-  T* get() noexcept {
-    if (Empty())
-      return nullptr;
-    return reinterpret_cast<T*>(union_.storage);
-  }
-
-  const T* get() const noexcept {
-    if (Empty())
-      return nullptr;
-    return reinterpret_cast<const T*>(union_.storage);
-  }
-
-  const T& operator*() const {
-    DCHECK(!Empty());
-    return *get();
-  }
-
-  T& operator*() {
-    DCHECK(!Empty());
-    return *get();
-  }
-
-  const T* operator->() const noexcept {
-    DCHECK(!Empty());
-    return get();
-  }
-
-  T* operator->() noexcept {
-    DCHECK(!Empty());
-    return get();
-  }
-
- private:
-  static constexpr size_t kMaxInts = MaxSize / sizeof(int);
-  union {
-    // The vtable will be stored somewhere within |storage|, the actual location
-    // is compiler specific but where ever it is, it can't be zero.
-    int flag[kMaxInts];
-    char storage[MaxSize];
-  } union_;
-};
-
-}  // namespace internal
-}  // namespace base
-
-#endif  // BASE_TASK_PROMISE_SMALL_UNIQUE_OBJECT_H_
diff --git a/base/task/promise/then_and_catch_executor.cc b/base/task/promise/then_and_catch_executor.cc
new file mode 100644
index 0000000..003eb574
--- /dev/null
+++ b/base/task/promise/then_and_catch_executor.cc
@@ -0,0 +1,73 @@
+// 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/promise/then_and_catch_executor.h"
+
+namespace base {
+namespace internal {
+
+ThenAndCatchExecutorCommon::ThenAndCatchExecutorCommon(
+    internal::CallbackBase&& resolve_executor,
+    internal::CallbackBase&& reject_executor)
+    : resolve_callback_(std::move(resolve_executor)),
+      reject_callback_(std::move(reject_executor)) {}
+
+ThenAndCatchExecutorCommon::~ThenAndCatchExecutorCommon() = default;
+
+bool ThenAndCatchExecutorCommon::IsCancelled() const {
+  if (!resolve_callback_.is_null()) {
+    // If there is both a resolve and a reject executor they must be canceled
+    // at the same time.
+    DCHECK(reject_callback_.is_null() ||
+           reject_callback_.IsCancelled() == resolve_callback_.IsCancelled());
+    return resolve_callback_.IsCancelled();
+  }
+  return reject_callback_.IsCancelled();
+}
+
+AbstractPromise::Executor::PrerequisitePolicy
+ThenAndCatchExecutorCommon::GetPrerequisitePolicy() const {
+  return AbstractPromise::Executor::PrerequisitePolicy::kAll;
+}
+
+void ThenAndCatchExecutorCommon::Execute(AbstractPromise* promise,
+                                         ExecuteCallback execute_then,
+                                         ExecuteCallback execute_catch) {
+  AbstractPromise* prerequisite =
+      promise->GetOnlyPrerequisite()->FindNonCurriedAncestor();
+  if (prerequisite->IsResolved()) {
+    if (ProcessNullCallback(resolve_callback_, prerequisite, promise)) {
+      promise->OnResolved();
+      return;
+    }
+
+    execute_then(prerequisite, promise, this);
+  } else {
+    DCHECK(prerequisite->IsRejected());
+    if (ProcessNullCallback(reject_callback_, prerequisite, promise)) {
+      promise->OnResolved();
+      return;
+    }
+
+    execute_catch(prerequisite, promise, this);
+  }
+}
+
+// static
+bool ThenAndCatchExecutorCommon::ProcessNullCallback(
+    const CallbackBase& callback,
+    AbstractPromise* arg,
+    AbstractPromise* result) {
+  if (callback.is_null()) {
+    // A curried promise is used to forward the result through null callbacks.
+    result->emplace(scoped_refptr<AbstractPromise>(arg));
+    DCHECK(result->IsResolvedWithPromise());
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/base/task/promise/then_and_catch_executor.h b/base/task/promise/then_and_catch_executor.h
new file mode 100644
index 0000000..6cc7bb5
--- /dev/null
+++ b/base/task/promise/then_and_catch_executor.h
@@ -0,0 +1,230 @@
+// 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_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
+#define BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
+
+#include <type_traits>
+
+#include "base/callback.h"
+#include "base/task/promise/abstract_promise.h"
+#include "base/task/promise/helpers.h"
+
+namespace base {
+namespace internal {
+
+// Exists to reduce template bloat.
+class BASE_EXPORT ThenAndCatchExecutorCommon {
+ public:
+  ThenAndCatchExecutorCommon(CallbackBase&& resolve_callback,
+                             CallbackBase&& reject_callback);
+
+  ~ThenAndCatchExecutorCommon();
+
+  // AbstractPromise::Executor:
+  bool IsCancelled() const;
+  AbstractPromise::Executor::PrerequisitePolicy GetPrerequisitePolicy() const;
+
+  using ExecuteCallback = void (*)(AbstractPromise* prerequisite,
+                                   AbstractPromise* promise,
+                                   ThenAndCatchExecutorCommon* executor);
+
+  void Execute(AbstractPromise* promise,
+               ExecuteCallback execute_then,
+               ExecuteCallback execute_catch);
+
+  // If |executor| is null then the value of |arg| is moved or copied into
+  // |result| and true is returned. Otherwise false is returned.
+  static bool ProcessNullCallback(const CallbackBase& executor,
+                                  AbstractPromise* arg,
+                                  AbstractPromise* result);
+
+  CallbackBase resolve_callback_;
+  CallbackBase reject_callback_;
+};
+
+// Tag signals no callback which is used to eliminate dead code.
+struct NoCallback {};
+
+struct CouldResolveOrReject {};
+struct CanOnlyResolve {};
+struct CanOnlyReject {};
+
+template <bool can_resolve, bool can_reject>
+struct CheckResultHelper;
+
+template <>
+struct CheckResultHelper<true, false> {
+  using TagType = CanOnlyResolve;
+};
+
+template <>
+struct CheckResultHelper<true, true> {
+  using TagType = CouldResolveOrReject;
+};
+
+template <>
+struct CheckResultHelper<false, true> {
+  using TagType = CanOnlyReject;
+};
+
+template <typename ResolveOnceCallback,
+          typename RejectOnceCallback,
+          typename ArgResolve,
+          typename ArgReject,
+          typename ResolveStorage,
+          typename RejectStorage>
+class ThenAndCatchExecutor {
+ public:
+  using ResolveReturnT =
+      typename CallbackTraits<ResolveOnceCallback>::ReturnType;
+  using RejectReturnT = typename CallbackTraits<RejectOnceCallback>::ReturnType;
+  using PrerequisiteCouldResolve =
+      std::integral_constant<bool,
+                             !std::is_same<ArgResolve, NoCallback>::value>;
+  using PrerequisiteCouldReject =
+      std::integral_constant<bool, !std::is_same<ArgReject, NoCallback>::value>;
+
+  ThenAndCatchExecutor(ResolveOnceCallback&& resolve_callback,
+                       RejectOnceCallback&& reject_callback)
+      : common_(std::move(resolve_callback), std::move(reject_callback)) {
+    static_assert(sizeof(CallbackBase) == sizeof(ResolveOnceCallback),
+                  "We assume it's possible to cast from CallbackBase to "
+                  "ResolveOnceCallback");
+    static_assert(sizeof(CallbackBase) == sizeof(RejectOnceCallback),
+                  "We assume it's possible to cast from CallbackBase to "
+                  "RejectOnceCallback");
+  }
+
+  bool IsCancelled() const { return common_.IsCancelled(); }
+
+  AbstractPromise::Executor::PrerequisitePolicy GetPrerequisitePolicy() const {
+    return common_.GetPrerequisitePolicy();
+  }
+
+  using ExecuteCallback = ThenAndCatchExecutorCommon::ExecuteCallback;
+
+  void Execute(AbstractPromise* promise) {
+    return common_.Execute(promise, &ExecuteThen, &ExecuteCatch);
+  }
+
+#if DCHECK_IS_ON()
+  AbstractPromise::Executor::ArgumentPassingType ResolveArgumentPassingType()
+      const {
+    return common_.resolve_callback_.is_null()
+               ? AbstractPromise::Executor::ArgumentPassingType::kNoCallback
+               : CallbackTraits<ResolveOnceCallback>::argument_passing_type;
+  }
+
+  AbstractPromise::Executor::ArgumentPassingType RejectArgumentPassingType()
+      const {
+    return common_.reject_callback_.is_null()
+               ? AbstractPromise::Executor::ArgumentPassingType::kNoCallback
+               : CallbackTraits<RejectOnceCallback>::argument_passing_type;
+  }
+
+  bool CanResolve() const {
+    return (!common_.resolve_callback_.is_null() &&
+            PromiseCallbackTraits<ResolveReturnT>::could_resolve) ||
+           (!common_.reject_callback_.is_null() &&
+            PromiseCallbackTraits<RejectReturnT>::could_resolve);
+  }
+
+  bool CanReject() const {
+    return (!common_.resolve_callback_.is_null() &&
+            PromiseCallbackTraits<ResolveReturnT>::could_reject) ||
+           (!common_.reject_callback_.is_null() &&
+            PromiseCallbackTraits<RejectReturnT>::could_reject);
+  }
+#endif
+
+ private:
+  static void ExecuteThen(AbstractPromise* prerequisite,
+                          AbstractPromise* promise,
+                          ThenAndCatchExecutorCommon* common) {
+    ExecuteThenInternal(prerequisite, promise, common,
+                        PrerequisiteCouldResolve());
+  }
+
+  static void ExecuteCatch(AbstractPromise* prerequisite,
+                           AbstractPromise* promise,
+                           ThenAndCatchExecutorCommon* common) {
+    ExecuteCatchInternal(prerequisite, promise, common,
+                         PrerequisiteCouldReject());
+  }
+
+  static void ExecuteThenInternal(AbstractPromise* prerequisite,
+                                  AbstractPromise* promise,
+                                  ThenAndCatchExecutorCommon* common,
+                                  std::true_type can_resolve) {
+    ResolveOnceCallback* resolve_callback =
+        static_cast<ResolveOnceCallback*>(&common->resolve_callback_);
+    RunHelper<ResolveOnceCallback, Resolved<ArgResolve>, ResolveStorage,
+              RejectStorage>::Run(std::move(*resolve_callback), prerequisite,
+                                  promise);
+
+    using CheckResultTagType = typename CheckResultHelper<
+        PromiseCallbackTraits<ResolveReturnT>::could_resolve,
+        PromiseCallbackTraits<ResolveReturnT>::could_reject>::TagType;
+
+    CheckResultType(promise, CheckResultTagType());
+  }
+
+  static void ExecuteThenInternal(AbstractPromise* prerequisite,
+                                  AbstractPromise* promise,
+                                  ThenAndCatchExecutorCommon* common,
+                                  std::false_type can_resolve) {
+    // |prerequisite| can't resolve so don't generate dead code.
+  }
+
+  static void ExecuteCatchInternal(AbstractPromise* prerequisite,
+                                   AbstractPromise* promise,
+                                   ThenAndCatchExecutorCommon* common,
+                                   std::true_type can_reject) {
+    RejectOnceCallback* reject_callback =
+        static_cast<RejectOnceCallback*>(&common->reject_callback_);
+    RunHelper<RejectOnceCallback, Rejected<ArgReject>, ResolveStorage,
+              RejectStorage>::Run(std::move(*reject_callback), prerequisite,
+                                  promise);
+
+    using CheckResultTagType = typename CheckResultHelper<
+        PromiseCallbackTraits<RejectReturnT>::could_resolve,
+        PromiseCallbackTraits<RejectReturnT>::could_reject>::TagType;
+
+    CheckResultType(promise, CheckResultTagType());
+  }
+
+  static void ExecuteCatchInternal(AbstractPromise* prerequisite,
+                                   AbstractPromise* promise,
+                                   ThenAndCatchExecutorCommon* common,
+                                   std::false_type can_reject) {
+    // |prerequisite| can't reject so don't generate dead code.
+  }
+
+  static void CheckResultType(AbstractPromise* promise, CouldResolveOrReject) {
+    if (promise->IsResolvedWithPromise() ||
+        promise->value().type() == TypeId::From<ResolveStorage>()) {
+      promise->OnResolved();
+    } else {
+      DCHECK_EQ(promise->value().type(), TypeId::From<RejectStorage>())
+          << " See " << promise->from_here().ToString();
+      promise->OnRejected();
+    }
+  }
+
+  static void CheckResultType(AbstractPromise* promise, CanOnlyResolve) {
+    promise->OnResolved();
+  }
+
+  static void CheckResultType(AbstractPromise* promise, CanOnlyReject) {
+    promise->OnRejected();
+  }
+
+  ThenAndCatchExecutorCommon common_;
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
diff --git a/base/task_runner.cc b/base/task_runner.cc
index 0bd9ba99..d8644a9 100644
--- a/base/task_runner.cc
+++ b/base/task_runner.cc
@@ -40,8 +40,40 @@
   return destination_->PostTask(from_here, std::move(task));
 }
 
+// TODO(alexclarke): Remove this when TaskRunner::PostPromiseInternal becomes
+// pure virtual.
+class PromiseHolder {
+ public:
+  explicit PromiseHolder(scoped_refptr<internal::AbstractPromise> promise)
+      : promise_(std::move(promise)) {}
+
+  ~PromiseHolder() {
+    // Detect if the promise was not executed and if so cancel to ensure memory
+    // is released.
+    if (promise_)
+      promise_->OnCanceled();
+  }
+
+  PromiseHolder(PromiseHolder&& other) : promise_(std::move(other.promise_)) {}
+
+  scoped_refptr<internal::AbstractPromise> Unwrap() const {
+    return std::move(promise_);
+  }
+
+ private:
+  mutable scoped_refptr<internal::AbstractPromise> promise_;
+};
+
 }  // namespace
 
+template <>
+struct BindUnwrapTraits<PromiseHolder> {
+  static scoped_refptr<internal::AbstractPromise> Unwrap(
+      const PromiseHolder& o) {
+    return o.Unwrap();
+  }
+};
+
 bool TaskRunner::PostTask(const Location& from_here, OnceClosure task) {
   return PostDelayedTask(from_here, std::move(task), base::TimeDelta());
 }
@@ -58,7 +90,8 @@
     base::TimeDelta delay) {
   return PostDelayedTask(
       promise->from_here(),
-      BindOnce(&internal::AbstractPromise::Execute, std::move(promise)), delay);
+      BindOnce(&internal::AbstractPromise::Execute, PromiseHolder(promise)),
+      delay);
 }
 
 TaskRunner::TaskRunner() = default;
diff --git a/base/task_runner.h b/base/task_runner.h
index 468e1383..471f23cd 100644
--- a/base/task_runner.h
+++ b/base/task_runner.h
@@ -22,7 +22,7 @@
 struct TaskRunnerTraits;
 
 // A TaskRunner is an object that runs posted tasks (in the form of
-// Closure objects).  The TaskRunner interface provides a way of
+// OnceClosure objects).  The TaskRunner interface provides a way of
 // decoupling task posting from the mechanics of how each task will be
 // run.  TaskRunner provides very weak guarantees as to how posted
 // tasks are run (or if they're run at all).  In particular, it only
@@ -97,8 +97,8 @@
   // |task| and |reply| are guaranteed to be deleted on the thread
   // from which PostTaskAndReply() is invoked.  This allows objects
   // that must be deleted on the originating thread to be bound into
-  // the |task| and |reply| Closures.  In particular, it can be useful
-  // to use WeakPtr<> in the |reply| Closure so that the reply
+  // the |task| and |reply| OnceClosures.  In particular, it can be useful
+  // to use WeakPtr<> in the |reply| OnceClosure so that the reply
   // operation can be canceled. See the following pseudo-code:
   //
   // class DataBuffer : public RefCountedThreadSafe<DataBuffer> {
@@ -115,8 +115,8 @@
   //      scoped_refptr<DataBuffer> buffer = new DataBuffer();
   //      target_thread_.task_runner()->PostTaskAndReply(
   //          FROM_HERE,
-  //          base::Bind(&DataBuffer::AddData, buffer),
-  //          base::Bind(&DataLoader::OnDataReceived, AsWeakPtr(), buffer));
+  //          base::BindOnce(&DataBuffer::AddData, buffer),
+  //          base::BindOnce(&DataLoader::OnDataReceived, AsWeakPtr(), buffer));
   //    }
   //
   //  private:
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
index 4a11bb2..046e8e8 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
@@ -24,11 +24,9 @@
 import dalvik.system.DexFile;
 
 import org.chromium.base.BuildConfig;
-import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.annotations.MainDex;
 import org.chromium.base.multidex.ChromiumMultiDexInstaller;
-import org.chromium.base.test.util.InMemorySharedPreferencesContext;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
@@ -77,14 +75,12 @@
     private static final String ARGUMENT_LOG_ONLY = "log";
 
     private static final String TAG = "BaseJUnitRunner";
-    static InMemorySharedPreferencesContext sInMemorySharedPreferencesContext;
 
     @Override
     public Application newApplication(ClassLoader cl, String className, Context context)
             throws ClassNotFoundException, IllegalAccessException, InstantiationException {
-        Context targetContext = super.getTargetContext();
         boolean hasUnderTestApk =
-                !getContext().getPackageName().equals(targetContext.getPackageName());
+                !getContext().getPackageName().equals(getTargetContext().getPackageName());
         // When there is an under-test APK, BuildConfig belongs to it and does not indicate whether
         // the test apk is multidex. In this case, just assume it is.
         boolean isTestMultidex = hasUnderTestApk || BuildConfig.IS_MULTIDEX_ENABLED;
@@ -93,28 +89,14 @@
                 // Need hacks to have multidex work when there is an under-test apk :(.
                 ChromiumMultiDexInstaller.install(
                         new BaseChromiumRunnerCommon.MultiDexContextWrapper(
-                                getContext(), targetContext));
-                BaseChromiumRunnerCommon.reorderDexPathElements(cl, getContext(), targetContext);
+                                getContext(), getTargetContext()));
+                BaseChromiumRunnerCommon.reorderDexPathElements(
+                        cl, getContext(), getTargetContext());
             } else {
                 ChromiumMultiDexInstaller.install(getContext());
             }
         }
-
-        // We would ideally be able to wrap |context| here, and pass that to newApplication.
-        // However, there is framework code that assumes Application.getBaseContext() can be
-        // casted to ContextImpl (on KitKat for broadcast receivers. Refer to ActivityThread.java).
-        Application ret = super.newApplication(cl, className, context);
-        sInMemorySharedPreferencesContext = new InMemorySharedPreferencesContext(ret);
-        // Set this as early as possible since Application start-up could access prefs.
-        ContextUtils.initApplicationContextForTests(sInMemorySharedPreferencesContext);
-        return ret;
-    }
-
-    @Override
-    public Context getTargetContext() {
-        // The target context by default points directly at the ContextImpl, which we can't wrap.
-        // Make it instead point at the Application.
-        return sInMemorySharedPreferencesContext;
+        return super.newApplication(cl, className, context);
     }
 
     /**
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
index 4fa14c6..81579c7 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
@@ -4,7 +4,8 @@
 
 package org.chromium.base.test;
 
-import android.app.Application;
+import static org.chromium.base.test.BaseChromiumAndroidJUnitRunner.shouldListTests;
+
 import android.content.Context;
 import android.support.annotation.CallSuper;
 import android.support.test.InstrumentationRegistry;
@@ -12,7 +13,6 @@
 import android.support.test.internal.util.AndroidRunnerParams;
 
 import org.junit.rules.MethodRule;
-import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runner.notification.RunNotifier;
@@ -21,6 +21,7 @@
 import org.junit.runners.model.Statement;
 
 import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.test.BaseTestResult.PreTestHook;
 import org.chromium.base.test.params.MethodParamAnnotationRule;
@@ -87,11 +88,6 @@
                 new AndroidRunnerParams(InstrumentationRegistry.getInstrumentation(),
                         InstrumentationRegistry.getArguments(), false, 0L, false));
 
-        assert InstrumentationRegistry.getInstrumentation()
-                        instanceof BaseChromiumAndroidJUnitRunner
-            : "Must use BaseChromiumAndroidJUnitRunner instrumentation with "
-              + "BaseJUnit4ClassRunner, but found: "
-              + InstrumentationRegistry.getInstrumentation().getClass();
         String traceOutput = InstrumentationRegistry.getArguments().getString(EXTRA_TRACE_FILE);
 
         if (traceOutput != null) {
@@ -106,12 +102,6 @@
         }
     }
 
-    /** Returns the singleton Application instance. */
-    public static Application getApplication() {
-        return (Application)
-                BaseChromiumAndroidJUnitRunner.sInMemorySharedPreferencesContext.getBaseContext();
-    }
-
     /**
      * Merge two List into a new ArrayList.
      *
@@ -184,14 +174,7 @@
      */
     @CallSuper
     protected List<TestRule> getDefaultTestRules() {
-        // Order is important here. Outer rule setUp's run first, and tearDown's run last.
-        // Base setUp() should go first to initialize ContextUtils and clear out prefs.
-        // Base's tearDown() should come last since it deletes files.
-        // Activities must be destroyed before lifetimes are checked, so DestroyActivitiesRule()
-        // must come last so that its tearDown() runs before LifetimeAssertRule's.
-        return Collections.singletonList(RuleChain.outerRule(new BaseJUnit4TestRule())
-                                                 .around(new LifetimeAssertRule())
-                                                 .around(new DestroyActivitiesRule()));
+        return Arrays.asList(new DestroyActivitiesRule(), new LifetimeAssertRule());
     }
 
     /**
@@ -221,8 +204,12 @@
      */
     @Override
     public void run(RunNotifier notifier) {
-        if (BaseChromiumAndroidJUnitRunner.shouldListTests(
-                    InstrumentationRegistry.getArguments())) {
+        // Most tests have an Application subclass that already call this.
+        if (ContextUtils.getApplicationContext() == null) {
+            ContextUtils.initApplicationContext(
+                    InstrumentationRegistry.getTargetContext().getApplicationContext());
+        }
+        if (shouldListTests(InstrumentationRegistry.getArguments())) {
             for (Description child : getDescription().getChildren()) {
                 notifier.fireTestStarted(child);
                 notifier.fireTestFinished(child);
@@ -290,4 +277,13 @@
     protected Statement withAfters(FrameworkMethod method, Object test, Statement base) {
         return super.withAfters(method, test, new ScreenshotOnFailureStatement(base));
     }
+
+    @Override
+    protected List<TestRule> classRules() {
+        List<TestRule> result = super.classRules();
+        // Class rules are the outermost TestRules, so CommitSharedPreferencesTestRule will commit
+        // SharedPreferences after all other rules have finished writing them.
+        result.add(new CommitSharedPreferencesTestRule());
+        return result;
+    }
 }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4TestRule.java b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4TestRule.java
deleted file mode 100644
index 1b736717..0000000
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4TestRule.java
+++ /dev/null
@@ -1,75 +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.
-
-package org.chromium.base.test;
-
-import android.support.test.InstrumentationRegistry;
-import android.support.v4.content.ContextCompat;
-import android.text.TextUtils;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.test.util.InMemorySharedPreferencesContext;
-
-import java.io.File;
-import java.util.ArrayList;
-
-/**
- * Holds setUp / tearDown logic common to all instrumentation tests.
- */
-class BaseJUnit4TestRule implements TestRule {
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                // Don't tests if there are prior on-disk shared prefs lying around.
-                checkOrDeleteOnDiskSharedPreferences(false);
-
-                InMemorySharedPreferencesContext context =
-                        BaseChromiumAndroidJUnitRunner.sInMemorySharedPreferencesContext;
-                // Reset Application context in case any tests have replaced it.
-                ContextUtils.initApplicationContextForTests(context);
-                // Ensure all tests start with empty (InMemory)SharedPreferences.
-                context.clearSharedPreferences();
-
-                base.evaluate();
-
-                // Do not use try/finally so that preferences asserts do not mask prior exceptions.
-                checkOrDeleteOnDiskSharedPreferences(true);
-            }
-        };
-    }
-
-    private void checkOrDeleteOnDiskSharedPreferences(boolean check) {
-        File dataDir = ContextCompat.getDataDir(InstrumentationRegistry.getTargetContext());
-        File prefsDir = new File(dataDir, "shared_prefs");
-        File[] files = prefsDir.listFiles();
-        if (files == null) {
-            return;
-        }
-        ArrayList<File> badFiles = new ArrayList<>();
-        for (File f : files) {
-            if (!f.getName().endsWith("multidex.version.xml")) {
-                if (check) {
-                    badFiles.add(f);
-                } else {
-                    f.delete();
-                }
-            }
-        }
-        if (!badFiles.isEmpty()) {
-            throw new AssertionError("Found shared prefs file(s).\n"
-                    + "Code should use ContextUtils.getApplicationContext() when accessing "
-                    + "SharedPreferences so that tests are hooked to use InMemorySharedPreferences."
-                    + " This could also mean needing to override getSharedPreferences() on custom "
-                    + " Context subclasses (e.g. ChromeBaseAppCompatActivity does this to make "
-                    + "Preferences screens work).\n"
-                    + "Files:\n * " + TextUtils.join("\n * ", badFiles));
-        }
-    }
-}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/CommitSharedPreferencesTestRule.java b/base/test/android/javatests/src/org/chromium/base/test/CommitSharedPreferencesTestRule.java
new file mode 100644
index 0000000..05203e0
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/CommitSharedPreferencesTestRule.java
@@ -0,0 +1,33 @@
+// 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.
+
+package org.chromium.base.test;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.ContextUtils;
+
+class CommitSharedPreferencesTestRule implements TestRule {
+    @Override
+    public Statement apply(Statement statement, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                // Clear app's SharedPreferences before each test to reduce flakiness.
+                // See https://crbug.com/908174, ttps://crbug.com/902774.
+                ContextUtils.getAppSharedPreferences().edit().clear().commit();
+                try {
+                    statement.evaluate();
+                } finally {
+                    // Some disk writes to update SharedPreferences may still be in progress if
+                    // apply() was used after editing. Commit these changes to SharedPreferences
+                    // before reporting the test as finished. See https://crbug.com/916717.
+                    ContextUtils.getAppSharedPreferences().edit().commit();
+                }
+            }
+        };
+    }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/LifetimeAssertRule.java b/base/test/android/javatests/src/org/chromium/base/test/LifetimeAssertRule.java
index 8784470..aeb3bda 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/LifetimeAssertRule.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/LifetimeAssertRule.java
@@ -4,25 +4,16 @@
 
 package org.chromium.base.test;
 
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
+import org.junit.rules.ExternalResource;
 
 import org.chromium.base.LifetimeAssert;
 
 /**
  * Ensures that all object instances that use LifetimeAssert are destroyed.
  */
-public class LifetimeAssertRule implements TestRule {
+public class LifetimeAssertRule extends ExternalResource {
     @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-                // Do not use try/finally so that lifetime asserts do not mask prior exceptions.
-                LifetimeAssert.assertAllInstancesDestroyedForTesting();
-            }
-        };
+    protected void after() {
+        LifetimeAssert.assertAllInstancesDestroyedForTesting();
     }
 }
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 4c83ff7..ad0e52fd 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -34,8 +34,8 @@
   "junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityVerifierTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImplTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/EventOffsetHandlerTest.java",
+  "junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandlerTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimatorTest.java",
-  "junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutUpdateHost.java",
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java",
   "junit/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulatorTest.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 287b27c3..87e52a9 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -213,11 +213,13 @@
   "javatests/src/org/chromium/chrome/browser/metrics/UkmTest.java",
   "javatests/src/org/chromium/chrome/browser/modaldialog/AppModalPresenterTest.java",
   "javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java",
+  "javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java",
   "javatests/src/org/chromium/chrome/browser/modaldialog/TabModalPresenterTest.java",
   "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java",
   "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java",
   "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java",
+  "javatests/src/org/chromium/chrome/browser/night_mode/NightModeTestUtils.java",
   "javatests/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilderTest.java",
   "javatests/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilderTest.java",
   "javatests/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java",
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java
index 1c3d82a9..6e48b4e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java
@@ -10,12 +10,15 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_INCOGNITO;
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_VISIBLE;
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_CONTROLS_HEIGHT;
+import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_PADDING;
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER;
 
 import android.support.annotation.Nullable;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.ObserverList;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeController;
@@ -33,6 +36,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
 import org.chromium.chrome.browser.tasks.tabgroup.TabGroupModelFilter;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -50,6 +54,8 @@
     // to be on the 2nd row.
     static final int INITIAL_SCROLL_INDEX_OFFSET = 2;
 
+    private static final int DEFAULT_TOP_PADDING = 0;
+
     private final ResetHandler mResetHandler;
     private final PropertyModel mContainerViewModel;
     private final TabModelSelector mTabModelSelector;
@@ -152,6 +158,11 @@
         mContainerViewModel.set(TOP_CONTROLS_HEIGHT, fullscreenManager.getTopControlsHeight());
         mContainerViewModel.set(
                 BOTTOM_CONTROLS_HEIGHT, fullscreenManager.getBottomControlsHeight());
+        int topPadding = ReturnToChromeExperimentsUtil.shouldShowOmniboxOnTabSwitcher()
+                ? ContextUtils.getApplicationContext().getResources().getDimensionPixelSize(
+                        R.dimen.toolbar_height_no_shadow)
+                : DEFAULT_TOP_PADDING;
+        mContainerViewModel.set(TOP_PADDING, topPadding);
 
         mCompositorViewHolder = compositorViewHolder;
         mTabGridDialogResetHandler = tabGridDialogResetHandler;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java
index 66fe798..0bc00b5 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java
@@ -10,6 +10,7 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_INCOGNITO;
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_VISIBLE;
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_CONTROLS_HEIGHT;
+import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_PADDING;
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER;
 
 import android.support.v7.widget.GridLayoutManager;
@@ -54,6 +55,9 @@
             FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams();
             params.bottomMargin = model.get(BOTTOM_CONTROLS_HEIGHT);
             view.requestLayout();
+        } else if (TOP_PADDING == propertyKey) {
+            view.setPadding(view.getPaddingLeft(), model.get(TOP_PADDING), view.getPaddingRight(),
+                    view.getPaddingBottom());
         }
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerProperties.java
index bbbce8d3..42be560 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerProperties.java
@@ -30,7 +30,10 @@
     public static final PropertyModel.WritableIntPropertyKey BOTTOM_CONTROLS_HEIGHT =
             new PropertyModel.WritableIntPropertyKey();
 
-    public static final PropertyKey[] ALL_KEYS =
-            new PropertyKey[] {IS_VISIBLE, IS_INCOGNITO, VISIBILITY_LISTENER, INITIAL_SCROLL_INDEX,
-                    ANIMATE_VISIBILITY_CHANGES, TOP_CONTROLS_HEIGHT, BOTTOM_CONTROLS_HEIGHT};
+    public static final PropertyModel.WritableIntPropertyKey TOP_PADDING =
+            new PropertyModel.WritableIntPropertyKey();
+
+    public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {IS_VISIBLE, IS_INCOGNITO,
+            VISIBILITY_LISTENER, INITIAL_SCROLL_INDEX, ANIMATE_VISIBILITY_CHANGES,
+            TOP_CONTROLS_HEIGHT, BOTTOM_CONTROLS_HEIGHT, TOP_PADDING};
 }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java
index f0d4773f..06e750f 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java
@@ -37,6 +37,7 @@
 import org.chromium.base.UserDataHost;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
@@ -49,6 +50,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
+import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.testing.local.LocalRobolectricTestRunner;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -62,6 +64,7 @@
  */
 @RunWith(LocalRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@Features.DisableFeatures(ChromeFeatureList.TAB_SWITCHER_ON_RETURN)
 public class GridTabSwitcherMediatorUnitTest {
     private static final int CONTROL_HEIGHT_DEFAULT = 56;
     private static final int CONTROL_HEIGHT_MODIFIED = 0;
diff --git a/chrome/android/java/res/layout/bottom_control_container.xml b/chrome/android/java/res/layout/bottom_control_container.xml
index e5189e5..03f19cff 100644
--- a/chrome/android/java/res/layout/bottom_control_container.xml
+++ b/chrome/android/java/res/layout/bottom_control_container.xml
@@ -13,7 +13,7 @@
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:id="@+id/bottom_controls_wrapper"
-        android:layout_height="@dimen/bottom_toolbar_height_with_shadow" >
+        android:layout_height="wrap_content" >
 
         <ImageView
             android:id="@+id/bottom_container_top_shadow"
@@ -27,12 +27,12 @@
         <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:id="@+id/bottom_container_slot">
+            android:id="@+id/bottom_container_slot" >
 
             <ViewStub
                 android:id="@+id/bottom_toolbar_stub"
                 android:layout_width="match_parent"
-                android:layout_height="@dimen/bottom_toolbar_height"
+                android:layout_height="match_parent"
                 android:layout_gravity="start|bottom"
                 android:inflatedId="@+id/bottom_toolbar"
                 android:layout="@layout/bottom_toolbar" />
diff --git a/chrome/android/java/res/layout/bottom_toolbar.xml b/chrome/android/java/res/layout/bottom_toolbar.xml
index 9921d3d..df816b8 100644
--- a/chrome/android/java/res/layout/bottom_toolbar.xml
+++ b/chrome/android/java/res/layout/bottom_toolbar.xml
@@ -6,7 +6,7 @@
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/bottom_toolbar_height" >
+    android:layout_height="match_parent" >
 
   <include layout="@layout/bottom_toolbar_browsing" />
 
diff --git a/chrome/android/java/res/layout/bottom_toolbar_browsing.xml b/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
index 570e943..86c535d 100644
--- a/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
+++ b/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
@@ -3,25 +3,22 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<!-- The toolbar is wrapped in a FrameLayout that matches the parent container's height so that
-     background color fills the entire parent. The LinearLayout then has a specific height and top
-     gravity so that items are anchored to the top of the parent container when the parent container
-     is bigger than the LinearLayout. -->
-<FrameLayout
+
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/bottom_toolbar_browsing"
+    android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/toolbar_background_primary">
+    android:background="@color/toolbar_background_primary"
+    android:layout_gravity="top|center_horizontal"
+    android:paddingStart="@dimen/bottom_toolbar_padding"
+    android:paddingEnd="@dimen/bottom_toolbar_padding" >
 
     <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/bottom_toolbar_height"
-        android:layout_gravity="top|center_horizontal"
-        android:orientation="horizontal"
-        android:paddingStart="@dimen/bottom_toolbar_start_padding"
-        android:paddingEnd="@dimen/bottom_toolbar_end_padding" >
+        android:id="@+id/home_button_wrapper"
+        style="@style/BottomToolbarButtonWrapper" >
 
         <org.chromium.chrome.browser.toolbar.HomeButton
             android:id="@+id/home_button"
@@ -29,16 +26,45 @@
             style="@style/BottomToolbarButton"
             android:contentDescription="@string/accessibility_toolbar_btn_home" />
 
-        <include layout="@layout/toolbar_space" />
+        <TextView
+            android:id="@+id/home_button_label"
+            style="@style/BottomToolbarLabel"
+            android:text="@string/accessibility_toolbar_btn_home" />
+
+    </LinearLayout>
+
+    <include layout="@layout/toolbar_space" />
+
+    <LinearLayout
+        android:id="@+id/share_button_wrapper"
+        style="@style/BottomToolbarButtonWrapper">
 
         <org.chromium.chrome.browser.toolbar.bottom.ShareButton
             android:id="@+id/share_button"
-            style="@style/BottomToolbarButton"
             android:src="@drawable/ic_share_white_24dp"
             app:tint="@color/standard_mode_tint"
+            style="@style/BottomToolbarButton"
             android:contentDescription="@string/share" />
 
-        <include layout="@layout/toolbar_space" />
+        <TextView
+            android:id="@+id/share_button_label"
+            style="@style/BottomToolbarLabel"
+            android:text="@string/share" />
+
+    </LinearLayout>
+
+    <include layout="@layout/toolbar_space" />
+
+    <LinearLayout
+        android:layout_width="72dp"
+        android:layout_height="wrap_content"
+        android:paddingStart="4dp"
+        android:paddingEnd="4dp"
+        android:orientation="vertical"
+        android:id="@+id/search_accelerator_wrapper"
+        android:background="?attr/selectableItemBackground"
+        android:layout_gravity="top"
+        android:layout_marginTop="6dp" >
 
         <org.chromium.chrome.browser.toolbar.bottom.SearchAccelerator
             android:id="@+id/search_accelerator"
@@ -48,19 +74,49 @@
             android:paddingTop="@dimen/search_accelerator_height_padding"
             android:paddingBottom="@dimen/search_accelerator_height_padding"
             android:src="@drawable/ic_search"
+            android:clickable="false"
             android:contentDescription="@string/accessibility_toolbar_btn_search_accelerator" />
 
-        <include layout="@layout/toolbar_space" />
+        <TextView
+            android:id="@+id/search_accelerator_label"
+            style="@style/BottomToolbarLabel"
+            android:paddingTop="0dp"
+            android:text="@string/accessibility_toolbar_btn_search_accelerator" />
+
+    </LinearLayout>
+
+    <include layout="@layout/toolbar_space" />
+
+    <LinearLayout
+        android:id="@+id/tab_switcher_button_wrapper"
+        style="@style/BottomToolbarButtonWrapper" >
 
         <org.chromium.chrome.browser.toolbar.TabSwitcherButtonView
             android:id="@+id/tab_switcher_button"
             style="@style/BottomToolbarButton"
             android:contentDescription="@string/accessibility_toolbar_btn_tabswitcher_toggle_default" />
 
-        <include layout="@layout/toolbar_space" />
-
-        <include layout="@layout/menu_button" />
+        <TextView
+            android:id="@+id/tab_switcher_button_label"
+            style="@style/BottomToolbarLabel"
+            android:text="@string/tab_switcher_button_label" />
 
     </LinearLayout>
-</FrameLayout>
 
+    <include layout="@layout/toolbar_space" />
+
+    <LinearLayout
+        android:id="@+id/labeled_menu_button_wrapper"
+        style="@style/BottomToolbarButtonWrapper" >
+
+        <include layout="@layout/bottom_toolbar_menu_button"
+            style="@style/BottomToolbarButton" />
+
+        <TextView
+            android:id="@+id/menu_button_label"
+            style="@style/BottomToolbarLabel"
+            android:text="@string/more" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/chrome/android/java/res/layout/bottom_toolbar_menu_button.xml b/chrome/android/java/res/layout/bottom_toolbar_menu_button.xml
new file mode 100644
index 0000000..f3f94e8
--- /dev/null
+++ b/chrome/android/java/res/layout/bottom_toolbar_menu_button.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+<org.chromium.chrome.browser.toolbar.MenuButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:id="@+id/menu_button_wrapper">
+
+    <org.chromium.ui.widget.ChromeImageButton
+        android:id="@+id/menu_button"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:background="@null"
+        android:src="@drawable/ic_more_vert_black_24dp"
+        android:contentDescription="@string/accessibility_toolbar_btn_menu"
+        android:layout_gravity="center"
+        app:tint="@color/standard_mode_tint" />
+
+    <ImageView
+        android:id="@+id/menu_badge"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:background="@null"
+        android:src="@drawable/badge_update_dark"
+        tools:ignore="ContentDescription"
+        android:importantForAccessibility="no"
+        android:layout_gravity="center"
+        android:visibility="invisible" />
+
+</org.chromium.chrome.browser.toolbar.MenuButton>
+
diff --git a/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml b/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml
index d7c9630..fd86594 100644
--- a/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml
+++ b/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml
@@ -22,31 +22,61 @@
         <LinearLayout
             android:orientation="horizontal"
             android:layout_width="match_parent"
-            android:layout_height="@dimen/bottom_toolbar_height"
-            android:paddingStart="@dimen/bottom_toolbar_start_padding"
-            android:paddingEnd="@dimen/bottom_toolbar_end_padding" >
+            android:layout_height="match_parent"
+            android:paddingStart="@dimen/bottom_toolbar_padding"
+            android:paddingEnd="@dimen/bottom_toolbar_padding" >
 
             <org.chromium.chrome.browser.toolbar.bottom.CloseAllTabsButton
                 android:id="@+id/close_all_tabs_button"
-                style="@style/BottomToolbarButton"
+                style="@style/BottomToolbarButtonWrapper"
                 android:src="@drawable/ic_close_all_tabs"
                 android:contentDescription="@string/accessibility_toolbar_btn_close_all_tabs"
                 app:tint="@color/standard_mode_tint" />
 
             <include layout="@layout/toolbar_space" />
 
-            <org.chromium.chrome.browser.toolbar.bottom.BottomToolbarNewTabButton
-                android:id="@+id/tab_switcher_new_tab_button"
-                android:layout_width="@dimen/search_accelerator_height"
-                android:layout_height="@dimen/search_accelerator_height"
-                android:layout_gravity="center"
-                android:paddingTop="@dimen/search_accelerator_height_padding"
-                android:paddingBottom="@dimen/search_accelerator_height_padding"
-                android:contentDescription="@string/accessibility_toolbar_btn_new_tab" />
+            <LinearLayout
+                android:layout_width="72dp"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:id="@+id/new_tab_button_wrapper"
+                android:background="?attr/selectableItemBackground"
+                android:layout_gravity="top"
+                android:layout_marginTop="6dp" >
+
+                <org.chromium.chrome.browser.toolbar.bottom.BottomToolbarNewTabButton
+                    android:id="@+id/tab_switcher_new_tab_button"
+                    android:layout_width="@dimen/search_accelerator_height"
+                    android:layout_height="@dimen/search_accelerator_height"
+                    android:layout_gravity="center"
+                    android:paddingTop="@dimen/search_accelerator_height_padding"
+                    android:paddingBottom="@dimen/search_accelerator_height_padding"
+                    android:clickable="false"
+                    android:contentDescription="@string/accessibility_toolbar_btn_new_tab" />
+
+                <TextView
+                    android:id="@+id/new_tab_button_label"
+                    style="@style/BottomToolbarLabel"
+                    android:paddingTop="0dp"
+                    android:text="@string/accessibility_toolbar_btn_new_tab" />
+
+            </LinearLayout>
 
             <include layout="@layout/toolbar_space" />
 
-            <include layout="@layout/menu_button" />
+            <LinearLayout
+                android:id="@+id/labeled_menu_button_wrapper"
+                style="@style/BottomToolbarButtonWrapper" >
+
+                <include layout="@layout/bottom_toolbar_menu_button"
+                    style="@style/BottomToolbarButton" />
+
+                <TextView
+                    android:id="@+id/menu_button_label"
+                    style="@style/BottomToolbarLabel"
+                    android:text="@string/more" />
+
+            </LinearLayout>
 
         </LinearLayout>
     </FrameLayout>
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index f5be155..16aa41a 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -637,9 +637,36 @@
         <item name="android:layout_width">43dp</item>
         <item name="android:paddingEnd">3.5dp</item>
     </style>
-    <style name="BottomToolbarButton" parent="ToolbarButton">
-        <item name="android:layout_height">48dp</item>
+    <style name="BottomToolbarButton">
+        <item name="android:layout_height">24dp</item>
+        <item name="android:layout_width">24dp</item>
         <item name="android:layout_gravity">center</item>
+        <item name="android:background">@android:color/transparent</item>
+        <item name="android:clickable">false</item>
+    </style>
+    <style name="TextAppearance.BottomToolbarLabel" parent="TextAppearance.RobotoMediumStyle">
+        <item name="android:textColor">@color/default_text_color_dark_secondary</item>
+        <item name="android:textSize">12sp</item>
+    </style>
+    <style name="BottomToolbarButtonWrapper">
+        <item name="android:layout_width">56dp</item>
+        <item name="android:paddingStart">4dp</item>
+        <item name="android:paddingEnd">4dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:gravity">center</item>
+        <item name="android:orientation">vertical</item>
+        <item name="android:background">?attr/selectableItemBackground</item>
+    </style>
+    <style name="BottomToolbarLabel">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingTop">7dp</item>
+        <item name="android:textAppearance">@style/TextAppearance.BottomToolbarLabel</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:maxLines">1</item>
+        <item name="android:gravity">center_horizontal</item>
+        <item name="android:visibility">gone</item>
     </style>
     <style name="TabBarShadow">
         <item name="android:layout_width">match_parent</item>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 15fd4b6..801e51a 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -272,8 +272,9 @@
     <!-- Bottom toolbar dimensions -->
     <dimen name="bottom_toolbar_height">@dimen/min_touch_target_size</dimen>
     <dimen name="bottom_toolbar_height_with_shadow">56dp</dimen>
-    <dimen name="bottom_toolbar_start_padding">12dp</dimen>
-    <dimen name="bottom_toolbar_end_padding">8dp</dimen>
+    <dimen name="labeled_bottom_toolbar_height">68dp</dimen>
+    <dimen name="labeled_bottom_toolbar_height_with_shadow">76dp</dimen>
+    <dimen name="bottom_toolbar_padding">12dp</dimen>
     <dimen name="search_accelerator_width">64dp</dimen>
     <dimen name="search_accelerator_height">36dp</dimen>
     <dimen name="search_accelerator_height_padding">6dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
index b5838f1..d9b28c33 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
@@ -13,7 +12,6 @@
 import android.support.annotation.StyleRes;
 import android.support.v7.app.AppCompatActivity;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.chrome.browser.night_mode.GlobalNightModeStateProviderHolder;
 import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
 import org.chromium.chrome.browser.night_mode.NightModeUtils;
@@ -107,12 +105,4 @@
     public void onNightModeStateChanged() {
         if (!isFinishing()) recreate();
     }
-
-    /**
-     * Required to make preference fragments use InMemorySharedPreferences in tests.
-     */
-    @Override
-    public SharedPreferences getSharedPreferences(String name, int mode) {
-        return ContextUtils.getApplicationContext().getSharedPreferences(name, mode);
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index d4f2487..194f531 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -194,6 +194,7 @@
     public static final String CHROME_DUET = "ChromeDuet";
     public static final String CHROME_DUET_ADAPTIVE = "ChromeDuetAdaptive";
     public static final String DONT_AUTO_HIDE_BROWSER_CONTROLS = "DontAutoHideBrowserControls";
+    public static final String CHROME_DUET_LABELED = "ChromeDuetLabeled";
     public static final String CHROME_SMART_SELECTION = "ChromeSmartSelection";
     public static final String CLEAR_OLD_BROWSING_DATA = "ClearOldBrowsingData";
     public static final String COMMAND_LINE_ON_NON_ROOTED = "CommandLineOnNonRooted";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java
index 887b8357..6f4bf90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandler.java
@@ -56,7 +56,7 @@
      * Add an animator to the list of known animators to start receiving updates.
      * @param animator The animator to start.
      */
-    public final void registerAndStartAnimator(final CompositorAnimator animator) {
+    final void registerAndStartAnimator(final CompositorAnimator animator) {
         // If animations are currently running, the last updated time is being updated. If not,
         // reset the value here. This prevents gaps in animations from breaking timing.
         if (getActiveAnimationCount() <= 0) mLastUpdateTimeMs = System.currentTimeMillis();
@@ -129,7 +129,7 @@
      * @return The number of animations that are active inside this handler.
      */
     @VisibleForTesting
-    public int getActiveAnimationCount() {
+    int getActiveAnimationCount() {
         return mAnimators.size();
     }
 
@@ -155,7 +155,7 @@
      * @return Whether update was successful or not.
      */
     @VisibleForTesting
-    public final boolean pushUpdateInTestingMode(long deltaTimeMs) {
+    final boolean pushUpdateInTestingMode(long deltaTimeMs) {
         return sIsInTestingMode ? pushUpdate(deltaTimeMs) : false;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java
index 63c2ff3e..edccdc5f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java
@@ -402,7 +402,8 @@
         assert mSceneLayer != null;
         // contentViewport is intentionally passed for both parameters below.
         mSceneLayer.pushLayers(getContext(), contentViewport, contentViewport, this,
-                layerTitleCache, tabContentManager, resourceManager, fullscreenManager);
+                layerTitleCache, tabContentManager, resourceManager, fullscreenManager,
+                SceneLayer.INVALID_RESOURCE_ID);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/SimpleAnimationLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/SimpleAnimationLayout.java
index 7cb8572..eaed96c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/SimpleAnimationLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/SimpleAnimationLayout.java
@@ -456,6 +456,7 @@
         assert mSceneLayer != null;
         // The content viewport is intentionally sent as both params below.
         mSceneLayer.pushLayers(getContext(), contentViewport, contentViewport, this,
-                layerTitleCache, tabContentManager, resourceManager, fullscreenManager);
+                layerTitleCache, tabContentManager, resourceManager, fullscreenManager,
+                SceneLayer.INVALID_RESOURCE_ID);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
index b4ec4f2..af41322 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
@@ -1596,7 +1596,8 @@
         assert mSceneLayer != null;
 
         mSceneLayer.pushLayers(getContext(), viewport, contentViewport, this, layerTitleCache,
-                tabContentManager, resourceManager, fullscreenManager);
+                tabContentManager, resourceManager, fullscreenManager,
+                SceneLayer.INVALID_RESOURCE_ID);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneLayer.java
index 082d698..a65cf167 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneLayer.java
@@ -12,6 +12,7 @@
  */
 @JNINamespace("android")
 public class SceneLayer {
+    public static final int INVALID_RESOURCE_ID = -1;
     private long mNativePtr;
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneOverlayLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneOverlayLayer.java
index 57c79a9..428dc852a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneOverlayLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/SceneOverlayLayer.java
@@ -8,7 +8,6 @@
  * An extension of SceneLayer for SceneOverlay.
  */
 public abstract class SceneOverlayLayer extends SceneLayer {
-    protected static final int INVALID_RESOURCE_ID = -1;
 
     /**
      * Sets a content tree inside this scene overlay tree.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabListSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabListSceneLayer.java
index 1ce4c5b..ae63775d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabListSceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabListSceneLayer.java
@@ -50,10 +50,13 @@
      * @param tabContentManager An object for accessing tab content.
      * @param resourceManager An object for accessing static and dynamic resources.
      * @param fullscreenManager The fullscreen manager for browser controls information.
+     * @param backgroundResourceId The resource ID for background. {@link #INVALID_RESOURCE_ID} if
+     *                             none. Only used in GridTabSwitcher.
      */
     public void pushLayers(Context context, RectF viewport, RectF contentViewport, Layout layout,
             LayerTitleCache layerTitleCache, TabContentManager tabContentManager,
-            ResourceManager resourceManager, ChromeFullscreenManager fullscreenManager) {
+            ResourceManager resourceManager, ChromeFullscreenManager fullscreenManager,
+            int backgroundResourceId) {
         if (mNativePtr == 0) return;
 
         Resources res = context.getResources();
@@ -68,6 +71,10 @@
         nativeUpdateLayer(mNativePtr, tabListBgColor, viewport.left, viewport.top, viewport.width(),
                 viewport.height(), layerTitleCache, tabContentManager, resourceManager);
 
+        if (backgroundResourceId != INVALID_RESOURCE_ID) {
+            nativePutBackgroundLayer(mNativePtr, backgroundResourceId);
+        }
+
         boolean isHTSEnabled =
                 ChromeFeatureList.isEnabled(ChromeFeatureList.HORIZONTAL_TAB_SWITCHER_ANDROID);
 
@@ -199,4 +206,6 @@
             int toolbarTextBoxResource, int toolbarTextBoxBackgroundColor,
             float toolbarTextBoxAlpha, float toolbarAlpha, float toolbarYOffset,
             float sideBorderScale, boolean insetVerticalBorder);
+
+    private native void nativePutBackgroundLayer(long nativeTabListSceneLayer, int resourceId);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index fa9fd26..f68bb6c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -51,6 +51,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
 import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.chrome.browser.toolbar.top.ToolbarActionModeCallback;
@@ -913,6 +914,8 @@
         // side is initialized
         assert mNativeInitialized : "Loading URL before native side initialized";
 
+        if (ReturnToChromeExperimentsUtil.willHandleLoadUrlFromLocationBar(url, transition)) return;
+
         if (currentTab != null
                 && (currentTab.isNativePage() || NewTabPage.isNTPUrl(currentTab.getUrl()))) {
             NewTabPageUma.recordOmniboxNavigation(url, transition);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
index c5a53834..35b6f542 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
@@ -206,6 +206,13 @@
     public static final String ADAPTIVE_TOOLBAR_ENABLED_KEY = "adaptive_toolbar_enabled";
 
     /**
+     * Whether or not the labeled bottom toolbar is enabled.
+     * Default value is false.
+     */
+    public static final String LABELED_BOTTOM_TOOLBAR_ENABLED_KEY =
+            "labeled_bottom_toolbar_enabled";
+
+    /**
      * Whether or not night mode is available.
      * Default value is false.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
index 0da17a0..28571b8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
@@ -49,6 +49,16 @@
     }
 
     @Override
+    public boolean isInOverview() {
+        return false;
+    }
+
+    @Override
+    public boolean shouldShowLocationBarInOverviewMode() {
+        return false;
+    }
+
+    @Override
     public Profile getProfile() {
         if (mTab == null) return null;
         return mTab.getProfile();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
index 27d2836e..a2dbe25 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
@@ -4,8 +4,19 @@
 
 package org.chromium.chrome.browser.tasks;
 
+import android.app.Activity;
+
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.locale.LocaleManager;
+import org.chromium.chrome.browser.tabmodel.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.util.FeatureUtilities;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.base.PageTransition;
 
 /**
  * This is a utility class for managing experiments related to returning to Chrome.
@@ -43,4 +54,50 @@
 
         return System.currentTimeMillis() > expirationTime;
     }
+
+    /**
+     * Whether we should display the omnibox on the tab switcher in addition to the
+     *  tab switcher toolbar.
+     *
+     * Depends on tab grid being enabled.
+     *
+     * @return true if the tab switcher on return and tab grid features are both ON, else false.
+     */
+    public static boolean shouldShowOmniboxOnTabSwitcher() {
+        return ChromeFeatureList.isInitialized()
+                && (FeatureUtilities.isGridTabSwitcherEnabled()
+                        || FeatureUtilities.isTabGroupsAndroidEnabled())
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_SWITCHER_ON_RETURN);
+    }
+
+    /**
+     * Check if we should handle the navigation. If so, create a new tab and load the URL.
+     *
+     * @param url The URL to load.
+     * @param transition The page transition type.
+     * @return true if we have handled the navigation, false otherwise.
+     */
+    public static boolean willHandleLoadUrlFromLocationBar(
+            String url, @PageTransition int transition) {
+        if (!shouldShowOmniboxOnTabSwitcher()) return false;
+
+        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+        if (!(activity instanceof ChromeActivity)) return false;
+
+        ChromeActivity chromeActivity = (ChromeActivity) activity;
+        if (!chromeActivity.isInOverviewMode()) return false;
+
+        // Create a new unparented tab.
+        TabModel model = ((ChromeActivity) activity).getCurrentTabModel();
+        LoadUrlParams params = new LoadUrlParams(url);
+        params.setTransitionType(transition | PageTransition.FROM_ADDRESS_BAR);
+        chromeActivity.getTabCreator(model.isIncognito())
+                .createNewTab(params, TabLaunchType.FROM_CHROME_UI, null);
+
+        // These are duplicated here but would have been recorded by LocationBarLayout#loadUrl.
+        RecordUserAction.record("MobileOmniboxUse");
+        LocaleManager.getInstance().recordLocaleBasedSearchMetrics(false, url, transition);
+
+        return true;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
index c3764512..b4e80a7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
@@ -14,6 +14,8 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
@@ -44,6 +46,12 @@
     /** The {@link ActivityTabProvider} used to know if the active tab is on the NTP. */
     private ActivityTabProvider mActivityTabProvider;
 
+    /** The home button text label. */
+    private TextView mLabel;
+
+    /** The wrapper View that contains the home button and the label. */
+    private View mWrapper;
+
     public HomeButton(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -59,6 +67,24 @@
         HomepageManager.getInstance().addListener(this);
     }
 
+    /**
+     * @param wrapper The wrapping View of this button.
+     */
+    public void setWrapperView(ViewGroup wrapper) {
+        mWrapper = wrapper;
+        mLabel = mWrapper.findViewById(R.id.home_button_label);
+        if (FeatureUtilities.isLabeledBottomToolbarEnabled()) mLabel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener listener) {
+        if (mWrapper != null) {
+            mWrapper.setOnClickListener(listener);
+        } else {
+            super.setOnClickListener(listener);
+        }
+    }
+
     public void destroy() {
         if (mThemeColorProvider != null) {
             mThemeColorProvider.removeTintObserver(this);
@@ -81,6 +107,7 @@
     @Override
     public void onTintChanged(ColorStateList tint, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(this, tint);
+        if (mLabel != null) mLabel.setTextColor(tint);
     }
 
     @Override
@@ -123,8 +150,11 @@
         // New tab page button takes precedence over homepage.
         final boolean isHomepageEnabled = !FeatureUtilities.isNewTabPageButtonEnabled()
                 && HomepageManager.isHomepageEnabled();
-        setEnabled(!isActiveTabNTP()
-                || (isHomepageEnabled && !NewTabPage.isNTPUrl(HomepageManager.getHomepageUri())));
+        final boolean isEnabled = !isActiveTabNTP()
+                || (isHomepageEnabled && !NewTabPage.isNTPUrl(HomepageManager.getHomepageUri()));
+        setEnabled(isEnabled);
+        if (mWrapper != null) mWrapper.setEnabled(isEnabled);
+        if (mLabel != null) mLabel.setEnabled(isEnabled);
     }
 
     private boolean isActiveTabNTP() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index a135630..325f4f2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.UrlConstants;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.dom_distiller.DomDistillerServiceFactory;
 import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils;
 import org.chromium.chrome.browser.native_page.NativePageFactory;
@@ -49,6 +50,8 @@
     private boolean mIsIncognito;
     private int mPrimaryColor;
     private boolean mIsUsingBrandColor;
+    private boolean mShouldShowOmniboxInOverviewMode;
+    private OverviewModeBehavior mOverviewModeBehavior;
 
     private long mNativeLocationBarModelAndroid;
 
@@ -264,6 +267,16 @@
     }
 
     @Override
+    public boolean isInOverview() {
+        return mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible();
+    }
+
+    @Override
+    public boolean shouldShowLocationBarInOverviewMode() {
+        return mShouldShowOmniboxInOverviewMode;
+    }
+
+    @Override
     public Profile getProfile() {
         Profile lastUsedProfile = Profile.getLastUsedProfile();
         if (mIsIncognito) {
@@ -273,6 +286,14 @@
         return lastUsedProfile.getOriginalProfile();
     }
 
+    public void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
+        mOverviewModeBehavior = overviewModeBehavior;
+    }
+
+    public void setShouldShowOmniboxInOverviewMode(boolean shouldShowOmniboxInOverviewMode) {
+        mShouldShowOmniboxInOverviewMode = shouldShowOmniboxInOverviewMode;
+    }
+
     /**
      * Sets the primary color and changes the state for isUsingBrandColor.
      * @param color The primary color for the current tab.
@@ -292,12 +313,17 @@
 
     @Override
     public int getPrimaryColor() {
-        return mPrimaryColor;
+        Context context = ContextUtils.getApplicationContext();
+        return isInOverview()
+                ? ColorUtils.getDefaultThemeColor(context.getResources(), isIncognito())
+                : mPrimaryColor;
     }
 
     @Override
     public boolean isUsingBrandColor() {
-        return mIsUsingBrandColor;
+        // If the overview is visible, force use of primary color, which is also overridden when the
+        // overview is visible.
+        return isInOverview() || mIsUsingBrandColor;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
index 7046874..ac66abe4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
@@ -14,10 +14,12 @@
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.LinearInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
+import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
@@ -26,6 +28,7 @@
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
 import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
 import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper.MenuButtonState;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.widget.PulseDrawable;
 import org.chromium.ui.interpolators.BakedBezierInterpolator;
 
@@ -52,6 +55,12 @@
     /** A provider that notifies components when the theme color changes.*/
     private ThemeColorProvider mThemeColorProvider;
 
+    /** The menu button text label. */
+    private TextView mLabel;
+
+    /** The wrapper View that contains the menu button and the label. */
+    private View mWrapper;
+
     public MenuButton(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -65,8 +74,10 @@
 
     public void setAppMenuButtonHelper(AppMenuButtonHelper appMenuButtonHelper) {
         mAppMenuButtonHelper = appMenuButtonHelper;
+        View touchView = mWrapper != null ? mWrapper : mMenuImageButton;
+        if (mWrapper != null) mWrapper.setOnTouchListener(mAppMenuButtonHelper);
         mMenuImageButton.setOnTouchListener(mAppMenuButtonHelper);
-        mMenuImageButton.setAccessibilityDelegate(mAppMenuButtonHelper);
+        touchView.setAccessibilityDelegate(mAppMenuButtonHelper);
     }
 
     public AppMenuButtonHelper getAppMenuButtonHelper() {
@@ -82,6 +93,16 @@
     }
 
     /**
+     * @param wrapper The wrapping View of this button.
+     */
+    public void setWrapperView(ViewGroup wrapper) {
+        mWrapper = wrapper;
+        mWrapper.setOnClickListener(null);
+        mLabel = mWrapper.findViewById(R.id.menu_button_label);
+        if (FeatureUtilities.isLabeledBottomToolbarEnabled()) mLabel.setVisibility(View.VISIBLE);
+    }
+
+    /**
      * Sets the update badge to visible.
      *
      * @param visible Whether the update badge should be visible. Always sets visibility to GONE
@@ -269,6 +290,8 @@
         ApiCompatibilityUtils.setImageTintList(mMenuImageButton, tintList);
         mUseLightDrawables = useLight;
         updateImageResources();
+
+        if (mLabel != null) mLabel.setTextColor(tintList);
     }
 
     public void destroy() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonView.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonView.java
index 3971e59..817f67c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonView.java
@@ -7,9 +7,13 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.TextView;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 
 /**
  * The Button used for switching tabs. Currently this class is only being used for the bottom
@@ -21,10 +25,34 @@
      */
     private TabSwitcherDrawable mTabSwitcherButtonDrawable;
 
+    /** The tab switcher button text label. */
+    private TextView mLabel;
+
+    /** The wrapper View that contains the tab switcher button and the label. */
+    private View mWrapper;
+
     public TabSwitcherButtonView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
+    /**
+     * @param wrapper The wrapping View of this button.
+     */
+    public void setWrapperView(ViewGroup wrapper) {
+        mWrapper = wrapper;
+        mLabel = mWrapper.findViewById(R.id.tab_switcher_button_label);
+        if (FeatureUtilities.isLabeledBottomToolbarEnabled()) mLabel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener listener) {
+        if (mWrapper != null) {
+            mWrapper.setOnClickListener(listener);
+            setClickable(false);
+        }
+        super.setOnClickListener(listener);
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -50,5 +78,6 @@
      */
     public void setTint(ColorStateList tint) {
         mTabSwitcherButtonDrawable.setTint(tint);
+        if (mLabel != null) mLabel.setTextColor(tint);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
index 76abe627..ef98205 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
@@ -50,6 +50,16 @@
     boolean isIncognito();
 
     /**
+     * @return Whether the toolbar is currently being displayed in overview mode.
+     */
+    boolean isInOverview();
+
+    /**
+     * @return Whether the location bar should show when in overview mode.
+     */
+    boolean shouldShowLocationBarInOverviewMode();
+
+    /**
      * @return The current {@link Profile}.
      */
     Profile getProfile();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 2028147..ce1fad5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -85,6 +85,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
 import org.chromium.chrome.browser.toolbar.bottom.BottomControlsCoordinator;
 import org.chromium.chrome.browser.toolbar.top.ActionModeController;
 import org.chromium.chrome.browser.toolbar.top.ActionModeController.ActionBarDelegate;
@@ -796,8 +797,10 @@
         mBottomControlsCoordinator.setBottomControlsVisible(mIsBottomToolbarVisible);
         mToolbar.onBottomToolbarVisibilityChanged(mIsBottomToolbarVisible);
 
-        Toast.setGlobalExtraYOffset(
-                mActivity.getResources().getDimensionPixelSize(R.dimen.bottom_toolbar_height));
+        Toast.setGlobalExtraYOffset(mActivity.getResources().getDimensionPixelSize(
+                FeatureUtilities.isLabeledBottomToolbarEnabled()
+                        ? R.dimen.labeled_bottom_toolbar_height
+                        : R.dimen.bottom_toolbar_height));
     }
 
     /** Record that homepage button was used for IPH reasons */
@@ -958,6 +961,8 @@
         });
 
         mLocationBarModel.initializeWithNative();
+        mLocationBarModel.setShouldShowOmniboxInOverviewMode(
+                ReturnToChromeExperimentsUtil.shouldShowOmniboxOnTabSwitcher());
 
         mFindToolbarManager = findToolbarManager;
 
@@ -972,7 +977,9 @@
             mOverviewModeBehavior = overviewModeBehavior;
             mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
             mAppThemeColorProvider.setOverviewModeBehavior(mOverviewModeBehavior);
+            mLocationBarModel.setOverviewModeBehavior(mOverviewModeBehavior);
         }
+
         if (layoutManager != null) {
             mLayoutManager = layoutManager;
             mLayoutManager.addSceneChangeObserver(mSceneChangeObserver);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
index d04721f..b2f712e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.support.annotation.Nullable;
+import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewStub;
@@ -77,10 +78,23 @@
         PropertyModelChangeProcessor.create(
                 model, new ViewHolder(root), BottomControlsViewBinder::bind);
 
+        int bottomToolbarHeightId;
+        int bottomToolbarHeightWithShadowId;
+
+        if (FeatureUtilities.isLabeledBottomToolbarEnabled()) {
+            bottomToolbarHeightId = R.dimen.labeled_bottom_toolbar_height;
+            bottomToolbarHeightWithShadowId = R.dimen.labeled_bottom_toolbar_height_with_shadow;
+        } else {
+            bottomToolbarHeightId = R.dimen.bottom_toolbar_height;
+            bottomToolbarHeightWithShadowId = R.dimen.bottom_toolbar_height_with_shadow;
+        }
+
+        View toolbar = root.findViewById(R.id.bottom_container_slot);
+        ViewGroup.LayoutParams params = toolbar.getLayoutParams();
+        params.height = root.getResources().getDimensionPixelOffset(bottomToolbarHeightId);
         mMediator = new BottomControlsMediator(model, fullscreenManager,
-                root.getResources().getDimensionPixelOffset(R.dimen.bottom_toolbar_height),
-                root.getResources().getDimensionPixelOffset(
-                        R.dimen.bottom_toolbar_height_with_shadow));
+                root.getResources().getDimensionPixelOffset(bottomToolbarHeightId),
+                root.getResources().getDimensionPixelOffset(bottomToolbarHeightWithShadowId));
 
         if (TabManagementModuleProvider.getDelegate() != null
                 && FeatureUtilities.isTabGroupsAndroidEnabled()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java
index 87fa6b99..4be16cb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java
@@ -12,6 +12,9 @@
 import android.support.annotation.StringRes;
 import android.support.graphics.drawable.VectorDrawableCompat;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
@@ -22,6 +25,7 @@
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider.IncognitoStateObserver;
 import org.chromium.chrome.browser.util.ColorUtils;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
@@ -41,6 +45,12 @@
     /** A provider that notifies when the theme color changes.*/
     private ThemeColorProvider mThemeColorProvider;
 
+    /** The new tab button text label. */
+    private TextView mLabel;
+
+    /** The wrapper View that contains the new tab button and the label. */
+    private View mWrapper;
+
     public BottomToolbarNewTabButton(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -55,6 +65,24 @@
     }
 
     /**
+     * @param wrapper The wrapping View of this button.
+     */
+    public void setWrapperView(ViewGroup wrapper) {
+        mWrapper = wrapper;
+        mLabel = mWrapper.findViewById(R.id.new_tab_button_label);
+        if (FeatureUtilities.isLabeledBottomToolbarEnabled()) mLabel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener listener) {
+        if (mWrapper != null) {
+            mWrapper.setOnClickListener(listener);
+        } else {
+            super.setOnClickListener(listener);
+        }
+    }
+
+    /**
      * Clean up any state when the new tab button is destroyed.
      */
     void destroy() {
@@ -103,6 +131,7 @@
     @Override
     public void onTintChanged(ColorStateList tint, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(this, tint);
+        if (mLabel != null) mLabel.setTextColor(tint);
         updateBackground();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
index a9bf593..c9b1bbc4b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.toolbar.TabSwitcherButtonCoordinator;
+import org.chromium.chrome.browser.toolbar.TabSwitcherButtonView;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -69,19 +70,27 @@
         mMediator = new BrowsingModeBottomToolbarMediator(model);
 
         mHomeButton = toolbarRoot.findViewById(R.id.home_button);
+        mHomeButton.setWrapperView(toolbarRoot.findViewById(R.id.home_button_wrapper));
         mHomeButton.setOnClickListener(homeButtonListener);
         mHomeButton.setActivityTabProvider(tabProvider);
 
         mShareButton = toolbarRoot.findViewById(R.id.share_button);
+        mShareButton.setWrapperView(toolbarRoot.findViewById(R.id.share_button_wrapper));
         mShareButton.setOnClickListener(shareButtonListener);
         mShareButton.setActivityTabProvider(tabProvider);
 
         mSearchAccelerator = toolbarRoot.findViewById(R.id.search_accelerator);
+        mSearchAccelerator.setWrapperView(
+                toolbarRoot.findViewById(R.id.search_accelerator_wrapper));
         mSearchAccelerator.setOnClickListener(searchAcceleratorListener);
 
         mTabSwitcherButtonCoordinator = new TabSwitcherButtonCoordinator(toolbarRoot);
+        // TODO(amaralp): Make this adhere to MVC framework.
+        ((TabSwitcherButtonView) toolbarRoot.findViewById(R.id.tab_switcher_button))
+                .setWrapperView(toolbarRoot.findViewById(R.id.tab_switcher_button_wrapper));
 
         mMenuButton = toolbarRoot.findViewById(R.id.menu_button_wrapper);
+        mMenuButton.setWrapperView(toolbarRoot.findViewById(R.id.labeled_menu_button_wrapper));
 
         tabProvider.addObserverAndTrigger(new HintlessActivityTabObserver() {
             @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
index e7ddc27..2897a89 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
@@ -10,6 +10,9 @@
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
@@ -19,6 +22,7 @@
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider.IncognitoStateObserver;
 import org.chromium.chrome.browser.util.ColorUtils;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
@@ -38,6 +42,12 @@
     /** A provider that notifies when incognito mode is entered or exited. */
     private IncognitoStateProvider mIncognitoStateProvider;
 
+    /** The search accelerator text label. */
+    private TextView mLabel;
+
+    /** The wrapper View that contains the search accelerator and the label. */
+    private View mWrapper;
+
     public SearchAccelerator(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -48,6 +58,24 @@
         setBackground(mBackground);
     }
 
+    /**
+     * @param wrapper The wrapping View of this button.
+     */
+    public void setWrapperView(ViewGroup wrapper) {
+        mWrapper = wrapper;
+        mLabel = mWrapper.findViewById(R.id.search_accelerator_label);
+        if (FeatureUtilities.isLabeledBottomToolbarEnabled()) mLabel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener listener) {
+        if (mWrapper != null) {
+            mWrapper.setOnClickListener(listener);
+        } else {
+            super.setOnClickListener(listener);
+        }
+    }
+
     void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
         mThemeColorProvider.addThemeColorObserver(this);
@@ -80,6 +108,7 @@
     @Override
     public void onTintChanged(ColorStateList tint, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(this, tint);
+        if (mLabel != null) mLabel.setTextColor(tint);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
index d61f271..2e281a8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
@@ -7,14 +7,19 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
@@ -24,13 +29,37 @@
     /** A provider that notifies components when the theme color changes.*/
     private ThemeColorProvider mThemeColorProvider;
 
-    /** The {@link sActivityTabTabObserver} used to know when the active page changed. */
+    /** The {@link ActivityTabTabObserver} used to know when the active page changed. */
     private ActivityTabTabObserver mActivityTabTabObserver;
 
+    /** The share button text label. */
+    private TextView mLabel;
+
+    /** The wrapper View that contains the share button and the label. */
+    private View mWrapper;
+
     public ShareButton(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
+    /**
+     * @param wrapper The wrapping View of this button.
+     */
+    public void setWrapperView(ViewGroup wrapper) {
+        mWrapper = wrapper;
+        mLabel = mWrapper.findViewById(R.id.share_button_label);
+        if (FeatureUtilities.isLabeledBottomToolbarEnabled()) mLabel.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener listener) {
+        if (mWrapper != null) {
+            mWrapper.setOnClickListener(listener);
+        } else {
+            super.setOnClickListener(listener);
+        }
+    }
+
     void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
         mThemeColorProvider.addTintObserver(this);
@@ -41,13 +70,13 @@
             @Override
             public void onObservingDifferentTab(Tab tab) {
                 if (tab == null) return;
-                setEnabled(shouldEnableShare(tab));
+                updateButtonEnabledState(tab);
             }
 
             @Override
             public void onUpdateUrl(Tab tab, String url) {
                 if (tab == null) return;
-                setEnabled(shouldEnableShare(tab));
+                updateButtonEnabledState(tab);
             }
         };
     }
@@ -63,15 +92,19 @@
         }
     }
 
-    private static boolean shouldEnableShare(Tab tab) {
+    private void updateButtonEnabledState(Tab tab) {
         final String url = tab.getUrl();
         final boolean isChromeScheme = url.startsWith(UrlConstants.CHROME_URL_PREFIX)
                 || url.startsWith(UrlConstants.CHROME_NATIVE_URL_PREFIX);
-        return !isChromeScheme && !tab.isShowingInterstitialPage();
+        final boolean isEnabled = !isChromeScheme && !tab.isShowingInterstitialPage();
+        setEnabled(isEnabled);
+        if (mWrapper != null) mWrapper.setEnabled(isEnabled);
+        if (mLabel != null) mLabel.setEnabled(isEnabled);
     }
 
     @Override
     public void onTintChanged(ColorStateList tint, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(this, tint);
+        if (mLabel != null) mLabel.setTextColor(tint);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
index 52f17518..0ef695d5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
@@ -56,7 +56,7 @@
             OnClickListener newTabClickListener, OnClickListener closeTabsClickListener,
             AppMenuButtonHelper menuButtonHelper, OverviewModeBehavior overviewModeBehavior,
             TabCountProvider tabCountProvider) {
-        final View root = stub.inflate();
+        final ViewGroup root = (ViewGroup) stub.inflate();
 
         TabSwitcherBottomToolbarModel model = new TabSwitcherBottomToolbarModel();
 
@@ -75,12 +75,14 @@
         mCloseAllTabsButton.setVisibility(View.INVISIBLE);
 
         mNewTabButton = root.findViewById(R.id.tab_switcher_new_tab_button);
+        mNewTabButton.setWrapperView(root.findViewById(R.id.new_tab_button_wrapper));
         mNewTabButton.setOnClickListener(newTabClickListener);
         mNewTabButton.setIncognitoStateProvider(incognitoStateProvider);
         mNewTabButton.setThemeColorProvider(themeColorProvider);
 
         assert menuButtonHelper != null;
         mMenuButton = root.findViewById(R.id.menu_button_wrapper);
+        mMenuButton.setWrapperView(root.findViewById(R.id.labeled_menu_button_wrapper));
         mMenuButton.setThemeColorProvider(themeColorProvider);
         mMenuButton.setAppMenuButtonHelper(menuButtonHelper);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java
index 160fe5d..0586f13 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java
@@ -23,6 +23,7 @@
 import org.chromium.chrome.browser.device.DeviceClassManager;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.IncognitoToggleTabLayout;
 import org.chromium.chrome.browser.toolbar.MenuButton;
@@ -158,6 +159,12 @@
             if (mIncognitoToggleTabLayout != null) mIncognitoToggleTabLayout.setClickable(false);
         } else {
             if (mNewTabButton != null) mNewTabButton.setEnabled(true);
+            if (ReturnToChromeExperimentsUtil.shouldShowOmniboxOnTabSwitcher()) {
+                // Bump this down by the height of the toolbar so the omnibox can be visible.
+                MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
+                params.topMargin =
+                        getResources().getDimensionPixelSize(R.dimen.toolbar_height_no_shadow);
+            }
         }
 
         mVisiblityAnimator.addListener(new CancelAwareAnimatorListener() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
index bb06a63..01e9f68b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
@@ -233,6 +233,16 @@
             }
 
             @Override
+            public boolean isInOverview() {
+                return false;
+            }
+
+            @Override
+            public boolean shouldShowLocationBarInOverviewMode() {
+                return false;
+            }
+
+            @Override
             public Profile getProfile() {
                 return null;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 832370e..de2f705 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -908,6 +908,7 @@
 
     private float getExpansionPercentForVisualState(@VisualState int visualState) {
         return visualState == VisualState.NEW_TAB_NORMAL && mTabSwitcherState == STATIC_TAB
+                        || getToolbarDataProvider().isInOverview()
                 ? 1
                 : mUrlExpansionPercent;
     }
@@ -940,7 +941,7 @@
         // toolbar is not visible (e.g. in tab switcher mode).
         if (isInTabSwitcherMode()) return;
 
-        if (!mIsBottomToolbarVisible) {
+        if (!mIsBottomToolbarVisible && !getToolbarDataProvider().isInOverview()) {
             int toolbarButtonVisibility = getToolbarButtonVisibility();
             mToolbarButtonsContainer.setVisibility(toolbarButtonVisibility);
             if (mHomeButton != null && mHomeButton.getVisibility() != GONE) {
@@ -1006,7 +1007,7 @@
                 ntp.setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
             }
 
-            if (isLocationBarShownInNTP()) {
+            if (isLocationBarShownInNTP() && !getToolbarDataProvider().isInOverview()) {
                 updateNtpTransitionAnimation();
             } else {
                 // Reset these values in case we transitioned to a different page during the
@@ -1776,6 +1777,8 @@
      */
     public void setTabSwitcherMode(boolean inTabSwitcherMode, boolean showToolbar,
             boolean delayAnimation, boolean animate) {
+        if (handleOmniboxInOverviewMode(inTabSwitcherMode)) return;
+
         // If setting tab switcher mode to true and the browser is already animating or in the tab
         // switcher skip.
         if (inTabSwitcherMode
@@ -1844,6 +1847,39 @@
         }
     }
 
+    /**
+     * Handles animating the omnibox state when switching in or out of the tab switcher when the tab
+     *  switcher is displaying the omnibox.
+     * @param inTabSwitcherMode Whether we are entering tab switcher.
+     * @return Whether we handled showing the omnibox on the tab switcher.
+     */
+    private boolean handleOmniboxInOverviewMode(boolean inTabSwitcherMode) {
+        if (!getToolbarDataProvider().shouldShowLocationBarInOverviewMode()) return false;
+
+        mIsHomeButtonEnabled = !inTabSwitcherMode;
+        mToggleTabStackButton.setVisibility(inTabSwitcherMode ? GONE : VISIBLE);
+        if (getMenuButton() != null)
+            getMenuButton().setVisibility(inTabSwitcherMode ? GONE : VISIBLE);
+
+        triggerUrlFocusAnimation(inTabSwitcherMode && !urlHasFocus());
+
+        if (inTabSwitcherMode) {
+            mUrlBar.setText("");
+            mLocationBar.updateStatusIcon();
+            // Early return here allows the location bar to remain visible and functional while
+            // the tab switcher (overview) is being displayed.
+            return true;
+        }
+
+        if (getToolbarDataProvider() != null && getToolbarDataProvider().getUrlBarData() != null) {
+            // Set the text back to the correct display text in case we've previously cleared
+            // it.
+            mUrlBar.setText(getToolbarDataProvider().getUrlBarData().displayText);
+        }
+
+        return false;
+    }
+
     @Override
     public void onTabSwitcherTransitionFinished() {
         setAlpha(1.f);
@@ -2058,6 +2094,12 @@
         super.onUrlFocusChange(hasFocus);
 
         if (mToggleTabStackButton != null) mToggleTabStackButton.setClickable(!hasFocus);
+
+        if (getToolbarDataProvider().isInOverview() && !hasFocus) {
+            mUrlBar.setText("");
+            return;
+        }
+
         triggerUrlFocusAnimation(hasFocus);
     }
 
@@ -2105,6 +2147,11 @@
                 }
                 mLocationBar.finishUrlFocusChange(hasFocus);
                 mUrlFocusChangeInProgress = false;
+
+                if (getToolbarDataProvider().isInOverview()) {
+                    mLocationBar.updateVisualsForState();
+                    mUrlBar.setText("");
+                }
             }
         });
         mUrlFocusLayoutAnimator.start();
@@ -2139,6 +2186,11 @@
         if (mTabSwitcherAnimationTabStackDrawable != null) {
             mTabSwitcherAnimationTabStackDrawable.updateForTabCount(numberOfTabs, isIncognito);
         }
+
+        if (getToolbarDataProvider().isInOverview()
+                && getToolbarDataProvider().shouldShowLocationBarInOverviewMode()) {
+            mUrlBar.setText("");
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
index 88db404..60b4e9e3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
@@ -77,6 +77,7 @@
     private static Boolean sIsNewTabPageButtonEnabled;
     private static Boolean sIsBottomToolbarEnabled;
     private static Boolean sIsAdaptiveToolbarEnabled;
+    private static Boolean sIsLabeledBottomToolbarEnabled;
     private static Boolean sIsNightModeAvailable;
     private static Boolean sIsNightModeForCustomTabsAvailable;
     private static Boolean sShouldPrioritizeBootstrapTasks;
@@ -202,6 +203,7 @@
         cacheNewTabPageButtonEnabled();
         cacheBottomToolbarEnabled();
         cacheAdaptiveToolbarEnabled();
+        cacheLabeledBottomToolbarEnabled();
         cacheNightModeAvailable();
         cacheNightModeForCustomTabsAvailable();
         cacheDownloadAutoResumptionEnabledInNative();
@@ -475,6 +477,16 @@
     }
 
     /**
+     * Cache whether or not the labeled bottom toolbar is enabled so on next startup, the value can
+     * be made available immediately.
+     */
+    public static void cacheLabeledBottomToolbarEnabled() {
+        ChromePreferenceManager.getInstance().writeBoolean(
+                ChromePreferenceManager.LABELED_BOTTOM_TOOLBAR_ENABLED_KEY,
+                ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_DUET_LABELED));
+    }
+
+    /**
      * Cache whether or not download auto-resumptions are enabled in native so on next startup, the
      * value can be made available immediately.
      */
@@ -515,6 +527,19 @@
     }
 
     /**
+     * @return Whether or not the labeled bottom toolbar is enabled.
+     */
+    public static boolean isLabeledBottomToolbarEnabled() {
+        if (sIsLabeledBottomToolbarEnabled == null) {
+            ChromePreferenceManager prefManager = ChromePreferenceManager.getInstance();
+
+            sIsLabeledBottomToolbarEnabled = prefManager.readBoolean(
+                    ChromePreferenceManager.LABELED_BOTTOM_TOOLBAR_ENABLED_KEY, false);
+        }
+        return sIsLabeledBottomToolbarEnabled && isBottomToolbarEnabled();
+    }
+
+    /**
      * Cache whether or not night mode is available (i.e. night mode experiment is enabled) so on
      * next startup, the value can be made available immediately.
      */
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index ba01e30..d5628d8 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -2316,7 +2316,7 @@
         Added to Home screen
       </message>
       <message name="IDS_MENU_ADD_TO_APPS" desc="Text to accompany icon that will navigate to a page showing a categorized view of different applications or sites">
-        Add to My apps
+        Add to my apps
       </message>
 
       <!-- Page info popup -->
@@ -3419,6 +3419,10 @@
         Tap to collapse
       </message>
 
+      <message name="IDS_TAB_SWITCHER_BUTTON_LABEL" desc="Label for tab switcher button">
+        Tabs
+      </message>
+
       <message name="IDS_SELECTED_ITEMS" desc="Content description for a number indicating how many items are selected. [ICU Syntax]">
         {NUM_SELECTED, plural,
           =1 {1 selected}
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_TAB_SWITCHER_BUTTON_LABEL.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_TAB_SWITCHER_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..4d3d9c0
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_TAB_SWITCHER_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+057be5fb800b3198a7e64544dcd80c466e1ce74f
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/RestoreHistogramTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/RestoreHistogramTest.java
index 0a8ef87..66b87c1b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/RestoreHistogramTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/RestoreHistogramTest.java
@@ -12,6 +12,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.PathUtils;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
@@ -40,6 +41,10 @@
         PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
     }
 
+    private void clearPrefs() {
+        ContextUtils.getAppSharedPreferences().edit().clear().apply();
+    }
+
     /**
      * Test that the fundamental method for writing the histogram
      * {@link ChromeBackupAgent#recordRestoreHistogram()} works correctly
@@ -65,6 +70,7 @@
                         ChromeBackupAgent.RestoreStatus.RESTORE_STATUS_RECORDED);
 
         // Check behavior with no preference set
+        clearPrefs();
         ChromeBackupAgent.recordRestoreHistogram();
         Assert.assertEquals(1, noRestoreDelta.getDelta());
         Assert.assertEquals(0, restoreCompletedDelta.getDelta());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/appmenu/DataSaverAppMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/appmenu/DataSaverAppMenuTest.java
index f83db15d..f5e234d7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/appmenu/DataSaverAppMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/appmenu/DataSaverAppMenuTest.java
@@ -13,6 +13,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -108,6 +109,7 @@
     @Feature({"Browser", "Main"})
     public void testMenuDataSaver() throws Throwable {
         mActivityTestRule.runOnUiThread((Runnable) () -> {
+            ContextUtils.getAppSharedPreferences().edit().clear().apply();
             // Data Saver hasn't been turned on, the footer shouldn't show.
             Assert.assertEquals(0, mAppMenuHandler.getDelegate().getFooterResourceId());
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
index 7b45257..d132989 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
@@ -25,6 +25,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
@@ -220,6 +221,7 @@
     @Feature({"Android-TabSwitcher"})
     @UiThreadTest
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
+    @DisabledTest(message = "https://crbug.com/965250")
     public void testStack() throws Exception {
         initializeLayoutManagerPhone(3, 0);
         mManagerPhone.showOverview(true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/datareduction/DataReductionSavingsMilestonePromoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/datareduction/DataReductionSavingsMilestonePromoTest.java
index 832b0c00..55657f8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/datareduction/DataReductionSavingsMilestonePromoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/datareduction/DataReductionSavingsMilestonePromoTest.java
@@ -12,6 +12,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -37,6 +38,7 @@
 
     @Before
     public void setUp() throws InterruptedException {
+        ContextUtils.getAppSharedPreferences().edit().clear().apply();
         mActivityTestRule.startMainActivityOnBlankPage();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java
index 56dc84c..994b400 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/instantapps/InstantAppsHandlerTest.java
@@ -15,6 +15,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -67,6 +68,11 @@
         editor.apply();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        ContextUtils.getAppSharedPreferences().edit().clear().apply();
+    }
+
     @Test
     @SmallTest
     public void testInstantAppsDisabled_incognito() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java
index 7b78a2b..555393b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogTestUtils.java
@@ -17,6 +17,7 @@
 import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 import java.util.List;
 import java.util.concurrent.ExecutionException;
@@ -118,4 +119,18 @@
         if (expectedDismissalCause == null) return;
         Assert.assertEquals(expectedDismissalCause.intValue(), dismissalCause);
     }
+
+    /**
+     * @param modelBuilder The builder for the modal dialog view model.
+     * @param view The {@link ModalDialogView} that should be bound.
+     * @return The {@link PropertyModel} that binds the {@code view}.
+     */
+    public static PropertyModel createModel(
+            PropertyModel.Builder modelBuilder, ModalDialogView view) {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            PropertyModel model = modelBuilder.build();
+            PropertyModelChangeProcessor.create(model, view, new ModalDialogViewBinder());
+            return model;
+        });
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java
new file mode 100644
index 0000000..4d4b8f58
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java
@@ -0,0 +1,178 @@
+// 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.
+
+package org.chromium.chrome.browser.modaldialog;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.test.filters.MediumTest;
+import android.text.TextUtils;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.params.ParameterAnnotations;
+import org.chromium.base.test.params.ParameterSet;
+import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.night_mode.NightModeTestUtils;
+import org.chromium.chrome.browser.night_mode.NightModeTestUtils.NightModeParams;
+import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
+import org.chromium.chrome.test.ui.DummyUiActivityTestCase;
+import org.chromium.chrome.test.util.RenderTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.UiUtils;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Render tests for {@link ModalDialogView}.
+ */
+@RunWith(ParameterizedRunner.class)
+@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
+public class ModalDialogViewRenderTest extends DummyUiActivityTestCase {
+    @ParameterAnnotations.ClassParameter
+    private static List<ParameterSet> sClassParams = new NightModeParams().getParameters();
+
+    private final @ColorInt int mFakeBgColor;
+
+    private Resources mResources;
+    private PropertyModel.Builder mModelBuilder;
+    private FrameLayout mContentView;
+    private ModalDialogView mModalDialogView;
+    private ScrollView mCustomScrollView;
+    private TextView mCustomTextView1;
+    private TextView mCustomTextView2;
+
+    @Rule
+    public RenderTestRule mRenderTestRule =
+            new RenderTestRule("chrome/test/data/android/render_tests");
+
+    public ModalDialogViewRenderTest(boolean nightModeEnabled) {
+        // Sets a fake background color to make the screenshots easier to compare with bare eyes.
+        mFakeBgColor = nightModeEnabled ? Color.BLACK : Color.WHITE;
+        NightModeTestUtils.setUpNightModeForDummyUiActivity(nightModeEnabled);
+        mRenderTestRule.setNightModeEnabled(nightModeEnabled);
+    }
+
+    @Override
+    public void setUpTest() throws Exception {
+        super.setUpTest();
+        setUpViews();
+    }
+
+    @Override
+    public void tearDownTest() throws Exception {
+        NightModeTestUtils.tearDownNightModeForDummyUiActivity();
+        super.tearDownTest();
+    }
+
+    private void setUpViews() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Activity activity = getActivity();
+            mResources = activity.getResources();
+            mModelBuilder = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS);
+
+            mContentView = new FrameLayout(activity);
+            mModalDialogView =
+                    (ModalDialogView) LayoutInflater
+                            .from(new ContextThemeWrapper(activity,
+                                    org.chromium.chrome.R.style.Theme_Chromium_ModalDialog))
+                            .inflate(org.chromium.chrome.R.layout.modal_dialog_view, null);
+            mModalDialogView.setBackgroundColor(mFakeBgColor);
+            activity.setContentView(mContentView);
+            mContentView.addView(mModalDialogView, MATCH_PARENT, WRAP_CONTENT);
+
+            mCustomScrollView = new ScrollView(activity);
+            mCustomTextView1 = new TextView(activity);
+            mCustomTextView1.setId(org.chromium.chrome.R.id.button_one);
+            mCustomTextView2 = new TextView(activity);
+            mCustomTextView2.setId(org.chromium.chrome.R.id.button_two);
+        });
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"ModalDialog", "RenderTest"})
+    public void testRender_TitleAndTitleIcon() throws IOException {
+        final Drawable icon =
+                UiUtils.getTintedDrawable(getActivity(), org.chromium.chrome.R.drawable.ic_add,
+                        org.chromium.chrome.R.color.default_icon_color);
+        createModel(mModelBuilder
+                            .with(ModalDialogProperties.TITLE, mResources,
+                                    org.chromium.chrome.R.string.title)
+                            .with(ModalDialogProperties.TITLE_ICON, icon));
+        mRenderTestRule.render(mModalDialogView, "title_and_title_icon");
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"ModalDialog", "RenderTest"})
+    public void testRender_TitleAndMessage() throws IOException {
+        createModel(mModelBuilder
+                            .with(ModalDialogProperties.TITLE, mResources,
+                                    org.chromium.chrome.R.string.title)
+                            .with(ModalDialogProperties.MESSAGE,
+                                    TextUtils.join("\n", Collections.nCopies(100, "Message")))
+                            .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, mResources,
+                                    org.chromium.chrome.R.string.ok)
+                            .with(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true)
+                            .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, mResources,
+                                    org.chromium.chrome.R.string.cancel));
+        mRenderTestRule.render(mModalDialogView, "title_and_message");
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"ModalDialog", "RenderTest"})
+    public void testRender_ScrollableTitle() throws IOException {
+        createModel(mModelBuilder
+                            .with(ModalDialogProperties.TITLE, mResources,
+                                    org.chromium.chrome.R.string.title)
+                            .with(ModalDialogProperties.TITLE_SCROLLABLE, true)
+                            .with(ModalDialogProperties.MESSAGE,
+                                    TextUtils.join("\n", Collections.nCopies(100, "Message")))
+                            .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, mResources,
+                                    org.chromium.chrome.R.string.ok));
+        mRenderTestRule.render(mModalDialogView, "scrollable_title");
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"ModalDialog", "RenderTest"})
+    public void testRender_CustomView() throws IOException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mCustomTextView1.setText(
+                    TextUtils.join("\n", Collections.nCopies(100, "Custom Message")));
+            mCustomScrollView.addView(mCustomTextView1);
+        });
+        createModel(mModelBuilder
+                            .with(ModalDialogProperties.TITLE, mResources,
+                                    org.chromium.chrome.R.string.title)
+                            .with(ModalDialogProperties.CUSTOM_VIEW, mCustomScrollView)
+                            .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, mResources,
+                                    org.chromium.chrome.R.string.ok));
+        mRenderTestRule.render(mModalDialogView, "custom_view");
+    }
+
+    private PropertyModel createModel(PropertyModel.Builder modelBuilder) {
+        return ModalDialogTestUtils.createModel(modelBuilder, mModalDialogView);
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java
index 0c24a03..86af886 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewTest.java
@@ -18,67 +18,50 @@
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.not;
 
+import android.app.Activity;
 import android.content.res.Resources;
 import android.support.test.filters.MediumTest;
-import android.text.TextUtils;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
-import android.widget.ScrollView;
 import android.widget.TextView;
 
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.util.RenderTestRule;
+import org.chromium.chrome.test.ui.DummyUiActivityTestCase;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.modelutil.PropertyModel;
-import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
-
-import java.io.IOException;
-import java.util.Collections;
 
 /**
  * Tests for {@link ModalDialogView}.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-public class ModalDialogViewTest {
-    @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
-
-    @Rule
-    public RenderTestRule mRenderTestRule =
-            new RenderTestRule("chrome/test/data/android/render_tests");
-
+public class ModalDialogViewTest extends DummyUiActivityTestCase {
     private Resources mResources;
     private PropertyModel.Builder mModelBuilder;
     private FrameLayout mContentView;
     private ModalDialogView mModalDialogView;
-    private ScrollView mCustomScrollView;
     private TextView mCustomTextView1;
     private TextView mCustomTextView2;
 
-    @Before
-    public void setUp() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
+    @Override
+    public void setUpTest() throws Exception {
+        super.setUpTest();
+        setUpViews();
+    }
 
+    private void setUpViews() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            ChromeActivity activity = mActivityTestRule.getActivity();
+            Activity activity = getActivity();
             mResources = activity.getResources();
             mModelBuilder = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS);
 
@@ -90,7 +73,6 @@
             activity.setContentView(mContentView);
             mContentView.addView(mModalDialogView, MATCH_PARENT, WRAP_CONTENT);
 
-            mCustomScrollView = new ScrollView(activity);
             mCustomTextView1 = new TextView(activity);
             mCustomTextView1.setId(R.id.button_one);
             mCustomTextView2 = new TextView(activity);
@@ -100,60 +82,6 @@
 
     @Test
     @MediumTest
-    @Feature({"ModalDialog", "RenderTest"})
-    public void testRender_TitleAndTitleIcon() throws IOException {
-        createModel(mModelBuilder.with(ModalDialogProperties.TITLE, mResources, R.string.title)
-                            .with(ModalDialogProperties.TITLE_ICON, mActivityTestRule.getActivity(),
-                                    R.drawable.ic_add));
-        mRenderTestRule.render(mModalDialogView, "title_and_title_icon");
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"ModalDialog", "RenderTest"})
-    public void testRender_TitleAndMessage() throws IOException {
-        createModel(
-                mModelBuilder.with(ModalDialogProperties.TITLE, mResources, R.string.title)
-                        .with(ModalDialogProperties.MESSAGE,
-                                TextUtils.join("\n", Collections.nCopies(100, "Message")))
-                        .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, mResources, R.string.ok)
-                        .with(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true)
-                        .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, mResources,
-                                R.string.cancel));
-        mRenderTestRule.render(mModalDialogView, "title_and_message");
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"ModalDialog", "RenderTest"})
-    public void testRender_ScrollableTitle() throws IOException {
-        createModel(
-                mModelBuilder.with(ModalDialogProperties.TITLE, mResources, R.string.title)
-                        .with(ModalDialogProperties.TITLE_SCROLLABLE, true)
-                        .with(ModalDialogProperties.MESSAGE,
-                                TextUtils.join("\n", Collections.nCopies(100, "Message")))
-                        .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, mResources, R.string.ok));
-        mRenderTestRule.render(mModalDialogView, "scrollable_title");
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"ModalDialog", "RenderTest"})
-    public void testRender_CustomView() throws IOException {
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mCustomTextView1.setText(
-                    TextUtils.join("\n", Collections.nCopies(100, "Custom Message")));
-            mCustomScrollView.addView(mCustomTextView1);
-        });
-        createModel(
-                mModelBuilder.with(ModalDialogProperties.TITLE, mResources, R.string.title)
-                        .with(ModalDialogProperties.CUSTOM_VIEW, mCustomScrollView)
-                        .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, mResources, R.string.ok));
-        mRenderTestRule.render(mModalDialogView, "custom_view");
-    }
-
-    @Test
-    @MediumTest
     @Feature({"ModalDialog"})
     public void testInitialStates() {
         // Verify that the default states are correct when properties are not set.
@@ -227,8 +155,8 @@
     @Feature({"ModalDialog"})
     public void testTitleIcon() {
         // Verify that the icon set from builder is displayed.
-        PropertyModel model = createModel(mModelBuilder.with(ModalDialogProperties.TITLE_ICON,
-                mActivityTestRule.getActivity(), R.drawable.ic_add));
+        PropertyModel model = createModel(mModelBuilder.with(
+                ModalDialogProperties.TITLE_ICON, getActivity(), R.drawable.ic_add));
         onView(allOf(withId(R.id.title), withParent(withId(R.id.title_container))))
                 .check(matches(not(isDisplayed())));
         onView(allOf(withId(R.id.title_icon), withParent(withId(R.id.title_container))))
@@ -382,11 +310,6 @@
     }
 
     private PropertyModel createModel(PropertyModel.Builder modelBuilder) {
-        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
-            PropertyModel model = modelBuilder.build();
-            PropertyModelChangeProcessor.create(
-                    model, mModalDialogView, new ModalDialogViewBinder());
-            return model;
-        });
+        return ModalDialogTestUtils.createModel(modelBuilder, mModalDialogView);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/night_mode/NightModeTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/night_mode/NightModeTestUtils.java
new file mode 100644
index 0000000..c87536b
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/night_mode/NightModeTestUtils.java
@@ -0,0 +1,49 @@
+// 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.
+
+package org.chromium.chrome.browser.night_mode;
+
+import android.support.v7.app.AppCompatDelegate;
+
+import org.chromium.base.test.params.ParameterProvider;
+import org.chromium.base.test.params.ParameterSet;
+import org.chromium.chrome.test.ui.DummyUiActivity;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Helper methods to be used in tests to specify night mode state.
+ */
+public class NightModeTestUtils {
+    /**
+     * {@link ParameterProvider} used for parameterized test that provides the night mode state.
+     */
+    public static class NightModeParams implements ParameterProvider {
+        private static List<ParameterSet> sNightModeParams =
+                Arrays.asList(new ParameterSet().value(false).name("NightModeDisabled"),
+                        new ParameterSet().value(true).name("NightModeEnabled"));
+
+        @Override
+        public List<ParameterSet> getParameters() {
+            return sNightModeParams;
+        }
+    }
+
+    /**
+     * Sets up the night mode state for {@link DummyUiActivity}.
+     * @param nightModeEnabled Whether night mode should be enabled.
+     */
+    public static void setUpNightModeForDummyUiActivity(boolean nightModeEnabled) {
+        AppCompatDelegate.setDefaultNightMode(nightModeEnabled ? AppCompatDelegate.MODE_NIGHT_YES
+                                                               : AppCompatDelegate.MODE_NIGHT_NO);
+    }
+
+    /**
+     * Resets the night mode state.
+     */
+    public static void tearDownNightModeForDummyUiActivity() {
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
index 747b9e5b..ffa51be 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
@@ -196,6 +196,16 @@
         }
 
         @Override
+        public boolean shouldShowLocationBarInOverviewMode() {
+            return false;
+        }
+
+        @Override
+        public boolean isInOverview() {
+            return false;
+        }
+
+        @Override
         public Profile getProfile() {
             return null;
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
index 66cc037..913716e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
@@ -6,6 +6,7 @@
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
+import android.content.SharedPreferences;
 import android.net.Uri;
 import android.os.Build;
 import android.support.test.InstrumentationRegistry;
@@ -21,6 +22,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
@@ -61,6 +63,13 @@
 
     @Before
     public void setUp() throws InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // TODO(newt): Remove this once SharedPreferences is cleared automatically at the
+            // beginning of every test. http://crbug.com/441859
+            SharedPreferences sp = ContextUtils.getAppSharedPreferences();
+            sp.edit().clear().apply();
+        });
+
         mActivityTestRule.startMainActivityFromLauncher();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
index 57b0c1a..c4325e2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
@@ -13,6 +13,7 @@
 import android.support.test.filters.SmallTest;
 
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -48,6 +49,11 @@
     public IntentsTestRule<HistoryActivity> mHistoryActivityTestRule =
             new IntentsTestRule<>(HistoryActivity.class, false, false);
 
+    @Before
+    public void setUp() {
+        clearAutofillAssistantSwitch();
+    }
+
     /**
      * Set the |PREF_AUTOFILL_ASSISTANT_SWITCH| shared preference to the given |value|.
      * @param value The value to set the preference to.
@@ -70,6 +76,16 @@
     }
 
     /**
+     * Removes the |PREF_AUTOFILL_ASSISTANT_SWITCH| shared preference.
+     */
+    private void clearAutofillAssistantSwitch() {
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .remove(AutofillAssistantPreferences.PREF_AUTOFILL_ASSISTANT_SWITCH)
+                .apply();
+    }
+
+    /**
      * Ensure that the on/off switch in "Autofill Assistant" settings works.
      */
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/test/ui/DummyUiActivity.java b/chrome/android/javatests/src/org/chromium/chrome/test/ui/DummyUiActivity.java
index e28bd100..2edc3a5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/test/ui/DummyUiActivity.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/test/ui/DummyUiActivity.java
@@ -4,13 +4,13 @@
 
 package org.chromium.chrome.test.ui;
 
-import android.app.Activity;
 import android.os.Bundle;
 import android.support.annotation.IdRes;
 import android.support.annotation.LayoutRes;
+import android.support.v7.app.AppCompatActivity;
 
 /** Dummy activity to test UI components without Chrome browser initialization and natives. */
-public class DummyUiActivity extends Activity {
+public class DummyUiActivity extends AppCompatActivity {
     private static int sTestTheme;
     private static int sTestLayout;
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandlerTest.java
similarity index 85%
rename from chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java
rename to chrome/android/junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandlerTest.java
index d3251d7..8b1beb5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/CompositorAnimationHandlerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/animation/CompositorAnimationHandlerTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.compositor.layouts;
+package org.chromium.chrome.browser.compositor.animation;
 
 import android.support.test.filters.SmallTest;
 
@@ -12,11 +12,11 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.compositor.animation.CompositorAnimationHandler;
-import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
+import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
+import org.chromium.chrome.browser.compositor.layouts.MockLayoutUpdateHost;
 
 /**
- * Unit tests for {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation}.
+ * Unit tests for {@link CompositorAnimator}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
@@ -24,8 +24,6 @@
     private static final long FAST_DURATION_MS = 100;
     private static final long SLOW_DURATION_MS = 1000;
 
-    private CompositorAnimator mFastAnimation;
-    private CompositorAnimator mSlowAnimation;
     private CompositorAnimationHandler mAnimations;
     private LayoutUpdateHost mUpdateHost;
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/prefetch/OfflineNotificationBackgroundTaskUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/prefetch/OfflineNotificationBackgroundTaskUnitTest.java
index f7eb9108..623c742 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/prefetch/OfflineNotificationBackgroundTaskUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/prefetch/OfflineNotificationBackgroundTaskUnitTest.java
@@ -41,6 +41,7 @@
 import org.robolectric.shadows.multidex.ShadowMultiDex;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.DeviceConditions;
@@ -156,6 +157,7 @@
         mCalendar.set(2017, 1, 1, 0, 0, 0);
 
         OfflineNotificationBackgroundTask.setCalendarForTesting(mCalendar);
+        clearPrefs();
     }
 
     @After
@@ -164,6 +166,10 @@
         verify(mPrefetchedPagesNotifier, never()).showNotification("");
     }
 
+    private void clearPrefs() {
+        ContextUtils.getAppSharedPreferences().edit().clear().apply();
+    }
+
     /**
      * Runs mOfflineNotificationBackgroundTask with the given params.
      * Asserts that reschedule was called exactly once and returns the reschedule value.
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3fc4c5e..d38a05d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -131,8 +131,6 @@
     "autofill/autofill_internals_logging_impl.h",
     "autofill/autofill_profile_validator_factory.cc",
     "autofill/autofill_profile_validator_factory.h",
-    "autofill/legacy_strike_database_factory.cc",
-    "autofill/legacy_strike_database_factory.h",
     "autofill/personal_data_manager_factory.cc",
     "autofill/personal_data_manager_factory.h",
     "autofill/risk_util.cc",
@@ -1095,6 +1093,10 @@
     "performance_manager/persistence/site_data/exponential_moving_average.cc",
     "performance_manager/persistence/site_data/exponential_moving_average.h",
     "performance_manager/persistence/site_data/feature_usage.h",
+    "performance_manager/persistence/site_data/site_data_impl.cc",
+    "performance_manager/persistence/site_data/site_data_impl.h",
+    "performance_manager/persistence/site_data/site_data_reader.cc",
+    "performance_manager/persistence/site_data/site_data_reader.h",
     "performance_manager/persistence/site_data/tab_visibility.h",
     "performance_manager/public/graph/frame_node.h",
     "performance_manager/public/graph/graph.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 5abd281..823405c5 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1403,10 +1403,6 @@
      flag_descriptions::kSuggestionsWithSubStringMatchName,
      flag_descriptions::kSuggestionsWithSubStringMatchDescription, kOsAll,
      FEATURE_VALUE_TYPE(autofill::features::kAutofillTokenPrefixMatching)},
-    {"lcd-text-aa", flag_descriptions::kLcdTextName,
-     flag_descriptions::kLcdTextDescription, kOsDesktop,
-     ENABLE_DISABLE_VALUE_TYPE(switches::kEnableLCDText,
-                               switches::kDisableLCDText)},
     {"enable-offer-store-unmasked-wallet-cards",
      flag_descriptions::kOfferStoreUnmaskedWalletCardsName,
      flag_descriptions::kOfferStoreUnmaskedWalletCardsDescription, kOsAll,
@@ -1506,6 +1502,9 @@
     {"enable-chrome-duet", flag_descriptions::kChromeDuetName,
      flag_descriptions::kChromeDuetDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kChromeDuetFeature)},
+    {"enable-chrome-duet-labels", flag_descriptions::kChromeDuetLabelsName,
+     flag_descriptions::kChromeDuetLabelsDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kChromeDuetLabeled)},
     {"force-enable-home-page-button", flag_descriptions::kHomePageButtonName,
      flag_descriptions::kHomePageButtonDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kHomePageButtonForceEnabled)},
@@ -2217,13 +2216,6 @@
      kOsDesktop,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillSaveCardImprovedUserConsent)},
-    {"enable-autofill-save-credit-card-uses-strike-system",
-     flag_descriptions::kEnableAutofillSaveCreditCardUsesStrikeSystemName,
-     flag_descriptions::
-         kEnableAutofillSaveCreditCardUsesStrikeSystemDescription,
-     kOsAll,
-     FEATURE_VALUE_TYPE(
-         autofill::features::kAutofillSaveCreditCardUsesStrikeSystem)},
     {"enable-autofill-save-credit-card-uses-strike-system-v2",
      flag_descriptions::kEnableAutofillSaveCreditCardUsesStrikeSystemV2Name,
      flag_descriptions::
diff --git a/chrome/browser/android/bookmarks/bookmark_bridge.cc b/chrome/browser/android/bookmarks/bookmark_bridge.cc
index f5b702ba..a85e6c4 100644
--- a/chrome/browser/android/bookmarks/bookmark_bridge.cc
+++ b/chrome/browser/android/bookmarks/bookmark_bridge.cc
@@ -808,7 +808,7 @@
   const BookmarkNode* folder = GetNodeByID(folder_id, type);
   if (!folder || folder->type() == BookmarkNode::URL ||
       !IsFolderAvailable(folder)) {
-    if (!managed_bookmark_service_->managed_node()->empty())
+    if (!managed_bookmark_service_->managed_node()->children().empty())
       folder = managed_bookmark_service_->managed_node();
     else
       folder = bookmark_model_->mobile_node();
@@ -876,7 +876,8 @@
     const BookmarkNode* folder) const {
   // The managed bookmarks folder is not shown if there are no bookmarks
   // configured via policy.
-  if (folder == managed_bookmark_service_->managed_node() && folder->empty())
+  if (folder == managed_bookmark_service_->managed_node() &&
+      folder->children().empty())
     return false;
 
   auto* identity_manager =
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 73530a3c..823c0cd 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -112,6 +112,7 @@
     &kChromeDuetFeature,
     &kChromeDuetAdaptive,
     &kDontAutoHideBrowserControls,
+    &kChromeDuetLabeled,
     &kChromeSmartSelection,
     &kCommandLineOnNonRooted,
     &kContactsPickerSelectAll,
@@ -328,6 +329,9 @@
 const base::Feature kDontAutoHideBrowserControls{
     "DontAutoHideBrowserControls", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kChromeDuetLabeled{"ChromeDuetLabeled",
+                                       base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kChromeSmartSelection{"ChromeSmartSelection",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index 37b2a60..03a93e5 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -44,6 +44,7 @@
 extern const base::Feature kChromeDuetFeature;
 extern const base::Feature kChromeDuetAdaptive;
 extern const base::Feature kDontAutoHideBrowserControls;
+extern const base::Feature kChromeDuetLabeled;
 extern const base::Feature kChromeSmartSelection;
 extern const base::Feature kCommandLineOnNonRooted;
 extern const base::Feature kContactsPickerSelectAll;
diff --git a/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.cc
index 6c1ace0..31eac39 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.cc
@@ -200,6 +200,29 @@
   content_obscures_self_ |= content.Contains(self);
 }
 
+void TabListSceneLayer::PutBackgroundLayer(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& jobj,
+    jint resource_id) {
+  int ui_resource_id = resource_manager_->GetUIResourceId(
+      ui::ANDROID_RESOURCE_TYPE_DYNAMIC, resource_id);
+  if (ui_resource_id == 0)
+    return;
+
+  if (!background_layer_) {
+    background_layer_ = cc::UIResourceLayer::Create();
+    background_layer_->SetIsDrawable(true);
+    own_tree_->AddChild(background_layer_);
+  }
+  DCHECK(background_layer_);
+  background_layer_->SetUIResourceId(ui_resource_id);
+  gfx::Size size =
+      resource_manager_
+          ->GetResource(ui::ANDROID_RESOURCE_TYPE_DYNAMIC, resource_id)
+          ->size();
+  background_layer_->SetBounds(size);
+}
+
 void TabListSceneLayer::OnDetach() {
   SceneLayer::OnDetach();
   for (auto tab : tab_map_)
diff --git a/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.h b/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.h
index 153eeb9..c4ea30b 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.h
+++ b/chrome/browser/android/compositor/scene_layer/tab_list_scene_layer.h
@@ -12,6 +12,7 @@
 
 #include "base/macros.h"
 #include "cc/layers/layer.h"
+#include "cc/layers/ui_resource_layer.h"
 #include "chrome/browser/android/compositor/layer/layer.h"
 #include "chrome/browser/android/compositor/scene_layer/scene_layer.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -107,6 +108,10 @@
                    jfloat side_border_scale,
                    jboolean inset_border);
 
+  void PutBackgroundLayer(JNIEnv* env,
+                          const base::android::JavaParamRef<jobject>& jobj,
+                          jint resource_id);
+
   void OnDetach() override;
   bool ShouldShowBackground() override;
   SkColor GetBackgroundColor() override;
@@ -119,6 +124,8 @@
   TabMap tab_map_;
   std::set<int> visible_tabs_this_frame_;
 
+  scoped_refptr<cc::UIResourceLayer> background_layer_;
+
   bool content_obscures_self_;
   ui::ResourceManager* resource_manager_;
   LayerTitleCache* layer_title_cache_;
diff --git a/chrome/browser/android/feed/feed_host_service_factory.cc b/chrome/browser/android/feed/feed_host_service_factory.cc
index c05bab9..a617c43 100644
--- a/chrome/browser/android/feed/feed_host_service_factory.cc
+++ b/chrome/browser/android/feed/feed_host_service_factory.cc
@@ -108,7 +108,8 @@
   offline_pages::OfflinePageModel* offline_page_model =
       offline_pages::OfflinePageModelFactory::GetForBrowserContext(profile);
   offline_pages::PrefetchService* prefetch_service =
-      offline_pages::PrefetchServiceFactory::GetForBrowserContext(profile);
+      offline_pages::PrefetchServiceFactory::GetForKey(
+          profile->GetProfileKey());
   // Using base::Unretained is safe because the FeedSchedulerHost ensures the
   // |scheduler_host| will outlive the |offline_host|, and calls to
   // |the scheduler_host| are never posted to a message loop.
diff --git a/chrome/browser/autofill/legacy_strike_database_factory.cc b/chrome/browser/autofill/legacy_strike_database_factory.cc
deleted file mode 100644
index ab3d7fa..0000000
--- a/chrome/browser/autofill/legacy_strike_database_factory.cc
+++ /dev/null
@@ -1,51 +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 "chrome/browser/autofill/legacy_strike_database_factory.h"
-
-#include "base/memory/singleton.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
-#include "chrome/browser/profiles/profile.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "components/leveldb_proto/content/proto_database_provider_factory.h"
-
-namespace autofill {
-
-// static
-LegacyStrikeDatabase* LegacyStrikeDatabaseFactory::GetForProfile(
-    Profile* profile) {
-  return static_cast<LegacyStrikeDatabase*>(
-      GetInstance()->GetServiceForBrowserContext(profile, true));
-}
-
-// static
-LegacyStrikeDatabaseFactory* LegacyStrikeDatabaseFactory::GetInstance() {
-  return base::Singleton<LegacyStrikeDatabaseFactory>::get();
-}
-
-LegacyStrikeDatabaseFactory::LegacyStrikeDatabaseFactory()
-    : BrowserContextKeyedServiceFactory(
-          "AutofillLegacyStrikeDatabase",
-          BrowserContextDependencyManager::GetInstance()) {
-  DependsOn(leveldb_proto::ProtoDatabaseProviderFactory::GetInstance());
-}
-
-LegacyStrikeDatabaseFactory::~LegacyStrikeDatabaseFactory() {}
-
-KeyedService* LegacyStrikeDatabaseFactory::BuildServiceInstanceFor(
-    content::BrowserContext* context) const {
-  Profile* profile = Profile::FromBrowserContext(context);
-
-  leveldb_proto::ProtoDatabaseProvider* db_provider =
-      leveldb_proto::ProtoDatabaseProviderFactory::GetInstance()->GetForKey(
-          profile->GetProfileKey());
-
-  // Note: This instance becomes owned by an object that never gets destroyed,
-  // effectively leaking it until browser close. Only one is created per
-  // profile, and closing-then-opening a profile returns the same instance.
-  return new LegacyStrikeDatabase(db_provider, profile->GetPath());
-}
-
-}  // namespace autofill
diff --git a/chrome/browser/autofill/legacy_strike_database_factory.h b/chrome/browser/autofill/legacy_strike_database_factory.h
deleted file mode 100644
index 1da13aa..0000000
--- a/chrome/browser/autofill/legacy_strike_database_factory.h
+++ /dev/null
@@ -1,48 +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 CHROME_BROWSER_AUTOFILL_LEGACY_STRIKE_DATABASE_FACTORY_H_
-#define CHROME_BROWSER_AUTOFILL_LEGACY_STRIKE_DATABASE_FACTORY_H_
-
-#include "base/compiler_specific.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-#include "components/keyed_service/core/keyed_service.h"
-
-namespace base {
-template <typename T>
-struct DefaultSingletonTraits;
-}
-
-class Profile;
-
-namespace autofill {
-
-class LegacyStrikeDatabase;
-
-// Singleton that owns all LegacyStrikeDatabases and associates them with
-// Profiles.
-class LegacyStrikeDatabaseFactory : public BrowserContextKeyedServiceFactory {
- public:
-  // Returns the LegacyStrikeDatabase for |profile|, creating it if it is not
-  // yet created.
-  static LegacyStrikeDatabase* GetForProfile(Profile* profile);
-
-  static LegacyStrikeDatabaseFactory* GetInstance();
-
- private:
-  friend struct base::DefaultSingletonTraits<LegacyStrikeDatabaseFactory>;
-
-  LegacyStrikeDatabaseFactory();
-  ~LegacyStrikeDatabaseFactory() override;
-
-  // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* profile) const override;
-
-  DISALLOW_COPY_AND_ASSIGN(LegacyStrikeDatabaseFactory);
-};
-
-}  // namespace autofill
-
-#endif  // CHROME_BROWSER_AUTOFILL_LEGACY_STRIKE_DATABASE_FACTORY_H_
diff --git a/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc b/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc
index 779b937..ef5ece5 100644
--- a/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc
+++ b/chrome/browser/bookmarks/managed_bookmark_service_unittest.cc
@@ -52,7 +52,7 @@
   DCHECK(managed);
 
   ASSERT_TRUE(managed->managed_node());
-  EXPECT_TRUE(managed->managed_node()->empty());
+  EXPECT_TRUE(managed->managed_node()->children().empty());
   EXPECT_FALSE(managed->managed_node()->IsVisible());
 }
 
@@ -166,9 +166,9 @@
 
 TEST_F(ManagedBookmarkServiceTest, LoadInitial) {
   // Verifies that the initial load picks up the initial policy too.
-  EXPECT_TRUE(model_->bookmark_bar_node()->empty());
-  EXPECT_TRUE(model_->other_node()->empty());
-  EXPECT_FALSE(managed_->managed_node()->empty());
+  EXPECT_TRUE(model_->bookmark_bar_node()->children().empty());
+  EXPECT_TRUE(model_->other_node()->children().empty());
+  EXPECT_FALSE(managed_->managed_node()->children().empty());
   EXPECT_TRUE(managed_->managed_node()->IsVisible());
 
   std::unique_ptr<base::DictionaryValue> expected(CreateExpectedTree());
@@ -239,7 +239,7 @@
   prefs_->RemoveManagedPref(bookmarks::prefs::kManagedBookmarks);
   Mock::VerifyAndClearExpectations(&observer_);
 
-  EXPECT_TRUE(managed_->managed_node()->empty());
+  EXPECT_TRUE(managed_->managed_node()->children().empty());
   EXPECT_FALSE(managed_->managed_node()->IsVisible());
 }
 
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index db39e7ea..b42ef0e6 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -22,7 +22,6 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "chrome/browser/android/feed/feed_host_service_factory.h"
-#include "chrome/browser/autofill/legacy_strike_database_factory.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/autofill/strike_database_factory.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
@@ -62,7 +61,6 @@
 #include "chrome/common/buildflags.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
@@ -842,10 +840,7 @@
           delete_end_);
       web_data_service->RemoveAutofillDataModifiedBetween(
           delete_begin_, delete_end_);
-
-      // Clear out the Autofill LegacyStrikeDatabase in its entirety.
-      // Both StrikeDatabase and LegacyStrikeDatabase use data from the same
-      // ProtoDatabase, so only one of them needs to call ClearAllStrikes(~).
+      // Clear out the Autofill StrikeDatabase in its entirety.
       // TODO(crbug.com/884817): Respect |delete_begin_| and |delete_end_| and
       // only clear out entries whose last strikes were created in that
       // timeframe.
@@ -858,17 +853,6 @@
             autofill::StrikeDatabaseFactory::GetForProfile(profile_);
         if (strike_database)
           strike_database->ClearAllStrikes();
-      } else if (base::FeatureList::IsEnabled(
-                     autofill::features::
-                         kAutofillSaveCreditCardUsesStrikeSystem)) {
-        autofill::LegacyStrikeDatabase* legacy_strike_database =
-            autofill::LegacyStrikeDatabaseFactory::GetForProfile(profile_);
-        if (legacy_strike_database) {
-          legacy_strike_database->ClearAllStrikes(
-              base::AdaptCallbackForRepeating(
-                  IgnoreArgument<bool>(CreateTaskCompletionClosure(
-                      TracingDataType::kLegacyStrikes))));
-        }
       }
 
       // Ask for a call back when the above calls are finished.
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index b091e5f0..df7646a 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -27,7 +27,6 @@
 #include "base/test/test_timeouts.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/browser/autofill/legacy_strike_database_factory.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/autofill/strike_database_factory.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
@@ -58,7 +57,6 @@
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
@@ -1009,29 +1007,6 @@
 };
 
 namespace autofill {
-// LegacyStrikeDatabaseTester is in the autofill namespace since
-// LegacyStrikeDatabase declares it as a friend in the autofill namespace.
-class LegacyStrikeDatabaseTester {
- public:
-  explicit LegacyStrikeDatabaseTester(Profile* profile)
-      : legacy_strike_database_(
-            autofill::LegacyStrikeDatabaseFactory::GetForProfile(profile)) {}
-
-  bool IsEmpty() {
-    int num_keys;
-    base::RunLoop run_loop;
-    legacy_strike_database_->LoadKeys(base::BindLambdaForTesting(
-        [&](bool success, std::unique_ptr<std::vector<std::string>> keys) {
-          num_keys = keys.get()->size();
-          run_loop.Quit();
-        }));
-    run_loop.Run();
-    return (num_keys == 0);
-  }
-
- private:
-  autofill::LegacyStrikeDatabase* legacy_strike_database_;
-};
 
 // StrikeDatabaseTester is in the autofill namespace since
 // StrikeDatabase declares it as a friend in the autofill namespace.
@@ -1731,31 +1706,6 @@
 }
 
 TEST_F(ChromeBrowsingDataRemoverDelegateTest,
-       LegacyStrikeDatabaseEmptyOnAutofillRemoveEverything) {
-  GetProfile()->CreateWebDataService();
-  RemoveAutofillTester tester(GetProfile());
-
-  ASSERT_FALSE(tester.HasProfile());
-  tester.AddProfilesAndCards();
-  ASSERT_TRUE(tester.HasProfile());
-
-  autofill::LegacyStrikeDatabaseTester legacy_strike_database_tester(
-      GetProfile());
-  BlockUntilBrowsingDataRemoved(
-      base::Time(), base::Time::Max(),
-      ChromeBrowsingDataRemoverDelegate::DATA_TYPE_FORM_DATA, false);
-
-  // LegacyStrikeDatabase should be empty when DATA_TYPE_FORM_DATA browsing data
-  // gets deleted.
-  ASSERT_TRUE(legacy_strike_database_tester.IsEmpty());
-  EXPECT_EQ(ChromeBrowsingDataRemoverDelegate::DATA_TYPE_FORM_DATA,
-            GetRemovalMask());
-  EXPECT_EQ(content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
-            GetOriginTypeMask());
-  ASSERT_FALSE(tester.HasProfile());
-}
-
-TEST_F(ChromeBrowsingDataRemoverDelegateTest,
        StrikeDatabaseEmptyOnAutofillRemoveEverything) {
   GetProfile()->CreateWebDataService();
   RemoveAutofillTester tester(GetProfile());
diff --git a/chrome/browser/browsing_data/cookies_tree_model.cc b/chrome/browser/browsing_data/cookies_tree_model.cc
index fcfb230..b09b2f2 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model.cc
@@ -1451,7 +1451,7 @@
   cookie_node->DeleteStoredObjects();
   CookieTreeNode* parent_node = cookie_node->parent();
   Remove(parent_node, cookie_node);
-  if (parent_node->empty())
+  if (parent_node->children().empty())
     DeleteCookieNode(parent_node);
 }
 
diff --git a/chrome/browser/browsing_data/cookies_tree_model_unittest.cc b/chrome/browser/browsing_data/cookies_tree_model_unittest.cc
index d0a699e..504f3bb 100644
--- a/chrome/browser/browsing_data/cookies_tree_model_unittest.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model_unittest.cc
@@ -230,13 +230,11 @@
       CookieTreeNode::DetailedInfo::NodeType node_type,
       content_settings::CookieSettings* cookie_settings,
       const GURL& expected_url) {
-    if (!node->empty()) {
-      for (int i = 0; i < node->child_count(); ++i) {
-        const CookieTreeNode* child = node->GetChild(i);
-        CheckContentSettingsUrlForHostNodes(child,
-                                            child->GetDetailedInfo().node_type,
-                                            cookie_settings, expected_url);
-      }
+    for (int i = 0; i < node->child_count(); ++i) {
+      const CookieTreeNode* child = node->GetChild(i);
+      CheckContentSettingsUrlForHostNodes(child,
+                                          child->GetDetailedInfo().node_type,
+                                          cookie_settings, expected_url);
     }
 
     ASSERT_EQ(node_type, node->GetDetailedInfo().node_type);
@@ -261,7 +259,7 @@
   std::string GetNodesOfChildren(
       const CookieTreeNode* node,
       CookieTreeNode::DetailedInfo::NodeType node_type) {
-    if (!node->empty()) {
+    if (!node->children().empty()) {
       std::string retval;
       for (int i = 0; i < node->child_count(); ++i) {
         retval += GetNodesOfChildren(node->GetChild(i), node_type);
diff --git a/chrome/browser/chromeos/arc/boot_phase_monitor/arc_instance_throttle.cc b/chrome/browser/chromeos/arc/boot_phase_monitor/arc_instance_throttle.cc
index 6c20153..dc902e09 100644
--- a/chrome/browser/chromeos/arc/boot_phase_monitor/arc_instance_throttle.cc
+++ b/chrome/browser/chromeos/arc/boot_phase_monitor/arc_instance_throttle.cc
@@ -46,21 +46,34 @@
 void ArcInstanceThrottle::OnWindowActivated(ActivationReason reason,
                                             aura::Window* gained_active,
                                             aura::Window* lost_active) {
+  // The current active window defines whether the instance should be throttled
+  // or not i.e. if it's a native window then throttle Android, if it's an
+  // Android window then unthrottle it.
+  //
+  // However, the app list needs to be handled differently. The app list is
+  // deemed as an temporary overlay over the user's main window for browsing
+  // through or selecting an app. Consequently, if the app list is brought over
+  // a Chrome window then IsAppListWindow(gained_active) is true and this event
+  // is ignored. The instance continues to be throttled. Once the app list is
+  // closed then IsAppListWindow(lost_active) is true and the instance continues
+  // to be throttled. Similarly, if the app list is brought over an Android
+  // window, the instance continues to be unthrottled.
+  //
   // On launching an app from the app list on Chrome OS the following events
   // happen -
   //
-  // 1. When app list is brought up it's treated as a Chrome window and the
-  // instance is throttled.
+  // - When an Android app icon is clicked the instance is unthrottled. This
+  // logic resides in |LaunchAppWithIntent| in
+  // src/chrome/browser/ui/app_list/arc/arc_app_utils.cc.
   //
-  // 2. When an app icon is clicked the instance is unthrottled.
-  //
-  // 3. Between the time the app opens there is a narrow slice of time where
+  // - Between the time the app opens there is a narrow slice of time where
   // this callback is triggered with |lost_active| equal to the app list window
   // and the gained active possibly a native window. Without the check below the
   // instance will be throttled again, further delaying the app launch. If the
-  // app was a native one then the instance was throttled anyway in step 1.
-  if (IsAppListWindow(lost_active))
+  // app was a native one then the instance was throttled anyway.
+  if (IsAppListWindow(lost_active) || IsAppListWindow(gained_active))
     return;
+
   ThrottleInstance(gained_active);
 }
 
diff --git a/chrome/browser/chromeos/first_run/chromeos_first_run_browsertest.cc b/chrome/browser/chromeos/first_run/chromeos_first_run_browsertest.cc
index f16a91d..2329b72 100644
--- a/chrome/browser/chromeos/first_run/chromeos_first_run_browsertest.cc
+++ b/chrome/browser/chromeos/first_run/chromeos_first_run_browsertest.cc
@@ -151,10 +151,6 @@
 
   views::Widget* GetOverlayWidget() { return controller()->widget_.get(); }
 
-  void FlushForTesting() {
-    controller()->first_run_helper_ptr_.FlushForTesting();
-  }
-
  private:
   std::unique_ptr<ash::SystemTrayTestApi> tray_test_api_;
   std::string current_step_name_;
@@ -170,12 +166,10 @@
   LaunchTutorial();
   WaitForInitialization();
   WaitForStep(first_run::kAppListStep);
-  FlushForTesting();
   EXPECT_FALSE(IsTrayBubbleOpen());
 
   AdvanceStep();
   WaitForStep(first_run::kTrayStep);
-  FlushForTesting();
   EXPECT_TRUE(IsTrayBubbleOpen());
 
   AdvanceStep();
@@ -194,7 +188,6 @@
   LaunchTutorial();
   WaitForInitialization();
   WaitForStep(first_run::kAppListStep);
-  FlushForTesting();
 
   // Simulate the browser opening a modal dialog.
   views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
@@ -223,7 +216,6 @@
   WaitForStep(first_run::kAppListStep);
   AdvanceStep();
   WaitForStep(first_run::kTrayStep);
-  FlushForTesting();
   EXPECT_TRUE(IsTrayBubbleOpen());
 
   // Press the escape key.
diff --git a/chrome/browser/chromeos/first_run/first_run_controller.cc b/chrome/browser/chromeos/first_run/first_run_controller.cc
index 1ff37236..9eb47e2 100644
--- a/chrome/browser/chromeos/first_run/first_run_controller.cc
+++ b/chrome/browser/chromeos/first_run/first_run_controller.cc
@@ -4,10 +4,9 @@
 
 #include "chrome/browser/chromeos/first_run/first_run_controller.h"
 
+#include "ash/public/cpp/first_run_helper.h"
 #include "ash/public/cpp/shelf_prefs.h"
 #include "ash/public/cpp/shell_window_ids.h"
-#include "ash/public/interfaces/constants.mojom.h"
-#include "ash/public/interfaces/first_run_helper.mojom.h"
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
@@ -22,8 +21,6 @@
 #include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "components/user_manager/user_manager.h"
-#include "content/public/common/service_manager_connection.h"
-#include "services/service_manager/public/cpp/connector.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/views/widget/widget.h"
@@ -115,12 +112,8 @@
   user_profile_ = ProfileHelper::Get()->GetProfileByUserUnsafe(
       user_manager->GetActiveUser());
 
-  content::ServiceManagerConnection::GetForProcess()
-      ->GetConnector()
-      ->BindInterface(ash::mojom::kServiceName, &first_run_helper_ptr_);
-  ash::mojom::FirstRunHelperClientPtr client_ptr;
-  binding_.Bind(mojo::MakeRequest(&client_ptr));
-  first_run_helper_ptr_->Start(std::move(client_ptr));
+  first_run_helper_ = ash::FirstRunHelper::Start(
+      base::BindOnce(&FirstRunController::OnCancelled, base::Unretained(this)));
 
   widget_ = CreateFirstRunWidget();
   FirstRunView* view = new FirstRunView();
@@ -150,7 +143,7 @@
   if (actor_)
     actor_->set_delegate(NULL);
   actor_ = NULL;
-  first_run_helper_ptr_->Stop();
+  first_run_helper_.reset();
   // Close the widget.
   widget_.reset();
 }
@@ -232,4 +225,3 @@
 }
 
 }  // namespace chromeos
-
diff --git a/chrome/browser/chromeos/first_run/first_run_controller.h b/chrome/browser/chromeos/first_run/first_run_controller.h
index 78f7ce3..82d37eb 100644
--- a/chrome/browser/chromeos/first_run/first_run_controller.h
+++ b/chrome/browser/chromeos/first_run/first_run_controller.h
@@ -12,15 +12,18 @@
 #include <vector>
 
 #include "ash/public/cpp/shelf_types.h"
-#include "ash/public/interfaces/first_run_helper.mojom.h"
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "chrome/browser/ui/webui/chromeos/first_run/first_run_actor.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "ui/gfx/geometry/size.h"
 
 class Profile;
 
+namespace ash {
+class FirstRunHelper;
+}
+
 namespace content {
 class WebContents;
 }
@@ -40,8 +43,7 @@
 // FirstRunController creates and manages first-run tutorial.
 // Object manages its lifetime and deletes itself after completion of the
 // tutorial.
-class FirstRunController : public FirstRunActor::Delegate,
-                           public ash::mojom::FirstRunHelperClient {
+class FirstRunController : public FirstRunActor::Delegate {
  public:
   ~FirstRunController() override;
 
@@ -60,9 +62,7 @@
   // Stops the tutorial and records early cancellation metrics.
   void Cancel();
 
-  const ash::mojom::FirstRunHelperPtr& first_run_helper_ptr() {
-    return first_run_helper_ptr_;
-  }
+  ash::FirstRunHelper* first_run_helper() { return first_run_helper_.get(); }
 
  private:
   friend class FirstRunUIBrowserTest;
@@ -82,8 +82,7 @@
   void OnActorFinalized() override;
   void OnActorDestroyed() override;
 
-  // ash::mojom::FirstRunHelperClient:
-  void OnCancelled() override;
+  void OnCancelled();
 
   void RegisterSteps();
   void ShowNextStep();
@@ -94,11 +93,7 @@
   // FirstRunController.
   FirstRunActor* actor_;
 
-  // Mojo interface for manipulating and retrieving information from ash.
-  ash::mojom::FirstRunHelperPtr first_run_helper_ptr_;
-
-  // Binding for callbacks from ash.
-  mojo::Binding<ash::mojom::FirstRunHelperClient> binding_{this};
+  std::unique_ptr<ash::FirstRunHelper> first_run_helper_;
 
   // List of all tutorial steps.
   std::vector<std::unique_ptr<first_run::Step>> steps_;
diff --git a/chrome/browser/chromeos/first_run/steps/app_list_step.cc b/chrome/browser/chromeos/first_run/steps/app_list_step.cc
index ffc0123..5fde8ee 100644
--- a/chrome/browser/chromeos/first_run/steps/app_list_step.cc
+++ b/chrome/browser/chromeos/first_run/steps/app_list_step.cc
@@ -4,8 +4,7 @@
 
 #include "chrome/browser/chromeos/first_run/steps/app_list_step.h"
 
-#include "ash/public/interfaces/first_run_helper.mojom.h"
-#include "base/bind.h"
+#include "ash/public/cpp/first_run_helper.h"
 #include "chrome/browser/chromeos/first_run/first_run_controller.h"
 #include "chrome/browser/chromeos/first_run/step_names.h"
 #include "chrome/browser/ui/webui/chromeos/first_run/first_run_actor.h"
@@ -26,12 +25,8 @@
 
 void AppListStep::DoShow() {
   // FirstRunController owns this object, so use Unretained.
-  first_run_controller()->first_run_helper_ptr()->GetAppListButtonBounds(
-      base::BindOnce(&AppListStep::ShowWithButtonBounds,
-                     base::Unretained(this)));
-}
-
-void AppListStep::ShowWithButtonBounds(const gfx::Rect& screen_bounds) {
+  gfx::Rect screen_bounds =
+      first_run_controller()->first_run_helper()->GetAppListButtonBounds();
   gfx::Point center = screen_bounds.CenterPoint();
   actor()->AddRoundHole(center.x(), center.y(), kCircleRadius);
   actor()->ShowStepPointingTo(name(), center.x(), center.y(), kCircleRadius);
@@ -39,4 +34,3 @@
 
 }  // namespace first_run
 }  // namespace chromeos
-
diff --git a/chrome/browser/chromeos/first_run/steps/app_list_step.h b/chrome/browser/chromeos/first_run/steps/app_list_step.h
index e3b3b0f1..6587c26 100644
--- a/chrome/browser/chromeos/first_run/steps/app_list_step.h
+++ b/chrome/browser/chromeos/first_run/steps/app_list_step.h
@@ -8,10 +8,6 @@
 #include "base/macros.h"
 #include "chrome/browser/chromeos/first_run/step.h"
 
-namespace gfx {
-class Rect;
-}
-
 namespace chromeos {
 namespace first_run {
 
@@ -23,8 +19,6 @@
   // Step:
   void DoShow() override;
 
-  void ShowWithButtonBounds(const gfx::Rect& screen_bounds);
-
   DISALLOW_COPY_AND_ASSIGN(AppListStep);
 };
 
@@ -32,4 +26,3 @@
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_CHROMEOS_FIRST_RUN_STEPS_APP_LIST_STEP_H_
-
diff --git a/chrome/browser/chromeos/first_run/steps/tray_step.cc b/chrome/browser/chromeos/first_run/steps/tray_step.cc
index 404351c..e31c862d 100644
--- a/chrome/browser/chromeos/first_run/steps/tray_step.cc
+++ b/chrome/browser/chromeos/first_run/steps/tray_step.cc
@@ -4,9 +4,8 @@
 
 #include "chrome/browser/chromeos/first_run/steps/tray_step.h"
 
+#include "ash/public/cpp/first_run_helper.h"
 #include "ash/public/cpp/shelf_types.h"
-#include "ash/public/interfaces/first_run_helper.mojom.h"
-#include "base/bind.h"
 #include "base/i18n/rtl.h"
 #include "chrome/browser/chromeos/first_run/first_run_controller.h"
 #include "chrome/browser/chromeos/first_run/step_names.h"
@@ -22,11 +21,8 @@
 
 void TrayStep::DoShow() {
   // FirstRunController owns this object, so use Unretained.
-  first_run_controller()->first_run_helper_ptr()->OpenTrayBubble(
-      base::BindOnce(&TrayStep::ShowWithBubbleBounds, base::Unretained(this)));
-}
-
-void TrayStep::ShowWithBubbleBounds(const gfx::Rect& bounds) {
+  gfx::Rect bounds =
+      first_run_controller()->first_run_helper()->OpenTrayBubble();
   actor()->AddRectangularHole(bounds.x(), bounds.y(), bounds.width(),
       bounds.height());
   FirstRunActor::StepPosition position;
@@ -45,4 +41,3 @@
 
 }  // namespace first_run
 }  // namespace chromeos
-
diff --git a/chrome/browser/chromeos/first_run/steps/tray_step.h b/chrome/browser/chromeos/first_run/steps/tray_step.h
index d27636e..b92ed53 100644
--- a/chrome/browser/chromeos/first_run/steps/tray_step.h
+++ b/chrome/browser/chromeos/first_run/steps/tray_step.h
@@ -8,10 +8,6 @@
 #include "base/macros.h"
 #include "chrome/browser/chromeos/first_run/step.h"
 
-namespace gfx {
-class Rect;
-}
-
 namespace chromeos {
 namespace first_run {
 
@@ -23,9 +19,6 @@
   // Step:
   void DoShow() override;
 
-  // Shows the step when the bubble bounds are available.
-  void ShowWithBubbleBounds(const gfx::Rect& screen_bounds);
-
   DISALLOW_COPY_AND_ASSIGN(TrayStep);
 };
 
@@ -33,4 +26,3 @@
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_CHROMEOS_FIRST_RUN_STEPS_TRAY_STEP_H_
-
diff --git a/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc b/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc
index 42797b7d..388211c 100644
--- a/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc
+++ b/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc
@@ -39,7 +39,6 @@
 #include "chromeos/dbus/cryptohome/rpc.pb.h"
 #include "chromeos/login/auth/key.h"
 #include "chromeos/login/auth/mock_auth_status_consumer.h"
-#include "chromeos/login/auth/mock_url_fetchers.h"
 #include "chromeos/login/auth/test_attempt_state.h"
 #include "chromeos/login/auth/user_context.h"
 #include "chromeos/login/login_state/login_state.h"
@@ -50,7 +49,6 @@
 #include "crypto/nss_key_util.h"
 #include "crypto/nss_util_internal.h"
 #include "crypto/scoped_test_nss_chromeos_user.h"
-#include "google_apis/gaia/mock_url_fetcher_factory.h"
 #include "net/base/net_errors.h"
 #include "net/url_request/url_request_status.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
index 90b9693..c84027bc 100644
--- a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
@@ -51,7 +51,6 @@
 #include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
 #include "chromeos/login/auth/key.h"
-#include "chromeos/login/auth/mock_url_fetchers.h"
 #include "chromeos/login/auth/stub_authenticator_builder.h"
 #include "chromeos/login/auth/user_context.h"
 #include "chromeos/network/network_state_test_helper.h"
@@ -75,7 +74,6 @@
 #include "components/user_manager/user_type.h"
 #include "content/public/test/mock_notification_observer.h"
 #include "content/public/test/test_utils.h"
-#include "google_apis/gaia/mock_url_fetcher_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -253,9 +251,6 @@
   std::unique_ptr<MockLoginDisplay> mock_login_display_;
   std::unique_ptr<MockLoginDisplayHost> mock_login_display_host_;
 
-  // Mock URLFetcher.
-  MockURLFetcherFactory<SuccessFetcher> factory_;
-
   const AccountId gaia_account_id_ =
       AccountId::FromUserEmailGaiaId(kUsername, kGaiaID);
   const AccountId ad_account_id_ =
diff --git a/chrome/browser/chromeos/login/saml/saml_browsertest.cc b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
index 6541da3..a754826 100644
--- a/chrome/browser/chromeos/login/saml/saml_browsertest.cc
+++ b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
@@ -472,24 +472,25 @@
   DISALLOW_COPY_AND_ASSIGN(SamlTest);
 };
 
-// Tests that signin frame should have 'saml' class and 'cancel' button is
-// visible when SAML IdP page is loaded. And 'cancel' button goes back to
-// gaia on clicking.
+// Tests that signin frame should display the SAML notice and the 'back' button
+// when SAML IdP page is loaded. And the 'back' button goes back to gaia on
+// clicking.
 IN_PROC_BROWSER_TEST_F(SamlTest, SamlUI) {
   fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
   StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
 
   // Saml flow UI expectations.
   test::OobeJS().ExpectVisible("saml-notice-container");
+  test::OobeJS().ExpectVisible("signin-back-button");
   std::string js = "$('saml-notice-message').textContent.indexOf('$Host') > -1";
   base::ReplaceSubstringsAfterOffset(&js, 0, "$Host", kIdPHost);
   test::OobeJS().ExpectTrue(js);
 
-  content::DOMMessageQueue message_queue;  // Observe before 'cancel'.
+  content::DOMMessageQueue message_queue;  // Observe before 'back'.
   SetupAuthFlowChangeListener();
-  // Click on 'cancel'.
+  // Click on 'back'.
   content::ExecuteScriptAsync(GetLoginUI()->GetWebContents(),
-                              "$('gaia-navigation').$.closeButton.click();");
+                             "$('signin-back-button').fire('tap');");
 
   // Auth flow should change back to Gaia.
   std::string message;
diff --git a/chrome/browser/chromeos/login/webview_login_browsertest.cc b/chrome/browser/chromeos/login/webview_login_browsertest.cc
index 33dbdbe..2f06ef7 100644
--- a/chrome/browser/chromeos/login/webview_login_browsertest.cc
+++ b/chrome/browser/chromeos/login/webview_login_browsertest.cc
@@ -180,21 +180,15 @@
 
  protected:
   void ExpectIdentifierPage() {
-    // First page: no back button, no close button, refresh button, #identifier
-    // input field.
-    test::OobeJS().ExpectTrue("!$('gaia-navigation').backVisible");
-    test::OobeJS().ExpectTrue("!$('gaia-navigation').closeVisible");
-    test::OobeJS().ExpectTrue("$('gaia-navigation').refreshVisible");
+    // First page: back button, #identifier input field.
+    test::OobeJS().ExpectVisible("signin-back-button");
     test::OobeJS().ExpectTrue(
         "$('signin-frame').src.indexOf('#identifier') != -1");
   }
 
   void ExpectPasswordPage() {
-    // Second page: back button, close button, no refresh button,
-    // #challengepassword input field.
-    test::OobeJS().ExpectTrue("$('gaia-navigation').backVisible");
-    test::OobeJS().ExpectTrue("$('gaia-navigation').closeVisible");
-    test::OobeJS().ExpectTrue("!$('gaia-navigation').refreshVisible");
+    // Second page: back button, #challengepassword input field.
+    test::OobeJS().ExpectVisible("signin-back-button");
     test::OobeJS().ExpectTrue(
         "$('signin-frame').src.indexOf('#challengepassword') != -1");
   }
@@ -276,7 +270,7 @@
   ExpectPasswordPage();
 
   // Click back to identifier page.
-  test::OobeJS().TapOnPath({"gaia-navigation", "backButton"});
+  test::OobeJS().TapOn("signin-back-button");
   WaitForGaiaPageBackButtonUpdate();
   ExpectIdentifierPage();
   // Click next to password page, user id is remembered.
diff --git a/chrome/browser/chromeos/scheduler_configuration_manager.cc b/chrome/browser/chromeos/scheduler_configuration_manager.cc
index ffe6024..ca6ef4a 100644
--- a/chrome/browser/chromeos/scheduler_configuration_manager.cc
+++ b/chrome/browser/chromeos/scheduler_configuration_manager.cc
@@ -77,7 +77,7 @@
   } else if (!feature_param_value.empty()) {
     config_name = feature_param_value;
   } else {
-    config_name = debugd::scheduler_configuration::kConservativeScheduler;
+    config_name = debugd::scheduler_configuration::kPerformanceScheduler;
   }
 
   // NB: Also send an update when the config gets reset to let the system pick
diff --git a/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc b/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc
index 331f13a..d614f591 100644
--- a/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc
+++ b/chrome/browser/chromeos/scheduler_configuration_manager_unittest.cc
@@ -54,7 +54,7 @@
   // Correct default is used when there is no configured value.
   SchedulerConfigurationManager manager(&debug_daemon_client_, &local_state_);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(debugd::scheduler_configuration::kConservativeScheduler,
+  EXPECT_EQ(debugd::scheduler_configuration::kPerformanceScheduler,
             debug_daemon_client_.scheduler_configuration_name());
 
   // Change user pref, which should trigger a config change.
@@ -77,7 +77,7 @@
   // Dropping the policy as well reverts to the default configuration.
   local_state_.RemoveManagedPref(prefs::kSchedulerConfiguration);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(debugd::scheduler_configuration::kConservativeScheduler,
+  EXPECT_EQ(debugd::scheduler_configuration::kPerformanceScheduler,
             debug_daemon_client_.scheduler_configuration_name());
 }
 
diff --git a/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc b/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc
index 8b04de24..4a0be82 100644
--- a/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc
+++ b/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.cc
@@ -137,7 +137,7 @@
     *error = bookmark_api_constants::kModifyManagedError;
     return false;
   }
-  if (node->is_folder() && !node->empty() && !recursive) {
+  if (node->is_folder() && !node->children().empty() && !recursive) {
     *error = bookmark_api_constants::kFolderNotEmptyError;
     return false;
   }
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index f7cb22f..9f20b4b 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -369,6 +369,13 @@
   EXPECT_EQ(content::PAGE_TYPE_ERROR, NavigateAndGetPageType(url));
 }
 
+// Tests chrome.webNavigation APIs.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, FilteredEvents) {
+  ASSERT_TRUE(RunExtensionTest(
+      "service_worker/worker_based_background/filtered_events"))
+      << message_;
+}
+
 // Listens for |message| from extension Service Worker early so that tests can
 // wait for the message on startup (and not miss it).
 class ServiceWorkerWithEarlyMessageListenerTest
@@ -1683,13 +1690,6 @@
   EXPECT_TRUE(push_message_listener.WaitUntilSatisfied());
   run_loop.Run();  // Wait until the message is handled by push service.
 }
-
-IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, FilteredEvents) {
-  // Extensions APIs from SW are only enabled on trunk.
-  ScopedCurrentChannel current_channel_override(version_info::Channel::UNKNOWN);
-  ASSERT_TRUE(RunExtensionTest("service_worker/filtered_events"));
-}
-
 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, MimeHandlerView) {
   ASSERT_TRUE(RunExtensionTest("service_worker/mime_handler_view"));
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index a1ac790..16b3ccc 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -902,6 +902,11 @@
     "expiry_milestone": 79
   },
   {
+    "name": "enable-chrome-duet-labels",
+    "owners": [ "amaralp" ],
+    "expiry_milestone": 79
+  },
+  {
     "name": "enable-chromeos-account-manager",
     "owners": [ "sinhak@chromium.org" ],
     "expiry_milestone": 76
@@ -2063,8 +2068,8 @@
   },
   {
     "name": "happiness-tracking-surveys-for-desktop",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
+    "owners": [ "robliao" ],
+    "expiry_milestone": 78
   },
   {
     "name": "hardware-media-key-handling",
@@ -2138,11 +2143,6 @@
     "expiry_milestone": 80
   },
   {
-    "name": "lcd-text-aa",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "list-all-display-modes",
     "owners": [ "//ui/display/OWNERS" ],
     // This flag is used for debugging and development purposes to list all
@@ -2277,8 +2277,8 @@
   },
   {
     "name": "no-credit-card-abort",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
+    "owners": [ "//components/payments/OWNERS" ],
+    "expiry_milestone": 79
   },
   {
     "name": "ntp-customization-menu-v2",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 732b61c2a..e73c08ee 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -469,12 +469,6 @@
     "If enabled, adds a [No thanks] button to credit card save bubbles and "
     "updates their title headers.";
 
-const char kEnableAutofillSaveCreditCardUsesStrikeSystemName[] =
-    "Enable limit on offering to save the same credit card repeatedly";
-const char kEnableAutofillSaveCreditCardUsesStrikeSystemDescription[] =
-    "If enabled, prevents popping up the credit card offer-to-save prompt if "
-    "it has repeatedly been ignored, declined, or failed.";
-
 const char kEnableAutofillSaveCreditCardUsesStrikeSystemV2Name[] =
     "Enable limit on offering to save the same credit card repeatedly using "
     "the updated strike system implementation";
@@ -1123,11 +1117,6 @@
     "Keep a render process alive when the process has a pending fetch request "
     "with `keepalive' specified.";
 
-const char kLcdTextName[] = "LCD text antialiasing";
-const char kLcdTextDescription[] =
-    "If disabled, text is rendered with grayscale antialiasing instead of LCD "
-    "(subpixel) when doing accelerated compositing.";
-
 const char kLoadMediaRouterComponentExtensionName[] =
     "Load Media Router Component Extension";
 const char kLoadMediaRouterComponentExtensionDescription[] =
@@ -2102,6 +2091,10 @@
 const char kChromeDuetDescription[] =
     "Enables Chrome Duet, split toolbar Chrome Home, on Android.";
 
+const char kChromeDuetLabelsName[] = "Chrome Duet Labels";
+const char kChromeDuetLabelsDescription[] =
+    "Enables Chrome Duet (split toolbar) labels.";
+
 const char kClearOldBrowsingDataName[] = "Clear older browsing data";
 const char kClearOldBrowsingDataDescription[] =
     "Enables clearing of browsing data which is older than a given time "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 4282207..112a05c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -296,9 +296,6 @@
 extern const char kEnableAutofillSaveCardImprovedUserConsentName[];
 extern const char kEnableAutofillSaveCardImprovedUserConsentDescription[];
 
-extern const char kEnableAutofillSaveCreditCardUsesStrikeSystemName[];
-extern const char kEnableAutofillSaveCreditCardUsesStrikeSystemDescription[];
-
 extern const char kEnableAutofillSaveCreditCardUsesStrikeSystemV2Name[];
 extern const char kEnableAutofillSaveCreditCardUsesStrikeSystemV2Description[];
 
@@ -684,9 +681,6 @@
 extern const char kKeepAliveRendererForKeepaliveRequestsName[];
 extern const char kKeepAliveRendererForKeepaliveRequestsDescription[];
 
-extern const char kLcdTextName[];
-extern const char kLcdTextDescription[];
-
 extern const char kLoadMediaRouterComponentExtensionName[];
 extern const char kLoadMediaRouterComponentExtensionDescription[];
 
@@ -1253,6 +1247,9 @@
 extern const char kChromeDuetName[];
 extern const char kChromeDuetDescription[];
 
+extern const char kChromeDuetLabelsName[];
+extern const char kChromeDuetLabelsDescription[];
+
 extern const char kClearOldBrowsingDataName[];
 extern const char kClearOldBrowsingDataDescription[];
 
diff --git a/chrome/browser/gcm/gcm_profile_service_factory.cc b/chrome/browser/gcm/gcm_profile_service_factory.cc
index 6d54d1d..afd9081a 100644
--- a/chrome/browser/gcm/gcm_profile_service_factory.cc
+++ b/chrome/browser/gcm/gcm_profile_service_factory.cc
@@ -152,7 +152,8 @@
 #endif
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
   offline_pages::PrefetchService* prefetch_service =
-      offline_pages::PrefetchServiceFactory::GetForBrowserContext(context);
+      offline_pages::PrefetchServiceFactory::GetForKey(
+          profile->GetProfileKey());
   if (prefetch_service != nullptr) {
     offline_pages::PrefetchGCMHandler* prefetch_gcm_handler =
         prefetch_service->GetPrefetchGCMHandler();
diff --git a/chrome/browser/google/google_brand_code_map_chromeos.cc b/chrome/browser/google/google_brand_code_map_chromeos.cc
index fe54292..a2187fa 100644
--- a/chrome/browser/google/google_brand_code_map_chromeos.cc
+++ b/chrome/browser/google/google_brand_code_map_chromeos.cc
@@ -41,6 +41,7 @@
                      {"ASUF", {"IVGE", "VNTM", "XELD"}},
                      {"ASUJ", {"HJUL", "XWWL", "WSCY"}},
                      {"ASUK", {"RGUX", "OXBQ", "LDTL"}},
+                     {"AYMH", {"BBMB", "VBWP", "BVTP"}},
                      {"BAQN", {"YJJJ", "LDCA", "QSJF"}},
                      {"BCOL", {"YJDV", "GSIC", "BAUL"}},
                      {"BDIW", {"UDUG", "TRYQ", "PWFV"}},
@@ -51,6 +52,7 @@
                      {"DEAF", {"TATK", "RWXF", "DQDT"}},
                      {"DEAG", {"JFEX", "CVLN", "UFWN"}},
                      {"DRYI", {"LWTQ", "OLEY", "NWUA"}},
+                     {"DVUG", {"HJHV", "KPAH", "DCQS"}},
                      {"FQPJ", {"ZTQG", "ZNEO", "LYMZ"}},
                      {"FSFR", {"ZDAR", "BERM", "COKX"}},
                      {"FSGY", {"PJQC", "RHZW", "POVI"}},
@@ -78,6 +80,7 @@
                      {"LEAL", {"EYPX", "SOCH", "PFPW"}},
                      {"LEAO", {"MKOE", "YJSI", "QQMN"}},
                      {"LEAP", {"AEZG", "JOYE", "JHWK"}},
+                     {"MAII", {"EOHR", "XZOT", "VJJS"}},
                      {"MCDN", {"BAOV", "GLVV", "XHGO"}},
                      {"MCOO", {"IPNW", "CRSK", "QTAX"}},
                      {"MNZG", {"PPTP", "OFXE", "ROJJ"}},
@@ -85,6 +88,8 @@
                      {"NOMD", {"GZLV", "UNZR", "FVOP"}},
                      {"NPEC", {"BMGD", "YETH", "XAWJ"}},
                      {"OFPE", {"YFOO", "UIGY", "PFGZ"}},
+                     {"OKWC", {"RGFB", "UPFP", "HUVK"}},
+                     {"PAZD", {"VARX", "KZSU", "WPLH"}},
                      {"PGQF", {"USPJ", "SFKO", "KNBH"}},
                      {"PXDO", {"ZXCF", "TQWC", "HOAL"}},
                      {"RVRM", {"MZJU", "IGXP", "DSJP"}},
@@ -98,9 +103,11 @@
                      {"TAAB", {"ZBMY", "NYDT", "CXYZ"}},
                      {"TKER", {"KOSM", "IUCL", "LIIM"}},
                      {"UGAY", {"YDHM", "HVCY", "ILHO"}},
+                     {"UMAU", {"FKAK", "JCTZ", "GDUU"}},
                      {"VEUT", {"JDFA", "ALIR", "DDJM"}},
                      {"VHUH", {"JYDF", "SFJY", "JMBU"}},
                      {"WBZQ", {"LAYK", "LQDM", "QBFV"}},
+                     {"XVTK", {"TMUU", "BTWW", "THQH"}},
                      {"XVYQ", {"UAVB", "OEMI", "VQVK"}},
                      {"XWJE", {"KDZI", "IYPJ", "ERIM"}},
                      {"YHYU", {"CDLM", "QDXQ", "HPTE"}},
@@ -134,4 +141,4 @@
 }
 
 }  // namespace chromeos
-}  // namespace google_brand
\ No newline at end of file
+}  // namespace google_brand
diff --git a/chrome/browser/importer/profile_writer.cc b/chrome/browser/importer/profile_writer.cc
index 78bb6b0..b5f31b4 100644
--- a/chrome/browser/importer/profile_writer.cc
+++ b/chrome/browser/importer/profile_writer.cc
@@ -128,7 +128,7 @@
   // If the bookmark bar is currently empty, we should import directly to it.
   // Otherwise, we should import everything to a subfolder.
   const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
-  bool import_to_top_level = bookmark_bar->empty();
+  bool import_to_top_level = bookmark_bar->children().empty();
 
   // Reorder bookmarks so that the toolbar entries come first.
   std::vector<ImportedBookmarkEntry> toolbar_bookmarks;
diff --git a/chrome/browser/media/router/providers/dial/dial_internal_message_fuzzer.cc b/chrome/browser/media/router/providers/dial/dial_internal_message_fuzzer.cc
index 1088d9ecc..4b857db 100644
--- a/chrome/browser/media/router/providers/dial/dial_internal_message_fuzzer.cc
+++ b/chrome/browser/media/router/providers/dial/dial_internal_message_fuzzer.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Fuzzer for DialInternalMessage and CustomDialLaunchMessageBody.
+// Fuzzer for dial_internal_message_util.cc.
 
 #include "base/json/json_reader.h"
 #include "base/values.h"
@@ -11,6 +11,11 @@
 namespace media_router {
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  // Limit input size to prevent out-of-memory failures like the one seen in
+  // crbug.com/964715.
+  if (size > 16 * 1024)
+    return 0;
+
   base::Optional<base::Value> input = base::JSONReader::Read(
       std::string(reinterpret_cast<const char*>(data), size));
   if (!input)
@@ -22,6 +27,8 @@
   if (!dial_internal_message)
     return 0;
 
+  DialInternalMessageUtil::IsStopSessionMessage(*dial_internal_message);
+
   auto custom_dial_launch_message_body_unused =
       CustomDialLaunchMessageBody::From(*dial_internal_message);
   return 0;
diff --git a/chrome/browser/media/router/providers/dial/dial_internal_message_util.cc b/chrome/browser/media/router/providers/dial/dial_internal_message_util.cc
index e8744a5c..605b71d 100644
--- a/chrome/browser/media/router/providers/dial/dial_internal_message_util.cc
+++ b/chrome/browser/media/router/providers/dial/dial_internal_message_util.cc
@@ -182,8 +182,9 @@
     : hash_token_(hash_token) {}
 DialInternalMessageUtil::~DialInternalMessageUtil() = default;
 
+// static
 bool DialInternalMessageUtil::IsStopSessionMessage(
-    const DialInternalMessage& message) const {
+    const DialInternalMessage& message) {
   if (message.type != DialInternalMessageType::kV2Message)
     return false;
 
diff --git a/chrome/browser/media/router/providers/dial/dial_internal_message_util.h b/chrome/browser/media/router/providers/dial/dial_internal_message_util.h
index c2da0c5..161eb68 100644
--- a/chrome/browser/media/router/providers/dial/dial_internal_message_util.h
+++ b/chrome/browser/media/router/providers/dial/dial_internal_message_util.h
@@ -95,7 +95,7 @@
   ~DialInternalMessageUtil();
 
   // Returns |true| if |message| is a valid STOP_SESSION message.
-  bool IsStopSessionMessage(const DialInternalMessage& message) const;
+  static bool IsStopSessionMessage(const DialInternalMessage& message);
 
   // Returns a NEW_SESSION message to be sent to the page when the user requests
   // an app launch.
diff --git a/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc b/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc
index 85110e6..f9e057c 100644
--- a/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc
+++ b/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc
@@ -107,7 +107,7 @@
   EXPECT_EQ("152127444812943594", message->client_id);
   EXPECT_EQ(-1, message->sequence_number);
 
-  EXPECT_TRUE(util_.IsStopSessionMessage(*message));
+  EXPECT_TRUE(DialInternalMessageUtil::IsStopSessionMessage(*message));
 }
 
 TEST_F(DialInternalMessageUtilTest, CreateReceiverActionCastMessage) {
diff --git a/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc b/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc
index 0208132..fe4ecdf 100644
--- a/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc
+++ b/chrome/browser/media/router/providers/dial/dial_media_route_provider.cc
@@ -249,7 +249,7 @@
   } else if (internal_message->type ==
              DialInternalMessageType::kCustomDialLaunch) {
     HandleCustomDialLaunchResponse(*activity, *internal_message);
-  } else if (internal_message_util_.IsStopSessionMessage(*internal_message)) {
+  } else if (DialInternalMessageUtil::IsStopSessionMessage(*internal_message)) {
     DoTerminateRoute(*activity, *sink, base::DoNothing());
   }
 }
diff --git a/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc b/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
index f39d808..e67a862 100644
--- a/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
+++ b/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
@@ -117,7 +117,7 @@
                              Profile* profile) {
   // There's a circular dependency between ContentSuggestionsService and
   // PrefetchService. This closes the circle.
-  offline_pages::PrefetchServiceFactory::GetForBrowserContext(profile)
+  offline_pages::PrefetchServiceFactory::GetForKey(profile->GetProfileKey())
       ->SetContentSuggestionsService(service);
 }
 
diff --git a/chrome/browser/offline_pages/android/prefetch_background_task_android.cc b/chrome/browser/offline_pages/android/prefetch_background_task_android.cc
index 5d11c0f4..cd1eacd 100644
--- a/chrome/browser/offline_pages/android/prefetch_background_task_android.cc
+++ b/chrome/browser/offline_pages/android/prefetch_background_task_android.cc
@@ -34,7 +34,7 @@
   DCHECK(profile);
 
   PrefetchService* prefetch_service =
-      PrefetchServiceFactory::GetForBrowserContext(profile);
+      PrefetchServiceFactory::GetForKey(profile->GetProfileKey());
   if (!prefetch_service)
     return false;
 
diff --git a/chrome/browser/offline_pages/offline_page_tab_helper.cc b/chrome/browser/offline_pages/offline_page_tab_helper.cc
index ff7de267..77d12cb 100644
--- a/chrome/browser/offline_pages/offline_page_tab_helper.cc
+++ b/chrome/browser/offline_pages/offline_page_tab_helper.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/offline_pages/offline_page_utils.h"
 #include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
 #include "chrome/browser/offline_pages/request_coordinator_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "components/offline_pages/core/background/request_coordinator.h"
 #include "components/offline_pages/core/model/offline_page_model_utils.h"
 #include "components/offline_pages/core/offline_page_item.h"
@@ -100,8 +101,10 @@
       mhtml_page_notifier_bindings_(web_contents, this),
       weak_ptr_factory_(this) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
-  prefetch_service_ = PrefetchServiceFactory::GetForBrowserContext(
-      web_contents->GetBrowserContext());
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  prefetch_service_ =
+      PrefetchServiceFactory::GetForKey(profile->GetProfileKey());
 }
 
 OfflinePageTabHelper::~OfflinePageTabHelper() {}
diff --git a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
index fd3bb48e..ee47818 100644
--- a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
+++ b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
@@ -92,13 +92,6 @@
       GetInstance()->GetServiceForKey(key, true));
 }
 
-// static
-PrefetchService* PrefetchServiceFactory::GetForBrowserContext(
-    content::BrowserContext* context) {
-  Profile* profile = Profile::FromBrowserContext(context);
-  return GetForKey(profile->GetProfileKey());
-}
-
 std::unique_ptr<KeyedService> PrefetchServiceFactory::BuildServiceInstanceFor(
     SimpleFactoryKey* key) const {
   ProfileKey* profile_key = ProfileKey::FromSimpleFactoryKey(key);
diff --git a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.h b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.h
index f62ff51..4cd64c6 100644
--- a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.h
+++ b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.h
@@ -15,10 +15,6 @@
 struct DefaultSingletonTraits;
 }  // namespace base
 
-namespace content {
-class BrowserContext;
-}  // namespace content
-
 namespace offline_pages {
 
 class PrefetchService;
@@ -31,12 +27,6 @@
   static PrefetchServiceFactory* GetInstance();
   static PrefetchService* GetForKey(SimpleFactoryKey* key);
 
-  // Helper method that calls GetForKey().
-  // Returns the DownloadService associated with the key associated with
-  // |context|.
-  static PrefetchService* GetForBrowserContext(
-      content::BrowserContext* context);
-
  private:
   friend struct base::DefaultSingletonTraits<PrefetchServiceFactory>;
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_impl.cc
new file mode 100644
index 0000000..64d5530
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_impl.cc
@@ -0,0 +1,430 @@
+// 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/performance_manager/persistence/site_data/site_data_impl.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/performance_manager/performance_manager_clock.h"
+
+namespace performance_manager {
+namespace internal {
+
+namespace {
+
+// The sample weighing factor for the exponential moving averages for
+// performance measurements. A factor of 1/2 gives each sample an equal weight
+// to the entire previous history. As we don't know much noise there is to the
+// measurement, this is essentially a shot in the dark.
+// TODO(siggi): Consider adding UMA metrics to capture e.g. the fractional delta
+//      from the current average, or some such.
+constexpr float kSampleWeightFactor = 0.5;
+
+base::TimeDelta GetTickDeltaSinceEpoch() {
+  return PerformanceManagerClock::NowTicks() - base::TimeTicks::UnixEpoch();
+}
+
+// Returns all the SiteDataFeatureProto elements contained in a
+// SiteDataProto protobuf object.
+std::vector<SiteDataFeatureProto*> GetAllFeaturesFromProto(
+    SiteDataProto* proto) {
+  std::vector<SiteDataFeatureProto*> ret(
+      {proto->mutable_updates_favicon_in_background(),
+       proto->mutable_updates_title_in_background(),
+       proto->mutable_uses_audio_in_background(),
+       proto->mutable_uses_notifications_in_background()});
+
+  return ret;
+}
+
+// Observations windows have a default value of 2 hours, 95% of backgrounded
+// tabs don't use any of these features in this time window.
+static constexpr base::TimeDelta kObservationWindowLength =
+    base::TimeDelta::FromHours(2);
+
+}  // namespace
+
+void SiteDataImpl::NotifySiteLoaded() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Update the last loaded time when this origin gets loaded for the first
+  // time.
+  if (loaded_tabs_count_ == 0) {
+    site_characteristics_.set_last_loaded(
+        TimeDeltaToInternalRepresentation(GetTickDeltaSinceEpoch()));
+
+    is_dirty_ = true;
+  }
+  loaded_tabs_count_++;
+}
+
+void SiteDataImpl::NotifySiteUnloaded(TabVisibility tab_visibility) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (tab_visibility == TabVisibility::kBackground)
+    DecrementNumLoadedBackgroundTabs();
+
+  loaded_tabs_count_--;
+  // Only update the last loaded time when there's no more loaded instance of
+  // this origin.
+  if (loaded_tabs_count_ > 0U)
+    return;
+
+  base::TimeDelta current_unix_time = GetTickDeltaSinceEpoch();
+
+  // Update the |last_loaded_time_| field, as the moment this site gets unloaded
+  // also corresponds to the last moment it was loaded.
+  site_characteristics_.set_last_loaded(
+      TimeDeltaToInternalRepresentation(current_unix_time));
+}
+
+void SiteDataImpl::NotifyLoadedSiteBackgrounded() {
+  if (loaded_tabs_in_background_count_ == 0)
+    background_session_begin_ = PerformanceManagerClock::NowTicks();
+
+  loaded_tabs_in_background_count_++;
+
+  DCHECK_LE(loaded_tabs_in_background_count_, loaded_tabs_count_);
+}
+
+void SiteDataImpl::NotifyLoadedSiteForegrounded() {
+  DecrementNumLoadedBackgroundTabs();
+}
+
+SiteFeatureUsage SiteDataImpl::UpdatesFaviconInBackground() const {
+  return GetFeatureUsage(site_characteristics_.updates_favicon_in_background());
+}
+
+SiteFeatureUsage SiteDataImpl::UpdatesTitleInBackground() const {
+  return GetFeatureUsage(site_characteristics_.updates_title_in_background());
+}
+
+SiteFeatureUsage SiteDataImpl::UsesAudioInBackground() const {
+  return GetFeatureUsage(site_characteristics_.uses_audio_in_background());
+}
+
+SiteFeatureUsage SiteDataImpl::UsesNotificationsInBackground() const {
+  return GetFeatureUsage(
+      site_characteristics_.uses_notifications_in_background());
+}
+
+bool SiteDataImpl::DataLoaded() const {
+  return fully_initialized_;
+}
+
+void SiteDataImpl::RegisterDataLoadedCallback(base::OnceClosure&& callback) {
+  if (fully_initialized_) {
+    std::move(callback).Run();
+    return;
+  }
+  data_loaded_callbacks_.emplace_back(std::move(callback));
+}
+
+void SiteDataImpl::NotifyUpdatesFaviconInBackground() {
+  NotifyFeatureUsage(
+      site_characteristics_.mutable_updates_favicon_in_background(),
+      "FaviconUpdateInBackground");
+}
+
+void SiteDataImpl::NotifyUpdatesTitleInBackground() {
+  NotifyFeatureUsage(
+      site_characteristics_.mutable_updates_title_in_background(),
+      "TitleUpdateInBackground");
+}
+
+void SiteDataImpl::NotifyUsesAudioInBackground() {
+  NotifyFeatureUsage(site_characteristics_.mutable_uses_audio_in_background(),
+                     "AudioUsageInBackground");
+}
+
+void SiteDataImpl::NotifyUsesNotificationsInBackground() {
+  NotifyFeatureUsage(
+      site_characteristics_.mutable_uses_notifications_in_background(),
+      "NotificationsUsageInBackground");
+}
+
+void SiteDataImpl::NotifyLoadTimePerformanceMeasurement(
+    base::TimeDelta load_duration,
+    base::TimeDelta cpu_usage_estimate,
+    uint64_t private_footprint_kb_estimate) {
+  is_dirty_ = true;
+
+  load_duration_.AppendDatum(load_duration.InMicroseconds());
+  cpu_usage_estimate_.AppendDatum(cpu_usage_estimate.InMicroseconds());
+  private_footprint_kb_estimate_.AppendDatum(private_footprint_kb_estimate);
+}
+
+void SiteDataImpl::ExpireAllObservationWindowsForTesting() {
+  for (auto* iter : GetAllFeaturesFromProto(&site_characteristics_))
+    IncrementFeatureObservationDuration(iter, kObservationWindowLength);
+}
+
+// static
+const base::TimeDelta
+SiteDataImpl::GetFeatureObservationWindowLengthForTesting() {
+  return kObservationWindowLength;
+}
+
+SiteDataImpl::SiteDataImpl(const url::Origin& origin,
+                           OnDestroyDelegate* delegate,
+                           SiteDataStore* data_store)
+    : load_duration_(kSampleWeightFactor),
+      cpu_usage_estimate_(kSampleWeightFactor),
+      private_footprint_kb_estimate_(kSampleWeightFactor),
+      origin_(origin),
+      loaded_tabs_count_(0U),
+      loaded_tabs_in_background_count_(0U),
+      data_store_(data_store),
+      delegate_(delegate),
+      fully_initialized_(false),
+      is_dirty_(false),
+      weak_factory_(this) {
+  DCHECK(data_store_);
+  DCHECK(delegate_);
+
+  data_store_->ReadSiteDataFromStore(
+      origin_, base::BindOnce(&SiteDataImpl::OnInitCallback,
+                              weak_factory_.GetWeakPtr()));
+}
+
+SiteDataImpl::~SiteDataImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // All users of this object should make sure that they send the same number of
+  // NotifySiteLoaded and NotifySiteUnloaded events, in practice this mean
+  // tracking the loaded state and sending an unload event in their destructor
+  // if needed.
+  DCHECK(!IsLoaded());
+  DCHECK_EQ(0U, loaded_tabs_in_background_count_);
+
+  DCHECK(delegate_);
+  delegate_->OnSiteDataImplDestroyed(this);
+
+  // TODO(sebmarchand): Some data might be lost here if the read operation has
+  // not completed, add some metrics to measure if this is really an issue.
+  if (is_dirty_ && fully_initialized_)
+    data_store_->WriteSiteDataIntoStore(origin_, FlushStateToProto());
+}
+
+base::TimeDelta SiteDataImpl::FeatureObservationDuration(
+    const SiteDataFeatureProto& feature_proto) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Get the current observation duration value if available.
+  base::TimeDelta observation_time_for_feature;
+  if (feature_proto.has_observation_duration()) {
+    observation_time_for_feature =
+        InternalRepresentationToTimeDelta(feature_proto.observation_duration());
+  }
+
+  // If this site is still in background and the feature isn't in use then the
+  // observation time since load needs to be added.
+  if (loaded_tabs_in_background_count_ > 0U &&
+      InternalRepresentationToTimeDelta(feature_proto.use_timestamp())
+          .is_zero()) {
+    base::TimeDelta observation_time_since_backgrounded =
+        PerformanceManagerClock::NowTicks() - background_session_begin_;
+    observation_time_for_feature += observation_time_since_backgrounded;
+  }
+
+  return observation_time_for_feature;
+}
+
+// static:
+void SiteDataImpl::IncrementFeatureObservationDuration(
+    SiteDataFeatureProto* feature_proto,
+    base::TimeDelta extra_observation_duration) {
+  if (!feature_proto->has_use_timestamp() ||
+      InternalRepresentationToTimeDelta(feature_proto->use_timestamp())
+          .is_zero()) {
+    feature_proto->set_observation_duration(TimeDeltaToInternalRepresentation(
+        InternalRepresentationToTimeDelta(
+            feature_proto->observation_duration()) +
+        extra_observation_duration));
+  }
+}
+
+void SiteDataImpl::ClearObservationsAndInvalidateReadOperation() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Invalidate the weak pointer that have been served, this will ensure that
+  // this object doesn't get initialized from the data store after being
+  // cleared.
+  weak_factory_.InvalidateWeakPtrs();
+
+  // Reset all the observations.
+  site_characteristics_.Clear();
+
+  // Clear the performance estimates, both the local state and the proto.
+  cpu_usage_estimate_.Clear();
+  private_footprint_kb_estimate_.Clear();
+  site_characteristics_.clear_load_time_estimates();
+
+  // Set the last loaded time to the current time if there's some loaded
+  // instances of this site.
+  if (IsLoaded()) {
+    site_characteristics_.set_last_loaded(
+        TimeDeltaToInternalRepresentation(GetTickDeltaSinceEpoch()));
+  }
+
+  // This object is now in a valid state and can be written in the data store.
+  TransitionToFullyInitialized();
+}
+
+SiteFeatureUsage SiteDataImpl::GetFeatureUsage(
+    const SiteDataFeatureProto& feature_proto) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  UMA_HISTOGRAM_BOOLEAN(
+      "ResourceCoordinator.LocalDB.ReadHasCompletedBeforeQuery",
+      fully_initialized_);
+
+  // Checks if this feature has already been observed.
+  // TODO(sebmarchand): Check the timestamp and reset features that haven't been
+  // observed in a long time, https://crbug.com/826446.
+  if (feature_proto.has_use_timestamp())
+    return SiteFeatureUsage::kSiteFeatureInUse;
+
+  if (FeatureObservationDuration(feature_proto) >= kObservationWindowLength)
+    return SiteFeatureUsage::kSiteFeatureNotInUse;
+
+  return SiteFeatureUsage::kSiteFeatureUsageUnknown;
+}
+
+void SiteDataImpl::NotifyFeatureUsage(SiteDataFeatureProto* feature_proto,
+                                      const char* feature_name) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(IsLoaded());
+  DCHECK_GT(loaded_tabs_in_background_count_, 0U);
+
+  // Report the observation time if this is the first time this feature is
+  // observed.
+  if (feature_proto->observation_duration() != 0) {
+    base::UmaHistogramCustomTimes(
+        base::StringPrintf(
+            "ResourceCoordinator.LocalDB.ObservationTimeBeforeFirstUse.%s",
+            feature_name),
+        InternalRepresentationToTimeDelta(
+            feature_proto->observation_duration()),
+        base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(1), 100);
+  }
+
+  feature_proto->Clear();
+  feature_proto->set_use_timestamp(
+      TimeDeltaToInternalRepresentation(GetTickDeltaSinceEpoch()));
+}
+
+void SiteDataImpl::OnInitCallback(
+    base::Optional<SiteDataProto> db_site_characteristics) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Check if the initialization has succeeded.
+  if (db_site_characteristics) {
+    // If so, iterates over all the features and initialize them.
+    auto this_features = GetAllFeaturesFromProto(&site_characteristics_);
+    auto db_features =
+        GetAllFeaturesFromProto(&db_site_characteristics.value());
+    auto this_features_iter = this_features.begin();
+    auto db_features_iter = db_features.begin();
+    for (; this_features_iter != this_features.end() &&
+           db_features_iter != db_features.end();
+         ++this_features_iter, ++db_features_iter) {
+      // If the |use_timestamp| field is set for the in-memory entry for this
+      // feature then there's nothing to do, otherwise update it with the values
+      // from the data store.
+      if (!(*this_features_iter)->has_use_timestamp()) {
+        if ((*db_features_iter)->has_use_timestamp() &&
+            (*db_features_iter)->use_timestamp() != 0) {
+          (*this_features_iter)->Clear();
+          // Keep the use timestamp from the data store, if any.
+          (*this_features_iter)
+              ->set_use_timestamp((*db_features_iter)->use_timestamp());
+        } else {
+          // Else, add the observation duration from the data store to the
+          // in-memory observation duration.
+          IncrementFeatureObservationDuration(
+              (*this_features_iter),
+              InternalRepresentationToTimeDelta(
+                  (*db_features_iter)->observation_duration()));
+        }
+      }
+    }
+    // Only update the last loaded field if we haven't updated it since the
+    // creation of this object.
+    if (!site_characteristics_.has_last_loaded()) {
+      site_characteristics_.set_last_loaded(
+          db_site_characteristics->last_loaded());
+    }
+    // If there was on-disk data, update the in-memory performance averages.
+    if (db_site_characteristics->has_load_time_estimates()) {
+      const auto& estimates = db_site_characteristics->load_time_estimates();
+      if (estimates.has_avg_load_duration_us())
+        load_duration_.PrependDatum(estimates.avg_load_duration_us());
+      if (estimates.has_avg_cpu_usage_us())
+        cpu_usage_estimate_.PrependDatum(estimates.avg_cpu_usage_us());
+      if (estimates.has_avg_footprint_kb()) {
+        private_footprint_kb_estimate_.PrependDatum(
+            estimates.avg_footprint_kb());
+      }
+    }
+  }
+
+  TransitionToFullyInitialized();
+}
+
+void SiteDataImpl::DecrementNumLoadedBackgroundTabs() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_GT(loaded_tabs_in_background_count_, 0U);
+  loaded_tabs_in_background_count_--;
+  // Only update the observation durations if there's no more backgounded
+  // instance of this origin.
+  if (loaded_tabs_in_background_count_ == 0U)
+    FlushFeaturesObservationDurationToProto();
+}
+
+const SiteDataProto& SiteDataImpl::FlushStateToProto() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Update the proto with the most current performance measurement averages.
+  if (cpu_usage_estimate_.num_datums() ||
+      private_footprint_kb_estimate_.num_datums()) {
+    auto* estimates = site_characteristics_.mutable_load_time_estimates();
+    if (load_duration_.num_datums())
+      estimates->set_avg_load_duration_us(load_duration_.value());
+    if (cpu_usage_estimate_.num_datums())
+      estimates->set_avg_cpu_usage_us(cpu_usage_estimate_.value());
+    if (private_footprint_kb_estimate_.num_datums()) {
+      estimates->set_avg_footprint_kb(private_footprint_kb_estimate_.value());
+    }
+  }
+
+  if (loaded_tabs_in_background_count_ > 0U)
+    FlushFeaturesObservationDurationToProto();
+
+  return site_characteristics_;
+}
+
+void SiteDataImpl::FlushFeaturesObservationDurationToProto() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!background_session_begin_.is_null());
+
+  base::TimeTicks now = PerformanceManagerClock::NowTicks();
+
+  base::TimeDelta extra_observation_duration = now - background_session_begin_;
+  background_session_begin_ = now;
+
+  // Update the observation duration fields.
+  for (auto* iter : GetAllFeaturesFromProto(&site_characteristics_))
+    IncrementFeatureObservationDuration(iter, extra_observation_duration);
+}
+
+void SiteDataImpl::TransitionToFullyInitialized() {
+  fully_initialized_ = true;
+  for (size_t i = 0; i < data_loaded_callbacks_.size(); ++i)
+    std::move(data_loaded_callbacks_[i]).Run();
+  data_loaded_callbacks_.clear();
+}
+
+}  // namespace internal
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h b/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h
new file mode 100644
index 0000000..0d5da94
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_impl.h
@@ -0,0 +1,287 @@
+// 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 CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "chrome/browser/performance_manager/persistence/site_data/exponential_moving_average.h"
+#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_store.h"
+#include "chrome/browser/performance_manager/persistence/site_data/tab_visibility.h"
+#include "url/origin.h"
+
+namespace performance_manager {
+
+class SiteDataReaderTest;
+
+FORWARD_DECLARE_TEST(SiteDataReaderTest,
+                     DestroyingReaderCancelsPendingCallbacks);
+FORWARD_DECLARE_TEST(SiteDataReaderTest,
+                     FreeingReaderDoesntCauseWriteOperation);
+FORWARD_DECLARE_TEST(SiteDataReaderTest, OnDataLoadedCallbackInvoked);
+
+namespace internal {
+
+FORWARD_DECLARE_TEST(SiteDataImplTest, LateAsyncReadDoesntBypassClearEvent);
+
+// Internal class used to read/write site data. This is a wrapper class around
+// a SiteDataProto object and offers various to query and/or modify
+// it. This class shouldn't be used directly, instead it should be created by
+// a LocalSiteCharacteristicsDataStore that will serve reader and writer
+// objects.
+//
+// Reader and writers objects that are interested in reading/writing information
+// about the same origin will share a unique ref counted instance of this
+// object, because of this all the operations done on these objects should be
+// done on the same thread, this class isn't thread safe.
+//
+// By default tabs associated with instances of this class are assumed to be
+// running in foreground, |NotifyTabBackgrounded| should get called to indicate
+// that the tab is running in background.
+class SiteDataImpl : public base::RefCounted<SiteDataImpl> {
+ public:
+  // Interface that should be implemented in order to receive notifications when
+  // this object is about to get destroyed.
+  class OnDestroyDelegate {
+   public:
+    // Called when this object is about to get destroyed.
+    virtual void OnSiteDataImplDestroyed(SiteDataImpl* impl) = 0;
+  };
+
+  // Must be called when a load event is received for this site, this can be
+  // invoked several times if instances of this class are shared between
+  // multiple tabs.
+  void NotifySiteLoaded();
+
+  // Must be called when an unload event is received for this site, this can be
+  // invoked several times if instances of this class are shared between
+  // multiple tabs.
+  void NotifySiteUnloaded(TabVisibility tab_visibility);
+
+  // Must be called when a loaded tab gets backgrounded.
+  void NotifyLoadedSiteBackgrounded();
+
+  // Must be called when a loaded tab gets foregrounded.
+  void NotifyLoadedSiteForegrounded();
+
+  // Returns the usage of a given feature for this origin.
+  SiteFeatureUsage UpdatesFaviconInBackground() const;
+  SiteFeatureUsage UpdatesTitleInBackground() const;
+  SiteFeatureUsage UsesAudioInBackground() const;
+  SiteFeatureUsage UsesNotificationsInBackground() const;
+
+  // Returns true if the most authoritative data has been loaded from the
+  // backing store.
+  bool DataLoaded() const;
+
+  // Registers a callback to be invoked when the data backing this object is
+  // loaded from disk, or otherwise authoritatively initialized.
+  void RegisterDataLoadedCallback(base::OnceClosure&& callback);
+
+  // Accessors for load-time performance measurement estimates.
+  // If |num_datum| is zero, there's no estimate available.
+  const ExponentialMovingAverage& load_duration() const {
+    return load_duration_;
+  }
+  const ExponentialMovingAverage& cpu_usage_estimate() const {
+    return cpu_usage_estimate_;
+  }
+  const ExponentialMovingAverage& private_footprint_kb_estimate() const {
+    return private_footprint_kb_estimate_;
+  }
+
+  // Must be called when a feature is used, calling this function updates the
+  // last observed timestamp for this feature.
+  void NotifyUpdatesFaviconInBackground();
+  void NotifyUpdatesTitleInBackground();
+  void NotifyUsesAudioInBackground();
+  void NotifyUsesNotificationsInBackground();
+
+  // Call when a load-time performance measurement becomes available.
+  void NotifyLoadTimePerformanceMeasurement(
+      base::TimeDelta load_duration,
+      base::TimeDelta cpu_usage_estimate,
+      uint64_t private_footprint_kb_estimate);
+
+  base::TimeDelta last_loaded_time_for_testing() const {
+    return InternalRepresentationToTimeDelta(
+        site_characteristics_.last_loaded());
+  }
+
+  const SiteDataProto& site_characteristics_for_testing() const {
+    return site_characteristics_;
+  }
+
+  size_t loaded_tabs_count_for_testing() const { return loaded_tabs_count_; }
+
+  size_t loaded_tabs_in_background_count_for_testing() const {
+    return loaded_tabs_in_background_count_;
+  }
+
+  base::TimeTicks background_session_begin_for_testing() const {
+    return background_session_begin_;
+  }
+
+  const url::Origin& origin() const { return origin_; }
+  bool is_dirty() const { return is_dirty_; }
+
+  void ExpireAllObservationWindowsForTesting();
+
+  void ClearObservationsAndInvalidateReadOperationForTesting() {
+    ClearObservationsAndInvalidateReadOperation();
+  }
+
+  bool fully_initialized_for_testing() const { return fully_initialized_; }
+
+  static const base::TimeDelta GetFeatureObservationWindowLengthForTesting();
+
+ protected:
+  friend class base::RefCounted<SiteDataImpl>;
+
+  // Friend all the tests.
+  friend class SiteDataImplTest;
+  friend class performance_manager::SiteDataReaderTest;
+
+  SiteDataImpl(const url::Origin& origin,
+               OnDestroyDelegate* delegate,
+               SiteDataStore* data_store);
+
+  virtual ~SiteDataImpl();
+
+  // Helper functions to convert from/to the internal representation that is
+  // used to store TimeDelta values in the |SiteDataProto| protobuf.
+  static base::TimeDelta InternalRepresentationToTimeDelta(
+      ::google::protobuf::int64 value) {
+    return base::TimeDelta::FromSeconds(value);
+  }
+  static int64_t TimeDeltaToInternalRepresentation(base::TimeDelta delta) {
+    return delta.InSeconds();
+  }
+
+  // Returns for how long a given feature has been observed, this is the sum of
+  // the recorded observation duration and the current observation duration
+  // since this site has been loaded (if applicable). If a feature has been
+  // used then it returns 0.
+  base::TimeDelta FeatureObservationDuration(
+      const SiteDataFeatureProto& feature_proto) const;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(SiteDataImplTest,
+                           FlushingStateToProtoDoesntAffectData);
+  FRIEND_TEST_ALL_PREFIXES(SiteDataImplTest,
+                           LateAsyncReadDoesntBypassClearEvent);
+  FRIEND_TEST_ALL_PREFIXES(performance_manager::SiteDataReaderTest,
+                           DestroyingReaderCancelsPendingCallbacks);
+  FRIEND_TEST_ALL_PREFIXES(performance_manager::SiteDataReaderTest,
+                           FreeingReaderDoesntCauseWriteOperation);
+  FRIEND_TEST_ALL_PREFIXES(performance_manager::SiteDataReaderTest,
+                           OnDataLoadedCallbackInvoked);
+
+  // Add |extra_observation_duration| to the observation window of a given
+  // feature if it hasn't been used yet, do nothing otherwise.
+  static void IncrementFeatureObservationDuration(
+      SiteDataFeatureProto* feature_proto,
+      base::TimeDelta extra_observation_duration);
+
+  // Clear all the past observations about this site and invalidate the pending
+  // read observations from the data store.
+  void ClearObservationsAndInvalidateReadOperation();
+
+  // Returns the usage of |site_feature| for this site.
+  SiteFeatureUsage GetFeatureUsage(
+      const SiteDataFeatureProto& feature_proto) const;
+
+  // Helper function to update a given |SiteDataFeatureProto| when a
+  // feature gets used.
+  void NotifyFeatureUsage(SiteDataFeatureProto* feature_proto,
+                          const char* feature_name);
+
+  bool IsLoaded() const { return loaded_tabs_count_ > 0U; }
+
+  // Callback that needs to be called by the data store once it has finished
+  // trying to read the protobuf.
+  void OnInitCallback(base::Optional<SiteDataProto> site_characteristic_proto);
+
+  // Decrement the |loaded_tabs_in_background_count_| counter and update the
+  // local feature observation durations if necessary.
+  void DecrementNumLoadedBackgroundTabs();
+
+  // Flush any state that's maintained in member variables to the proto.
+  const SiteDataProto& FlushStateToProto();
+
+  // Updates the proto with the current total observation duration and updates
+  // |background_session_begin_| to NowTicks().
+  void FlushFeaturesObservationDurationToProto();
+
+  void TransitionToFullyInitialized();
+
+  // This site's characteristics, contains the features and other values are
+  // measured.
+  SiteDataProto site_characteristics_;
+
+  // The in-memory storage for the moving performance averages.
+  ExponentialMovingAverage load_duration_;       // microseconds.
+  ExponentialMovingAverage cpu_usage_estimate_;  // microseconds.
+  ExponentialMovingAverage private_footprint_kb_estimate_;
+
+  // This site's origin.
+  const url::Origin origin_;
+
+  // The number of loaded tabs for this origin. Several tabs with the
+  // same origin might share the same instance of this object, this counter
+  // will allow to properly update the observation time (starts when the first
+  // tab gets loaded, stops when the last one gets unloaded).
+  size_t loaded_tabs_count_;
+
+  // Number of loaded tabs currently in background for this origin, the
+  // implementation doesn't need to track unloaded tabs running in background.
+  size_t loaded_tabs_in_background_count_;
+
+  // The time at which the |loaded_tabs_in_background_count_| counter changed
+  // from 0 to 1.
+  base::TimeTicks background_session_begin_;
+
+  // The data store used to store the site characteristics, it should outlive
+  // this object.
+  SiteDataStore* const data_store_;
+
+  // The delegate that should get notified when this object is about to get
+  // destroyed, it should outlive this object.
+  OnDestroyDelegate* const delegate_;
+
+  // Indicates if this object has been fully initialized, either because the
+  // read operation from the database has completed or because it has been
+  // cleared.
+  bool fully_initialized_;
+
+  // Dirty bit, indicates if any of the fields in |site_characteristics_| has
+  // changed since it has been initialized.
+  bool is_dirty_;
+
+  // A collection of callbacks to be invoked when this object becomes fully
+  // initialized.
+  std::vector<base::OnceClosure> data_loaded_callbacks_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<SiteDataImpl> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SiteDataImpl);
+};
+
+}  // namespace internal
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_IMPL_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc
new file mode 100644
index 0000000..b98f94c
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc
@@ -0,0 +1,701 @@
+// 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 "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "chrome/browser/performance_manager/performance_manager_clock.h"
+#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace performance_manager {
+namespace internal {
+
+namespace {
+
+constexpr base::TimeDelta kInitialTimeSinceEpoch =
+    base::TimeDelta::FromSeconds(1);
+
+class TestSiteDataImpl : public SiteDataImpl {
+ public:
+  using SiteDataImpl::FeatureObservationDuration;
+  using SiteDataImpl::OnDestroyDelegate;
+  using SiteDataImpl::site_characteristics_for_testing;
+  using SiteDataImpl::TimeDeltaToInternalRepresentation;
+
+  explicit TestSiteDataImpl(const url::Origin& origin,
+                            SiteDataImpl::OnDestroyDelegate* delegate,
+                            SiteDataStore* data_store)
+      : SiteDataImpl(origin, delegate, data_store) {}
+
+  base::TimeDelta FeatureObservationTimestamp(
+      const SiteDataFeatureProto& feature_proto) {
+    return InternalRepresentationToTimeDelta(feature_proto.use_timestamp());
+  }
+
+ protected:
+  ~TestSiteDataImpl() override {}
+};
+
+class MockDataStore : public testing::NoopSiteDataStore {
+ public:
+  MockDataStore() = default;
+  ~MockDataStore() = default;
+
+  // Note: As move-only parameters (e.g. OnceCallback) aren't supported by mock
+  // methods, add On... methods to pass a non-const reference to OnceCallback.
+  void ReadSiteDataFromStore(
+      const url::Origin& origin,
+      SiteDataStore::ReadSiteDataFromStoreCallback callback) override {
+    OnReadSiteDataFromStore(origin, callback);
+  }
+  MOCK_METHOD2(OnReadSiteDataFromStore,
+               void(const url::Origin&,
+                    SiteDataStore::ReadSiteDataFromStoreCallback&));
+
+  MOCK_METHOD2(WriteSiteDataIntoStore,
+               void(const url::Origin&, const SiteDataProto&));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockDataStore);
+};
+
+// Returns a SiteDataFeatureProto that indicates that a feature hasn't been
+// used.
+SiteDataFeatureProto GetUnusedFeatureProto() {
+  SiteDataFeatureProto unused_feature_proto;
+  unused_feature_proto.set_observation_duration(1U);
+  unused_feature_proto.set_use_timestamp(0U);
+  return unused_feature_proto;
+}
+
+// Returns a SiteDataFeatureProto that indicates that a feature has been used.
+SiteDataFeatureProto GetUsedFeatureProto() {
+  SiteDataFeatureProto used_feature_proto;
+  used_feature_proto.set_observation_duration(0U);
+  used_feature_proto.set_use_timestamp(1U);
+  return used_feature_proto;
+}
+
+}  // namespace
+
+class SiteDataImplTest : public ::testing::Test {
+ public:
+  SiteDataImplTest() {
+    PerformanceManagerClock::SetClockForTesting(&test_clock_);
+  }
+
+  ~SiteDataImplTest() override {
+    PerformanceManagerClock::ResetClockForTesting();
+  }
+
+  void SetUp() override {
+    test_clock_.SetNowTicks(base::TimeTicks::UnixEpoch());
+    // Advance the test clock by a small delay, as some tests will fail if the
+    // current time is equal to Epoch.
+    test_clock_.Advance(kInitialTimeSinceEpoch);
+  }
+
+ protected:
+  scoped_refptr<TestSiteDataImpl> GetDataImpl(
+      const url::Origin& origin,
+      SiteDataImpl::OnDestroyDelegate* destroy_delegate,
+      SiteDataStore* data_store) {
+    return base::MakeRefCounted<TestSiteDataImpl>(origin, destroy_delegate,
+                                                  data_store);
+  }
+
+  // Use a mock data store to intercept the initialization callback and save it
+  // locally so it can be run later.
+  scoped_refptr<TestSiteDataImpl> GetDataImplAndInterceptReadCallback(
+      const url::Origin& origin,
+      SiteDataImpl::OnDestroyDelegate* destroy_delegate,
+      MockDataStore* mock_data_store,
+      SiteDataStore::ReadSiteDataFromStoreCallback* read_cb) {
+    auto read_from_store_mock_impl =
+        [&](const url::Origin& origin,
+            SiteDataStore::ReadSiteDataFromStoreCallback& callback) {
+          *read_cb = std::move(callback);
+        };
+
+    EXPECT_CALL(*mock_data_store,
+                OnReadSiteDataFromStore(::testing::_, ::testing::_))
+        .WillOnce(::testing::Invoke(read_from_store_mock_impl));
+    auto local_site_data =
+        GetDataImpl(origin, &destroy_delegate_, mock_data_store);
+    ::testing::Mock::VerifyAndClear(mock_data_store);
+    return local_site_data;
+  }
+
+  const url::Origin kDummyOrigin = url::Origin::Create(GURL("foo.com"));
+  const url::Origin kDummyOrigin2 = url::Origin::Create(GURL("bar.com"));
+
+  base::SimpleTestTickClock test_clock_;
+  // Use a NiceMock as there's no need to add expectations in these tests,
+  // there's a dedicated test that ensure that the delegate works as expected.
+  ::testing::NiceMock<testing::MockSiteDataImplOnDestroyDelegate>
+      destroy_delegate_;
+
+  testing::NoopSiteDataStore data_store;
+};
+
+TEST_F(SiteDataImplTest, BasicTestEndToEnd) {
+  auto local_site_data =
+      GetDataImpl(kDummyOrigin, &destroy_delegate_, &data_store);
+
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+
+  // Initially the feature usage should be reported as unknown.
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UsesAudioInBackground());
+
+  // Advance the clock by a time lower than the minimum observation time for
+  // the audio feature.
+  test_clock_.Advance(
+      SiteDataImpl::GetFeatureObservationWindowLengthForTesting() -
+      base::TimeDelta::FromSeconds(1));
+
+  // The audio feature usage is still unknown as the observation window hasn't
+  // expired.
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UsesAudioInBackground());
+
+  // Report that the audio feature has been used.
+  local_site_data->NotifyUsesAudioInBackground();
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesAudioInBackground());
+
+  // When a feature is in use it's expected that its recorded observation
+  // timestamp is equal to the time delta since Unix Epoch when the observation
+  // has been made.
+  EXPECT_EQ(local_site_data->FeatureObservationTimestamp(
+                local_site_data->site_characteristics_for_testing()
+                    .uses_audio_in_background()),
+            (test_clock_.NowTicks() - base::TimeTicks::UnixEpoch()));
+  EXPECT_EQ(local_site_data->FeatureObservationDuration(
+                local_site_data->site_characteristics_for_testing()
+                    .uses_audio_in_background()),
+            base::TimeDelta());
+
+  // Advance the clock and make sure that notifications feature gets
+  // reported as unused.
+  test_clock_.Advance(
+      SiteDataImpl::GetFeatureObservationWindowLengthForTesting());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureNotInUse,
+            local_site_data->UsesNotificationsInBackground());
+
+  // Observating that a feature has been used after its observation window has
+  // expired should still be recorded, the feature should then be reported as
+  // used.
+  local_site_data->NotifyUsesNotificationsInBackground();
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesNotificationsInBackground());
+
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+}
+
+TEST_F(SiteDataImplTest, LastLoadedTime) {
+  auto local_site_data =
+      GetDataImpl(kDummyOrigin, &destroy_delegate_, &data_store);
+
+  // Create a second instance of this object, simulates having several tab
+  // owning it.
+  auto local_site_data2(local_site_data);
+
+  local_site_data->NotifySiteLoaded();
+  base::TimeDelta last_loaded_time =
+      local_site_data->last_loaded_time_for_testing();
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+  // Loading the site a second time shouldn't change the last loaded time.
+  local_site_data2->NotifySiteLoaded();
+  EXPECT_EQ(last_loaded_time, local_site_data2->last_loaded_time_for_testing());
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+  // Unloading the site shouldn't update the last loaded time as there's still
+  // a loaded instance.
+  local_site_data2->NotifySiteUnloaded(TabVisibility::kForeground);
+  EXPECT_EQ(last_loaded_time, local_site_data->last_loaded_time_for_testing());
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+  local_site_data->NotifySiteUnloaded(TabVisibility::kForeground);
+  EXPECT_NE(last_loaded_time, local_site_data->last_loaded_time_for_testing());
+}
+
+TEST_F(SiteDataImplTest, GetFeatureUsageForUnloadedSite) {
+  auto local_site_data =
+      GetDataImpl(kDummyOrigin, &destroy_delegate_, &data_store);
+
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+  local_site_data->NotifyUsesAudioInBackground();
+
+  test_clock_.Advance(
+      SiteDataImpl::GetFeatureObservationWindowLengthForTesting() -
+      base::TimeDelta::FromSeconds(1));
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesAudioInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UsesNotificationsInBackground());
+
+  const base::TimeDelta observation_duration_before_unload =
+      local_site_data->FeatureObservationDuration(
+          local_site_data->site_characteristics_for_testing()
+              .uses_notifications_in_background());
+
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+
+  // Once unloaded the feature observations should still be accessible.
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesAudioInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UsesNotificationsInBackground());
+
+  // Advancing the clock shouldn't affect the observation duration for this
+  // feature.
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+  EXPECT_EQ(observation_duration_before_unload,
+            local_site_data->FeatureObservationDuration(
+                local_site_data->site_characteristics_for_testing()
+                    .uses_notifications_in_background()));
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UsesNotificationsInBackground());
+
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesAudioInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureNotInUse,
+            local_site_data->UsesNotificationsInBackground());
+
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+}
+
+TEST_F(SiteDataImplTest, AllDurationGetSavedOnUnload) {
+  // This test helps making sure that the observation/timestamp fields get saved
+  // for all the features being tracked.
+  auto local_site_data =
+      GetDataImpl(kDummyOrigin, &destroy_delegate_, &data_store);
+
+  const base::TimeDelta kInterval = base::TimeDelta::FromSeconds(1);
+  const auto kIntervalInternalRepresentation =
+      TestSiteDataImpl::TimeDeltaToInternalRepresentation(kInterval);
+  const auto kZeroIntervalInternalRepresentation =
+      TestSiteDataImpl::TimeDeltaToInternalRepresentation(base::TimeDelta());
+
+  // The internal representation of a zero interval is expected to be equal to
+  // zero as the protobuf use variable size integers and so storing zero values
+  // is really efficient (uses only one bit).
+  EXPECT_EQ(0U, kZeroIntervalInternalRepresentation);
+
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+  test_clock_.Advance(kInterval);
+  // Makes use of a feature to make sure that the observation timestamps get
+  // saved.
+  local_site_data->NotifyUsesAudioInBackground();
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+
+  SiteDataProto expected_proto;
+
+  auto expected_last_loaded_time =
+      TestSiteDataImpl::TimeDeltaToInternalRepresentation(
+          kInterval + kInitialTimeSinceEpoch);
+
+  expected_proto.set_last_loaded(expected_last_loaded_time);
+
+  // Features that haven't been used should have an observation duration of
+  // |kIntervalInternalRepresentation| and an observation timestamp equal to
+  // zero.
+  SiteDataFeatureProto unused_feature_proto;
+  unused_feature_proto.set_observation_duration(
+      kIntervalInternalRepresentation);
+
+  expected_proto.mutable_updates_favicon_in_background()->CopyFrom(
+      unused_feature_proto);
+  expected_proto.mutable_updates_title_in_background()->CopyFrom(
+      unused_feature_proto);
+  expected_proto.mutable_uses_notifications_in_background()->CopyFrom(
+      unused_feature_proto);
+
+  // The audio feature has been used, so its observation duration value should
+  // be equal to zero, and its observation timestamp should be equal to the last
+  // loaded time in this case (as this feature has been used right before
+  // unloading).
+  SiteDataFeatureProto used_feature_proto;
+  used_feature_proto.set_use_timestamp(expected_last_loaded_time);
+  expected_proto.mutable_uses_audio_in_background()->CopyFrom(
+      used_feature_proto);
+
+  EXPECT_EQ(
+      expected_proto.SerializeAsString(),
+      local_site_data->site_characteristics_for_testing().SerializeAsString());
+}
+
+// Verify that the OnDestroyDelegate gets notified when a
+// SiteDataImpl object gets destroyed.
+TEST_F(SiteDataImplTest, DestroyNotifiesDelegate) {
+  ::testing::StrictMock<testing::MockSiteDataImplOnDestroyDelegate>
+      strict_delegate;
+  {
+    auto local_site_data =
+        GetDataImpl(kDummyOrigin, &strict_delegate, &data_store);
+    EXPECT_CALL(strict_delegate,
+                OnSiteDataImplDestroyed(local_site_data.get()));
+  }
+  ::testing::Mock::VerifyAndClear(&strict_delegate);
+}
+
+TEST_F(SiteDataImplTest, OnInitCallbackMergePreviousObservations) {
+  // Use a mock data store to intercept the initialization callback and save it
+  // locally so it can be run later. This simulates an asynchronous
+  // initialization of this object and is used to test that the observations
+  // made between the time this object has been created and the callback is
+  // called get properly merged.
+  ::testing::StrictMock<MockDataStore> mock_data_store;
+  SiteDataStore::ReadSiteDataFromStoreCallback read_cb;
+
+  auto local_site_data = GetDataImplAndInterceptReadCallback(
+      kDummyOrigin, &destroy_delegate_, &mock_data_store, &read_cb);
+
+  // Simulates audio in background usage before the callback gets called.
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+  local_site_data->NotifyUsesAudioInBackground();
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesAudioInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UsesNotificationsInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UpdatesFaviconInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UpdatesTitleInBackground());
+
+  // Unload the site and save the last loaded time to make sure the
+  // initialization doesn't overwrite it.
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+  auto last_loaded = local_site_data->last_loaded_time_for_testing();
+
+  // Add a couple of performance samples.
+  local_site_data->NotifyLoadTimePerformanceMeasurement(
+      base::TimeDelta::FromMicroseconds(100),
+      base::TimeDelta::FromMicroseconds(1000), 2000u);
+  local_site_data->NotifyLoadTimePerformanceMeasurement(
+      base::TimeDelta::FromMicroseconds(200),
+      base::TimeDelta::FromMicroseconds(500), 1000u);
+
+  // Make sure the local performance samples are averaged as expected.
+  EXPECT_EQ(2U, local_site_data->load_duration().num_datums());
+  EXPECT_EQ(150, local_site_data->load_duration().value());
+
+  EXPECT_EQ(2U, local_site_data->cpu_usage_estimate().num_datums());
+  EXPECT_EQ(750.0, local_site_data->cpu_usage_estimate().value());
+
+  EXPECT_EQ(2U, local_site_data->private_footprint_kb_estimate().num_datums());
+  EXPECT_EQ(1500.0, local_site_data->private_footprint_kb_estimate().value());
+
+  // This protobuf should have a valid |last_loaded| field and valid observation
+  // durations for each features, but the |use_timestamp| field shouldn't have
+  // been initialized for the features that haven't been used.
+  EXPECT_TRUE(
+      local_site_data->site_characteristics_for_testing().has_last_loaded());
+  EXPECT_TRUE(local_site_data->site_characteristics_for_testing()
+                  .uses_audio_in_background()
+                  .has_use_timestamp());
+  EXPECT_FALSE(local_site_data->site_characteristics_for_testing()
+                   .uses_notifications_in_background()
+                   .has_use_timestamp());
+  EXPECT_TRUE(local_site_data->site_characteristics_for_testing()
+                  .uses_notifications_in_background()
+                  .has_observation_duration());
+  EXPECT_FALSE(local_site_data->site_characteristics_for_testing()
+                   .has_load_time_estimates());
+
+  // Initialize a fake protobuf that indicates that this site updates its title
+  // while in background and set a fake last loaded time (this should be
+  // overridden once the callback runs).
+  base::Optional<SiteDataProto> test_proto = SiteDataProto();
+  SiteDataFeatureProto unused_feature_proto = GetUnusedFeatureProto();
+  test_proto->mutable_updates_title_in_background()->CopyFrom(
+      GetUsedFeatureProto());
+  test_proto->mutable_updates_favicon_in_background()->CopyFrom(
+      unused_feature_proto);
+  test_proto->mutable_uses_audio_in_background()->CopyFrom(
+      unused_feature_proto);
+  test_proto->mutable_uses_notifications_in_background()->CopyFrom(
+      unused_feature_proto);
+  test_proto->set_last_loaded(42);
+
+  // Set the previously saved performance averages.
+  auto* estimates = test_proto->mutable_load_time_estimates();
+  estimates->set_avg_load_duration_us(50);
+  estimates->set_avg_cpu_usage_us(250);
+  estimates->set_avg_footprint_kb(500);
+
+  // Run the callback to indicate that the initialization has completed.
+  std::move(read_cb).Run(test_proto);
+
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesAudioInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UpdatesTitleInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UpdatesFaviconInBackground());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            local_site_data->UsesNotificationsInBackground());
+  EXPECT_EQ(last_loaded, local_site_data->last_loaded_time_for_testing());
+
+  // Make sure the local performance samples have been updated with the previous
+  // averages.
+  EXPECT_EQ(3U, local_site_data->load_duration().num_datums());
+  EXPECT_EQ(137.5, local_site_data->load_duration().value());
+
+  EXPECT_EQ(3U, local_site_data->cpu_usage_estimate().num_datums());
+  EXPECT_EQ(562.5, local_site_data->cpu_usage_estimate().value());
+
+  EXPECT_EQ(3U, local_site_data->private_footprint_kb_estimate().num_datums());
+  EXPECT_EQ(1125, local_site_data->private_footprint_kb_estimate().value());
+
+  // Verify that the in-memory data is flushed to the protobuffer on write.
+  EXPECT_CALL(mock_data_store,
+              WriteSiteDataIntoStore(::testing::_, ::testing::_))
+      .WillOnce(::testing::Invoke(
+          [](const url::Origin& origin, const SiteDataProto& proto) {
+            ASSERT_TRUE(proto.has_load_time_estimates());
+            const auto& estimates = proto.load_time_estimates();
+            ASSERT_TRUE(estimates.has_avg_load_duration_us());
+            EXPECT_EQ(137.5, estimates.avg_load_duration_us());
+            ASSERT_TRUE(estimates.has_avg_cpu_usage_us());
+            EXPECT_EQ(562.5, estimates.avg_cpu_usage_us());
+            ASSERT_TRUE(estimates.has_avg_footprint_kb());
+            EXPECT_EQ(1125, estimates.avg_footprint_kb());
+          }));
+
+  local_site_data = nullptr;
+  ::testing::Mock::VerifyAndClear(&mock_data_store);
+}
+
+TEST_F(SiteDataImplTest, LateAsyncReadDoesntEraseData) {
+  // Ensure that no historical data get lost if an asynchronous read from the
+  // data store finishes after the last reference to a SiteDataImpl gets
+  // destroyed.
+
+  ::testing::StrictMock<MockDataStore> mock_data_store;
+  SiteDataStore::ReadSiteDataFromStoreCallback read_cb;
+
+  auto local_site_data_writer = GetDataImplAndInterceptReadCallback(
+      kDummyOrigin, &destroy_delegate_, &mock_data_store, &read_cb);
+
+  local_site_data_writer->NotifySiteLoaded();
+  local_site_data_writer->NotifyLoadedSiteBackgrounded();
+  local_site_data_writer->NotifyUsesAudioInBackground();
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data_writer->UsesAudioInBackground());
+
+  local_site_data_writer->NotifySiteUnloaded(TabVisibility::kBackground);
+
+  // Releasing |local_site_data_writer| should cause this object to get
+  // destroyed but there shouldn't be any write operation as the read hasn't
+  // completed.
+  EXPECT_CALL(destroy_delegate_, OnSiteDataImplDestroyed(::testing::_));
+  EXPECT_CALL(mock_data_store,
+              WriteSiteDataIntoStore(::testing::_, ::testing::_))
+      .Times(0);
+  local_site_data_writer = nullptr;
+  ::testing::Mock::VerifyAndClear(&destroy_delegate_);
+  ::testing::Mock::VerifyAndClear(&mock_data_store);
+
+  EXPECT_TRUE(read_cb.IsCancelled());
+}
+
+TEST_F(SiteDataImplTest, LateAsyncReadDoesntBypassClearEvent) {
+  ::testing::NiceMock<MockDataStore> mock_data_store;
+  SiteDataStore::ReadSiteDataFromStoreCallback read_cb;
+
+  auto local_site_data = GetDataImplAndInterceptReadCallback(
+      kDummyOrigin, &destroy_delegate_, &mock_data_store, &read_cb);
+
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+  local_site_data->NotifyUsesAudioInBackground();
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            local_site_data->UsesAudioInBackground());
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+
+  // TODO(sebmarchand): Test that data is cleared here.
+  local_site_data->ClearObservationsAndInvalidateReadOperation();
+
+  EXPECT_TRUE(read_cb.IsCancelled());
+}
+
+TEST_F(SiteDataImplTest, BackgroundedCountTests) {
+  auto local_site_data =
+      GetDataImpl(kDummyOrigin, &destroy_delegate_, &data_store);
+
+  // By default the tabs are expected to be foregrounded.
+  EXPECT_EQ(0U, local_site_data->loaded_tabs_in_background_count_for_testing());
+
+  local_site_data->NotifySiteLoaded();
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+  local_site_data->NotifyLoadedSiteBackgrounded();
+
+  auto background_session_begin =
+      local_site_data->background_session_begin_for_testing();
+  EXPECT_EQ(test_clock_.NowTicks(), background_session_begin);
+
+  EXPECT_EQ(1U, local_site_data->loaded_tabs_in_background_count_for_testing());
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+  // Add a second instance of this object, this one pretending to be in
+  // foreground.
+  auto local_site_data_copy(local_site_data);
+  local_site_data_copy->NotifySiteLoaded();
+  EXPECT_EQ(1U, local_site_data->loaded_tabs_in_background_count_for_testing());
+
+  EXPECT_EQ(background_session_begin,
+            local_site_data->background_session_begin_for_testing());
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+  local_site_data->NotifyLoadedSiteForegrounded();
+  EXPECT_EQ(0U, local_site_data->loaded_tabs_in_background_count_for_testing());
+
+  auto expected_observation_duration =
+      test_clock_.NowTicks() - background_session_begin;
+
+  auto observed_observation_duration =
+      local_site_data->FeatureObservationDuration(
+          local_site_data->site_characteristics_for_testing()
+              .uses_notifications_in_background());
+
+  EXPECT_EQ(expected_observation_duration, observed_observation_duration);
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+  local_site_data->NotifyLoadedSiteBackgrounded();
+  EXPECT_EQ(1U, local_site_data->loaded_tabs_in_background_count_for_testing());
+  background_session_begin =
+      local_site_data->background_session_begin_for_testing();
+  EXPECT_EQ(test_clock_.NowTicks(), background_session_begin);
+
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+  local_site_data_copy->NotifySiteUnloaded(TabVisibility::kForeground);
+}
+
+TEST_F(SiteDataImplTest, OptionalFieldsNotPopulatedWhenClean) {
+  ::testing::StrictMock<MockDataStore> mock_data_store;
+  SiteDataStore::ReadSiteDataFromStoreCallback read_cb;
+
+  auto local_site_data = GetDataImplAndInterceptReadCallback(
+      kDummyOrigin, &destroy_delegate_, &mock_data_store, &read_cb);
+
+  EXPECT_EQ(0u, local_site_data->cpu_usage_estimate().num_datums());
+  EXPECT_EQ(0u, local_site_data->private_footprint_kb_estimate().num_datums());
+
+  base::Optional<SiteDataProto> test_proto = SiteDataProto();
+
+  // Run the callback to indicate that the initialization has completed.
+  std::move(read_cb).Run(test_proto);
+
+  // There still should be no perf data.
+  EXPECT_EQ(0u, local_site_data->cpu_usage_estimate().num_datums());
+  EXPECT_EQ(0u, local_site_data->private_footprint_kb_estimate().num_datums());
+
+  // Dirty the record to force a write.
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+  local_site_data->NotifyUsesAudioInBackground();
+
+  // Verify that the saved protobuffer isn't populated with the perf fields.
+  EXPECT_CALL(mock_data_store,
+              WriteSiteDataIntoStore(::testing::_, ::testing::_))
+      .WillOnce(::testing::Invoke(
+          [](const url::Origin& origin, const SiteDataProto& proto) {
+            ASSERT_FALSE(proto.has_load_time_estimates());
+          }));
+
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+  local_site_data = nullptr;
+  ::testing::Mock::VerifyAndClear(&mock_data_store);
+}
+
+TEST_F(SiteDataImplTest, FlushingStateToProtoDoesntAffectData) {
+  // Create 2 DataImpl object and do the same operations on them, ensures that
+  // calling FlushStateToProto doesn't affect the data that gets recorded.
+
+  auto local_site_data =
+      GetDataImpl(kDummyOrigin, &destroy_delegate_, &data_store);
+  auto local_site_data_ref =
+      GetDataImpl(kDummyOrigin2, &destroy_delegate_, &data_store);
+
+  local_site_data->NotifySiteLoaded();
+  local_site_data->NotifyLoadedSiteBackgrounded();
+  local_site_data_ref->NotifySiteLoaded();
+  local_site_data_ref->NotifyLoadedSiteBackgrounded();
+
+  test_clock_.Advance(base::TimeDelta::FromSeconds(15));
+  local_site_data->FlushStateToProto();
+  test_clock_.Advance(base::TimeDelta::FromSeconds(15));
+
+  local_site_data->NotifyUsesAudioInBackground();
+  local_site_data_ref->NotifyUsesAudioInBackground();
+
+  local_site_data->FlushStateToProto();
+
+  EXPECT_EQ(local_site_data->FeatureObservationTimestamp(
+                local_site_data->site_characteristics_for_testing()
+                    .uses_audio_in_background()),
+            local_site_data_ref->FeatureObservationTimestamp(
+                local_site_data_ref->site_characteristics_for_testing()
+                    .uses_audio_in_background()));
+
+  EXPECT_EQ(local_site_data->FeatureObservationDuration(
+                local_site_data->site_characteristics_for_testing()
+                    .updates_title_in_background()),
+            local_site_data_ref->FeatureObservationDuration(
+                local_site_data_ref->site_characteristics_for_testing()
+                    .updates_title_in_background()));
+
+  local_site_data->NotifySiteUnloaded(TabVisibility::kBackground);
+  local_site_data_ref->NotifySiteUnloaded(TabVisibility::kBackground);
+}
+
+TEST_F(SiteDataImplTest, DataLoadedCallbackInvoked) {
+  ::testing::StrictMock<MockDataStore> mock_data_store;
+  SiteDataStore::ReadSiteDataFromStoreCallback read_cb;
+
+  auto local_site_data = GetDataImplAndInterceptReadCallback(
+      kDummyOrigin, &destroy_delegate_, &mock_data_store, &read_cb);
+
+  EXPECT_FALSE(local_site_data->DataLoaded());
+
+  bool callback_invoked = false;
+  local_site_data->RegisterDataLoadedCallback(
+      base::BindLambdaForTesting([&]() { callback_invoked = true; }));
+
+  // Run the callback to indicate that the initialization has completed.
+  base::Optional<SiteDataProto> test_proto = SiteDataProto();
+  std::move(read_cb).Run(test_proto);
+
+  EXPECT_TRUE(callback_invoked);
+  EXPECT_TRUE(local_site_data->DataLoaded());
+}
+
+}  // namespace internal
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc
new file mode 100644
index 0000000..9ed6c74
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc
@@ -0,0 +1,57 @@
+// 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/performance_manager/persistence/site_data/site_data_reader.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+
+namespace performance_manager {
+
+SiteDataReader::SiteDataReader(scoped_refptr<internal::SiteDataImpl> impl)
+    : impl_(std::move(impl)), weak_factory_(this) {}
+
+SiteDataReader::~SiteDataReader() {}
+
+performance_manager::SiteFeatureUsage
+SiteDataReader::UpdatesFaviconInBackground() const {
+  return impl_->UpdatesFaviconInBackground();
+}
+
+performance_manager::SiteFeatureUsage SiteDataReader::UpdatesTitleInBackground()
+    const {
+  return impl_->UpdatesTitleInBackground();
+}
+
+performance_manager::SiteFeatureUsage SiteDataReader::UsesAudioInBackground()
+    const {
+  return impl_->UsesAudioInBackground();
+}
+
+performance_manager::SiteFeatureUsage
+SiteDataReader::UsesNotificationsInBackground() const {
+  return impl_->UsesNotificationsInBackground();
+}
+
+bool SiteDataReader::DataLoaded() const {
+  return impl_->DataLoaded();
+}
+
+void SiteDataReader::RegisterDataLoadedCallback(base::OnceClosure&& callback) {
+  // Register a closure that is bound using a weak pointer to this instance.
+  // In that way it won't be invoked by the underlying |impl_| after this
+  // reader is destroyed.
+  base::OnceClosure closure(base::BindOnce(&SiteDataReader::RunClosure,
+                                           weak_factory_.GetWeakPtr(),
+                                           std::move(callback)));
+  impl_->RegisterDataLoadedCallback(std::move(closure));
+}
+
+void SiteDataReader::RunClosure(base::OnceClosure&& closure) {
+  std::move(closure).Run();
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h
new file mode 100644
index 0000000..ccf7172
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.h
@@ -0,0 +1,75 @@
+// 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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/performance_manager/persistence/site_data/feature_usage.h"
+
+namespace performance_manager {
+
+FORWARD_DECLARE_TEST(SiteDataReaderTest,
+                     FreeingReaderDoesntCauseWriteOperation);
+
+namespace internal {
+class SiteDataImpl;
+}  // namespace internal
+
+class SiteDataReader {
+ public:
+  ~SiteDataReader();
+
+  // Accessors for the site characteristics usage.
+  performance_manager::SiteFeatureUsage UpdatesFaviconInBackground() const;
+  performance_manager::SiteFeatureUsage UpdatesTitleInBackground() const;
+  performance_manager::SiteFeatureUsage UsesAudioInBackground() const;
+  performance_manager::SiteFeatureUsage UsesNotificationsInBackground() const;
+
+  // Returns true if this reader is fully initialized and serving the most
+  // authoritative data. This can initially return false as the backing store is
+  // loaded asynchronously.
+  bool DataLoaded() const;
+
+  // Registers a callback that will be invoked when the data backing this object
+  // has been loaded. Note that if "DataLoaded" is true at the time this is
+  // called it may immediately invoke the callback. The callback will not be
+  // invoked after this object has been destroyed.
+  void RegisterDataLoadedCallback(base::OnceClosure&& callback);
+
+  const internal::SiteDataImpl* impl_for_testing() const { return impl_.get(); }
+
+ private:
+  friend class SiteDataReaderTest;
+  FRIEND_TEST_ALL_PREFIXES(SiteDataReaderTest,
+                           DestroyingReaderCancelsPendingCallbacks);
+  FRIEND_TEST_ALL_PREFIXES(SiteDataReaderTest,
+                           FreeingReaderDoesntCauseWriteOperation);
+  FRIEND_TEST_ALL_PREFIXES(SiteDataReaderTest, OnDataLoadedCallbackInvoked);
+
+  // Private constructor, these objects are meant to be created by a site data
+  // store.
+  explicit SiteDataReader(scoped_refptr<internal::SiteDataImpl> impl);
+
+  // Runs the provided closure. This is used as a wrapper so that callbacks
+  // registered with the |impl_| by this reader are invalidated when the
+  // reader is destroyed.
+  void RunClosure(base::OnceClosure&& closure);
+
+  // The SiteDataImpl object we delegate to.
+  const scoped_refptr<internal::SiteDataImpl> impl_;
+
+  // Used for invalidating callbacks.
+  base::WeakPtrFactory<SiteDataReader> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SiteDataReader);
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_SITE_DATA_READER_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc
new file mode 100644
index 0000000..81354759
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc
@@ -0,0 +1,239 @@
+// 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/performance_manager/persistence/site_data/site_data_reader.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "chrome/browser/performance_manager/performance_manager_clock.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace performance_manager {
+
+namespace {
+
+class MockSiteDataStore : public testing::NoopSiteDataStore {
+ public:
+  MockSiteDataStore() = default;
+  ~MockSiteDataStore() = default;
+
+  // Note: As move-only parameters (e.g. OnceCallback) aren't supported by mock
+  // methods, add On... methods to pass a non-const reference to OnceCallback.
+  void ReadSiteDataFromStore(
+      const url::Origin& origin,
+      SiteDataStore::ReadSiteDataFromStoreCallback callback) override {
+    OnReadSiteDataFromStore(std::move(origin), callback);
+  }
+  MOCK_METHOD2(OnReadSiteDataFromStore,
+               void(const url::Origin&,
+                    SiteDataStore::ReadSiteDataFromStoreCallback&));
+
+  MOCK_METHOD2(WriteSiteDataIntoStore,
+               void(const url::Origin&, const SiteDataProto&));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSiteDataStore);
+};
+
+void InitializeSiteDataProto(SiteDataProto* site_data) {
+  DCHECK(site_data);
+  site_data->set_last_loaded(42);
+
+  SiteDataFeatureProto used_feature_proto;
+  used_feature_proto.set_observation_duration(0U);
+  used_feature_proto.set_use_timestamp(1U);
+
+  site_data->mutable_updates_favicon_in_background()->CopyFrom(
+      used_feature_proto);
+  site_data->mutable_updates_title_in_background()->CopyFrom(
+      used_feature_proto);
+  site_data->mutable_uses_audio_in_background()->CopyFrom(used_feature_proto);
+  site_data->mutable_uses_notifications_in_background()->CopyFrom(
+      used_feature_proto);
+
+  DCHECK(site_data->IsInitialized());
+}
+
+}  // namespace
+
+class SiteDataReaderTest : public ::testing::Test {
+ protected:
+  // The constructors needs to call 'new' directly rather than using the
+  // base::MakeRefCounted helper function because the constructor of
+  // SiteDataImpl is protected and not visible to
+  // base::MakeRefCounted.
+  SiteDataReaderTest() {
+    PerformanceManagerClock::SetClockForTesting(&test_clock_);
+
+    test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+    test_impl_ = base::WrapRefCounted(new internal::SiteDataImpl(
+        url::Origin::Create(GURL("foo.com")), &delegate_, &data_store_));
+    test_impl_->NotifySiteLoaded();
+    test_impl_->NotifyLoadedSiteBackgrounded();
+    SiteDataReader* reader = new SiteDataReader(test_impl_.get());
+    reader_ = base::WrapUnique(reader);
+  }
+
+  ~SiteDataReaderTest() override {
+    test_impl_->NotifySiteUnloaded(
+        performance_manager::TabVisibility::kBackground);
+    PerformanceManagerClock::ResetClockForTesting();
+  }
+
+  base::SimpleTestTickClock test_clock_;
+
+  // The mock delegate used by the SiteDataImpl objects
+  // created by this class, NiceMock is used to avoid having to set expectations
+  // in test cases that don't care about this.
+  ::testing::NiceMock<testing::MockSiteDataImplOnDestroyDelegate> delegate_;
+
+  // The SiteDataImpl object used in these tests.
+  scoped_refptr<internal::SiteDataImpl> test_impl_;
+
+  // A SiteDataReader object associated with the origin used
+  // to create this object.
+  std::unique_ptr<SiteDataReader> reader_;
+
+  testing::NoopSiteDataStore data_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(SiteDataReaderTest);
+};
+
+TEST_F(SiteDataReaderTest, TestAccessors) {
+  // Initially we have no information about any of the features.
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader_->UpdatesFaviconInBackground());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader_->UpdatesTitleInBackground());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader_->UsesAudioInBackground());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader_->UsesNotificationsInBackground());
+
+  // Simulates a title update event, make sure it gets reported directly.
+  test_impl_->NotifyUpdatesTitleInBackground();
+
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+            reader_->UpdatesTitleInBackground());
+
+  // Advance the clock by a large amount of time, enough for the unused features
+  // observation windows to expire.
+  test_clock_.Advance(base::TimeDelta::FromDays(31));
+
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureNotInUse,
+            reader_->UpdatesFaviconInBackground());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+            reader_->UpdatesTitleInBackground());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureNotInUse,
+            reader_->UsesAudioInBackground());
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureNotInUse,
+            reader_->UsesNotificationsInBackground());
+}
+
+TEST_F(SiteDataReaderTest, FreeingReaderDoesntCauseWriteOperation) {
+  const url::Origin kOrigin = url::Origin::Create(GURL("foo.com"));
+  ::testing::StrictMock<MockSiteDataStore> data_store;
+
+  // Override the read callback to simulate a successful read from the
+  // data store.
+  SiteDataProto proto = {};
+  InitializeSiteDataProto(&proto);
+  auto read_from_store_mock_impl =
+      [&](const url::Origin& origin,
+          SiteDataStore::ReadSiteDataFromStoreCallback& callback) {
+        std::move(callback).Run(base::Optional<SiteDataProto>(proto));
+      };
+
+  EXPECT_CALL(data_store, OnReadSiteDataFromStore(
+                              ::testing::Property(&url::Origin::Serialize,
+                                                  kOrigin.Serialize()),
+                              ::testing::_))
+      .WillOnce(::testing::Invoke(read_from_store_mock_impl));
+
+  std::unique_ptr<SiteDataReader> reader =
+      base::WrapUnique(new SiteDataReader(base::WrapRefCounted(
+          new internal::SiteDataImpl(kOrigin, &delegate_, &data_store))));
+  ::testing::Mock::VerifyAndClear(&data_store);
+
+  EXPECT_TRUE(reader->impl_for_testing()->fully_initialized_for_testing());
+
+  // Resetting the reader shouldn't cause any write operation to the data store.
+  EXPECT_CALL(data_store, WriteSiteDataIntoStore(::testing::_, ::testing::_))
+      .Times(0);
+  reader.reset();
+  ::testing::Mock::VerifyAndClear(&data_store);
+}
+
+TEST_F(SiteDataReaderTest, OnDataLoadedCallbackInvoked) {
+  const url::Origin kOrigin = url::Origin::Create(GURL("foo.com"));
+  ::testing::StrictMock<MockSiteDataStore> data_store;
+
+  // Create the impl.
+  EXPECT_CALL(data_store, OnReadSiteDataFromStore(
+                              ::testing::Property(&url::Origin::Serialize,
+                                                  kOrigin.Serialize()),
+                              ::testing::_));
+  scoped_refptr<internal::SiteDataImpl> impl = base::WrapRefCounted(
+      new internal::SiteDataImpl(kOrigin, &delegate_, &data_store));
+
+  // Create the reader.
+  std::unique_ptr<SiteDataReader> reader =
+      base::WrapUnique(new SiteDataReader(impl));
+  EXPECT_FALSE(reader->DataLoaded());
+
+  // Register a data ready closure.
+  bool on_data_loaded = false;
+  reader->RegisterDataLoadedCallback(base::BindLambdaForTesting(
+      [&on_data_loaded]() { on_data_loaded = true; }));
+
+  // Transition the impl to fully initialized, which should cause the callbacks
+  // to fire.
+  EXPECT_FALSE(impl->DataLoaded());
+  EXPECT_FALSE(on_data_loaded);
+  impl->TransitionToFullyInitialized();
+  EXPECT_TRUE(impl->DataLoaded());
+  EXPECT_TRUE(on_data_loaded);
+}
+
+TEST_F(SiteDataReaderTest, DestroyingReaderCancelsPendingCallbacks) {
+  const url::Origin kOrigin = url::Origin::Create(GURL("foo.com"));
+  ::testing::StrictMock<MockSiteDataStore> data_store;
+
+  // Create the impl.
+  EXPECT_CALL(data_store, OnReadSiteDataFromStore(
+                              ::testing::Property(&url::Origin::Serialize,
+                                                  kOrigin.Serialize()),
+                              ::testing::_));
+  scoped_refptr<internal::SiteDataImpl> impl = base::WrapRefCounted(
+      new internal::SiteDataImpl(kOrigin, &delegate_, &data_store));
+
+  // Create the reader.
+  std::unique_ptr<SiteDataReader> reader =
+      base::WrapUnique(new SiteDataReader(impl));
+  EXPECT_FALSE(reader->DataLoaded());
+
+  // Register a data ready closure.
+  reader->RegisterDataLoadedCallback(
+      base::MakeExpectedNotRunClosure(FROM_HERE));
+
+  // Reset the reader.
+  reader.reset();
+
+  // Transition the impl to fully initialized, which should cause the callbacks
+  // to fire. The reader's callback should *not* be invoked.
+  EXPECT_FALSE(impl->DataLoaded());
+  impl->TransitionToFullyInitialized();
+  EXPECT_TRUE(impl->DataLoaded());
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
new file mode 100644
index 0000000..5b782dd
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
@@ -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.
+
+#include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+
+#include <utility>
+
+namespace performance_manager {
+namespace testing {
+
+MockSiteDataImplOnDestroyDelegate::MockSiteDataImplOnDestroyDelegate() =
+    default;
+MockSiteDataImplOnDestroyDelegate::~MockSiteDataImplOnDestroyDelegate() =
+    default;
+
+NoopSiteDataStore::NoopSiteDataStore() = default;
+NoopSiteDataStore::~NoopSiteDataStore() = default;
+
+void NoopSiteDataStore::ReadSiteDataFromStore(
+    const url::Origin& origin,
+    ReadSiteDataFromStoreCallback callback) {
+  std::move(callback).Run(base::nullopt);
+}
+
+void NoopSiteDataStore::WriteSiteDataIntoStore(
+    const url::Origin& origin,
+    const SiteDataProto& site_characteristic_proto) {}
+
+void NoopSiteDataStore::RemoveSiteDataFromStore(
+    const std::vector<url::Origin>& site_origins) {}
+
+void NoopSiteDataStore::ClearStore() {}
+
+void NoopSiteDataStore::GetStoreSize(GetStoreSizeCallback callback) {
+  std::move(callback).Run(base::nullopt, base::nullopt);
+}
+
+}  // namespace testing
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
new file mode 100644
index 0000000..f7ea028
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
@@ -0,0 +1,55 @@
+// 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_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_UNITTEST_UTILS_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_UNITTEST_UTILS_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace performance_manager {
+namespace testing {
+
+class MockSiteDataImplOnDestroyDelegate
+    : public internal::SiteDataImpl::OnDestroyDelegate {
+ public:
+  MockSiteDataImplOnDestroyDelegate();
+  ~MockSiteDataImplOnDestroyDelegate();
+
+  MOCK_METHOD1(OnSiteDataImplDestroyed, void(internal::SiteDataImpl*));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSiteDataImplOnDestroyDelegate);
+};
+
+// An implementation of a SiteDataStore that doesn't record anything.
+class NoopSiteDataStore : public SiteDataStore {
+ public:
+  NoopSiteDataStore();
+  ~NoopSiteDataStore() override;
+
+  // SiteDataStore:
+  void ReadSiteDataFromStore(const url::Origin& origin,
+                             ReadSiteDataFromStoreCallback callback) override;
+  void WriteSiteDataIntoStore(
+      const url::Origin& origin,
+      const SiteDataProto& site_characteristic_proto) override;
+  void RemoveSiteDataFromStore(
+      const std::vector<url::Origin>& site_origins) override;
+  void ClearStore() override;
+  void GetStoreSize(GetStoreSizeCallback callback) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NoopSiteDataStore);
+};
+
+}  // namespace testing
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_UNITTEST_UTILS_H_
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index 1ef3e3f..fd1e7be 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -1060,49 +1060,7 @@
 
 IN_PROC_BROWSER_TEST_P(
     PreviewsLitePageServerBrowserTest,
-    DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsReloadDisabled)) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures(
-      {}, {previews::features::kPreviewsReloadsAreSoftOptOuts});
-
-  content::ReloadType tests[] = {
-      content::ReloadType::NORMAL,
-      content::ReloadType::BYPASSING_CACHE,
-      content::ReloadType::ORIGINAL_REQUEST_URL,
-  };
-  for (content::ReloadType type : tests) {
-    // Start with a non-preview load.
-    g_browser_process->network_quality_tracker()
-        ->ReportEffectiveConnectionTypeForTesting(
-            net::EFFECTIVE_CONNECTION_TYPE_3G);
-
-    ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
-    VerifyPreviewNotLoaded();
-
-    // Set the conditions so a Preview would trigger if not for the reload.
-    g_browser_process->network_quality_tracker()
-        ->ReportEffectiveConnectionTypeForTesting(
-            net::EFFECTIVE_CONNECTION_TYPE_2G);
-    GetWebContents()->GetController().Reload(type, false);
-    VerifyPreviewNotLoaded();
-
-    // Verify that a reload on a preview page triggers a redirect back to the
-    // original page.
-    ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
-    VerifyPreviewLoaded();
-
-    GetWebContents()->GetController().Reload(type, false);
-    VerifyPreviewNotLoaded();
-  }
-}
-
-IN_PROC_BROWSER_TEST_P(
-    PreviewsLitePageServerBrowserTest,
-    DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsReloadDisabled_SoftOptOut)) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures(
-      {previews::features::kPreviewsReloadsAreSoftOptOuts}, {});
-
+    DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsReloadSoftOptOut)) {
   ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
   VerifyPreviewLoaded();
 
diff --git a/chrome/browser/previews/previews_ui_tab_helper.cc b/chrome/browser/previews/previews_ui_tab_helper.cc
index 1c5172c..bacf8b698 100644
--- a/chrome/browser/previews/previews_ui_tab_helper.cc
+++ b/chrome/browser/previews/previews_ui_tab_helper.cc
@@ -231,12 +231,6 @@
 
 void PreviewsUITabHelper::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
-  // If reloads are treated as soft opt outs, and this is a main frame reload
-  // from a preview. Report the Preview reload to the decider.
-  if (!base::FeatureList::IsEnabled(
-          previews::features::kPreviewsReloadsAreSoftOptOuts)) {
-    return;
-  }
   if (navigation_handle->GetReloadType() == content::ReloadType::NONE)
     return;
   if (!navigation_handle->IsInMainFrame())
diff --git a/chrome/browser/resources/chromeos/login/screen_gaia_signin.css b/chrome/browser/resources/chromeos/login/screen_gaia_signin.css
index 8755be4..2c79b05 100644
--- a/chrome/browser/resources/chromeos/login/screen_gaia_signin.css
+++ b/chrome/browser/resources/chromeos/login/screen_gaia_signin.css
@@ -100,20 +100,6 @@
   visibility: visible;
 }
 
-#gaia-navigation {
-  color: white;
-  left: 0;
-  position: absolute;
-  right: 0;
-  top: 0;
-  z-index: 1;
-}
-
-.loading #gaia-navigation {
-  color: rgba(0, 0, 0, .54);
-}
-
-#gaia-signin.v2 #gaia-navigation,
 #gaia-signin.v2 #gaia-step-contents {
   display: none;
 }
diff --git a/chrome/browser/resources/chromeos/login/screen_gaia_signin.html b/chrome/browser/resources/chromeos/login/screen_gaia_signin.html
index 6a4ae91..67762a2f 100644
--- a/chrome/browser/resources/chromeos/login/screen_gaia_signin.html
+++ b/chrome/browser/resources/chromeos/login/screen_gaia_signin.html
@@ -43,5 +43,4 @@
     </span>
     <span id="saml-notice-message"></span>
   </div>
-  <navigation-bar id="gaia-navigation"></navigation-bar>
 </div>
diff --git a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
index 0e337a9..2702ee3 100644
--- a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
@@ -132,10 +132,7 @@
     /**
      * Whether the dialog could be closed.
      * This is being checked in cancel() when user clicks on signin-back-button
-     * (normal gaia flow) or the buttons in gaia-navigation (used in enterprise
-     * enrollment) etc.
-     * This value also controls the visibility of refresh button and close
-     * button in the gaia-navigation.
+     * (normal gaia flow) or the "Back" button in other authentication frames.
      * @type {boolean}
      */
     get closable() {
@@ -143,37 +140,36 @@
     },
 
     /**
-     * Returns true if GAIA is at the begging of flow (i.e. the email page).
+     * Returns true if the screen is at the beginning of flow (i.e. the email
+     * page).
      * @type {boolean}
      */
     isAtTheBeginning: function() {
-      return !this.navigation_.backVisible && !this.isSAML() &&
+      return !this.canGoBack_() && !this.isSAML() &&
           !this.classList.contains('whitelist-error') && !this.authCompleted_;
     },
 
     /**
-     * Updates visibility of navigation buttons.
+     * Updates visibility of UI control buttons.
      */
     updateControlsState: function() {
-      var isWhitelistError = this.classList.contains('whitelist-error');
-
-      this.navigation_.backVisible = this.lastBackMessageValue_ &&
-          !isWhitelistError && !this.authCompleted_ && !this.loading &&
-          !this.isSAML();
-
-      this.navigation_.refreshVisible = !this.closable &&
-          this.isAtTheBeginning() &&
-          this.screenMode_ != ScreenMode.SAML_INTERSTITIAL;
-
-      this.navigation_.closeVisible = !this.navigation_.refreshVisible &&
-          !isWhitelistError && !this.authCompleted_ &&
-          this.screenMode_ != ScreenMode.SAML_INTERSTITIAL;
-
       let showGuestInOobe = !this.closable && this.isAtTheBeginning();
       chrome.send('showGuestInOobe', [showGuestInOobe]);
     },
 
     /**
+     * Returns whether it's possible to rewind the sign-in frame one step back
+     * (as opposed to cancelling the sign-in flow).
+     * @type {boolean}
+     * @private
+     */
+    canGoBack_: function() {
+      var isWhitelistError = this.classList.contains('whitelist-error');
+      return this.lastBackMessageValue_ && !isWhitelistError &&
+          !this.authCompleted_ && !this.loading && !this.isSAML();
+    },
+
+    /**
      * SAML password confirmation attempt count.
      * @type {number}
      */
@@ -192,16 +188,9 @@
      */
     mostRecentUserActivity_: Date.now(),
 
-    /**
-     * An element containg navigation buttons.
-     */
-    navigation_: undefined,
-
 
     /** @override */
     decorate: function() {
-      this.navigation_ = $('gaia-navigation');
-
       this.authenticator_ = new cr.login.Authenticator($('signin-frame'));
       this.authenticator_.addEventListener(
           'ready', this.onAuthReady_.bind(this));
@@ -267,21 +256,11 @@
       this.authenticator_.addEventListener(
           'identifierEntered', this.onIdentifierEnteredMessage_.bind(this));
 
-      this.navigation_.addEventListener(
-          'back', this.onBackButtonClicked_.bind(this, null));
-
       $('signin-back-button')
-          .addEventListener('tap', this.onBackButtonClicked_.bind(this, true));
+          .addEventListener('tap', this.onBackButtonClicked_.bind(this));
       $('offline-gaia')
           .addEventListener('offline-gaia-cancel', this.cancel.bind(this));
 
-      this.navigation_.addEventListener('close', function() {
-        this.cancel();
-      }.bind(this));
-      this.navigation_.addEventListener('refresh', function() {
-        this.cancel();
-      }.bind(this));
-
       $('gaia-whitelist-error').addEventListener('buttonclick', function() {
         this.showWhitelistCheckFailedError(false);
       }.bind(this));
@@ -312,13 +291,11 @@
 
     /**
      * Handles clicks on "Back" button.
-     * @param {boolean} isDialogButton If event comes from gaia-dialog.
      */
-    onBackButtonClicked_: function(isDialogButton) {
-      if (isDialogButton && !this.navigation_.backVisible) {
+    onBackButtonClicked_: function() {
+      if (!this.canGoBack_()) {
         this.cancel();
       } else {
-        this.navigation_.backVisible = false;
         this.getSigninFrame_().back();
       }
     },
@@ -580,20 +557,9 @@
       return $('signin-frame-dialog').onBeforeShow();
     },
 
-    get navigationDisabled_() {
-      return this.navigation_.disabled;
-    },
-
     set navigationDisabled_(value) {
-      this.navigation_.disabled = value;
-
-      if (value)
-        $('gaia-screen-buttons-overlay').removeAttribute('hidden');
-      else
-        $('gaia-screen-buttons-overlay').setAttribute('hidden', null);
-
-      if ($('signin-back-button'))
-        $('signin-back-button').disabled = value;
+      $('gaia-screen-buttons-overlay').hidden = !value;
+      $('signin-back-button').disabled = value;
     },
 
     getSigninFrame_: function() {
@@ -1158,8 +1124,14 @@
      */
     cancel: function() {
       this.clearVideoTimer_();
-      if (!this.navigation_.refreshVisible && !this.navigation_.closeVisible)
+
+      const isWhitelistError = this.classList.contains('whitelist-error');
+      // TODO(crbug.com/470893): Figure out whether/which of these exit
+      // conditions are useful.
+      if (this.screenMode_ == ScreenMode.SAML_INTERSTITIAL ||
+          isWhitelistError || this.authCompleted_) {
         return;
+      }
 
       if (this.screenMode_ == ScreenMode.AD_AUTH)
         chrome.send('cancelAdAuthentication');
diff --git a/chrome/browser/resources/omnibox/omnibox.html b/chrome/browser/resources/omnibox/omnibox.html
index 1df1bef..679a77d 100644
--- a/chrome/browser/resources/omnibox/omnibox.html
+++ b/chrome/browser/resources/omnibox/omnibox.html
@@ -111,15 +111,15 @@
           <option value="2">about:blank</option>
           <option value="3">user's home page</option>
           <option value="4" selected>arbitrary URL</option>
+          <option value="6">
+            search result page doing search term replacement
+          </option>
           <option value="9">
             search result page not doing search term replacement
           </option>
           <option value="7">new tab page omnibox</option>
           <option value="8">new tab page fakebox</option>
           <option value="1">(OBSOLETE) new tab page</option>
-          <option value="6">
-            (OBSOLETE) search result page doing search term replacement
-          </option>
         </select>
       </div>
 
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
index 03e1949..7323018 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
@@ -15,7 +15,6 @@
     "../..:page_visibility",
     "../..:route",
     "../..:search_settings",
-    "../../about_page",
     "../../settings_page:main_page_behavior",
     "../os_settings_page:os_settings_page",
     "//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html
index 46df198..b25d393 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.html
@@ -9,7 +9,6 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 <link rel="import" href="../os_settings_page/os_settings_page.html">
-<link rel="import" href="../../about_page/about_page.html">
 <link rel="import" href="../../i18n_setup.html">
 <link rel="import" href="../../prefs/prefs.html">
 <link rel="import" href="../../route.html">
@@ -77,15 +76,6 @@
           advanced-toggle-expanded="{{advancedToggleExpanded}}">
       </os-settings-page>
     </template>
-    <template is="dom-if" if="[[showPages_.about]]">
-      <settings-about-page role="main"
-          in-search-mode="[[inSearchMode_]]"
-          on-subpage-expand="onShowingSubpage_"
-          on-showing-main-page="onShowingMainPage_"
-          prefs="{{prefs}}"
-          show-crostini="[[showCrostini]]">
-      </settings-about-page>
-    </template>
     <div id="overscroll" style="padding-bottom: [[overscroll_]]px"></div>
   </template>
   <script src="os_settings_main.js"></script>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js
index 3f02b25..71b8b84 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js
@@ -173,13 +173,9 @@
   /**
    * Returns the root page (if it exists) for a route.
    * @param {!settings.Route} route
-   * @return {(?SettingsAboutPageElement|?OsSettingsPageElement)}
+   * @return {?OsSettingsPageElement}
    */
   getPage_: function(route) {
-    if (settings.routes.ABOUT.contains(route)) {
-      return /** @type {?SettingsAboutPageElement} */ (
-          this.$$('settings-about-page'));
-    }
     if (settings.routes.BASIC.contains(route) ||
         (settings.routes.ADVANCED &&
          settings.routes.ADVANCED.contains(route))) {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
index 7a3aa888..d0a69c2a5 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
@@ -174,7 +174,6 @@
         </iron-selector>
       </iron-collapse>
       <div id="menuSeparator"></div>
-      <a id="about-menu" href="/help">$i18n{aboutPageTitle}</a>
     </iron-selector>
   </template>
   <script src="os_settings_menu.js"></script>
diff --git a/chrome/browser/resources/settings/os_settings_resources.grd b/chrome/browser/resources/settings/os_settings_resources.grd
index eebda2c..f18f257b 100644
--- a/chrome/browser/resources/settings/os_settings_resources.grd
+++ b/chrome/browser/resources/settings/os_settings_resources.grd
@@ -36,40 +36,6 @@
       <structure name="IDR_OS_SETTINGS_MANIFEST"
                  file="manifest.json"
                  type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_ABOUT_PAGE_BROWSER_PROXY_HTML"
-                 file="about_page/about_page_browser_proxy.html"
-                 type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_ABOUT_PAGE_BROWSER_PROXY_JS"
-                 file="about_page/about_page_browser_proxy.js"
-                 type="chrome_html"
-                 preprocess="true" />
-      <structure name="IDR_OS_SETTINGS_ABOUT_PAGE_JS"
-                 file="about_page/about_page.js"
-                 type="chrome_html"
-                 preprocess="true" />
-      <structure name="IDR_OS_SETTINGS_ABOUT_PAGE_HTML"
-                 file="about_page/about_page.html"
-                 type="chrome_html"
-                 preprocess="true"
-                 allowexternalscript="true" />
-      <structure name="IDR_OS_SETTINGS_CHANNEL_SWITCHER_DIALOG_HTML"
-                 file="about_page/channel_switcher_dialog.html"
-                 type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_CHANNEL_SWITCHER_DIALOG_JS"
-                 file="about_page/channel_switcher_dialog.js"
-                 type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_DETAILED_BUILD_INFO_JS"
-                 file="about_page/detailed_build_info.js"
-                 type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_DETAILED_BUILD_INFO_HTML"
-                 file="about_page/detailed_build_info.html"
-                 type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_UPDATE_WARNING_DIALOG_HTML"
-                 file="about_page/update_warning_dialog.html"
-                 type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_UPDATE_WARNING_DIALOG_JS"
-                 file="about_page/update_warning_dialog.js"
-                 type="chrome_html" />
       <structure name="IDR_OS_SETTINGS_ADD_SITE_DIALOG_HTML"
                  file="site_settings/add_site_dialog.html"
                  type="chrome_html" />
diff --git a/chrome/browser/sync/test/integration/performance/bookmarks_sync_perf_test.cc b/chrome/browser/sync/test/integration/performance/bookmarks_sync_perf_test.cc
index 400352d0..db033a2 100644
--- a/chrome/browser/sync/test/integration/performance/bookmarks_sync_perf_test.cc
+++ b/chrome/browser/sync/test/integration/performance/bookmarks_sync_perf_test.cc
@@ -70,7 +70,7 @@
 }
 
 void BookmarksSyncPerfTest::RemoveURLs(int profile) {
-  while (!GetBookmarkBarNode(profile)->empty()) {
+  while (!GetBookmarkBarNode(profile)->children().empty()) {
     Remove(profile, GetBookmarkBarNode(profile), 0);
   }
 }
diff --git a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
index 802229e..f45a1f4 100644
--- a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
@@ -2106,18 +2106,18 @@
   // Verify that there are no managed bookmarks at startup in either profile.
   // The Managed Bookmarks folder should not be visible at this stage.
   const BookmarkNode* managed_node0 = GetManagedNode(0);
-  ASSERT_TRUE(managed_node0->empty());
+  ASSERT_TRUE(managed_node0->children().empty());
   ASSERT_FALSE(managed_node0->IsVisible());
   const BookmarkNode* managed_node1 = GetManagedNode(1);
-  ASSERT_TRUE(managed_node1->empty());
+  ASSERT_TRUE(managed_node1->children().empty());
   ASSERT_FALSE(managed_node1->IsVisible());
 
   // Verify that the bookmark bar node is empty on both profiles too.
   const BookmarkNode* bar_node0 = GetBookmarkBarNode(0);
-  ASSERT_TRUE(bar_node0->empty());
+  ASSERT_TRUE(bar_node0->children().empty());
   ASSERT_TRUE(bar_node0->IsVisible());
   const BookmarkNode* bar_node1 = GetBookmarkBarNode(1);
-  ASSERT_TRUE(bar_node1->empty());
+  ASSERT_TRUE(bar_node1->children().empty());
   ASSERT_TRUE(bar_node1->IsVisible());
 
   // Verify that adding a bookmark is observed by the second Profile.
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.cc b/chrome/browser/ui/app_list/app_list_client_impl.cc
index 5afe27a..5eef4e7 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl.cc
@@ -94,12 +94,11 @@
   }
 }
 
-void AppListClientImpl::OpenSearchResult(
-    const std::string& result_id,
-    int event_flags,
-    ash::mojom::AppListLaunchedFrom launched_from,
-    ash::mojom::AppListLaunchType launch_type,
-    int suggestion_index) {
+void AppListClientImpl::OpenSearchResult(const std::string& result_id,
+                                         int event_flags,
+                                         ash::AppListLaunchedFrom launched_from,
+                                         ash::AppListLaunchType launch_type,
+                                         int suggestion_index) {
   if (!search_controller_)
     return;
 
@@ -111,7 +110,7 @@
   search_controller_->Train(result_id,
                             app_list::RankingItemTypeFromSearchResult(*result));
 
-  if (launch_type == ash::mojom::AppListLaunchType::kAppSearchResult) {
+  if (launch_type == ash::AppListLaunchType::kAppSearchResult) {
     // Log the AppResult (either in the search result page, or in chip form in
     // AppsGridView) to the UKM system.
     app_launch_event_logger_.OnSuggestionChipOrSearchBoxClicked(
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.h b/chrome/browser/ui/app_list/app_list_client_impl.h
index 2796499..e07f92e 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.h
+++ b/chrome/browser/ui/app_list/app_list_client_impl.h
@@ -50,8 +50,8 @@
   void StartSearch(const base::string16& trimmed_query) override;
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
-                        ash::mojom::AppListLaunchedFrom launched_from,
-                        ash::mojom::AppListLaunchType launch_type,
+                        ash::AppListLaunchedFrom launched_from,
+                        ash::AppListLaunchType launch_type,
                         int suggestion_index) override;
   void InvokeSearchResultAction(const std::string& result_id,
                                 int action_index,
diff --git a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
index f22a963..b2420f48 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
@@ -180,10 +180,9 @@
   ASSERT_TRUE(search_controller->FindSearchResult(app_result_id));
 
   // Open the app result.
-  client->OpenSearchResult(
-      app_result_id, ui::EF_NONE,
-      ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
-      ash::mojom::AppListLaunchType::kAppSearchResult, 0);
+  client->OpenSearchResult(app_result_id, ui::EF_NONE,
+                           ash::AppListLaunchedFrom::kLaunchedFromSearchBox,
+                           ash::AppListLaunchType::kAppSearchResult, 0);
 
   // App list should be dismissed.
   EXPECT_FALSE(client->app_list_target_visibility());
diff --git a/chrome/browser/ui/app_list/app_list_model_updater.h b/chrome/browser/ui/app_list/app_list_model_updater.h
index 52476fa..3efd76e 100644
--- a/chrome/browser/ui/app_list/app_list_model_updater.h
+++ b/chrome/browser/ui/app_list/app_list_model_updater.h
@@ -9,7 +9,7 @@
 #include <string>
 #include <vector>
 
-#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/callback_forward.h"
 #include "base/containers/flat_map.h"
 #include "base/strings/string16.h"
@@ -89,7 +89,7 @@
 
   virtual void SetSearchResultMetadata(
       const std::string& id,
-      ash::mojom::SearchResultMetadataPtr metadata) {}
+      std::unique_ptr<ash::SearchResultMetadata> metadata) {}
   virtual void SetSearchResultIsInstalling(const std::string& id,
                                            bool is_installing) {}
   virtual void SetSearchResultPercentDownloaded(const std::string& id,
diff --git a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
index 4c91cff..a408dbd 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
+++ b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
@@ -144,7 +144,7 @@
     result->set_model_updater(this);
   if (!app_list_controller_)
     return;
-  std::vector<ash::mojom::SearchResultMetadataPtr> result_data;
+  std::vector<std::unique_ptr<ash::SearchResultMetadata>> result_data;
   for (auto* result : results)
     result_data.push_back(result->CloneMetadata());
   app_list_controller_->PublishSearchResults(std::move(result_data));
@@ -276,7 +276,7 @@
 
 void ChromeAppListModelUpdater::SetSearchResultMetadata(
     const std::string& id,
-    ash::mojom::SearchResultMetadataPtr metadata) {
+    std::unique_ptr<ash::SearchResultMetadata> metadata) {
   if (!app_list_controller_)
     return;
   app_list_controller_->SetSearchResultMetadata(std::move(metadata));
diff --git a/chrome/browser/ui/app_list/chrome_app_list_model_updater.h b/chrome/browser/ui/app_list/chrome_app_list_model_updater.h
index f172275..ece19f2 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_model_updater.h
+++ b/chrome/browser/ui/app_list/chrome_app_list_model_updater.h
@@ -66,7 +66,7 @@
   // Methods only used by ChromeSearchResult that talk to ash directly.
   void SetSearchResultMetadata(
       const std::string& id,
-      ash::mojom::SearchResultMetadataPtr metadata) override;
+      std::unique_ptr<ash::SearchResultMetadata> metadata) override;
   void SetSearchResultIsInstalling(const std::string& id,
                                    bool is_installing) override;
   void SetSearchResultPercentDownloaded(const std::string& id,
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.cc b/chrome/browser/ui/app_list/search/chrome_search_result.cc
index ffb4714..8ed4ecaa2 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.cc
+++ b/chrome/browser/ui/app_list/search/chrome_search_result.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/ui/app_list/app_context_menu.h"
 
 ChromeSearchResult::ChromeSearchResult()
-    : metadata_(ash::mojom::SearchResultMetadata::New()) {}
+    : metadata_(std::make_unique<ash::SearchResultMetadata>()) {}
 
 ChromeSearchResult::~ChromeSearchResult() = default;
 
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.h b/chrome/browser/ui/app_list/search/chrome_search_result.h
index 5cb8b37..a1bc3782 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.h
+++ b/chrome/browser/ui/app_list/search/chrome_search_result.h
@@ -89,11 +89,11 @@
   void SetPercentDownloaded(int percent_downloaded);
   void NotifyItemInstalled();
 
-  void SetMetadata(ash::mojom::SearchResultMetadataPtr metadata) {
+  void SetMetadata(std::unique_ptr<ash::SearchResultMetadata> metadata) {
     metadata_ = std::move(metadata);
   }
-  ash::mojom::SearchResultMetadataPtr CloneMetadata() const {
-    return metadata_.Clone();
+  std::unique_ptr<ash::SearchResultMetadata> CloneMetadata() const {
+    return std::make_unique<ash::SearchResultMetadata>(*metadata_);
   }
 
   void set_model_updater(AppListModelUpdater* model_updater) {
@@ -151,7 +151,7 @@
   // sorted order, group multiplier and group boost.
   double relevance_ = 0;
 
-  ash::mojom::SearchResultMetadataPtr metadata_;
+  std::unique_ptr<ash::SearchResultMetadata> metadata_;
 
   AppListModelUpdater* model_updater_ = nullptr;
 
diff --git a/chrome/browser/ui/ash/launcher_drag_interactive_uitest.cc b/chrome/browser/ui/ash/launcher_drag_interactive_uitest.cc
index 601add7..dc8d5d46 100644
--- a/chrome/browser/ui/ash/launcher_drag_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/launcher_drag_interactive_uitest.cc
@@ -69,7 +69,7 @@
   gfx::Point end_point(start_point);
   end_point.set_y(10);
   ui_test_utils::DragEventGenerator generator(
-      std::make_unique<ui_test_utils::InterporateProducer>(
+      std::make_unique<ui_test_utils::InterpolatedProducer>(
           start_point, end_point, base::TimeDelta::FromMilliseconds(1000)),
       /*touch=*/true);
   generator.Wait();
@@ -96,7 +96,7 @@
   gfx::Point end_point(start_point);
   end_point.set_y(display_bounds.bottom() - ash::kShelfSize / 2);
   ui_test_utils::DragEventGenerator generator(
-      std::make_unique<ui_test_utils::InterporateProducer>(
+      std::make_unique<ui_test_utils::InterpolatedProducer>(
           start_point, end_point, base::TimeDelta::FromMilliseconds(1000)),
       /*touch=*/true);
   generator.Wait();
diff --git a/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc b/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc
index 05b009e3..5246687 100644
--- a/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/overview_window_drag_interactive_uitest.cc
@@ -139,7 +139,7 @@
   gfx::Point end_point(start_point);
   end_point.set_x(end_point.x() + display_size.width() / 2);
   ui_test_utils::DragEventGenerator generator(
-      std::make_unique<ui_test_utils::InterporateProducer>(
+      std::make_unique<ui_test_utils::InterpolatedProducer>(
           start_point, end_point, base::TimeDelta::FromMilliseconds(1000)),
       /*touch=*/true);
   generator.Wait();
@@ -167,7 +167,7 @@
   end_point.set_y(0);
   end_point.set_x(end_point.x() + 10);
   ui_test_utils::DragEventGenerator generator(
-      std::make_unique<ui_test_utils::InterporateProducer>(
+      std::make_unique<ui_test_utils::InterpolatedProducer>(
           start_point, end_point, base::TimeDelta::FromMilliseconds(500),
           gfx::Tween::EASE_IN_2),
       /*touch=*/true);
@@ -192,7 +192,7 @@
   gfx::Point end_point(start_point);
   end_point.set_x(0);
   ui_test_utils::DragEventGenerator generator(
-      std::make_unique<ui_test_utils::InterporateProducer>(
+      std::make_unique<ui_test_utils::InterpolatedProducer>(
           start_point, end_point, base::TimeDelta::FromMilliseconds(1000)),
       /*touch=*/true);
   generator.Wait();
diff --git a/chrome/browser/ui/ash/window_resize_interactive_uitest.cc b/chrome/browser/ui/ash/window_resize_interactive_uitest.cc
new file mode 100644
index 0000000..46cebb5
--- /dev/null
+++ b/chrome/browser/ui/ash/window_resize_interactive_uitest.cc
@@ -0,0 +1,274 @@
+// 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 "ash/public/cpp/window_properties.h"
+#include "ash/public/cpp/window_state_type.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/base/perf/drag_event_generator.h"
+#include "chrome/test/base/perf/performance_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/window.h"
+#include "ui/base/test/ui_controls.h"
+#include "ui/base/ui_base_features.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/wm/core/wm_core_switches.h"
+
+namespace {
+
+// A mouse movement producer that generates the position between mulitiple
+// points based on the |progress| given to |GetPosition|.  It is guarantted that
+// this produces the point specified in |ProducePointsTo| except that
+// the DragEventGenerator progressed faster than it can produce points.
+// (You can't produce more than 60 points in a seconds, for example).
+class MultiPointProducer
+    : public ui_test_utils::DragEventGenerator::PointProducer {
+ public:
+  explicit MultiPointProducer(const gfx::Point& start) : current_(start) {}
+  ~MultiPointProducer() override = default;
+
+  // Instructs the produer so that it produces the seuqnece of points from the
+  // current point to the next |point| in |duration| time.  |point| will become
+  // the new current point.
+  void ProducePointsTo(const gfx::Point& point,
+                       const base::TimeDelta duration) {
+    ASSERT_FALSE(initialized_);
+
+    step_list_.emplace_back(
+        Step{0.f, 0.f,
+             std::make_unique<ui_test_utils::InterpolatedProducer>(
+                 current_, point, duration)});
+    current_ = point;
+    total_duration_ += duration;
+  }
+
+  // PointProducer:
+  gfx::Point GetPosition(float progress) override {
+    if (!initialized_) {
+      initialized_ = true;
+      InitSteps();
+    }
+    EXPECT_FALSE(step_list_.empty());
+
+    // The generating a point given in |ProducePointsTo| is important as it is
+    // typically specified to trigger mouse based actions (such as window
+    // resize).  Make sure that we generate such points by giving 0.f when the
+    // |progress| exceeds the current step.
+    if (progress > step_list_[0].to_progress) {
+      EXPECT_NE(progress, 1.f)
+          << "Failed to produce all points added to the MutiPointProducer";
+      EXPECT_GE(step_list_.size(), 2u)
+          << "prog=" << progress << ", to=" << step_list_[0].to_progress
+          << ", size=" << step_list_.size();
+      step_list_.erase(step_list_.begin());
+      return step_list_[0].producer->GetPosition(0.f);
+    }
+    float adjusted = (progress - step_list_[0].from_progress) /
+                     (step_list_[0].to_progress - step_list_[0].from_progress);
+    return step_list_[0].producer->GetPosition(adjusted);
+  }
+  const base::TimeDelta GetDuration() const override { return total_duration_; }
+
+ private:
+  // Specifies the producer used between |from_progress| and |to_progress|.  The
+  // |producer| will receive the 0.f-1.f progress value relative to the range
+  // between these two values.
+  struct Step {
+    float from_progress{0.f};
+    float to_progress{0.f};
+    std::unique_ptr<ui_test_utils::InterpolatedProducer> producer;
+  };
+
+  // Initializes the |from_progress|/|to_progress| for each step
+  // based on the |total_duration_|.
+  void InitSteps() {
+    base::TimeDelta time;
+    for (auto& step : step_list_) {
+      step.from_progress =
+          float{time.InMicroseconds()} / total_duration_.InMicroseconds();
+      time += step.producer->GetDuration();
+      step.to_progress =
+          float{time.InMicroseconds()} / total_duration_.InMicroseconds();
+    }
+  }
+
+  bool initialized_ = false;
+  std::vector<Step> step_list_;
+  gfx::Point current_;
+  base::TimeDelta total_duration_;
+
+  DISALLOW_COPY_AND_ASSIGN(MultiPointProducer);
+};
+
+// Wait until the window's state changed to given snapped state.
+// The window should stay alive, so no need to observer destroying.
+class SnapWaiter : public aura::WindowObserver {
+ public:
+  SnapWaiter(aura::Window* window, ash::WindowStateType type)
+      : window_(window), type_(type) {
+    window->AddObserver(this);
+  }
+  ~SnapWaiter() override { window_->RemoveObserver(this); }
+
+  // aura::WindowObserver:
+  void OnWindowPropertyChanged(aura::Window* window,
+                               const void* key,
+                               intptr_t old) override {
+    if (key == ash::kWindowStateTypeKey && IsSnapped())
+      run_loop_.Quit();
+  }
+
+  void Wait() {
+    if (!IsSnapped())
+      run_loop_.Run();
+  }
+
+ private:
+  bool IsSnapped() const {
+    return window_->GetProperty(ash::kWindowStateTypeKey) == type_;
+  }
+
+  aura::Window* window_;
+  ash::WindowStateType type_;
+  base::RunLoop run_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(SnapWaiter);
+};
+
+}  // namespace
+
+// Test window resize performance in clamshell mode.
+class WindowResizeTest
+    : public UIPerformanceTest,
+      public testing::WithParamInterface<::testing::tuple<bool>> {
+ public:
+  WindowResizeTest() = default;
+  ~WindowResizeTest() override = default;
+
+  // UIPerformanceTest:
+  void SetUpOnMainThread() override {
+    UIPerformanceTest::SetUpOnMainThread();
+
+    use_ntp_ = std::get<0>(GetParam());
+
+    GURL ntp_url("chrome://newtab");
+    if (use_ntp_)
+      ui_test_utils::NavigateToURL(browser(), ntp_url);
+
+    // Make sure startup tasks won't affect measurement.
+    if (base::SysInfo::IsRunningOnChromeOS()) {
+      base::RunLoop run_loop;
+      base::PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
+                            base::TimeDelta::FromSeconds(5));
+      run_loop.Run();
+    }
+
+    auto* cmd = base::CommandLine::ForCurrentProcess();
+    cmd->AppendSwitch(wm::switches::kWindowAnimationsDisabled);
+  }
+
+  // UIPerformanceTest:
+  std::vector<std::string> GetUMAHistogramNames() const override {
+    return {
+        "Ash.InteractiveWindowResize.TimeToPresent",
+    };
+  }
+
+  bool use_ntp() const { return use_ntp_; }
+
+ private:
+  bool use_ntp_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowResizeTest);
+};
+
+IN_PROC_BROWSER_TEST_P(WindowResizeTest, Single) {
+  aura::Window* browser_window = browser()->window()->GetNativeWindow();
+  SnapWaiter snap_waiter(browser_window, ash::WindowStateType::kLeftSnapped);
+  ui_controls::SendKeyPress(browser_window, ui::VKEY_OEM_4,
+                            /*control=*/false,
+                            /*shift=*/false,
+                            /*alt=*/true,
+                            /*command=*/false);
+  snap_waiter.Wait();
+
+  gfx::Rect bounds = browser_window->GetBoundsInScreen();
+  gfx::Point start_point = gfx::Point(bounds.right_center());
+  start_point.set_y(start_point.y() + 100);
+  gfx::Point mid_point(start_point);
+
+  // Move enough amount to produce 60 updates in 1 seconds, but
+  // do not exceeds brower's minimum size.
+  mid_point.Offset(-60, 0);
+  gfx::Point end_point(start_point);
+  end_point.Offset(120, 0);
+
+  auto producer = std::make_unique<MultiPointProducer>(start_point);
+  producer->ProducePointsTo(mid_point, base::TimeDelta::FromSeconds(1));
+  producer->ProducePointsTo(end_point, base::TimeDelta::FromSeconds(1));
+
+  ui_test_utils::DragEventGenerator generator(std::move(producer),
+                                              /*use_touch=*/false);
+  generator.Wait();
+}
+
+IN_PROC_BROWSER_TEST_P(WindowResizeTest, Multi) {
+  aura::Window* browser_window = browser()->window()->GetNativeWindow();
+  {
+    SnapWaiter snap_waiter(browser_window, ash::WindowStateType::kLeftSnapped);
+    ui_controls::SendKeyPress(browser_window, ui::VKEY_OEM_4,
+                              /*control=*/false,
+                              /*shift=*/false,
+                              /*alt=*/true,
+                              /*command=*/false);
+    snap_waiter.Wait();
+  }
+
+  Browser* browser2 = CreateBrowser(browser()->profile());
+  if (use_ntp()) {
+    GURL ntp_url("chrome://newtab");
+    ui_test_utils::NavigateToURL(browser2, ntp_url);
+  }
+
+  // Snap Right
+  {
+    aura::Window* browser_window2 = browser2->window()->GetNativeWindow();
+    SnapWaiter snap_waiter(browser_window2,
+                           ash::WindowStateType::kRightSnapped);
+    ui_controls::SendKeyPress(browser_window2, ui::VKEY_OEM_6,
+                              /*control=*/false,
+                              /*shift=*/false,
+                              /*alt=*/true,
+                              /*command=*/false);
+    snap_waiter.Wait();
+  }
+
+  gfx::Rect bounds = browser_window->GetBoundsInScreen();
+  gfx::Point start_point = gfx::Point(bounds.right_center());
+  ui_controls::SendMouseMove(start_point.x(), start_point.y());
+  base::RunLoop run_loop;
+  // Wait to trigger resize handle.
+  base::PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
+                        base::TimeDelta::FromMilliseconds(500));
+  run_loop.Run();
+  start_point.Offset(0, 50);
+  auto producer = std::make_unique<MultiPointProducer>(start_point);
+  start_point.Offset(-60, 0);
+  producer->ProducePointsTo(start_point, base::TimeDelta::FromSeconds(1));
+  start_point.Offset(120, 0);
+  producer->ProducePointsTo(start_point, base::TimeDelta::FromSeconds(1));
+  ui_test_utils::DragEventGenerator generator(std::move(producer),
+                                              /*use_touch=*/false);
+  generator.Wait();
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         WindowResizeTest,
+                         ::testing::Combine(/*ntp=*/testing::Bool()));
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index de6828c..5faaba7 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -13,7 +13,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/autofill/address_normalizer_factory.h"
 #include "chrome/browser/autofill/autocomplete_history_manager_factory.h"
-#include "chrome/browser/autofill/legacy_strike_database_factory.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/autofill/risk_util.h"
 #include "chrome/browser/autofill/strike_database_factory.h"
@@ -142,22 +141,13 @@
   return payments_client_.get();
 }
 
-LegacyStrikeDatabase* ChromeAutofillClient::GetLegacyStrikeDatabase() {
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
-  // No need to return a LegacyStrikeDatabase in incognito mode. It is primarily
-  // used to determine whether or not to offer save of Autofill data. However,
-  // we don't allow saving of Autofill data while in incognito anyway, so an
-  // incognito code path should never get far enough to query
-  // LegacyStrikeDatabase.
-  DCHECK(!profile->IsOffTheRecord());
-  return LegacyStrikeDatabaseFactory::GetForProfile(profile);
-}
-
 StrikeDatabase* ChromeAutofillClient::GetStrikeDatabase() {
   Profile* profile =
       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
-  // Nullptr is returned if browser is in incognito mode.
+  // No need to return a StrikeDatabase in incognito mode. It is primarily
+  // used to determine whether or not to offer save of Autofill data. However,
+  // we don't allow saving of Autofill data while in incognito anyway, so an
+  // incognito code path should never get far enough to query StrikeDatabase.
   return StrikeDatabaseFactory::GetForProfile(profile);
 }
 
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index e7ddacb..403cbfb 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -57,7 +57,6 @@
   identity::IdentityManager* GetIdentityManager() override;
   FormDataImporter* GetFormDataImporter() override;
   payments::PaymentsClient* GetPaymentsClient() override;
-  LegacyStrikeDatabase* GetLegacyStrikeDatabase() override;
   StrikeDatabase* GetStrikeDatabase() override;
   ukm::UkmRecorder* GetUkmRecorder() override;
   ukm::SourceId GetUkmSourceId() override;
diff --git a/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc b/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc
index b133ffe..1461452 100644
--- a/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc
@@ -482,7 +482,7 @@
     // there are any managed bookmarks configured at all.
     bookmarks::ManagedBookmarkService* managed =
         ManagedBookmarkServiceFactory::GetForProfile(profile_);
-    return !managed->managed_node()->empty();
+    return !managed->managed_node()->children().empty();
   }
 
   return true;
diff --git a/chrome/browser/ui/bookmarks/bookmark_utils_desktop.cc b/chrome/browser/ui/bookmarks/bookmark_utils_desktop.cc
index f69c727..7b7d099 100644
--- a/chrome/browser/ui/bookmarks/bookmark_utils_desktop.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_utils_desktop.cc
@@ -170,7 +170,7 @@
 
 bool ConfirmDeleteBookmarkNode(const BookmarkNode* node,
                                gfx::NativeWindow window) {
-  DCHECK(node && node->is_folder() && !node->empty());
+  DCHECK(node && node->is_folder() && !node->children().empty());
   return ShowQuestionMessageBox(
              window, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
              l10n_util::GetPluralStringFUTF16(
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm
index 54b4ed2..9055baf 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.mm
@@ -112,31 +112,31 @@
       ManagedBookmarkServiceFactory::GetForProfile(profile_);
   const BookmarkNode* barNode = model->bookmark_bar_node();
   const BookmarkNode* managedNode = managed->managed_node();
-  if (!barNode->empty() || !managedNode->empty())
+  if (!barNode->children().empty() || !managedNode->children().empty())
     [menu_root_ addItem:[NSMenuItem separatorItem]];
-  if (!managedNode->empty()) {
+  if (!managedNode->children().empty()) {
     // Most users never see this node, so the image is only loaded if needed.
     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     NSImage* image =
         rb.GetNativeImageNamed(IDR_BOOKMARK_BAR_FOLDER_MANAGED).ToNSImage();
     AddNodeAsSubmenu(menu_root_, managedNode, image);
   }
-  if (!barNode->empty())
+  if (!barNode->children().empty())
     AddNodeToMenu(barNode, menu_root_);
 
   // If the "Other Bookmarks" folder has any content, make a submenu for it and
   // fill it in.
-  if (!model->other_node()->empty()) {
+  if (!model->other_node()->children().empty()) {
     [menu_root_ addItem:[NSMenuItem separatorItem]];
     AddNodeAsSubmenu(menu_root_, model->other_node(), folder_image_);
   }
 
   // If the "Mobile Bookmarks" folder has any content, make a submenu for it and
   // fill it in.
-  if (!model->mobile_node()->empty()) {
+  if (!model->mobile_node()->children().empty()) {
     // Add a separator if we did not already add one due to a non-empty
     // "Other Bookmarks" folder.
-    if (model->other_node()->empty())
+    if (model->other_node()->children().empty())
       [menu_root_ addItem:[NSMenuItem separatorItem]];
 
     AddNodeAsSubmenu(menu_root_, model->mobile_node(), folder_image_);
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
index c0e7d57..87d485c 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
@@ -643,10 +643,8 @@
     SaveCardBubbleViews* save_card_bubble_views = GetSaveCardBubbleViews();
     DCHECK(save_card_bubble_views);
     if (strike_expected &&
-        (base::FeatureList::IsEnabled(
-             features::kAutofillSaveCreditCardUsesStrikeSystem) ||
-         base::FeatureList::IsEnabled(
-             features::kAutofillSaveCreditCardUsesStrikeSystemV2))) {
+        base::FeatureList::IsEnabled(
+            features::kAutofillSaveCreditCardUsesStrikeSystemV2)) {
       ResetEventWaiterForSequence(
           {DialogEvent::STRIKE_CHANGE_COMPLETE, DialogEvent::BUBBLE_CLOSED});
     } else {
@@ -800,8 +798,7 @@
       // Enabled
       {features::kAutofillSaveCardImprovedUserConsent},
       // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem,
-       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
 
   // Submitting the form and having Payments decline offering to save should
   // show the local save bubble.
@@ -1376,8 +1373,7 @@
       {features::kAutofillSaveCardImprovedUserConsent,
        features::kAutofillUpstream},
       // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem,
-       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
 
   // Start sync.
   harness_->SetupSync();
@@ -2437,8 +2433,7 @@
       // Enabled
       {features::kAutofillSaveCardImprovedUserConsent},
       // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem,
-       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
 
   // Submitting the form and having Payments decline offering to save should
   // show the local save bubble.
@@ -2469,8 +2464,7 @@
       {features::kAutofillUpstream,
        features::kAutofillSaveCardImprovedUserConsent},
       // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem,
-       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
 
   // Start sync.
   harness_->SetupSync();
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
index 93fd77e..0f0ba6a 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
@@ -1950,7 +1950,7 @@
 }
 
 bool BookmarkBarView::UpdateOtherAndManagedButtonsVisibility() {
-  bool has_other_children = !model_->other_node()->empty();
+  bool has_other_children = !model_->other_node()->children().empty();
   bool update_other =
       has_other_children != other_bookmarks_button_->GetVisible();
   if (update_other) {
@@ -1958,7 +1958,7 @@
     UpdateBookmarksSeparatorVisibility();
   }
 
-  bool show_managed = !managed_->managed_node()->empty() &&
+  bool show_managed = !managed_->managed_node()->children().empty() &&
                       browser_->profile()->GetPrefs()->GetBoolean(
                           bookmarks::prefs::kShowManagedBookmarksInBookmarkBar);
   bool update_managed = show_managed != managed_bookmarks_button_->GetVisible();
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc b/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc
index a235485..40c05d7c 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_context_menu_unittest.cc
@@ -374,7 +374,7 @@
   // Verify that there are no managed nodes yet.
   bookmarks::ManagedBookmarkService* managed =
       ManagedBookmarkServiceFactory::GetForProfile(profile_.get());
-  EXPECT_TRUE(managed->managed_node()->empty());
+  EXPECT_TRUE(managed->managed_node()->children().empty());
 
   // The context menu should not show the option to "Show managed bookmarks".
   EXPECT_FALSE(
@@ -393,9 +393,9 @@
   dict->SetString("url", "http://google.com");
   base::ListValue list;
   list.Append(std::move(dict));
-  EXPECT_TRUE(managed->managed_node()->empty());
+  EXPECT_TRUE(managed->managed_node()->children().empty());
   profile_->GetPrefs()->Set(bookmarks::prefs::kManagedBookmarks, list);
-  EXPECT_FALSE(managed->managed_node()->empty());
+  EXPECT_FALSE(managed->managed_node()->children().empty());
 
   // New context menus now show the "Show managed bookmarks" option.
   controller.reset(new BookmarkContextMenu(
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
index d7839d3..2c9e362 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
@@ -204,9 +204,9 @@
     if (node->value != 0) {
       const BookmarkNode* b_node =
           bookmarks::GetBookmarkNodeByID(bb_model_, node->value);
-      if (!b_node->empty() &&
+      if (!b_node->children().empty() &&
           !chrome::ConfirmDeleteBookmarkNode(b_node,
-            GetWidget()->GetNativeWindow())) {
+                                             GetWidget()->GetNativeWindow())) {
         // The folder is not empty and the user didn't confirm.
         return;
       }
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc b/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc
index 0817ed3..726ac4c5 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc
@@ -108,7 +108,7 @@
     bool show_forced_folders = show_options == SHOW_PERMANENT_FOLDERS &&
                                node == model->bookmark_bar_node();
     bool show_managed =
-        show_forced_folders && !managed->managed_node()->empty();
+        show_forced_folders && !managed->managed_node()->children().empty();
     bool has_children =
         (start_child_index < node->child_count()) || show_managed;
     if (has_children && parent->GetSubmenu() &&
@@ -535,7 +535,8 @@
 void BookmarkMenuDelegate::BuildMenu(const BookmarkNode* parent,
                                      int start_child_index,
                                      MenuItemView* menu) {
-  DCHECK(parent->empty() || start_child_index < parent->child_count());
+  DCHECK(parent->children().empty() ||
+         start_child_index < parent->child_count());
   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
   const gfx::ImageSkia folder_icon =
       chrome::GetBookmarkFolderIcon(TextColorForMenu(menu, parent_));
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
index 724a180..6081e7c6 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
@@ -615,6 +615,13 @@
 };
 
 TEST_P(OmniboxViewViewsClipboardTest, ClipboardCopyOrCutURL) {
+  // TODO(crbug.com/396477) Make this an interactive ui test so there isn't
+  // contention for the system clipboard.
+  // Force use of the system clipboard because this test checks for URL format
+  // on Linux, which is not supported by test clipboard.
+  ui::Clipboard::DestroyClipboardForCurrentThread();
+  ui::Clipboard::GetForCurrentThread();
+
   omnibox_view()->SelectAll(false);
   ASSERT_TRUE(omnibox_view()->IsSelectAll());
 
diff --git a/chrome/browser/ui/views/payments/payment_handler_change_payment_method_browsertest.cc b/chrome/browser/ui/views/payments/payment_handler_change_payment_method_browsertest.cc
index a2e6754..13696f2 100644
--- a/chrome/browser/ui/views/payments/payment_handler_change_payment_method_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_handler_change_payment_method_browsertest.cc
@@ -73,7 +73,7 @@
                                      GetParam().init_test_code));
 
   if (GetParam().method_identifier == MethodIdentifier::kBasicCard)
-    EnalbeSkipUIForForBasicCard();
+    EnableSkipUIForForBasicCard();
 
   ASSERT_TRUE(content::ExecuteScriptAndExtractString(
       GetActiveWebContents(), "outputChangePaymentMethodReturnValue(request);",
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
index bb02095..2ca3be1 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
@@ -836,12 +836,12 @@
   event_waiter_->Wait();
 }
 
-void PaymentRequestBrowserTestBase::EnalbeSkipUIForForBasicCard() {
+void PaymentRequestBrowserTestBase::EnableSkipUIForForBasicCard() {
   std::vector<PaymentRequest*> requests =
       GetPaymentRequests(GetActiveWebContents());
   ASSERT_EQ(1U, requests.size());
   requests.front()
-      ->set_skip_ui_for_non_url_payment_method_identifiers_for_test();
+      ->SetSkipUiForNonUrlPaymentMethodIdentifiersForTest();
 }
 
 }  // namespace payments
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.h b/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
index 4a511e1..ea22e8e 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
@@ -270,7 +270,7 @@
   void WaitForObservedEvent();
 
   // Allows to skip UI into payment handler for "basic-card".
-  void EnalbeSkipUIForForBasicCard();
+  void EnableSkipUIForForBasicCard();
 
  private:
   std::unique_ptr<autofill::EventWaiter<DialogEvent>> event_waiter_;
diff --git a/chrome/browser/ui/views/payments/payment_request_can_make_payment_metrics_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_can_make_payment_metrics_browsertest.cc
index 578dec6..6e44e06 100644
--- a/chrome/browser/ui/views/payments/payment_request_can_make_payment_metrics_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_can_make_payment_metrics_browsertest.cc
@@ -51,6 +51,8 @@
                                  DialogEvent::DIALOG_OPENED});
     ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(), "queryShow();"));
     WaitForObservedEvent();
+    // Wait for all callbacks to run.
+    base::RunLoop().RunUntilIdle();
   }
 
  private:
diff --git a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
index 387fb0d..9fb68d875 100644
--- a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
@@ -253,10 +253,9 @@
 
   HideProcessingSpinner();
 
-  if (request_->state()->are_requested_methods_supported()) {
-    request_->RecordDialogShownEventInJourneyLogger();
-    if (observer_for_testing_)
-      observer_for_testing_->OnDialogOpened();
+  if (request_->state()->are_requested_methods_supported() &&
+      observer_for_testing_) {
+    observer_for_testing_->OnDialogOpened();
   }
 }
 
@@ -435,10 +434,9 @@
   if (number_of_initialization_tasks_ > 0)
     return;
 
-  if (request_->state()->are_requested_methods_supported()) {
-    request_->RecordDialogShownEventInJourneyLogger();
-    if (observer_for_testing_)
-      observer_for_testing_->OnDialogOpened();
+  if (request_->state()->are_requested_methods_supported() &&
+      observer_for_testing_) {
+    observer_for_testing_->OnDialogOpened();
   }
 }
 
diff --git a/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
index 85d82ccf..9a067a7 100644
--- a/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
@@ -239,7 +239,7 @@
   NavigateTo("/show_promise/digital_goods.html");
   InstallEchoPaymentHandlerForBasicCard();
   ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(), "create();"));
-  EnalbeSkipUIForForBasicCard();
+  EnableSkipUIForForBasicCard();
   ResetEventWaiterForSequence(
       {DialogEvent::PROCESSING_SPINNER_SHOWN,
        DialogEvent::PROCESSING_SPINNER_HIDDEN, DialogEvent::SPEC_DONE_UPDATING,
diff --git a/chrome/browser/ui/views/webauthn/hover_list_view.cc b/chrome/browser/ui/views/webauthn/hover_list_view.cc
index 5b16e70..14a48c60 100644
--- a/chrome/browser/ui/views/webauthn/hover_list_view.cc
+++ b/chrome/browser/ui/views/webauthn/hover_list_view.cc
@@ -43,6 +43,10 @@
 
   gfx::Insets GetInsets() const override {
     gfx::Insets ret = HoverButton::GetInsets();
+    if (vert_inset_.has_value()) {
+      ret.set_top(*vert_inset_);
+      ret.set_bottom(*vert_inset_);
+    }
     if (left_inset_.has_value()) {
       ret.set_left(*left_inset_);
     }
@@ -56,8 +60,11 @@
     left_inset_ = 0;
   }
 
+  void SetVertInset(int vert_inset) { vert_inset_ = vert_inset; }
+
  private:
   base::Optional<int> left_inset_;
+  base::Optional<int> vert_inset_;
 };
 
 std::unique_ptr<HoverButton> CreateHoverButtonForListItem(
@@ -82,6 +89,12 @@
   }
 
   std::unique_ptr<views::ImageView> chevron_image = nullptr;
+  // kTwoLineVertInset is the top and bottom padding of the HoverButton if
+  // |is_two_line_item| is true. This ensures that the spacing between the two
+  // lines isn't too large because HoverButton will otherwise spread the lines
+  // evenly over the given vertical space.
+  constexpr int kTwoLineVertInset = 6;
+
   if (!is_placeholder_item) {
     constexpr int kChevronSize = 8;
     chevron_image = std::make_unique<views::ImageView>();
@@ -94,7 +107,8 @@
       // chevron image to pad single-line items out to a uniform height of
       // |kHeight|.
       constexpr int kHeight = 56;
-      chevron_vert_inset = (kHeight - kChevronSize) / 2;
+      chevron_vert_inset =
+          (kHeight - (2 * kTwoLineVertInset) - kChevronSize) / 2;
     }
     chevron_image->SetBorder(views::CreateEmptyBorder(
         gfx::Insets(/*top=*/chevron_vert_inset, /*left=*/12,
@@ -108,6 +122,9 @@
   if (!vector_icon) {
     hover_button->SetInsetForNoIcon();
   }
+  if (is_two_line_item) {
+    hover_button->SetVertInset(kTwoLineVertInset);
+  }
 
   // Because there is an icon on both sides, set a custom border that has only
   // half of the normal padding horizontally.
diff --git a/chrome/browser/ui/webui/cookies_tree_model_util.cc b/chrome/browser/ui/webui/cookies_tree_model_util.cc
index d372f98..9c411e4 100644
--- a/chrome/browser/ui/webui/cookies_tree_model_util.cc
+++ b/chrome/browser/ui/webui/cookies_tree_model_util.cc
@@ -88,7 +88,7 @@
   // Use node's address as an id for WebUI to look it up.
   dict->SetString(kKeyId, GetTreeNodeId(&node));
   dict->SetString(kKeyTitle, node.GetTitle());
-  dict->SetBoolean(kKeyHasChildren, !node.empty());
+  dict->SetBoolean(kKeyHasChildren, !node.children().empty());
 
   switch (node.GetDetailedInfo().node_type) {
     case CookieTreeNode::DetailedInfo::TYPE_HOST: {
diff --git a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
index 7802d7201..c5e4553 100644
--- a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
+++ b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
@@ -674,8 +674,8 @@
       offline_pages::OfflinePageModelFactory::GetForBrowserContext(profile);
   request_coordinator_ =
       offline_pages::RequestCoordinatorFactory::GetForBrowserContext(profile);
-  prefetch_service_ =
-      offline_pages::PrefetchServiceFactory::GetForBrowserContext(profile);
+  prefetch_service_ = offline_pages::PrefetchServiceFactory::GetForKey(
+      profile->GetProfileKey());
 }
 
 void OfflineInternalsUIMessageHandler::OnJavascriptDisallowed() {
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.cc b/chrome/browser/vr/test/webxr_vr_browser_test.cc
index 59e4756..2ce474c2 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.cc
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.cc
@@ -56,4 +56,16 @@
       kPollTimeoutLong, web_contents);
 }
 
+#if defined(OS_WIN)
+XrBrowserTestBase::RuntimeType WebXrVrOpenVrBrowserTestBase::GetRuntimeType()
+    const {
+  return XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR;
+}
+
+XrBrowserTestBase::RuntimeType WebXrVrWMRBrowserTestBase::GetRuntimeType()
+    const {
+  return XrBrowserTestBase::RuntimeType::RUNTIME_WMR;
+}
+#endif  // OS_WIN
+
 }  // namespace vr
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.h b/chrome/browser/vr/test/webxr_vr_browser_test.h
index 2ff2376..e846be2 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.h
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.h
@@ -71,13 +71,28 @@
 };
 #endif
 
-// OpenVR feature only defined on Windows.
+// OpenVR and WMR feature only defined on Windows.
 #ifdef OS_WIN
+// OpenVR-specific subclass of WebXrVrBrowserTestBase.
+class WebXrVrOpenVrBrowserTestBase : public WebXrVrBrowserTestBase {
+ public:
+  WebXrVrOpenVrBrowserTestBase() {
+    enable_features_.push_back(features::kOpenVR);
+  }
+  XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
+};
+
+// WMR-specific subclass of WebXrVrBrowserTestBase.
+class WebXrVrWMRBrowserTestBase : public WebXrVrBrowserTestBase {
+ public:
+  // WMR enabled by default, so no need to add anything in the constructor.
+  XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
+};
+
 // Test class with standard features enabled: WebXR and OpenVR.
-class WebXrVrBrowserTestStandard : public WebXrVrBrowserTestBase {
+class WebXrVrBrowserTestStandard : public WebXrVrOpenVrBrowserTestBase {
  public:
   WebXrVrBrowserTestStandard() {
-    enable_features_.push_back(features::kOpenVR);
     enable_features_.push_back(features::kWebXr);
 
     runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
@@ -88,7 +103,7 @@
   }
 };
 
-class WebXrVrBrowserTestWMR : public WebXrVrBrowserTestBase {
+class WebXrVrBrowserTestWMR : public WebXrVrWMRBrowserTestBase {
  public:
   WebXrVrBrowserTestWMR() {
     // WMR already enabled by default.
@@ -97,11 +112,9 @@
 };
 
 // Test class with WebXR disabled.
-class WebXrVrBrowserTestWebXrDisabled : public WebXrVrBrowserTestBase {
+class WebXrVrBrowserTestWebXrDisabled : public WebXrVrOpenVrBrowserTestBase {
  public:
   WebXrVrBrowserTestWebXrDisabled() {
-    enable_features_.push_back(features::kOpenVR);
-
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
     disable_features_.push_back(features::kWindowsMixedReality);
 #endif
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index 0c2fad9f..8e95fd9 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -154,6 +154,10 @@
   InProcessBrowserTest::TearDown();
 }
 
+XrBrowserTestBase::RuntimeType XrBrowserTestBase::GetRuntimeType() const {
+  return XrBrowserTestBase::RuntimeType::RUNTIME_NONE;
+}
+
 GURL XrBrowserTestBase::GetFileUrlForHtmlTestFile(
     const std::string& test_name) {
   return ui_test_utils::GetTestUrl(
diff --git a/chrome/browser/vr/test/xr_browser_test.h b/chrome/browser/vr/test/xr_browser_test.h
index bd5eac5..c2a6dca0 100644
--- a/chrome/browser/vr/test/xr_browser_test.h
+++ b/chrome/browser/vr/test/xr_browser_test.h
@@ -62,12 +62,20 @@
     STATUS_FAILED = 2
   };
 
+  enum class RuntimeType {
+    RUNTIME_NONE = 0,
+    RUNTIME_OPENVR = 1,
+    RUNTIME_WMR = 2
+  };
+
   XrBrowserTestBase();
   ~XrBrowserTestBase() override;
 
   void SetUp() override;
   void TearDown() override;
 
+  virtual RuntimeType GetRuntimeType() const;
+
   // Returns a GURL to the XR test HTML file of the given name, e.g.
   // GetHtmlTestFile("foo") returns a GURL for the foo.html file in the XR
   // test HTML directory.
diff --git a/chrome/browser/vr/test/xr_browser_test_details.md b/chrome/browser/vr/test/xr_browser_test_details.md
new file mode 100644
index 0000000..0f95a12
--- /dev/null
+++ b/chrome/browser/vr/test/xr_browser_test_details.md
@@ -0,0 +1,100 @@
+# XR Browser Test Details
+
+[TOC]
+
+## Test Input Architecture
+
+### Overview
+
+When a [`MockXRDeviceHookBase`][xr hook base] is constructed, it sets itself as
+the current test hook in [`XRServiceTestHook::SetTestHook()`][xr service hook].
+This then causes the provided test hook to be set as the current hook for each
+runtime. What specifically each runtime does after that differs (covered in each
+runtime's specific section), but the important part is that the runtimes will
+start calling into the test via Mojo to get controller and headset data instead
+of attempting to use the real implementation.
+
+[xr hook base]: https://chromium.googlesource.com/chromium/src/+/master/chrome/browser/vr/test/mock_xr_device_hook_base.h
+[xr service hook]: https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/services/isolated_xr_device/xr_service_test_hook.cc
+
+### OpenVR
+
+OpenVR is handled by a mock implementation of the API found in
+[`//device/vr/openvr/test/fake_openvr_impl_api.cc`][fake openvr]. This, along
+with some helper files, are compiled into a standalone DLL defined in the
+[`//device/vr:openvr_mock`][openvr target] target. This target is compiled
+alongside the browser tests if OpenVR support is enabled, and several Windows
+environment variables set during test setup. These environment variables cause
+OpenVR to load the fake DLL on startup instead of the real one, which both
+allows tests to provide data that appears to come from OpenVR (as opposed to
+a wrapper) and prevents the need for having the real OpenVR implementation
+installed.
+
+[fake openvr]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/openvr/test/fake_openvr_impl_api.cc
+[openvr target]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/BUILD.gn
+
+When [`XRServiceTestHook::SetTestHook()`][xr service hook] is called, it in
+turn calls [`OpenVRWrapper::SetTestHook()`][openvr wrapper]. This both stores
+a reference to the provided hook, and if the runtime has already been started
+with the fake OpenVR DLL, calls [`TestHelper::SetTestHook()`][test helper]. If
+the runtime has not yet been started, [`TestHelper::SetTestHook()`][test helper]
+will be called during the runtime initialization with the stored hook reference.
+
+[openvr wrapper]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/openvr/openvr_api_wrapper.h
+[test helper]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/openvr/test/test_helper.h
+
+In either case, once the hook as been set in `TestHelper`, the Mojo setup is
+complete. Anytime the fake OpenVR implementation gets a call that would require
+fetching data, it calls into `TestHelper` to retrieve the data. If the hook is
+set, `TestHelper` will then use it to call into the test and retrieve the set
+data. If not, it returns reasonable defaults.
+
+**Note**
+Because the fake OpenVR implementation is in a separate DLL, it has a separate
+heap from the rest of Chrome. Because of this, the Mojo data types returned by
+the test hook cannot be used directly in the OpenVR implementation, as doing so
+causes them to be destructed on the wrong heap once they go out of scope.
+Instead, the data gets copied into duplicate, non-Mojo structs before being
+passed back to OpenVR.
+
+### WMR
+
+WMR is handled by mock implementations of the wrapper classes that surround all
+calls to the WMR API. The real wrappers are found in
+[`//device/vr/windows_mixed_reality/wrappers/`][real wmr wrappers] while the
+mocks are in
+[`//device/vr/windows_mixed_reality/wrappers/test/`][mock wmr wrappers].
+
+[real wmr wrappers]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/windows_mixed_reality/wrappers
+[mock wmr wrappers]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/windows_mixed_reality/wrappers/test/
+
+When [`XRServiceTestHook::SetTestHook()`][xr service hook] is called, it in turn
+calls [`MixedRealityDeviceStatics::SetTestHook()`][wmr statics]. This stores the
+reference to the hook for later use.
+
+[wmr statics]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/windows_mixed_reality/mixed_reality_statics.h
+
+When any of several WMR wrappers are created through the factories in
+[`//device/vr/windows_mixed_reality/wrappers/wmr_wrapper_factories.h`][wmr factories],
+they check [`MixedRealityDeviceStatics::ShouldUseMocks()`][wmr statics]. The
+first time this is called, it returns true if the hook has already been set, or
+false otherwise. Subsequent calls will always return the same value as the first
+call did. This means that usage of the real and mock wrappers are impossible to
+accidentally mix. **Note** This also means that it's currently impossible to
+switch to or from the mock wrappers during a test after the runtime has been
+attempted to be started.
+
+[wmr factories]: https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/windows_mixed_reality/wrappers/wmr_wrapper_factories.h
+
+When the mock wrappers are in use, any calls made to them that would require
+data from the headset or controllers calls into the test via the test hook.
+Since multiple wrapper classes could potentially be using the hook at once (as
+opposed to OpenVR where all interaction is done through a single class), the
+hook is actually provided by acquiring a [`LockedVRTestHook`][locked hook] via
+[`MixedRealityDeviceStatics::GetLockedTestHook()`][wmr statics]. This is
+effectively a reference to the currently set
+[`MockXRDeviceHookBase`][xr hook base] that automatically acquires and releases
+a static lock on construction and destruction, making usage of the hook thread
+safe.
+
+[locked hook]:https://chromium.googlesource.com/chromium/src/+/HEAD/device/vr/test/locked_vr_test_hook.h
diff --git a/chrome/browser/vr/test/xr_browser_tests.md b/chrome/browser/vr/test/xr_browser_tests.md
index 42bddb1..fc7f3a9 100644
--- a/chrome/browser/vr/test/xr_browser_tests.md
+++ b/chrome/browser/vr/test/xr_browser_tests.md
@@ -1,16 +1,20 @@
 # XR Browser Tests
 
+[TOC]
+
 ## Introduction
 
 This documentation concerns `xr_browser_test.h`, `xr_browser_test.cc`, and files
 that use them or their subclasses.
 
 These files port the framework used by XR instrumentation tests (located in
-[`//chrome/android/javatests/src/org/chromium/chrome/browser/vr/`][1] and
-documented in
+[`//chrome/android/javatests/src/org/chromium/chrome/browser/vr/`][vr android dir]
+and documented in
 `//chrome/android/javatests/src/org/chromium/chrome/browser/vr/*.md`) for
 use in browser tests in order to test XR features on desktop platforms.
 
+[vr android dir]: https://chromium.googlesource.com/chromium/src/+/master/chrome/android/javatests/src/org/chromium/chrome/browser/vr
+
 This is pretty much a direct port, with the same JavaScript/HTML files being
 used for both and the Java/C++ code being functionally equivalent to each other,
 so the instrumentation test's documentation on writing tests using the framework
@@ -71,4 +75,56 @@
 `python` to the front of the command on Windows if Python file association is
 not set up on your machine.
 
-[1]: https://chromium.googlesource.com/chromium/src/+/master/chrome/android/javatests/src/org/chromium/chrome/browser/vr
\ No newline at end of file
+## Controller and Head Input
+
+The XR browser tests provide a way to plumb controller and headset data (e.g.
+currently touched/pressed buttons and poses) from the test through the runtime
+being tested. Details about what goes on under the hood can be found in
+[`//chrome/browser/vr/test/xr_browser_test_details.md`][xr details], but below
+is a quick guide on how to use them.
+
+[xr details]: https://chromium.googlesource.com/chromium/src/+/master/chrome/browser/vr/test/xr_browser_test_details.md
+
+In order to let a test provide data to a runtime, it must create an instance of
+[`MockXRDeviceHookBase`][xr hook base] or some subclass of it. This should be
+created at the beginning of the test before any attempts to enter VR are made,
+as there are currently assumptions that prevent switching to or from the mock
+runtimes once they have been attempted to be started.
+
+[xr hook base]: https://chromium.googlesource.com/chromium/src/+/master/chrome/browser/vr/test/mock_xr_device_hook_base.h
+
+Once created, the runtime being used will call the various functions inherited
+from [`VRTestHook`][vr test hook] whenever it would normally acquire or submit
+data from or to an actual device. For example, `WaitGetControllerData()` will be
+called every time the runtime would normally check the state of a real
+controller, and `OnFrameSubmitted()` will be called each time the runtime
+submits a finished frame to the headset.
+
+[vr test hook]: https://chromium.googlesource.com/chromium/src/+/master/device/vr/test/test_hook.h
+
+For real examples on how to use the input capabilities, look at the tests in
+[`//chrome/browser/vr/webxr_vr_input_browser_test.cc`][input test].
+
+[input test]: https://chromium.googlesource.com/chromium/src/+/master/chrome/browser/vr/webxr_vr_input_browser_test.cc
+
+### Assumptions
+
+There are currently several assumptions made that must be adhered to in order
+for input to work properly in both OpenVR and Windows Mixed Reality.
+
+#### Primary Axes
+
+OpenVR assumes that axis 0 is the primary axis (usually a touchpad). However,
+WMR assumes that axis 2 is the primary axis (must be a joystick). This will
+eventually be abstracted away, but in the meantime, if you care about the
+primary axis (i.e. which one shows up as axis 0 in WebXR), you'll need to
+conditionally change which axis you set based on runtime.
+
+#### WMR and Incomplete Gamepads
+
+OpenVR supports arbitrary controller mappings, but WMR only supports one actual
+controller type (+ voice input). What this means is that WMR will always report
+a certain set of buttons and axes when a controller is connected, regardless of
+which buttons and axes are set as supported. This means that tests involving
+things not supported by WMR (e.g. a third touchpad/joystick) must be restricted
+to OpenVR.
diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc
index 75e9215..1c6edae 100644
--- a/chrome/browser/vr/webxr_vr_input_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc
@@ -117,15 +117,20 @@
     TogglePrimaryTrigger(index);
   }
 
-  unsigned int CreateAndConnectMinimalGamepad() {
+  unsigned int CreateAndConnectMinimalGamepad(
+      vr::EVRButtonId primary_axis = vr::k_EButton_Axis0) {
     // Create a controller that only supports select and one TrackPad, i.e. it
     // has just enough data to be considered a gamepad.
     uint64_t supported_buttons =
         vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger) |
-        vr::ButtonMaskFromId(vr::k_EButton_Axis0);
+        vr::ButtonMaskFromId(primary_axis);
+
+    unsigned int primary_axis_type = primary_axis == vr::k_EButton_Axis0
+                                         ? vr::k_eControllerAxis_TrackPad
+                                         : vr::k_eControllerAxis_Joystick;
 
     std::map<vr::EVRButtonId, unsigned int> axis_types = {
-        {vr::k_EButton_Axis0, vr::k_eControllerAxis_TrackPad},
+        {primary_axis, primary_axis_type},
         {vr::k_EButton_SteamVR_Trigger, vr::k_eControllerAxis_Trigger},
     };
 
@@ -248,7 +253,7 @@
 // Also validates that if an input source changes substantially we get an event
 // containing both the removal of the old one and the additon of the new one,
 // rather than two events.
-IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestInputSourcesChange) {
+void TestInputSourcesChangeImpl(WebXrVrBrowserTestBase* t) {
   WebXrControllerInputMock my_mock;
 
   // TODO(crbug.com/963676): Figure out if the race is a product or test bug.
@@ -263,27 +268,29 @@
       device::ControllerRole::kControllerRoleRight, insufficient_axis_types,
       insufficient_buttons);
 
-  LoadUrlAndAwaitInitialization(
-      GetFileUrlForHtmlTestFile("test_webxr_input_sources_change_event"));
-  EnterSessionWithUserGestureOrFail();
+  t->LoadUrlAndAwaitInitialization(
+      t->GetFileUrlForHtmlTestFile("test_webxr_input_sources_change_event"));
+  t->EnterSessionWithUserGestureOrFail();
 
   // Wait for the first changed event
-  PollJavaScriptBooleanOrFail("inputChangeEvents === 1", kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("inputChangeEvents === 1",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
 
   // Validate that we only have one controller added, and no controller removed.
-  RunJavaScriptOrFail("validateAdded(1)");
-  RunJavaScriptOrFail("validateRemoved(0)");
-  RunJavaScriptOrFail("updateCachedInputSource(0)");
-  RunJavaScriptOrFail("validateCachedAddedPresence(true)");
+  t->RunJavaScriptOrFail("validateAdded(1)");
+  t->RunJavaScriptOrFail("validateRemoved(0)");
+  t->RunJavaScriptOrFail("updateCachedInputSource(0)");
+  t->RunJavaScriptOrFail("validateCachedAddedPresence(true)");
 
   // Disconnect the controller and validate that we only have one controller
   // removed, and that our previously cached controller is in the removed array.
   my_mock.DisconnectController(controller_index);
-  PollJavaScriptBooleanOrFail("inputChangeEvents === 2", kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("inputChangeEvents === 2",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
 
-  RunJavaScriptOrFail("validateAdded(0)");
-  RunJavaScriptOrFail("validateRemoved(1)");
-  RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
+  t->RunJavaScriptOrFail("validateAdded(0)");
+  t->RunJavaScriptOrFail("validateRemoved(1)");
+  t->RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
 
   // Connect a controller, and then change enough properties that the system
   // recalculates its status as a valid controller, so that we can verify
@@ -291,26 +298,43 @@
   // Since we're changing the controller state without disconnecting it, we can
   // (and should) use the minimal gamepad here.
   controller_index = my_mock.CreateAndConnectMinimalGamepad();
-  PollJavaScriptBooleanOrFail("inputChangeEvents === 3", kPollTimeoutShort);
-  RunJavaScriptOrFail("updateCachedInputSource(0)");
+  t->PollJavaScriptBooleanOrFail("inputChangeEvents === 3",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
+  t->RunJavaScriptOrFail("updateCachedInputSource(0)");
 
-  my_mock.UpdateControllerSupport(controller_index, insufficient_axis_types,
-                                  insufficient_buttons);
+  // At least currently, there is no way for WMR to have insufficient buttons
+  // for a gamepad as long as a controller is connected, so skip this part on
+  // WMR since it'll always fail
+  if (t->GetRuntimeType() != XrBrowserTestBase::RuntimeType::RUNTIME_WMR) {
+    my_mock.UpdateControllerSupport(controller_index, insufficient_axis_types,
+                                    insufficient_buttons);
 
-  PollJavaScriptBooleanOrFail("inputChangeEvents === 4", kPollTimeoutShort);
-  RunJavaScriptOrFail("validateAdded(1)");
-  RunJavaScriptOrFail("validateRemoved(1)");
-  RunJavaScriptOrFail("validateCachedAddedPresence(false)");
-  RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
+    t->PollJavaScriptBooleanOrFail("inputChangeEvents === 4",
+                                   WebXrVrBrowserTestBase::kPollTimeoutShort);
+    t->RunJavaScriptOrFail("validateAdded(1)");
+    t->RunJavaScriptOrFail("validateRemoved(1)");
+    t->RunJavaScriptOrFail("validateCachedAddedPresence(false)");
+    t->RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
+  }
 
-  RunJavaScriptOrFail("done()");
-  EndTest();
+  t->RunJavaScriptOrFail("done()");
+  t->EndTest();
+}
+
+// OpenVR version of TestInputSourcesChange.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestInputSourcesChange) {
+  TestInputSourcesChangeImpl(this);
+}
+
+// WMR version of TestInputSourcesChange.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestWMR, TestInputSourcesChange) {
+  TestInputSourcesChangeImpl(this);
 }
 
 // Ensure that changes to a gamepad object respect that it is the same object
 // and that if whether or not an input source has a gamepad changes that the
 // input source change event is fired and a new input source is created.
-IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestInputGamepadSameObject) {
+void TestInputGamepadSameObjectImpl(WebXrVrBrowserTestBase* t) {
   WebXrControllerInputMock my_mock;
 
   // Create a set of buttons and axes that don't have enough data to be made
@@ -338,49 +362,64 @@
       device::ControllerRole::kControllerRoleRight, insufficient_axis_types,
       insufficient_buttons);
 
-  LoadUrlAndAwaitInitialization(
-      GetFileUrlForHtmlTestFile("test_webxr_input_same_object"));
-  EnterSessionWithUserGestureOrFail();
+  t->LoadUrlAndAwaitInitialization(
+      t->GetFileUrlForHtmlTestFile("test_webxr_input_same_object"));
+  t->EnterSessionWithUserGestureOrFail();
 
   // We should only have seen the first change indicating we have input sources.
-  PollJavaScriptBooleanOrFail("inputChangeEvents === 1", kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("inputChangeEvents === 1",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
 
   // We only expect one input source, cache it.
-  RunJavaScriptOrFail("validateInputSourceLength(1)");
-  RunJavaScriptOrFail("updateCachedInputSource(0)");
+  t->RunJavaScriptOrFail("validateInputSourceLength(1)");
+  t->RunJavaScriptOrFail("updateCachedInputSource(0)");
 
   // Toggle a button and confirm that the controller is still the same.
   my_mock.PressReleasePrimaryTrigger(controller_index);
-  RunJavaScriptOrFail("validateCachedSourcePresence(true)");
-  RunJavaScriptOrFail("validateCurrentAndCachedGamepadMatch()");
+  t->RunJavaScriptOrFail("validateCachedSourcePresence(true)");
+  t->RunJavaScriptOrFail("validateCurrentAndCachedGamepadMatch()");
 
   // Update the controller to now support a gamepad and verify that we get a
   // change event and that the old controller isn't present.  Then cache the new
   // one.
   my_mock.UpdateControllerSupport(controller_index, sufficient_axis_types,
                                   sufficient_buttons);
-  PollJavaScriptBooleanOrFail("inputChangeEvents === 2", kPollTimeoutShort);
-  RunJavaScriptOrFail("validateCachedSourcePresence(false)");
-  RunJavaScriptOrFail("validateInputSourceLength(1)");
-  RunJavaScriptOrFail("updateCachedInputSource(0)");
+  t->PollJavaScriptBooleanOrFail("inputChangeEvents === 2",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
+  t->RunJavaScriptOrFail("validateCachedSourcePresence(false)");
+  t->RunJavaScriptOrFail("validateInputSourceLength(1)");
+  t->RunJavaScriptOrFail("updateCachedInputSource(0)");
 
   // Toggle a button and confirm that the controller is still the same.
   my_mock.PressReleasePrimaryTrigger(controller_index);
-  RunJavaScriptOrFail("validateCachedSourcePresence(true)");
-  RunJavaScriptOrFail("validateCurrentAndCachedGamepadMatch()");
+  t->RunJavaScriptOrFail("validateCachedSourcePresence(true)");
+  t->RunJavaScriptOrFail("validateCurrentAndCachedGamepadMatch()");
 
   // Switch back to the insufficient gamepad and confirm that we get the change.
   my_mock.UpdateControllerSupport(controller_index, insufficient_axis_types,
                                   insufficient_buttons);
-  PollJavaScriptBooleanOrFail("inputChangeEvents === 3", kPollTimeoutShort);
-  RunJavaScriptOrFail("validateCachedSourcePresence(false)");
-  RunJavaScriptOrFail("validateInputSourceLength(1)");
-  RunJavaScriptOrFail("done()");
-  EndTest();
+  t->PollJavaScriptBooleanOrFail("inputChangeEvents === 3",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
+  t->RunJavaScriptOrFail("validateCachedSourcePresence(false)");
+  t->RunJavaScriptOrFail("validateInputSourceLength(1)");
+  t->RunJavaScriptOrFail("done()");
+  t->EndTest();
+}
+
+// OpenVR version of TestInputGamepadSameObject.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestInputGamepadSameObject) {
+  TestInputSourcesChangeImpl(this);
+}
+
+// WMR version of TestInputGamepadSameObject.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestWMR, TestInputGamepadSameObject) {
+  TestInputSourcesChangeImpl(this);
 }
 
 // Ensure that if the controller lacks enough data to be considered a Gamepad
 // that the input source that it is associated with does not have a Gamepad.
+// OpenVR-only because WMR does not currently support the notion of incomplete
+// gamepads other than voice input.
 IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestGamepadIncompleteData) {
   WebXrControllerInputMock my_mock;
 
@@ -399,38 +438,56 @@
   EndTest();
 }
 
-// Ensure that if a Gamepad has the minimum required number of axes/buttons to
-// be considered an xr-standard Gamepad, that it is exposed as such, and that
-// we can check the state of it's priamry axes/button.
-IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestGamepadMinimumData) {
+void TestGamepadMinimumDataImpl(WebXrVrBrowserTestBase* t) {
   WebXrControllerInputMock my_mock;
-  unsigned int controller_index = my_mock.CreateAndConnectMinimalGamepad();
+  // The touchpad is the primary axis for OpenVR, while the Joystick is the
+  // primary axis for WMR.
+  vr::EVRButtonId primary_axis;
+  if (t->GetRuntimeType() == XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR) {
+    primary_axis = vr::k_EButton_Axis0;
+  } else {
+    primary_axis = vr::k_EButton_Axis2;
+  }
+  unsigned int controller_index =
+      my_mock.CreateAndConnectMinimalGamepad(primary_axis);
 
-  LoadUrlAndAwaitInitialization(
-      GetFileUrlForHtmlTestFile("test_webxr_gamepad_support"));
-  EnterSessionWithUserGestureOrFail();
+  t->LoadUrlAndAwaitInitialization(
+      t->GetFileUrlForHtmlTestFile("test_webxr_gamepad_support"));
+  t->EnterSessionWithUserGestureOrFail();
 
   // Press the trigger and set the axis to a non-zero amount, so we can ensure
   // we aren't getting just default gamepad data.
   my_mock.TogglePrimaryTrigger(controller_index);
-  my_mock.SetAxes(controller_index, vr::k_EButton_Axis0, 0.5, -0.5);
+  my_mock.SetAxes(controller_index, primary_axis, 0.5, -0.5);
+  my_mock.ToggleButtonTouches(controller_index, primary_axis);
 
   // The trigger should be button 0, and the first set of axes should have it's
   // value set.
-  PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
-                              kPollTimeoutShort);
-  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true)",
-                              kPollTimeoutShort);
-  PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(0, 0.5, -0.5)",
-                              kPollTimeoutShort);
-  RunJavaScriptOrFail("done()");
-  EndTest();
+  t->PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true)",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(0, 0.5, -0.5)",
+                                 WebXrVrBrowserTestBase::kPollTimeoutShort);
+  t->RunJavaScriptOrFail("done()");
+  t->EndTest();
+}
+
+// Ensure that if a Gamepad has the minimum required number of axes/buttons to
+// be considered an xr-standard Gamepad, that it is exposed as such, and that
+// we can check the state of it's priamry axes/button.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestGamepadMinimumData) {
+  TestGamepadMinimumDataImpl(this);
+}
+
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestWMR, TestGamepadMinimumData) {
+  TestGamepadMinimumDataImpl(this);
 }
 
 // Ensure that if a Gamepad has all of the required and optional buttons as
 // specified by the xr-standard mapping, that those buttons are plumbed up
 // in their required places.
-IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestGamepadCompleteData) {
+void TestGamepadCompleteDataImpl(WebXrVrBrowserTestBase* t) {
   WebXrControllerInputMock my_mock;
 
   // Create a controller that supports all reserved buttons.  Note that per
@@ -442,59 +499,77 @@
       vr::ButtonMaskFromId(vr::k_EButton_Grip);
 
   std::map<vr::EVRButtonId, unsigned int> axis_types = {
-      {vr::k_EButton_Axis0, vr::k_eControllerAxis_Joystick},
+      {vr::k_EButton_Axis0, vr::k_eControllerAxis_TrackPad},
       {vr::k_EButton_SteamVR_Trigger, vr::k_eControllerAxis_Trigger},
-      {vr::k_EButton_Axis2, vr::k_eControllerAxis_TrackPad},
+      {vr::k_EButton_Axis2, vr::k_eControllerAxis_Joystick},
   };
 
   unsigned int controller_index = my_mock.CreateAndConnectController(
       device::ControllerRole::kControllerRoleRight, axis_types,
       supported_buttons);
 
-  LoadUrlAndAwaitInitialization(
-      GetFileUrlForHtmlTestFile("test_webxr_gamepad_support"));
-  EnterSessionWithUserGestureOrFail();
+  t->LoadUrlAndAwaitInitialization(
+      t->GetFileUrlForHtmlTestFile("test_webxr_gamepad_support"));
+  t->EnterSessionWithUserGestureOrFail();
 
   // Setup some state on the optional buttons (as TestGamepadMinimumData should
   // ensure proper state on the required buttons).
   // Set a value on the secondary set of axes.
-  my_mock.SetAxes(controller_index, vr::k_EButton_Axis2, 0.25, -0.25);
+  // Because the touchpad is primary for OpenVR but the Joystick is primary in
+  // WMR, we have to use different axes for each.
+  vr::EVRButtonId secondary_axis;
+  if (t->GetRuntimeType() == XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR) {
+    secondary_axis = vr::k_EButton_Axis2;
+  } else {
+    secondary_axis = vr::k_EButton_Axis0;
+  }
+
+  my_mock.SetAxes(controller_index, secondary_axis, 0.25, -0.25);
 
   // Set the secondary trackpad/joystick to be touched.
   my_mock.ToggleButtonTouches(controller_index,
-                              vr::ButtonMaskFromId(vr::k_EButton_Axis2));
+                              vr::ButtonMaskFromId(secondary_axis));
 
   // Set the grip button to be pressed.
   my_mock.ToggleButtons(controller_index,
                         vr::ButtonMaskFromId(vr::k_EButton_Grip));
 
   // Controller should meet the requirements for the 'xr-standard' mapping.
-  PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
-                              kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
+                                 WebVrBrowserTestBase::kPollTimeoutShort);
 
   // The secondary set of axes should be set appropriately.
-  PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(1, 0.25, -0.25)",
-                              kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(1, 0.25, -0.25)",
+                                 WebVrBrowserTestBase::kPollTimeoutShort);
 
   // Button 2 is reserved for the Grip, and should be pressed.
-  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(2, true)",
-                              kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(2, true)",
+                                 WebVrBrowserTestBase::kPollTimeoutShort);
 
   // Button 3 is reserved for the secondary trackpad/joystick and should be
   // touched but not pressed.
-  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(3, false)",
-                              kPollTimeoutShort);
-  PollJavaScriptBooleanOrFail("isButtonTouchedEqualTo(3, true)",
-                              kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(3, false)",
+                                 WebVrBrowserTestBase::kPollTimeoutShort);
+  t->PollJavaScriptBooleanOrFail("isButtonTouchedEqualTo(3, true)",
+                                 WebVrBrowserTestBase::kPollTimeoutShort);
 
-  RunJavaScriptOrFail("done()");
-  EndTest();
+  t->RunJavaScriptOrFail("done()");
+  t->EndTest();
+}
+
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestGamepadCompleteData) {
+  TestGamepadCompleteDataImpl(this);
+}
+
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestWMR, TestGamepadCompleteData) {
+  TestGamepadCompleteDataImpl(this);
 }
 
 // Ensure that if a Gamepad has all required buttons, an extra button not
 // mapped in the xr-standard specification, and is missing reserved buttons
 // from the XR Standard specification, that the extra button does not appear
-// in either of the reserved button slots.
+// in either of the reserved button slots. OpenVR-only since WMR only supports
+// one controller type.
 IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestGamepadReservedData) {
   WebXrControllerInputMock my_mock;
 
@@ -542,22 +617,19 @@
   EndTest();
 }
 
-// Test that OpenVR controller input is registered via WebXR's input method.
-// Equivalent to
-// WebXrVrInputTest#testControllerClicksRegisteredOnDaydream_WebXr.
-IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard,
-                       TestControllerInputRegistered) {
+void TestControllerInputRegisteredImpl(WebXrVrBrowserTestBase* t) {
   WebXrControllerInputMock my_mock;
 
   unsigned int controller_index = my_mock.CreateAndConnectMinimalGamepad();
 
   // Load the test page and enter presentation.
-  LoadUrlAndAwaitInitialization(GetFileUrlForHtmlTestFile("test_webxr_input"));
-  EnterSessionWithUserGestureOrFail();
+  t->LoadUrlAndAwaitInitialization(
+      t->GetFileUrlForHtmlTestFile("test_webxr_input"));
+  t->EnterSessionWithUserGestureOrFail();
 
   unsigned int num_iterations = 10;
-  RunJavaScriptOrFail("stepSetupListeners(" +
-                      base::NumberToString(num_iterations) + ")");
+  t->RunJavaScriptOrFail("stepSetupListeners(" +
+                         base::NumberToString(num_iterations) + ")");
 
   // Press and unpress the controller's trigger a bunch of times and make sure
   // they're all registered.
@@ -565,9 +637,23 @@
     my_mock.PressReleasePrimaryTrigger(controller_index);
     // After each trigger release, wait for the JavaScript to receive the
     // "select" event.
-    WaitOnJavaScriptStep();
+    t->WaitOnJavaScriptStep();
   }
-  EndTest();
+  t->EndTest();
+}
+
+// Test that OpenVR controller input is registered via WebXR's input method.
+// Equivalent to
+// WebXrVrInputTest#testControllerClicksRegisteredOnDaydream_WebXr.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard,
+                       TestControllerInputRegistered) {
+  TestControllerInputRegisteredImpl(this);
+}
+// Test that WMR controller input is registered via WebXR's input method.
+// Equivalent to
+// WebXrVrInputTest#testControllerClicksRegisteredOnDaydream_WebXr.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestWMR, TestControllerInputRegistered) {
+  TestControllerInputRegisteredImpl(this);
 }
 
 // Test that OpenVR controller input is registered via the Gamepad API.
diff --git a/chrome/common/importer/BUILD.gn b/chrome/common/importer/BUILD.gn
index 9fbb7030f..4a1d01f 100644
--- a/chrome/common/importer/BUILD.gn
+++ b/chrome/common/importer/BUILD.gn
@@ -10,7 +10,7 @@
   ]
 
   public_deps = [
-    "//components/autofill/content/common:mojo_types",
+    "//components/autofill/core/common/mojom:mojo_types",
     "//mojo/public/mojom/base",
     "//url/mojom:url_mojom_gurl",
   ]
diff --git a/chrome/common/importer/profile_import.mojom b/chrome/common/importer/profile_import.mojom
index 081955b..3733b3d 100644
--- a/chrome/common/importer/profile_import.mojom
+++ b/chrome/common/importer/profile_import.mojom
@@ -4,7 +4,7 @@
 
 module chrome.mojom;
 
-import "components/autofill/content/common/autofill_types.mojom";
+import "components/autofill/core/common/mojom/autofill_types.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 import "url/mojom/url.mojom";
 
diff --git a/chrome/renderer/chromeos_merge_session_loader_throttle.cc b/chrome/renderer/chromeos_merge_session_loader_throttle.cc
index c7133f9..cb079cb 100644
--- a/chrome/renderer/chromeos_merge_session_loader_throttle.cc
+++ b/chrome/renderer/chromeos_merge_session_loader_throttle.cc
@@ -17,7 +17,7 @@
           switches::kShortMergeSessionTimeoutForTest)) {
     return base::TimeDelta::FromSeconds(1);
   } else {
-    return base::TimeDelta::FromSeconds(10);
+    return base::TimeDelta::FromSeconds(20);
   }
 }
 
diff --git a/chrome/renderer/v8_unwinder_unittest.cc b/chrome/renderer/v8_unwinder_unittest.cc
index 938f4f6..016c951 100644
--- a/chrome/renderer/v8_unwinder_unittest.cc
+++ b/chrome/renderer/v8_unwinder_unittest.cc
@@ -79,11 +79,10 @@
 // C++ function to be invoked from V8 which calls back into the provided closure
 // pointer (passed via a holder object) to wait for a stack sample to be taken.
 void WaitForSampleNative(const v8::FunctionCallbackInfo<v8::Value>& info) {
-  const base::RepeatingClosure* wait_for_sample =
-      GetPointerFromHolder<const base::RepeatingClosure>(
-          info[0].As<v8::Object>());
+  base::OnceClosure* wait_for_sample =
+      GetPointerFromHolder<base::OnceClosure>(info[0].As<v8::Object>());
   if (wait_for_sample)
-    wait_for_sample->Run();
+    std::move(*wait_for_sample).Run();
 }
 
 // Causes a stack sample to be taken after setting up a call stack from C++ to
@@ -91,7 +90,7 @@
 base::FunctionAddressRange CallThroughV8(
     const base::RepeatingCallback<void(const v8::UnwindState&)>&
         report_unwind_state,
-    const base::RepeatingClosure& wait_for_sample) {
+    base::OnceClosure wait_for_sample) {
   const void* start_program_counter = base::GetProgramCounter();
 
   if (wait_for_sample) {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9064f25..6482a11 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2548,7 +2548,7 @@
       "../utility/importer/firefox_importer_unittest_utils_mac.mojom",
     ]
     public_deps = [
-      "//components/autofill/content/common:mojo_types",
+      "//components/autofill/core/common/mojom:mojo_types",
       "//mojo/public/mojom/base",
     ]
   }
@@ -2851,6 +2851,10 @@
     "../browser/performance_manager/performance_manager_test_harness.h",
     "../browser/performance_manager/performance_manager_unittest.cc",
     "../browser/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc",
+    "../browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc",
+    "../browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc",
+    "../browser/performance_manager/persistence/site_data/unittest_utils.cc",
+    "../browser/performance_manager/persistence/site_data/unittest_utils.h",
     "../browser/performance_manager/web_contents_proxy_unittest.cc",
     "../browser/performance_manager/webui_graph_dump_impl_unittest.cc",
     "../browser/performance_monitor/metric_evaluator_helper_win_unittest.cc",
@@ -5307,6 +5311,7 @@
         "../browser/ui/ash/screen_rotation_interactive_uitest.cc",
         "../browser/ui/ash/split_view_interactive_uitest.cc",
         "../browser/ui/ash/tablet_mode_transition_interactive_uitest.cc",
+        "../browser/ui/ash/window_resize_interactive_uitest.cc",
         "base/perf/drag_event_generator.cc",
         "base/perf/drag_event_generator.h",
       ]
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java
index 2210626..cc544f1 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java
@@ -79,6 +79,7 @@
             // SharedPreferences are cached in memory, so clearing their files doesn't help anyways.
             // Some preferences need to persist (e.g. multidex.version.xml).
             if (file.getName().equals("shared_prefs")) {
+                removeSharedPrefs(file);
                 continue;
             }
             if (!FileUtils.recursivelyDeleteFile(file)) {
@@ -87,4 +88,13 @@
         }
         return true;
     }
+
+    // TODO(agrieve): Use InMemorySharedPrefs rather than having to delete from disk.
+    private static void removeSharedPrefs(File sharedPrefsDir) {
+        for (File f : sharedPrefsDir.listFiles()) {
+            if (!f.getName().endsWith("multidex.version.xml")) {
+                f.delete();
+            }
+        }
+    }
 }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/RenderTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/RenderTestRule.java
index df9fff0..3068143 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/RenderTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/RenderTestRule.java
@@ -94,6 +94,9 @@
     /** Parameterized tests have a prefix inserted at the front of the test description. */
     private String mVariantPrefix;
 
+    /** Prefix on the render test images that describes light/dark mode. */
+    private String mNightModePrefix;
+
     // How much a channel must differ when comparing pixels in order to be considered different.
     private int mPixelDiffThreshold;
 
@@ -275,6 +278,13 @@
     }
 
     /**
+     * Sets a string prefix that describes the light/dark mode in the golden image name.
+     */
+    public void setNightModeEnabled(boolean nightModeEnabled) {
+        mNightModePrefix = nightModeEnabled ? "NightModeEnabled" : "NightModeDisabled";
+    }
+
+    /**
      * Sets the threshold that a pixel must differ by when comparing channels in order to be
      * considered different.
      */
@@ -290,7 +300,11 @@
      * This function must be kept in sync with |RE_RENDER_IMAGE_NAME| from
      * src/build/android/pylib/local/device/local_device_instrumentation_test_run.py.
      */
-    private static String imageName(String testClass, String variantPrefix, String desc) {
+    private String imageName(String testClass, String variantPrefix, String desc) {
+        if (!TextUtils.isEmpty(mNightModePrefix)) {
+            desc = mNightModePrefix + "-" + desc;
+        }
+
         if (!TextUtils.isEmpty(variantPrefix)) {
             desc = variantPrefix + "-" + desc;
         }
diff --git a/chrome/test/base/perf/drag_event_generator.cc b/chrome/test/base/perf/drag_event_generator.cc
index 24bf5ce..98f935d 100644
--- a/chrome/test/base/perf/drag_event_generator.cc
+++ b/chrome/test/base/perf/drag_event_generator.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/run_loop.h"
 #include "base/task/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "ui/base/test/ui_controls.h"
@@ -34,7 +35,10 @@
     ui_controls::SendTouchEvents(ui_controls::PRESS, 0, initial_position.x(),
                                  initial_position.y());
   } else {
-    ui_controls::SendMouseMove(initial_position.x(), initial_position.y());
+    base::RunLoop run_loop;
+    ui_controls::SendMouseMoveNotifyWhenDone(
+        initial_position.x(), initial_position.y(), run_loop.QuitClosure());
+    run_loop.Run();
     ui_controls::SendMouseEvents(ui_controls::LEFT, ui_controls::DOWN);
   }
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
@@ -116,24 +120,24 @@
 DragEventGenerator::PointProducer::~PointProducer() = default;
 
 ////////////////////////////////////////////////////////////////////////////////
-// InterporateProducer:
+// InterpolatedProducer:
 
-InterporateProducer::InterporateProducer(const gfx::Point& start,
-                                         const gfx::Point& end,
-                                         const base::TimeDelta duration,
-                                         gfx::Tween::Type type)
+InterpolatedProducer::InterpolatedProducer(const gfx::Point& start,
+                                           const gfx::Point& end,
+                                           const base::TimeDelta duration,
+                                           gfx::Tween::Type type)
     : start_(start), end_(end), duration_(duration), type_(type) {}
 
-InterporateProducer::~InterporateProducer() = default;
+InterpolatedProducer::~InterpolatedProducer() = default;
 
-gfx::Point InterporateProducer::GetPosition(float progress) {
+gfx::Point InterpolatedProducer::GetPosition(float progress) {
   float value = gfx::Tween::CalculateValue(type_, progress);
   return gfx::Point(
       gfx::Tween::LinearIntValueBetween(value, start_.x(), end_.x()),
       gfx::Tween::LinearIntValueBetween(value, start_.y(), end_.y()));
 }
 
-const base::TimeDelta InterporateProducer::GetDuration() const {
+const base::TimeDelta InterpolatedProducer::GetDuration() const {
   return duration_;
 }
 
diff --git a/chrome/test/base/perf/drag_event_generator.h b/chrome/test/base/perf/drag_event_generator.h
index 82bfcde..103cd1e 100644
--- a/chrome/test/base/perf/drag_event_generator.h
+++ b/chrome/test/base/perf/drag_event_generator.h
@@ -51,15 +51,15 @@
   DISALLOW_COPY_AND_ASSIGN(DragEventGenerator);
 };
 
-// InterporateProducer produces the interpolated location between two points
+// InterpolatedProducer produces the interpolated location between two points
 // based on tween type.
-class InterporateProducer : public DragEventGenerator::PointProducer {
+class InterpolatedProducer : public DragEventGenerator::PointProducer {
  public:
-  InterporateProducer(const gfx::Point& start,
-                      const gfx::Point& end,
-                      const base::TimeDelta duration,
-                      gfx::Tween::Type type = gfx::Tween::LINEAR);
-  ~InterporateProducer() override;
+  InterpolatedProducer(const gfx::Point& start,
+                       const gfx::Point& end,
+                       const base::TimeDelta duration,
+                       gfx::Tween::Type type = gfx::Tween::LINEAR);
+  ~InterpolatedProducer() override;
 
   // PointProducer:
   gfx::Point GetPosition(float progress) override;
@@ -70,7 +70,7 @@
   base::TimeDelta duration_;
   gfx::Tween::Type type_;
 
-  DISALLOW_COPY_AND_ASSIGN(InterporateProducer);
+  DISALLOW_COPY_AND_ASSIGN(InterpolatedProducer);
 };
 
 }  // namespace ui_test_utils
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-custom_view.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-custom_view.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..17256198
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-custom_view.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+690a2a9f8b3310321902345d3a9c284b0f9f67ec
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-scrollable_title.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-scrollable_title.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..1ce8e1e
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-scrollable_title.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+a8422dc187be8777e2badb744fc8598b853eab0c
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-title_and_message.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-title_and_message.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..2f6ae9e0
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-title_and_message.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+75c54a6b4e93a8b33e8e8c1618617074ebc6a745
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-title_and_title_icon.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-title_and_title_icon.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..bd2a90ca
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeDisabled-title_and_title_icon.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+a4b753a76a447edff278445a997f989f5e4f07ab
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-custom_view.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-custom_view.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..1413de4
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-custom_view.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+42d498a023a8570c6949ce5e5b0222573c543e73
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-scrollable_title.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-scrollable_title.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..edbfa292
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-scrollable_title.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+1b60d99591d15fbf8e1cc114dbbb8a9c36c5f775
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-title_and_message.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-title_and_message.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..85867fd
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-title_and_message.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+a396434c72ebc25189c968349c53daa297db3361
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-title_and_title_icon.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-title_and_title_icon.Nexus_5-19.png.sha1
new file mode 100644
index 0000000..b9e28da6
--- /dev/null
+++ b/chrome/test/data/android/render_tests/ModalDialogViewRenderTest.NightModeEnabled-title_and_title_icon.Nexus_5-19.png.sha1
@@ -0,0 +1 @@
+b4a5f0c38b145a9a598aad6a7fcc263075e0d7a3
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewTest.custom_view.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewTest.custom_view.Nexus_5-19.png.sha1
deleted file mode 100644
index 585589cb..0000000
--- a/chrome/test/data/android/render_tests/ModalDialogViewTest.custom_view.Nexus_5-19.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d3a779ddf0b2ccf75649cd67c5a57444e6b11745
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewTest.scrollable_title.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewTest.scrollable_title.Nexus_5-19.png.sha1
deleted file mode 100644
index 2a926ec..0000000
--- a/chrome/test/data/android/render_tests/ModalDialogViewTest.scrollable_title.Nexus_5-19.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a2fb8b7b45c472308713feb8a4243a8962537d68
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewTest.title_and_message.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewTest.title_and_message.Nexus_5-19.png.sha1
deleted file mode 100644
index 2338750..0000000
--- a/chrome/test/data/android/render_tests/ModalDialogViewTest.title_and_message.Nexus_5-19.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-689333a6fd997cc53c524fe5423146fce3229430
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/ModalDialogViewTest.title_and_title_icon.Nexus_5-19.png.sha1 b/chrome/test/data/android/render_tests/ModalDialogViewTest.title_and_title_icon.Nexus_5-19.png.sha1
deleted file mode 100644
index 0c9a429..0000000
--- a/chrome/test/data/android/render_tests/ModalDialogViewTest.title_and_title_icon.Nexus_5-19.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-1a1c99cf5eca68a19d4242ea1d1fe3c51b0bd1c4
\ No newline at end of file
diff --git a/chrome/test/data/extensions/api_test/service_worker/filtered_events/manifest.json b/chrome/test/data/extensions/api_test/service_worker/filtered_events/manifest.json
deleted file mode 100644
index 63fb4075..0000000
--- a/chrome/test/data/extensions/api_test/service_worker/filtered_events/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "name": "Extension service worker - filtered events",
-  "version": "1.0",
-  "manifest_version": 2,
-  "description": "Extension service worker - filtered events test with webNavigation",
-  "background": {
-    "scripts": ["test_filtered.js"]
-  },
-  "permissions": ["webNavigation", "tabs"]
-}
diff --git a/chrome/test/data/extensions/api_test/service_worker/filtered_events/sw.js b/chrome/test/data/extensions/api_test/service_worker/filtered_events/sw.js
deleted file mode 100644
index 77b2327d..0000000
--- a/chrome/test/data/extensions/api_test/service_worker/filtered_events/sw.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2017 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.
-
-self.onmessage = function(e) {
-  chrome.test.assertEq('testServiceWorkerFilteredEvents', e.data);
-  runTest();
-};
-
-function runTest() {
-  var getURL = chrome.extension.getURL;
-  chrome.tabs.create({url: 'about:blank'}, function(tab) {
-    var tabId = tab.id;
-    var aVisited = false;
-    chrome.webNavigation.onCommitted.addListener(function(details) {
-      chrome.test.fail();
-    }, {url: [{pathSuffix: 'never-navigated.html'}]});
-    chrome.webNavigation.onCommitted.addListener(function(details) {
-      chrome.test.log('chrome.webNavigation.onCommitted - a.html');
-      chrome.test.assertEq(getURL('a.html'), details.url);
-      aVisited = true;
-    }, {url: [{pathSuffix: 'a.html'}]});
-    chrome.webNavigation.onCommitted.addListener(function(details) {
-      chrome.test.log('chrome.webNavigation.onCommitted - b.html');
-      chrome.test.assertEq(getURL('b.html'), details.url);
-      chrome.test.assertTrue(aVisited);
-      chrome.test.succeed();
-    }, {url: [{pathSuffix: 'b.html'}]});
-
-    chrome.tabs.update(tabId, {url: getURL('a.html')});
-  });
-}
diff --git a/chrome/test/data/extensions/api_test/service_worker/filtered_events/test_filtered.js b/chrome/test/data/extensions/api_test/service_worker/filtered_events/test_filtered.js
deleted file mode 100644
index f8726ff3..0000000
--- a/chrome/test/data/extensions/api_test/service_worker/filtered_events/test_filtered.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2017 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.
-
-var workerPromise = new Promise(function(resolve, reject) {
-  navigator.serviceWorker.register('sw.js').then(function() {
-    return navigator.serviceWorker.ready;
-  }).then(function(registration) {
-    var sw = registration.active;
-    sw.postMessage('testServiceWorkerFilteredEvents');
-  }).catch(function(err) {
-    reject(err);
-  });
-});
-
-
-function testServiceWorkerFilteredEvents() {
-  // The worker calls chrome.test.succeed() if the test passes.
-  workerPromise.catch(function(err) {
-    chrome.test.fail();
-  });
-};
-
-chrome.test.runTests([testServiceWorkerFilteredEvents]);
diff --git a/chrome/test/data/extensions/api_test/service_worker/filtered_events/a.html b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/a.html
similarity index 100%
rename from chrome/test/data/extensions/api_test/service_worker/filtered_events/a.html
rename to chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/a.html
diff --git a/chrome/test/data/extensions/api_test/service_worker/filtered_events/a.js b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/a.js
similarity index 100%
rename from chrome/test/data/extensions/api_test/service_worker/filtered_events/a.js
rename to chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/a.js
diff --git a/chrome/test/data/extensions/api_test/service_worker/filtered_events/b.html b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/b.html
similarity index 100%
rename from chrome/test/data/extensions/api_test/service_worker/filtered_events/b.html
rename to chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/b.html
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/manifest.json b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/manifest.json
new file mode 100644
index 0000000..3556cbd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/manifest.json
@@ -0,0 +1,8 @@
+{
+  "name": "Service Worker-based background script",
+  "version": "0.1",
+  "manifest_version": 2,
+  "description": "Test Web Navigation APIs for service worker-based background scripts.",
+  "permissions": ["webNavigation"],
+  "background": {"service_worker": "service_worker_background.js"}
+}
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/service_worker_background.js b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/service_worker_background.js
new file mode 100644
index 0000000..ee0741a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/filtered_events/service_worker_background.js
@@ -0,0 +1,29 @@
+// 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.
+
+chrome.test.runTests([
+  function testWebNavigationOnCommitted() {
+    var getURL = chrome.extension.getURL;
+    chrome.tabs.create({url: 'about:blank'}, function(tab) {
+      var tabId = tab.id;
+      var aVisited = false;
+      chrome.webNavigation.onCommitted.addListener(function(details) {
+        chrome.test.fail();
+      }, {url: [{pathSuffix: 'never-navigated.html'}]});
+      chrome.webNavigation.onCommitted.addListener(function(details) {
+        chrome.test.log('chrome.webNavigation.onCommitted - a.html');
+        chrome.test.assertEq(getURL('a.html'), details.url);
+        aVisited = true;
+      }, {url: [{pathSuffix: 'a.html'}]});
+      chrome.webNavigation.onCommitted.addListener(function(details) {
+        chrome.test.log('chrome.webNavigation.onCommitted - b.html');
+        chrome.test.assertEq(getURL('b.html'), details.url);
+        chrome.test.assertTrue(aVisited);
+        chrome.test.succeed();
+      }, {url: [{pathSuffix: 'b.html'}]});
+
+      chrome.tabs.update(tabId, {url: getURL('a.html')});
+    });
+  }
+]);
diff --git a/chrome/utility/importer/firefox_importer_unittest_utils_mac.mojom b/chrome/utility/importer/firefox_importer_unittest_utils_mac.mojom
index 9219b7e..499e773e 100644
--- a/chrome/utility/importer/firefox_importer_unittest_utils_mac.mojom
+++ b/chrome/utility/importer/firefox_importer_unittest_utils_mac.mojom
@@ -4,7 +4,7 @@
 
 module firefox_importer_unittest_utils_mac.mojom;
 
-import "components/autofill/content/common/autofill_types.mojom";
+import "components/autofill/core/common/mojom/autofill_types.mojom";
 import "mojo/public/mojom/base/file_path.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 
diff --git a/chromecast/browser/exo/wayland_server_controller.cc b/chromecast/browser/exo/wayland_server_controller.cc
index b529fa3..85081c56 100644
--- a/chromecast/browser/exo/wayland_server_controller.cc
+++ b/chromecast/browser/exo/wayland_server_controller.cc
@@ -16,8 +16,7 @@
 WaylandServerController::WaylandServerController(
     CastWindowManagerAura* window_manager) {
   wm_helper_ = std::make_unique<exo::WMHelperCastShell>(
-      aura::Env::GetInstance(), window_manager,
-      static_cast<CastScreen*>(CastScreen::GetScreen()));
+      window_manager, static_cast<CastScreen*>(CastScreen::GetScreen()));
   exo::WMHelper::SetInstance(wm_helper_.get());
   display_ = std::make_unique<exo::Display>();
   wayland_server_ = exo::wayland::Server::Create(display_.get());
diff --git a/chromecast/browser/exo/wm_helper_cast_shell.cc b/chromecast/browser/exo/wm_helper_cast_shell.cc
index 4ca03b2..6fbb296 100644
--- a/chromecast/browser/exo/wm_helper_cast_shell.cc
+++ b/chromecast/browser/exo/wm_helper_cast_shell.cc
@@ -21,11 +21,9 @@
 namespace exo {
 
 WMHelperCastShell::WMHelperCastShell(
-    aura::Env* env,
     chromecast::CastWindowManagerAura* cast_window_manager_aura,
     chromecast::CastScreen* cast_screen)
     : cast_window_manager_aura_(cast_window_manager_aura),
-      env_(env),
       cast_screen_(cast_screen),
       vsync_timing_manager_(this) {
   cast_screen_->AddObserver(&display_observer_);
@@ -35,10 +33,6 @@
   cast_screen_->RemoveObserver(&display_observer_);
 }
 
-aura::Env* WMHelperCastShell::env() {
-  return env_;
-}
-
 void WMHelperCastShell::AddActivationObserver(
     wm::ActivationChangeObserver* observer) {
   NOTIMPLEMENTED();
diff --git a/chromecast/browser/exo/wm_helper_cast_shell.h b/chromecast/browser/exo/wm_helper_cast_shell.h
index feb9c3b..77832c95 100644
--- a/chromecast/browser/exo/wm_helper_cast_shell.h
+++ b/chromecast/browser/exo/wm_helper_cast_shell.h
@@ -18,7 +18,6 @@
 #include "ui/display/display_observer.h"
 
 namespace aura {
-class env;
 class Window;
 namespace client {
 class CursorClient;
@@ -57,13 +56,11 @@
 // features.
 class WMHelperCastShell : public WMHelper, public VSyncTimingManager::Delegate {
  public:
-  WMHelperCastShell(aura::Env* env,
-                    chromecast::CastWindowManagerAura* cast_window_manager_aura,
+  WMHelperCastShell(chromecast::CastWindowManagerAura* cast_window_manager_aura,
                     chromecast::CastScreen* cast_screen);
   ~WMHelperCastShell() override;
 
   // Overridden from WMHelper
-  aura::Env* env() override;
   void AddActivationObserver(wm::ActivationChangeObserver* observer) override;
   void RemoveActivationObserver(
       wm::ActivationChangeObserver* observer) override;
diff --git a/chromeos/login/auth/BUILD.gn b/chromeos/login/auth/BUILD.gn
index c65905b..3d96015 100644
--- a/chromeos/login/auth/BUILD.gn
+++ b/chromeos/login/auth/BUILD.gn
@@ -92,8 +92,6 @@
     "mock_auth_attempt_state_resolver.h",
     "mock_auth_status_consumer.cc",
     "mock_auth_status_consumer.h",
-    "mock_url_fetchers.cc",
-    "mock_url_fetchers.h",
   ]
 }
 
diff --git a/chromeos/login/auth/mock_url_fetchers.cc b/chromeos/login/auth/mock_url_fetchers.cc
deleted file mode 100644
index 4229db59..0000000
--- a/chromeos/login/auth/mock_url_fetchers.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/login/auth/mock_url_fetchers.h"
-
-#include <errno.h>
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/run_loop.h"
-#include "base/single_thread_task_runner.h"
-#include "base/strings/stringprintf.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "net/base/net_errors.h"
-#include "net/http/http_status_code.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_fetcher_delegate.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace chromeos {
-
-ExpectCanceledFetcher::ExpectCanceledFetcher(
-    bool success,
-    const GURL& url,
-    const std::string& results,
-    net::URLFetcher::RequestType request_type,
-    net::URLFetcherDelegate* d)
-    : net::TestURLFetcher(0, url, d), weak_factory_(this) {
-}
-
-ExpectCanceledFetcher::~ExpectCanceledFetcher() = default;
-
-void ExpectCanceledFetcher::Start() {
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&ExpectCanceledFetcher::CompleteFetch,
-                     weak_factory_.GetWeakPtr()),
-      base::TimeDelta::FromMilliseconds(100));
-}
-
-void ExpectCanceledFetcher::CompleteFetch() {
-  ADD_FAILURE() << "Fetch completed in ExpectCanceledFetcher!";
-
-  // Allow exiting even if we mess up.
-  base::RunLoop::QuitCurrentWhenIdleDeprecated();
-}
-
-GotCanceledFetcher::GotCanceledFetcher(
-    bool success,
-    const GURL& url,
-    const std::string& results,
-    net::URLFetcher::RequestType request_type,
-    net::URLFetcherDelegate* d)
-    : net::TestURLFetcher(0, url, d) {
-  set_url(url);
-  set_status(net::URLRequestStatus::FromError(net::ERR_ABORTED));
-  set_response_code(net::HTTP_FORBIDDEN);
-}
-
-GotCanceledFetcher::~GotCanceledFetcher() = default;
-
-void GotCanceledFetcher::Start() {
-  delegate()->OnURLFetchComplete(this);
-}
-
-SuccessFetcher::SuccessFetcher(bool success,
-                               const GURL& url,
-                               const std::string& results,
-                               net::URLFetcher::RequestType request_type,
-                               net::URLFetcherDelegate* d)
-    : net::TestURLFetcher(0, url, d) {
-  set_url(url);
-  set_status(net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0));
-  set_response_code(net::HTTP_OK);
-}
-
-SuccessFetcher::~SuccessFetcher() = default;
-
-void SuccessFetcher::Start() {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&SuccessFetcher::RunDelegate, weak_factory_.GetWeakPtr()));
-}
-
-void SuccessFetcher::RunDelegate() {
-  delegate()->OnURLFetchComplete(this);
-}
-
-FailFetcher::FailFetcher(bool success,
-                         const GURL& url,
-                         const std::string& results,
-                         net::URLFetcher::RequestType request_type,
-                         net::URLFetcherDelegate* d)
-    : net::TestURLFetcher(0, url, d) {
-  set_url(url);
-  set_status(net::URLRequestStatus::FromError(net::ERR_CONNECTION_RESET));
-  set_response_code(net::HTTP_OK);
-}
-
-FailFetcher::~FailFetcher() = default;
-
-void FailFetcher::Start() {
-  delegate()->OnURLFetchComplete(this);
-}
-
-// static
-const char CaptchaFetcher::kCaptchaToken[] = "token";
-// static
-const char CaptchaFetcher::kCaptchaUrlBase[] = "http://accounts.google.com/";
-// static
-const char CaptchaFetcher::kCaptchaUrlFragment[] = "fragment";
-// static
-const char CaptchaFetcher::kUnlockUrl[] = "http://what.ever";
-
-CaptchaFetcher::CaptchaFetcher(bool success,
-                               const GURL& url,
-                               const std::string& results,
-                               net::URLFetcher::RequestType request_type,
-                               net::URLFetcherDelegate* d)
-    : net::TestURLFetcher(0, url, d) {
-  set_url(url);
-  set_status(net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0));
-  set_response_code(net::HTTP_FORBIDDEN);
-  SetResponseString(base::StringPrintf(
-      "Error=%s\n"
-      "Url=%s\n"
-      "CaptchaUrl=%s\n"
-      "CaptchaToken=%s\n",
-      "CaptchaRequired",
-      kUnlockUrl,
-      kCaptchaUrlFragment,
-      kCaptchaToken));
-}
-
-CaptchaFetcher::~CaptchaFetcher() = default;
-
-// static
-std::string CaptchaFetcher::GetCaptchaToken() {
-  return kCaptchaToken;
-}
-
-// static
-std::string CaptchaFetcher::GetCaptchaUrl() {
-  return std::string(kCaptchaUrlBase).append(kCaptchaUrlFragment);
-}
-
-// static
-std::string CaptchaFetcher::GetUnlockUrl() {
-  return kUnlockUrl;
-}
-
-void CaptchaFetcher::Start() {
-  delegate()->OnURLFetchComplete(this);
-}
-
-HostedFetcher::HostedFetcher(bool success,
-                             const GURL& url,
-                             const std::string& results,
-                             net::URLFetcher::RequestType request_type,
-                             net::URLFetcherDelegate* d)
-    : net::TestURLFetcher(0, url, d) {
-  set_url(url);
-  set_status(net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0));
-  set_response_code(net::HTTP_OK);
-}
-
-HostedFetcher::~HostedFetcher() = default;
-
-void HostedFetcher::Start() {
-  VLOG(1) << upload_data();
-  if (upload_data().find("HOSTED") == std::string::npos) {
-    VLOG(1) << "HostedFetcher failing request";
-    set_response_code(net::HTTP_FORBIDDEN);
-    SetResponseString("Error=BadAuthentication");
-  }
-  delegate()->OnURLFetchComplete(this);
-}
-
-}  // namespace chromeos
diff --git a/chromeos/login/auth/mock_url_fetchers.h b/chromeos/login/auth/mock_url_fetchers.h
deleted file mode 100644
index 27f1b33a..0000000
--- a/chromeos/login/auth/mock_url_fetchers.h
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_LOGIN_AUTH_MOCK_URL_FETCHERS_H_
-#define CHROMEOS_LOGIN_AUTH_MOCK_URL_FETCHERS_H_
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/component_export.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "net/url_request/test_url_fetcher_factory.h"
-#include "net/url_request/url_request_status.h"
-#include "url/gurl.h"
-
-namespace net {
-class URLFetcherDelegate;
-}
-
-namespace chromeos {
-
-// Simulates a URL fetch by posting a delayed task. This fetch expects to be
-// canceled, and fails the test if it is not
-class ExpectCanceledFetcher : public net::TestURLFetcher {
- public:
-  ExpectCanceledFetcher(bool success,
-                        const GURL& url,
-                        const std::string& results,
-                        net::URLFetcher::RequestType request_type,
-                        net::URLFetcherDelegate* d);
-  ~ExpectCanceledFetcher() override;
-
-  void Start() override;
-
-  void CompleteFetch();
-
- private:
-  base::WeakPtrFactory<ExpectCanceledFetcher> weak_factory_;
-  DISALLOW_COPY_AND_ASSIGN(ExpectCanceledFetcher);
-};
-
-class GotCanceledFetcher : public net::TestURLFetcher {
- public:
-  GotCanceledFetcher(bool success,
-                     const GURL& url,
-                     const std::string& results,
-                     net::URLFetcher::RequestType request_type,
-                     net::URLFetcherDelegate* d);
-  ~GotCanceledFetcher() override;
-
-  void Start() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(GotCanceledFetcher);
-};
-
-class SuccessFetcher : public net::TestURLFetcher {
- public:
-  SuccessFetcher(bool success,
-                 const GURL& url,
-                 const std::string& results,
-                 net::URLFetcher::RequestType request_type,
-                 net::URLFetcherDelegate* d);
-  ~SuccessFetcher() override;
-
-  void Start() override;
-
- private:
-  void RunDelegate();
-
-  base::WeakPtrFactory<SuccessFetcher> weak_factory_{this};
-  DISALLOW_COPY_AND_ASSIGN(SuccessFetcher);
-};
-
-class FailFetcher : public net::TestURLFetcher {
- public:
-  FailFetcher(bool success,
-              const GURL& url,
-              const std::string& results,
-              net::URLFetcher::RequestType request_type,
-              net::URLFetcherDelegate* d);
-  ~FailFetcher() override;
-
-  void Start() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(FailFetcher);
-};
-
-class CaptchaFetcher : public net::TestURLFetcher {
- public:
-  CaptchaFetcher(bool success,
-                 const GURL& url,
-                 const std::string& results,
-                 net::URLFetcher::RequestType request_type,
-                 net::URLFetcherDelegate* d);
-  ~CaptchaFetcher() override;
-
-  static std::string GetCaptchaToken();
-  static std::string GetCaptchaUrl();
-  static std::string GetUnlockUrl();
-
-  void Start() override;
-
- private:
-  static const char kCaptchaToken[];
-  static const char kCaptchaUrlBase[];
-  static const char kCaptchaUrlFragment[];
-  static const char kUnlockUrl[];
-  DISALLOW_COPY_AND_ASSIGN(CaptchaFetcher);
-};
-
-class HostedFetcher : public net::TestURLFetcher {
- public:
-  HostedFetcher(bool success,
-                const GURL& url,
-                const std::string& results,
-                net::URLFetcher::RequestType request_type,
-                net::URLFetcherDelegate* d);
-  ~HostedFetcher() override;
-
-  void Start() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(HostedFetcher);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_LOGIN_AUTH_MOCK_URL_FETCHERS_H_
diff --git a/chromeos/services/device_sync/BUILD.gn b/chromeos/services/device_sync/BUILD.gn
index a0e4e67..8bd6d72f 100644
--- a/chromeos/services/device_sync/BUILD.gn
+++ b/chromeos/services/device_sync/BUILD.gn
@@ -13,6 +13,8 @@
     "cryptauth_client_impl.h",
     "cryptauth_constants.cc",
     "cryptauth_constants.h",
+    "cryptauth_device.cc",
+    "cryptauth_device.h",
     "cryptauth_device_manager.cc",
     "cryptauth_device_manager.h",
     "cryptauth_device_manager_impl.cc",
@@ -86,6 +88,8 @@
     "sync_scheduler.h",
     "sync_scheduler_impl.cc",
     "sync_scheduler_impl.h",
+    "value_string_encoding.cc",
+    "value_string_encoding.h",
   ]
 
   public_deps = [
@@ -98,6 +102,7 @@
 
   deps = [
     "//base",
+    "//base/util/values:values_util",
     "//chromeos/components/multidevice",
     "//chromeos/components/multidevice/logging",
     "//chromeos/constants:constants",
@@ -173,6 +178,7 @@
     "cryptauth_api_call_flow_unittest.cc",
     "cryptauth_client_impl_unittest.cc",
     "cryptauth_device_manager_impl_unittest.cc",
+    "cryptauth_device_unittest.cc",
     "cryptauth_enroller_impl_unittest.cc",
     "cryptauth_enrollment_manager_impl_unittest.cc",
     "cryptauth_gcm_manager_impl_unittest.cc",
diff --git a/chromeos/services/device_sync/cryptauth_device.cc b/chromeos/services/device_sync/cryptauth_device.cc
new file mode 100644
index 0000000..c689047
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_device.cc
@@ -0,0 +1,172 @@
+// 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 "chromeos/services/device_sync/cryptauth_device.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/util/values/values_util.h"
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+namespace {
+
+// Strings used as the DictionaryValue keys in {From,As}DictionaryValue().
+const char kInstanceIdDictKey[] = "instance_id";
+const char kDeviceNameDictKey[] = "device_name";
+const char kLastUpdateTimeDictKey[] = "last_update_time";
+const char kDeviceBetterTogetherPublicKeyDictKey[] =
+    "device_better_together_public_key";
+const char kBetterTogetherDeviceMetadataDictKey[] =
+    "better_together_device_metadata";
+const char kFeatureStatesDictKey[] = "feature_states";
+
+base::Optional<
+    std::map<multidevice::SoftwareFeature, multidevice::SoftwareFeatureState>>
+FeatureStatesFromDictionary(const base::Value* dict) {
+  if (!dict || !dict->is_dict())
+    return base::nullopt;
+
+  std::map<multidevice::SoftwareFeature, multidevice::SoftwareFeatureState>
+      feature_states;
+  for (const auto& feature_state_pair : dict->DictItems()) {
+    int feature;
+    if (!base::StringToInt(feature_state_pair.first, &feature) ||
+        !feature_state_pair.second.is_int()) {
+      return base::nullopt;
+    }
+
+    feature_states[static_cast<multidevice::SoftwareFeature>(feature)] =
+        static_cast<multidevice::SoftwareFeatureState>(
+            feature_state_pair.second.GetInt());
+  }
+
+  return feature_states;
+}
+
+base::Value FeatureStatesToDictionary(
+    const std::map<multidevice::SoftwareFeature,
+                   multidevice::SoftwareFeatureState>& feature_states) {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  for (const auto& feature_state_pair : feature_states) {
+    dict.SetIntKey(
+        base::NumberToString(static_cast<int>(feature_state_pair.first)),
+        static_cast<int>(feature_state_pair.second));
+  }
+
+  return dict;
+}
+
+}  // namespace
+
+// static
+base::Optional<CryptAuthDevice> CryptAuthDevice::FromDictionary(
+    const base::Value& dict) {
+  if (!dict.is_dict())
+    return base::nullopt;
+
+  base::Optional<std::string> instance_id =
+      util::DecodeFromValueString(dict.FindKey(kInstanceIdDictKey));
+  if (!instance_id || instance_id->empty())
+    return base::nullopt;
+
+  base::Optional<std::string> device_name =
+      util::DecodeFromValueString(dict.FindKey(kDeviceNameDictKey));
+  if (!device_name || device_name->empty())
+    return base::nullopt;
+
+  base::Optional<std::string> device_better_together_public_key =
+      util::DecodeFromValueString(
+          dict.FindKey(kDeviceBetterTogetherPublicKeyDictKey));
+  if (!device_better_together_public_key ||
+      device_better_together_public_key->empty()) {
+    return base::nullopt;
+  }
+
+  base::Optional<base::Time> last_update_time =
+      ::util::ValueToTime(dict.FindKey(kLastUpdateTimeDictKey));
+  if (!last_update_time)
+    return base::nullopt;
+
+  base::Optional<cryptauthv2::BetterTogetherDeviceMetadata>
+      better_together_device_metadata = util::DecodeProtoMessageFromValueString<
+          cryptauthv2::BetterTogetherDeviceMetadata>(
+          dict.FindKey(kBetterTogetherDeviceMetadataDictKey));
+  if (!better_together_device_metadata)
+    return base::nullopt;
+
+  base::Optional<
+      std::map<multidevice::SoftwareFeature, multidevice::SoftwareFeatureState>>
+      feature_states =
+          FeatureStatesFromDictionary(dict.FindDictKey(kFeatureStatesDictKey));
+  if (!feature_states)
+    return base::nullopt;
+
+  return CryptAuthDevice(*instance_id, *device_name,
+                         *device_better_together_public_key, *last_update_time,
+                         *better_together_device_metadata, *feature_states);
+}
+
+CryptAuthDevice::CryptAuthDevice(
+    const std::string& instance_id,
+    const std::string& device_name,
+    const std::string& device_better_together_public_key,
+    const base::Time& last_update_time,
+    const cryptauthv2::BetterTogetherDeviceMetadata&
+        better_together_device_metadata,
+    const std::map<multidevice::SoftwareFeature,
+                   multidevice::SoftwareFeatureState>& feature_states)
+    : instance_id_(instance_id),
+      device_name_(device_name),
+      device_better_together_public_key_(device_better_together_public_key),
+      last_update_time_(last_update_time),
+      better_together_device_metadata_(better_together_device_metadata),
+      feature_states_(feature_states) {
+  DCHECK(!instance_id.empty());
+  DCHECK(!device_name.empty());
+  DCHECK(!device_better_together_public_key.empty());
+  DCHECK(!last_update_time.is_null());
+}
+
+CryptAuthDevice::CryptAuthDevice(const CryptAuthDevice&) = default;
+
+CryptAuthDevice::~CryptAuthDevice() = default;
+
+base::Value CryptAuthDevice::AsDictionary() const {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey(kInstanceIdDictKey, util::EncodeAsValueString(instance_id_));
+  dict.SetKey(kDeviceNameDictKey, util::EncodeAsValueString(device_name_));
+  dict.SetKey(kDeviceBetterTogetherPublicKeyDictKey,
+              util::EncodeAsValueString(device_better_together_public_key_));
+  dict.SetKey(kLastUpdateTimeDictKey, ::util::TimeToValue(last_update_time_));
+  dict.SetKey(
+      kBetterTogetherDeviceMetadataDictKey,
+      util::EncodeProtoMessageAsValueString(&better_together_device_metadata_));
+  dict.SetKey(kFeatureStatesDictKey,
+              FeatureStatesToDictionary(feature_states_));
+
+  return dict;
+}
+
+bool CryptAuthDevice::operator==(const CryptAuthDevice& other) const {
+  return instance_id_ == other.instance_id_ &&
+         device_name_ == other.device_name_ &&
+         device_better_together_public_key_ ==
+             other.device_better_together_public_key_ &&
+         last_update_time_ == other.last_update_time_ &&
+         better_together_device_metadata_.SerializeAsString() ==
+             other.better_together_device_metadata_.SerializeAsString() &&
+         feature_states_ == other.feature_states_;
+}
+
+bool CryptAuthDevice::operator!=(const CryptAuthDevice& other) const {
+  return !(*this == other);
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/cryptauth_device.h b/chromeos/services/device_sync/cryptauth_device.h
new file mode 100644
index 0000000..1bf35265
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_device.h
@@ -0,0 +1,110 @@
+// 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 CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_H_
+#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_H_
+
+#include <map>
+#include <string>
+
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/proto/cryptauth_better_together_device_metadata.pb.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+// Holds information for a device managed by CryptAuth.
+class CryptAuthDevice {
+ public:
+  // Returns null if |dict| cannot be converted into a CryptAuthDevice.
+  static base::Optional<CryptAuthDevice> FromDictionary(
+      const base::Value& dict);
+
+  // |instance_id|: The Instance ID, used as a unique device identifier. Cannot
+  //     be empty.
+  // |device_name|: The name known to the CryptAuth server or which was assigned
+  //     by the user to the device. Might contain personally identifiable
+  //     information. Cannot be empty.
+  // |device_better_together_public_key|: The device's
+  //     "DeviceSync:BetterTogether" public key that is enrolled with
+  //     CryptAuth--known as CryptAuthKeyBundle::kDeviceSyncBetterTogether on
+  //     Chrome OS. This is not to be confused with the shared, unenrolled group
+  //     public key. Cannot be empty.
+  // |last_update_time|: The last time the device updated its information with
+  //     the CryptAuth server. Cannot be null.
+  // |better_together_device_metadata|: Device metadata relevant to the suite of
+  //     multi-device ("Better Together") features.
+  // |feature_states|: A map from the multi-device feature type (example:
+  //     kBetterTogetherHost) to feature state (example: kEnabled).
+  CryptAuthDevice(
+      const std::string& instance_id,
+      const std::string& device_name,
+      const std::string& device_better_together_public_key,
+      const base::Time& last_update_time,
+      const cryptauthv2::BetterTogetherDeviceMetadata&
+          better_together_device_metadata,
+      const std::map<multidevice::SoftwareFeature,
+                     multidevice::SoftwareFeatureState>& feature_states);
+
+  CryptAuthDevice(const CryptAuthDevice&);
+
+  ~CryptAuthDevice();
+
+  const std::string& instance_id() const { return instance_id_; }
+
+  const std::string& device_name() const { return device_name_; }
+
+  const std::string& device_better_together_public_key() const {
+    return device_better_together_public_key_;
+  }
+
+  const base::Time& last_update_time() const { return last_update_time_; }
+
+  const cryptauthv2::BetterTogetherDeviceMetadata&
+  better_together_device_metadata() const {
+    return better_together_device_metadata_;
+  }
+
+  const std::map<multidevice::SoftwareFeature,
+                 multidevice::SoftwareFeatureState>&
+  feature_states() const {
+    return feature_states_;
+  }
+
+  // Converts CryptAuthDevice to a dictionary-type Value of the form
+  //   {
+  //     "instance_id": |instance_id_|,
+  //     "device_name": |device_name_|,
+  //     "device_better_together_public_key_":
+  //         <|device_better_together_public_key_| base64 encoded>,
+  //     "last_update_time": <|last_update_time_| converted to string>,
+  //     "better_together_device_metadata":
+  //         <|better_together_device_metadata_| serialized and base64 encoded>,
+  //     "feature_states": <|feature_states_| as dictionary>
+  //   }
+  base::Value AsDictionary() const;
+
+  bool operator==(const CryptAuthDevice& other) const;
+  bool operator!=(const CryptAuthDevice& other) const;
+
+ private:
+  std::string instance_id_;
+  std::string device_name_;
+  std::string device_better_together_public_key_;
+  base::Time last_update_time_;
+  cryptauthv2::BetterTogetherDeviceMetadata better_together_device_metadata_;
+  std::map<multidevice::SoftwareFeature, multidevice::SoftwareFeatureState>
+      feature_states_;
+};
+
+}  // namespace device_sync
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_H_
diff --git a/chromeos/services/device_sync/cryptauth_device_unittest.cc b/chromeos/services/device_sync/cryptauth_device_unittest.cc
new file mode 100644
index 0000000..becf88f
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_device_unittest.cc
@@ -0,0 +1,55 @@
+// 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 "chromeos/services/device_sync/cryptauth_device.h"
+
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/proto/cryptauth_better_together_device_metadata.pb.h"
+#include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+namespace {
+
+const char kFakeInstanceId[] = "fake_instance_id";
+const char kFakeDeviceName[] = "fake_device_name";
+const char kFakeDeviceBetterTogetherPublicKey[] =
+    "fake_device_better_together_public_key";
+
+}  // namespace
+
+TEST(DeviceSyncCryptAuthDevice, ToAndFromDictionary) {
+  const base::Time kFakeLastUpdateTime = base::Time::FromDoubleT(100);
+  const std::map<multidevice::SoftwareFeature,
+                 multidevice::SoftwareFeatureState>
+      kFakeFeatureStates = {
+          {multidevice::SoftwareFeature::kBetterTogetherClient,
+           multidevice::SoftwareFeatureState::kEnabled},
+          {multidevice::SoftwareFeature::kBetterTogetherHost,
+           multidevice::SoftwareFeatureState::kNotSupported},
+          {multidevice::SoftwareFeature::kMessagesForWebClient,
+           multidevice::SoftwareFeatureState::kSupported}};
+
+  CryptAuthDevice expected_device(
+      kFakeInstanceId, kFakeDeviceName, kFakeDeviceBetterTogetherPublicKey,
+      kFakeLastUpdateTime,
+      cryptauthv2::GetBetterTogetherDeviceMetadataForTest(),
+      kFakeFeatureStates);
+
+  base::Optional<CryptAuthDevice> device =
+      CryptAuthDevice::FromDictionary(expected_device.AsDictionary());
+
+  ASSERT_TRUE(device);
+  EXPECT_EQ(expected_device, *device);
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/cryptauth_enrollment_manager_impl.cc b/chromeos/services/device_sync/cryptauth_enrollment_manager_impl.cc
index 1391da3..5f3cbd9 100644
--- a/chromeos/services/device_sync/cryptauth_enrollment_manager_impl.cc
+++ b/chromeos/services/device_sync/cryptauth_enrollment_manager_impl.cc
@@ -8,17 +8,18 @@
 #include <sstream>
 #include <utility>
 
-#include "base/base64url.h"
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/components/multidevice/secure_message_delegate.h"
 #include "chromeos/services/device_sync/cryptauth_enroller.h"
 #include "chromeos/services/device_sync/pref_names.h"
 #include "chromeos/services/device_sync/proto/enum_util.h"
 #include "chromeos/services/device_sync/sync_scheduler_impl.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
@@ -229,25 +230,25 @@
 }
 
 std::string CryptAuthEnrollmentManagerImpl::GetUserPublicKey() const {
-  std::string public_key;
-  if (!base::Base64UrlDecode(
-          pref_service_->GetString(prefs::kCryptAuthEnrollmentUserPublicKey),
-          base::Base64UrlDecodePolicy::REQUIRE_PADDING, &public_key)) {
+  base::Optional<std::string> public_key = util::DecodeFromValueString(
+      pref_service_->Get(prefs::kCryptAuthEnrollmentUserPublicKey));
+  if (!public_key) {
     PA_LOG(ERROR) << "Invalid public key stored in user prefs.";
     return std::string();
   }
-  return public_key;
+
+  return *public_key;
 }
 
 std::string CryptAuthEnrollmentManagerImpl::GetUserPrivateKey() const {
-  std::string private_key;
-  if (!base::Base64UrlDecode(
-          pref_service_->GetString(prefs::kCryptAuthEnrollmentUserPrivateKey),
-          base::Base64UrlDecodePolicy::REQUIRE_PADDING, &private_key)) {
+  base::Optional<std::string> private_key = util::DecodeFromValueString(
+      pref_service_->Get(prefs::kCryptAuthEnrollmentUserPrivateKey));
+  if (!private_key) {
     PA_LOG(ERROR) << "Invalid private key stored in user prefs.";
     return std::string();
   }
-  return private_key;
+
+  return *private_key;
 }
 
 void CryptAuthEnrollmentManagerImpl::SetSyncSchedulerForTest(
@@ -272,19 +273,12 @@
     const std::string& private_key) {
   if (!public_key.empty() && !private_key.empty()) {
     PA_LOG(VERBOSE) << "Key pair generated for CryptAuth enrollment";
-    // Store the keypair in Base64 format because pref values require readable
-    // string values.
-    std::string public_key_b64, private_key_b64;
-    base::Base64UrlEncode(public_key,
-                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
-                          &public_key_b64);
-    base::Base64UrlEncode(private_key,
-                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
-                          &private_key_b64);
-    pref_service_->SetString(prefs::kCryptAuthEnrollmentUserPublicKey,
-                             public_key_b64);
-    pref_service_->SetString(prefs::kCryptAuthEnrollmentUserPrivateKey,
-                             private_key_b64);
+
+    // Pref values must be UTF-8 valid base::Value strings.
+    pref_service_->Set(prefs::kCryptAuthEnrollmentUserPublicKey,
+                       util::EncodeAsValueString(public_key));
+    pref_service_->Set(prefs::kCryptAuthEnrollmentUserPrivateKey,
+                       util::EncodeAsValueString(private_key));
     DoCryptAuthEnrollment();
   } else {
     OnEnrollmentFinished(false);
@@ -352,12 +346,9 @@
   device_info.set_gcm_registration_id(gcm_manager_->GetRegistrationId());
   device_info.set_device_software_package(kDeviceSoftwarePackage);
 
-  std::string public_key_b64;
-  base::Base64UrlEncode(GetUserPublicKey(),
-                        base::Base64UrlEncodePolicy::INCLUDE_PADDING,
-                        &public_key_b64);
   PA_LOG(VERBOSE) << "Making enrollment:\n"
-                  << "  public_key: " << public_key_b64 << "\n"
+                  << "  public_key: "
+                  << util::EncodeAsValueString(GetUserPublicKey()) << "\n"
                   << "  invocation_reason: " << invocation_reason << "\n"
                   << "  gcm_registration_id: "
                   << device_info.gcm_registration_id()
diff --git a/chromeos/services/device_sync/cryptauth_enrollment_manager_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_enrollment_manager_impl_unittest.cc
index 3645bda..3d686413 100644
--- a/chromeos/services/device_sync/cryptauth_enrollment_manager_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_enrollment_manager_impl_unittest.cc
@@ -7,7 +7,6 @@
 #include <memory>
 #include <utility>
 
-#include "base/base64url.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
@@ -19,6 +18,7 @@
 #include "chromeos/services/device_sync/fake_cryptauth_gcm_manager.h"
 #include "chromeos/services/device_sync/mock_sync_scheduler.h"
 #include "chromeos/services/device_sync/pref_names.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -177,18 +177,10 @@
     pref_service_.SetUserPref(
         prefs::kCryptAuthEnrollmentReason,
         std::make_unique<base::Value>(cryptauth::INVOCATION_REASON_UNKNOWN));
-
-    std::string public_key_b64, private_key_b64;
-    base::Base64UrlEncode(public_key_,
-                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
-                          &public_key_b64);
-    base::Base64UrlEncode(private_key_,
-                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
-                          &private_key_b64);
-    pref_service_.SetString(prefs::kCryptAuthEnrollmentUserPublicKey,
-                            public_key_b64);
-    pref_service_.SetString(prefs::kCryptAuthEnrollmentUserPrivateKey,
-                            private_key_b64);
+    pref_service_.Set(prefs::kCryptAuthEnrollmentUserPublicKey,
+                      util::EncodeAsValueString(public_key_));
+    pref_service_.Set(prefs::kCryptAuthEnrollmentUserPrivateKey,
+                      util::EncodeAsValueString(private_key_));
 
     ON_CALL(*sync_scheduler(), GetStrategy())
         .WillByDefault(Return(SyncScheduler::Strategy::PERIODIC_REFRESH));
diff --git a/chromeos/services/device_sync/cryptauth_key.cc b/chromeos/services/device_sync/cryptauth_key.cc
index f23428b..25240f87 100644
--- a/chromeos/services/device_sync/cryptauth_key.cc
+++ b/chromeos/services/device_sync/cryptauth_key.cc
@@ -4,7 +4,8 @@
 
 #include "chromeos/services/device_sync/cryptauth_key.h"
 
-#include "base/base64.h"
+#include "base/base64url.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "crypto/sha2.h"
 
 namespace chromeos {
@@ -21,10 +22,11 @@
 const char kPublicKeyDictKey[] = "public_key";
 const char kPrivateKeyDictKey[] = "private_key";
 
-// Returns the base64-encoded SHA256 hash of the input string.
+// Returns the base64url-encoded SHA256 hash of the input string.
 std::string CreateHandle(const std::string& string_to_hash) {
   std::string handle;
-  base::Base64Encode(crypto::SHA256HashString(string_to_hash), &handle);
+  base::Base64UrlEncode(crypto::SHA256HashString(string_to_hash),
+                        base::Base64UrlEncodePolicy::INCLUDE_PADDING, &handle);
   return handle;
 }
 
@@ -61,35 +63,25 @@
     return base::nullopt;
 
   if (IsSymmetricKeyType(type)) {
-    const std::string* encoded_symmetric_key =
-        dict.FindStringKey(kSymmetricKeyDictKey);
-    if (!encoded_symmetric_key || encoded_symmetric_key->empty())
+    base::Optional<std::string> symmetric_key =
+        util::DecodeFromValueString(dict.FindKey(kSymmetricKeyDictKey));
+    if (!symmetric_key || symmetric_key->empty())
       return base::nullopt;
 
-    std::string decoded_symmetric_key;
-    if (!base::Base64Decode(*encoded_symmetric_key, &decoded_symmetric_key))
-      return base::nullopt;
-
-    return CryptAuthKey(decoded_symmetric_key, status, type, *handle);
+    return CryptAuthKey(*symmetric_key, status, type, *handle);
   }
 
   DCHECK(IsAsymmetricKeyType(type));
-  const std::string* encoded_public_key = dict.FindStringKey(kPublicKeyDictKey);
-  const std::string* encoded_private_key =
-      dict.FindStringKey(kPrivateKeyDictKey);
-  if (!encoded_public_key || encoded_public_key->empty() ||
-      !encoded_private_key) {
+
+  base::Optional<std::string> public_key =
+      util::DecodeFromValueString(dict.FindKey(kPublicKeyDictKey));
+  base::Optional<std::string> private_key =
+      util::DecodeFromValueString(dict.FindKey(kPrivateKeyDictKey));
+  if (!public_key || !private_key || public_key->empty()) {
     return base::nullopt;
   }
 
-  std::string decoded_public_key, decoded_private_key;
-  if (!base::Base64Decode(*encoded_public_key, &decoded_public_key) ||
-      !base::Base64Decode(*encoded_private_key, &decoded_private_key)) {
-    return base::nullopt;
-  }
-
-  return CryptAuthKey(decoded_public_key, decoded_private_key, status, type,
-                      *handle);
+  return CryptAuthKey(*public_key, *private_key, status, type, *handle);
 }
 
 CryptAuthKey::CryptAuthKey(const std::string& symmetric_key,
@@ -139,10 +131,7 @@
   dict.SetKey(kHandleDictKey, base::Value(handle_));
   dict.SetKey(kStatusDictKey, base::Value(status_));
   dict.SetKey(kTypeDictKey, base::Value(type_));
-
-  std::string encoded_symmetric_key;
-  base::Base64Encode(symmetric_key_, &encoded_symmetric_key);
-  dict.SetKey(kSymmetricKeyDictKey, base::Value(encoded_symmetric_key));
+  dict.SetKey(kSymmetricKeyDictKey, util::EncodeAsValueString(symmetric_key_));
 
   return dict;
 }
@@ -154,13 +143,8 @@
   dict.SetKey(kHandleDictKey, base::Value(handle_));
   dict.SetKey(kStatusDictKey, base::Value(status_));
   dict.SetKey(kTypeDictKey, base::Value(type_));
-
-  std::string encoded_public_key;
-  base::Base64Encode(public_key_, &encoded_public_key);
-  dict.SetKey(kPublicKeyDictKey, base::Value(encoded_public_key));
-  std::string encoded_private_key;
-  base::Base64Encode(private_key_, &encoded_private_key);
-  dict.SetKey(kPrivateKeyDictKey, base::Value(encoded_private_key));
+  dict.SetKey(kPublicKeyDictKey, util::EncodeAsValueString(public_key_));
+  dict.SetKey(kPrivateKeyDictKey, util::EncodeAsValueString(private_key_));
 
   return dict;
 }
diff --git a/chromeos/services/device_sync/cryptauth_key_bundle.cc b/chromeos/services/device_sync/cryptauth_key_bundle.cc
index 19ac2b72..f8dabff 100644
--- a/chromeos/services/device_sync/cryptauth_key_bundle.cc
+++ b/chromeos/services/device_sync/cryptauth_key_bundle.cc
@@ -4,11 +4,12 @@
 
 #include "chromeos/services/device_sync/cryptauth_key_bundle.h"
 
-#include "base/base64.h"
 #include "base/no_destructor.h"
 #include "base/stl_util.h"
+#include "base/values.h"
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/services/device_sync/cryptauth_constants.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 
 namespace chromeos {
 
@@ -22,30 +23,6 @@
 const char kKeyListDictKey[] = "keys";
 const char kKeyDirectiveDictKey[] = "key_directive";
 
-base::Optional<cryptauthv2::KeyDirective> KeyDirectiveFromPrefString(
-    const std::string& encoded_serialized_key_directive) {
-  std::string decoded_serialized_key_directive;
-  base::Base64Decode(encoded_serialized_key_directive,
-                     &decoded_serialized_key_directive);
-
-  cryptauthv2::KeyDirective key_directive;
-  if (!key_directive.ParseFromString(decoded_serialized_key_directive)) {
-    PA_LOG(ERROR) << "Error parsing KeyDirective from pref string";
-    return base::nullopt;
-  }
-
-  return key_directive;
-}
-
-std::string KeyDirectiveToPrefString(
-    const cryptauthv2::KeyDirective& key_directive) {
-  std::string encoded_serialized_key_directive;
-  base::Base64Encode(key_directive.SerializeAsString(),
-                     &encoded_serialized_key_directive);
-
-  return encoded_serialized_key_directive;
-}
-
 }  // namespace
 
 // static
@@ -139,11 +116,12 @@
     bundle.AddKey(*key);
   }
 
-  const std::string* encoded_serialized_key_directive =
-      dict.FindStringKey(kKeyDirectiveDictKey);
+  const base::Value* encoded_serialized_key_directive =
+      dict.FindKey(kKeyDirectiveDictKey);
   if (encoded_serialized_key_directive) {
     base::Optional<cryptauthv2::KeyDirective> key_directive =
-        KeyDirectiveFromPrefString(*encoded_serialized_key_directive);
+        util::DecodeProtoMessageFromValueString<cryptauthv2::KeyDirective>(
+            encoded_serialized_key_directive);
     if (!key_directive)
       return base::nullopt;
 
@@ -220,7 +198,7 @@
 
   if (key_directive_) {
     dict.SetKey(kKeyDirectiveDictKey,
-                base::Value(KeyDirectiveToPrefString(*key_directive_)));
+                util::EncodeProtoMessageAsValueString(&key_directive_.value()));
   }
 
   return dict;
diff --git a/chromeos/services/device_sync/cryptauth_key_unittest.cc b/chromeos/services/device_sync/cryptauth_key_unittest.cc
index c16fe4c1..8ef5473 100644
--- a/chromeos/services/device_sync/cryptauth_key_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_unittest.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "chromeos/services/device_sync/cryptauth_key.h"
+
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -13,15 +15,12 @@
 
 const char kFakeHandle[] = "fake-handle";
 const char kFakeSymmetricKey[] = "fake-symmetric-key";
-const char kFakeSymmetricKeyBase64[] = "ZmFrZS1zeW1tZXRyaWMta2V5";
-const char kFakeSymmetricKeySha256HashBase64[] =
-    "+lh4oqYTenQmzyIY8XJreGDJ95A4Sk41c15BQPKOmCY=";
+const char kFakeSymmetricKeySha256HashBase64Url[] =
+    "-lh4oqYTenQmzyIY8XJreGDJ95A4Sk41c15BQPKOmCY=";
 const char kFakePublicKey[] = "fake-public-key";
-const char kFakePublicKeyBase64[] = "ZmFrZS1wdWJsaWMta2V5";
-const char kFakePublicKeySha256HashBase64[] =
-    "vj5oRVhZmlDrE4G4RKNV37Etgr/XuNOwEFAzb888/KM=";
+const char kFakePublicKeySha256HashBase64Url[] =
+    "vj5oRVhZmlDrE4G4RKNV37Etgr_XuNOwEFAzb888_KM=";
 const char kFakePrivateKey[] = "fake-private-key";
-const char kFakePrivateKeyBase64[] = "ZmFrZS1wcml2YXRlLWtleQ==";
 
 }  // namespace
 
@@ -34,7 +33,7 @@
   EXPECT_EQ(key.symmetric_key(), kFakeSymmetricKey);
   EXPECT_EQ(key.status(), CryptAuthKey::Status::kActive);
   EXPECT_EQ(key.type(), cryptauthv2::KeyType::RAW256);
-  EXPECT_EQ(key.handle(), kFakeSymmetricKeySha256HashBase64);
+  EXPECT_EQ(key.handle(), kFakeSymmetricKeySha256HashBase64Url);
 
   CryptAuthKey key_given_handle(kFakeSymmetricKey,
                                 CryptAuthKey::Status::kActive,
@@ -52,7 +51,7 @@
   EXPECT_EQ(key.private_key(), kFakePrivateKey);
   EXPECT_EQ(key.status(), CryptAuthKey::Status::kActive);
   EXPECT_EQ(key.type(), cryptauthv2::KeyType::P256);
-  EXPECT_EQ(key.handle(), kFakePublicKeySha256HashBase64);
+  EXPECT_EQ(key.handle(), kFakePublicKeySha256HashBase64Url);
 
   CryptAuthKey key_given_handle(kFakePublicKey, kFakePrivateKey,
                                 CryptAuthKey::Status::kActive,
@@ -68,7 +67,7 @@
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
   dict.SetKey("type", base::Value(cryptauthv2::KeyType::RAW256));
-  dict.SetKey("symmetric_key", base::Value(kFakeSymmetricKeyBase64));
+  dict.SetKey("symmetric_key", util::EncodeAsValueString(kFakeSymmetricKey));
 
   EXPECT_EQ(symmetric_key.AsSymmetricKeyDictionary(), dict);
 }
@@ -82,8 +81,8 @@
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
   dict.SetKey("type", base::Value(cryptauthv2::KeyType::P256));
-  dict.SetKey("public_key", base::Value(kFakePublicKeyBase64));
-  dict.SetKey("private_key", base::Value(kFakePrivateKeyBase64));
+  dict.SetKey("public_key", util::EncodeAsValueString(kFakePublicKey));
+  dict.SetKey("private_key", util::EncodeAsValueString(kFakePrivateKey));
 
   EXPECT_EQ(asymmetric_key.AsAsymmetricKeyDictionary(), dict);
 }
@@ -93,7 +92,7 @@
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
   dict.SetKey("type", base::Value(cryptauthv2::KeyType::RAW256));
-  dict.SetKey("symmetric_key", base::Value(kFakeSymmetricKeyBase64));
+  dict.SetKey("symmetric_key", util::EncodeAsValueString(kFakeSymmetricKey));
 
   base::Optional<CryptAuthKey> key = CryptAuthKey::FromDictionary(dict);
   ASSERT_TRUE(key);
@@ -106,8 +105,8 @@
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
   dict.SetKey("type", base::Value(cryptauthv2::KeyType::P256));
-  dict.SetKey("public_key", base::Value(kFakePublicKeyBase64));
-  dict.SetKey("private_key", base::Value(kFakePrivateKeyBase64));
+  dict.SetKey("public_key", util::EncodeAsValueString(kFakePublicKey));
+  dict.SetKey("private_key", util::EncodeAsValueString(kFakePrivateKey));
 
   base::Optional<CryptAuthKey> key = CryptAuthKey::FromDictionary(dict);
   ASSERT_TRUE(key);
diff --git a/chromeos/services/device_sync/cryptauth_scheduler_impl.cc b/chromeos/services/device_sync/cryptauth_scheduler_impl.cc
index acc6474..7be82f58 100644
--- a/chromeos/services/device_sync/cryptauth_scheduler_impl.cc
+++ b/chromeos/services/device_sync/cryptauth_scheduler_impl.cc
@@ -14,6 +14,7 @@
 #include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/services/device_sync/pref_names.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
@@ -70,50 +71,20 @@
   return client_directive;
 }
 
-// Decodes and parses the base64-encoded serialized ClientDirective string.
-// TODO(https://crbug.com/964563): Replace when utility functions are added.
-base::Optional<cryptauthv2::ClientDirective> ClientDirectiveFromPrefString(
-    const std::string& encoded_serialized_client_directive) {
-  if (encoded_serialized_client_directive == kNoClientDirective)
-    return base::nullopt;
-
-  std::string decoded_serialized_client_directive;
-  if (!base::Base64Decode(encoded_serialized_client_directive,
-                          &decoded_serialized_client_directive)) {
-    PA_LOG(ERROR) << "Error decoding ClientDirective pref string";
-    return base::nullopt;
-  }
-
-  cryptauthv2::ClientDirective client_directive;
-  if (!client_directive.ParseFromString(decoded_serialized_client_directive)) {
-    PA_LOG(ERROR) << "Error parsing ClientDirective from pref string";
-    return base::nullopt;
-  }
-
-  return client_directive;
-}
-
-// Serializes and base64 encodes the input ClientDirective.
-// TODO(https://crbug.com/964563): Replace when utility functions are added.
-std::string ClientDirectiveToPrefString(
-    const cryptauthv2::ClientDirective& client_directive) {
-  std::string encoded_serialized_client_directive;
-  base::Base64Encode(client_directive.SerializeAsString(),
-                     &encoded_serialized_client_directive);
-
-  return encoded_serialized_client_directive;
-}
-
 cryptauthv2::ClientDirective BuildClientDirective(PrefService* pref_service) {
   DCHECK(pref_service);
+  const base::Value* encoded_client_directive =
+      pref_service->Get(prefs::kCryptAuthSchedulerClientDirective);
+  if (encoded_client_directive &&
+      encoded_client_directive->GetString() == kNoClientDirective) {
+    return CreateDefaultClientDirective();
+  }
 
   base::Optional<cryptauthv2::ClientDirective> client_directive_from_pref =
-      ClientDirectiveFromPrefString(
-          pref_service->GetString(prefs::kCryptAuthSchedulerClientDirective));
-  if (client_directive_from_pref)
-    return *client_directive_from_pref;
+      util::DecodeProtoMessageFromValueString<cryptauthv2::ClientDirective>(
+          encoded_client_directive);
 
-  return CreateDefaultClientDirective();
+  return client_directive_from_pref.value_or(CreateDefaultClientDirective());
 }
 
 cryptauthv2::ClientMetadata BuildClientMetadata(
@@ -129,38 +100,6 @@
   return client_metadata;
 }
 
-// TODO(https://crbug.com/964563): Replace when utility functions are added.
-base::Optional<cryptauthv2::ClientMetadata> ClientMetadataFromPrefString(
-    const std::string& encoded_serialized_client_metadata) {
-  if (encoded_serialized_client_metadata == kNoClientMetadata)
-    return base::nullopt;
-
-  std::string decoded_serialized_client_metadata;
-  if (!base::Base64Decode(encoded_serialized_client_metadata,
-                          &decoded_serialized_client_metadata)) {
-    PA_LOG(ERROR) << "Error decoding ClientMetadata pref string";
-    return base::nullopt;
-  }
-
-  cryptauthv2::ClientMetadata client_metadata;
-  if (!client_metadata.ParseFromString(decoded_serialized_client_metadata)) {
-    PA_LOG(ERROR) << "Error parsing ClientMetadata from pref string";
-    return base::nullopt;
-  }
-
-  return client_metadata;
-}
-
-// TODO(https://crbug.com/964563): Replace when utility functions are added.
-std::string ClientMetadataToPrefString(
-    const cryptauthv2::ClientMetadata& client_metadata) {
-  std::string encoded_serialized_client_metadata;
-  base::Base64Encode(client_metadata.SerializeAsString(),
-                     &encoded_serialized_client_metadata);
-
-  return encoded_serialized_client_metadata;
-}
-
 }  // namespace
 
 // static
@@ -223,9 +162,14 @@
   DCHECK(IsClientDirectiveValid(client_directive_));
 
   // Queue up the most recently scheduled enrollment request if applicable.
-  pending_enrollment_request_ =
-      ClientMetadataFromPrefString(pref_service_->GetString(
-          prefs::kCryptAuthSchedulerNextEnrollmentRequestClientMetadata));
+  const base::Value* client_metadata_from_pref = pref_service_->Get(
+      prefs::kCryptAuthSchedulerNextEnrollmentRequestClientMetadata);
+  if (client_metadata_from_pref &&
+      client_metadata_from_pref->GetString() != kNoClientMetadata) {
+    pending_enrollment_request_ =
+        util::DecodeProtoMessageFromValueString<cryptauthv2::ClientMetadata>(
+            client_metadata_from_pref);
+  }
 
   // If we are recovering from a failure, reset the failure count to 1 in the
   // hopes that the restart solved the issue. This will allow for immediate
@@ -283,8 +227,9 @@
       IsClientDirectiveValid(*enrollment_result.client_directive())) {
     client_directive_ = *enrollment_result.client_directive();
 
-    pref_service_->SetString(prefs::kCryptAuthSchedulerClientDirective,
-                             ClientDirectiveToPrefString(client_directive_));
+    pref_service_->Set(
+        prefs::kCryptAuthSchedulerClientDirective,
+        util::EncodeProtoMessageAsValueString(&client_directive_));
   }
 
   current_enrollment_request_.reset();
@@ -410,9 +355,10 @@
   }
 
   DCHECK(pending_enrollment_request_);
-  pref_service_->SetString(
+  pref_service_->Set(
       prefs::kCryptAuthSchedulerNextEnrollmentRequestClientMetadata,
-      ClientMetadataToPrefString(*pending_enrollment_request_));
+      util::EncodeProtoMessageAsValueString(
+          &pending_enrollment_request_.value()));
 
   if (!HasEnrollmentSchedulingStarted())
     return;
diff --git a/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc
index aa6e112..e14e551 100644
--- a/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc
@@ -19,6 +19,7 @@
 #include "chromeos/services/device_sync/fake_cryptauth_scheduler.h"
 #include "chromeos/services/device_sync/pref_names.h"
 #include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
@@ -37,28 +38,6 @@
 const char kWifiServiceGuid[] = "wifiGuid";
 const char kSessionId[] = "sessionId";
 
-// Serializes and base64 encodes the input ClientDirective.
-// Copied from cryptauth_enrollment_scheduler_impl.cc.
-// TODO(https://crbug.com/964563): Replace when utility functions are added.
-std::string ClientDirectiveToPrefString(
-    const cryptauthv2::ClientDirective& client_directive) {
-  std::string encoded_serialized_client_directive;
-  base::Base64Encode(client_directive.SerializeAsString(),
-                     &encoded_serialized_client_directive);
-
-  return encoded_serialized_client_directive;
-}
-
-// TODO(https://crbug.com/964563): Replace when utility functions are added.
-std::string ClientMetadataToPrefString(
-    const cryptauthv2::ClientMetadata& client_metadata) {
-  std::string encoded_serialized_client_metadata;
-  base::Base64Encode(client_metadata.SerializeAsString(),
-                     &encoded_serialized_client_metadata);
-
-  return encoded_serialized_client_metadata;
-}
-
 }  // namespace
 
 class DeviceSyncCryptAuthSchedulerImplTest : public testing::Test {
@@ -80,15 +59,16 @@
       const base::Optional<base::Time>&
           persisted_last_successful_enrollment_time) {
     if (persisted_client_directive) {
-      pref_service_.SetString(
-          prefs::kCryptAuthSchedulerClientDirective,
-          ClientDirectiveToPrefString(*persisted_client_directive));
+      pref_service_.Set(prefs::kCryptAuthSchedulerClientDirective,
+                        util::EncodeProtoMessageAsValueString(
+                            &persisted_client_directive.value()));
     }
 
     if (persisted_client_metadata) {
-      pref_service_.SetString(
+      pref_service_.Set(
           prefs::kCryptAuthSchedulerNextEnrollmentRequestClientMetadata,
-          ClientMetadataToPrefString(*persisted_client_metadata));
+          util::EncodeProtoMessageAsValueString(
+              &persisted_client_metadata.value()));
     }
 
     if (persisted_last_enrollment_attempt_time) {
@@ -226,9 +206,8 @@
 
   void VerifyClientDirective(
       const cryptauthv2::ClientDirective& expected_client_directive) {
-    EXPECT_EQ(
-        ClientDirectiveToPrefString(expected_client_directive),
-        pref_service_.GetString(prefs::kCryptAuthSchedulerClientDirective));
+    EXPECT_EQ(util::EncodeProtoMessageAsValueString(&expected_client_directive),
+              *pref_service_.Get(prefs::kCryptAuthSchedulerClientDirective));
     EXPECT_EQ(base::TimeDelta::FromMilliseconds(
                   expected_client_directive.checkin_delay_millis()),
               scheduler()->GetRefreshPeriod());
@@ -270,8 +249,8 @@
   void VerifyNextEnrollmentRequest(
       const cryptauthv2::ClientMetadata& expected_enrollment_request) {
     EXPECT_EQ(
-        ClientMetadataToPrefString(expected_enrollment_request),
-        pref_service_.GetString(
+        util::EncodeProtoMessageAsValueString(&expected_enrollment_request),
+        *pref_service_.Get(
             prefs::kCryptAuthSchedulerNextEnrollmentRequestClientMetadata));
   }
 
diff --git a/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl.cc b/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl.cc
index 7471a04..0c28686 100644
--- a/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl.cc
+++ b/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl.cc
@@ -6,19 +6,20 @@
 
 #include <utility>
 
-#include "base/base64url.h"
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/no_destructor.h"
 #include "base/time/clock.h"
 #include "base/timer/timer.h"
+#include "base/values.h"
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/services/device_sync/cryptauth_constants.h"
 #include "chromeos/services/device_sync/cryptauth_key_registry.h"
 #include "chromeos/services/device_sync/cryptauth_v2_enroller_impl.h"
 #include "chromeos/services/device_sync/pref_names.h"
 #include "chromeos/services/device_sync/public/cpp/client_app_metadata_provider.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
@@ -466,25 +467,25 @@
 }
 
 std::string CryptAuthV2EnrollmentManagerImpl::GetV1UserPublicKey() const {
-  std::string public_key;
-  if (!base::Base64UrlDecode(
-          pref_service_->GetString(prefs::kCryptAuthEnrollmentUserPublicKey),
-          base::Base64UrlDecodePolicy::REQUIRE_PADDING, &public_key)) {
+  base::Optional<std::string> public_key = util::DecodeFromValueString(
+      pref_service_->Get(prefs::kCryptAuthEnrollmentUserPublicKey));
+  if (!public_key) {
     PA_LOG(ERROR) << "Invalid public key stored in user prefs.";
     return std::string();
   }
-  return public_key;
+
+  return *public_key;
 }
 
 std::string CryptAuthV2EnrollmentManagerImpl::GetV1UserPrivateKey() const {
-  std::string private_key;
-  if (!base::Base64UrlDecode(
-          pref_service_->GetString(prefs::kCryptAuthEnrollmentUserPrivateKey),
-          base::Base64UrlDecodePolicy::REQUIRE_PADDING, &private_key)) {
+  base::Optional<std::string> private_key = util::DecodeFromValueString(
+      pref_service_->Get(prefs::kCryptAuthEnrollmentUserPrivateKey));
+  if (!private_key) {
     PA_LOG(ERROR) << "Invalid private key stored in user prefs.";
     return std::string();
   }
-  return private_key;
+
+  return *private_key;
 }
 
 void CryptAuthV2EnrollmentManagerImpl::AddV1UserKeyPairToRegistryIfNecessary() {
@@ -493,7 +494,7 @@
   const CryptAuthKey* key_v2 =
       key_registry_->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair);
 
-  RecordUserKeyPairState(public_key_v1, public_key_v1, key_v2);
+  RecordUserKeyPairState(public_key_v1, private_key_v1, key_v2);
 
   // If the v1 user key pair does not exist, no action is needed.
   if (public_key_v1.empty() || private_key_v1.empty())
diff --git a/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl_unittest.cc
index 3bb8de8d..f548ff3 100644
--- a/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_v2_enrollment_manager_impl_unittest.cc
@@ -8,7 +8,6 @@
 #include <unordered_map>
 #include <utility>
 
-#include "base/base64url.h"
 #include "base/no_destructor.h"
 #include "base/optional.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -35,6 +34,7 @@
 #include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h"
 #include "chromeos/services/device_sync/public/cpp/fake_client_app_metadata_provider.h"
 #include "chromeos/services/device_sync/public/cpp/gcm_constants.h"
+#include "chromeos/services/device_sync/value_string_encoding.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -155,18 +155,10 @@
 
   void AddV1UserKeyPairToV1Prefs(const std::string& public_key,
                                  const std::string& private_key) {
-    std::string public_key_b64, private_key_b64;
-    base::Base64UrlEncode(public_key,
-                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
-                          &public_key_b64);
-    base::Base64UrlEncode(private_key,
-                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
-                          &private_key_b64);
-
-    test_pref_service_.SetString(prefs::kCryptAuthEnrollmentUserPublicKey,
-                                 public_key_b64);
-    test_pref_service_.SetString(prefs::kCryptAuthEnrollmentUserPrivateKey,
-                                 private_key_b64);
+    test_pref_service_.Set(prefs::kCryptAuthEnrollmentUserPublicKey,
+                           util::EncodeAsValueString(public_key));
+    test_pref_service_.Set(prefs::kCryptAuthEnrollmentUserPrivateKey,
+                           util::EncodeAsValueString(private_key));
   }
 
   void CreateEnrollmentManager() {
@@ -302,6 +294,8 @@
 
   base::MockOneShotTimer* mock_timer() { return mock_timer_; }
 
+  const base::HistogramTester* histogram_tester() { return &histogram_tester_; }
+
   CryptAuthEnrollmentManager* enrollment_manager() {
     return enrollment_manager_.get();
   }
@@ -683,6 +677,9 @@
       key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair));
 
   CreateEnrollmentManager();
+  histogram_tester()->ExpectBucketCount(
+      "CryptAuth.EnrollmentV2.UserKeyPairState",
+      1 /* UserKeyPairState::kYesV1KeyNoV2Key */, 1 /* count */);
 
   EXPECT_EQ(
       expected_user_key_pair_v1,
@@ -712,6 +709,9 @@
   // A legacy v1 user key pair should overwrite any existing v2 user key pair
   // when the enrollment manager is constructed.
   CreateEnrollmentManager();
+  histogram_tester()->ExpectBucketCount(
+      "CryptAuth.EnrollmentV2.UserKeyPairState",
+      4 /* UserKeyPairState::kYesV1KeyYesV2KeyDisagree */, 1 /* count */);
 
   EXPECT_EQ(
       expected_user_key_pair_v1,
@@ -722,8 +722,54 @@
             enrollment_manager()->GetUserPrivateKey());
 }
 
+TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest,
+       V1AndV2UserKeyPairsAgree) {
+  AddV1UserKeyPairToV1Prefs(kFakeV1PublicKey, kFakeV1PrivateKey);
+  CryptAuthKey user_key_pair_v1(
+      kFakeV1PublicKey, kFakeV1PrivateKey, CryptAuthKey::Status::kActive,
+      cryptauthv2::KeyType::P256, kCryptAuthFixedUserKeyPairHandle);
+
+  key_registry()->AddKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+                         user_key_pair_v1);
+
+  CreateEnrollmentManager();
+  histogram_tester()->ExpectBucketCount(
+      "CryptAuth.EnrollmentV2.UserKeyPairState",
+      3 /* UserKeyPairState::kYesV1KeyYesV2KeyAgree */, 1 /* count */);
+
+  EXPECT_EQ(user_key_pair_v1, *key_registry()->GetActiveKey(
+                                  CryptAuthKeyBundle::Name::kUserKeyPair));
+  EXPECT_EQ(user_key_pair_v1.public_key(),
+            enrollment_manager()->GetUserPublicKey());
+  EXPECT_EQ(user_key_pair_v1.private_key(),
+            enrollment_manager()->GetUserPrivateKey());
+}
+
+TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, V2KeyButNoV1Key) {
+  CryptAuthKey user_key_pair_v2(
+      kFakeV2PublicKey, kFakeV2PrivateKey, CryptAuthKey::Status::kActive,
+      cryptauthv2::KeyType::P256, kCryptAuthFixedUserKeyPairHandle);
+
+  key_registry()->AddKey(CryptAuthKeyBundle::Name::kUserKeyPair,
+                         user_key_pair_v2);
+
+  CreateEnrollmentManager();
+  histogram_tester()->ExpectBucketCount(
+      "CryptAuth.EnrollmentV2.UserKeyPairState",
+      2 /* UserKeyPairState::kNoV1KeyYesV2Key */, 1 /* count */);
+
+  EXPECT_EQ(user_key_pair_v2, *key_registry()->GetActiveKey(
+                                  CryptAuthKeyBundle::Name::kUserKeyPair));
+  EXPECT_EQ(kFakeV2PublicKey, enrollment_manager()->GetUserPublicKey());
+  EXPECT_EQ(kFakeV2PrivateKey, enrollment_manager()->GetUserPrivateKey());
+}
+
 TEST_F(DeviceSyncCryptAuthV2EnrollmentManagerImplTest, GetUserKeyPair) {
   CreateEnrollmentManager();
+  histogram_tester()->ExpectBucketCount(
+      "CryptAuth.EnrollmentV2.UserKeyPairState",
+      0 /* UserKeyPairState::kNoV1KeyNoV2Key */, 1 /* count */);
+
   EXPECT_TRUE(enrollment_manager()->GetUserPublicKey().empty());
   EXPECT_TRUE(enrollment_manager()->GetUserPrivateKey().empty());
 
diff --git a/chromeos/services/device_sync/pref_names.cc b/chromeos/services/device_sync/pref_names.cc
index f2a5a57..b4c56f0a 100644
--- a/chromeos/services/device_sync/pref_names.cc
+++ b/chromeos/services/device_sync/pref_names.cc
@@ -64,8 +64,8 @@
 // populate and persist the CryptAuthKeyRegistry.
 const char kCryptAuthKeyRegistry[] = "cryptauth.key_registry";
 
-// (CryptAuth v2) The most recent ClientDirective--serialized to a string and
-// base64 encoded--sent to the CryptAuthScheduler.
+// (CryptAuth v2) The most recent ClientDirective sent to the
+// CryptAuthScheduler.
 const char kCryptAuthSchedulerClientDirective[] =
     "cryptauth.scheduler.client_directive";
 
diff --git a/chromeos/services/device_sync/proto/cryptauth_v2_test_util.cc b/chromeos/services/device_sync/proto/cryptauth_v2_test_util.cc
index c516cd5a..7133bd38 100644
--- a/chromeos/services/device_sync/proto/cryptauth_v2_test_util.cc
+++ b/chromeos/services/device_sync/proto/cryptauth_v2_test_util.cc
@@ -5,6 +5,7 @@
 #include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h"
 
 #include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
 #include "chromeos/services/device_sync/public/cpp/gcm_constants.h"
 
 namespace cryptauthv2 {
@@ -14,6 +15,8 @@
 const char kTestInstanceId[] = "instance_id";
 const char kTestInstanceIdToken[] = "instance_id_token";
 const char kTestLongDeviceId[] = "long_device_id";
+const char kTestNoPiiDeviceName[] = "no_pii_device_name";
+const char kTestUserPublicKey[] = "user_public_key";
 
 // Attributes of test ClientDirective.
 const int32_t kTestClientDirectiveRetryAttempts = 3;
@@ -88,6 +91,17 @@
   return device_feature_status;
 }
 
+BeaconSeed BuildBeaconSeedForTest(int64_t start_time_millis,
+                                  int64_t end_time_millis) {
+  BeaconSeed seed;
+  seed.set_data("start_" + base::NumberToString(start_time_millis) + "_end_" +
+                base::NumberToString(end_time_millis));
+  seed.set_start_time_millis(start_time_millis);
+  seed.set_end_time_millis(end_time_millis);
+
+  return seed;
+}
+
 const ClientAppMetadata& GetClientAppMetadataForTest() {
   static const base::NoDestructor<ClientAppMetadata> metadata([] {
     ApplicationSpecificMetadata app_specific_metadata;
@@ -153,4 +167,20 @@
   return *request_context;
 }
 
+const BetterTogetherDeviceMetadata& GetBetterTogetherDeviceMetadataForTest() {
+  static const base::NoDestructor<BetterTogetherDeviceMetadata>
+      better_together_device_metadata([] {
+        BetterTogetherDeviceMetadata metadata;
+        metadata.set_public_key(kTestUserPublicKey);
+        metadata.set_no_pii_device_name(kTestNoPiiDeviceName);
+        metadata.add_beacon_seeds()->CopyFrom(BuildBeaconSeedForTest(
+            100 /* start_time_millis */, 200 /* end_time_millis */));
+        metadata.add_beacon_seeds()->CopyFrom(BuildBeaconSeedForTest(
+            200 /* start_time_millis */, 300 /* end_time_millis */));
+
+        return metadata;
+      }());
+  return *better_together_device_metadata;
+}
+
 }  // namespace cryptauthv2
diff --git a/chromeos/services/device_sync/proto/cryptauth_v2_test_util.h b/chromeos/services/device_sync/proto/cryptauth_v2_test_util.h
index 47ddf8b..f15d355 100644
--- a/chromeos/services/device_sync/proto/cryptauth_v2_test_util.h
+++ b/chromeos/services/device_sync/proto/cryptauth_v2_test_util.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/optional.h"
+#include "chromeos/services/device_sync/proto/cryptauth_better_together_device_metadata.pb.h"
 #include "chromeos/services/device_sync/proto/cryptauth_better_together_feature_metadata.pb.h"
 #include "chromeos/services/device_sync/proto/cryptauth_client_app_metadata.pb.h"
 #include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
@@ -23,6 +24,8 @@
 extern const char kTestInstanceId[];
 extern const char kTestInstanceIdToken[];
 extern const char kTestLongDeviceId[];
+extern const char kTestNoPiiDeviceName[];
+extern const char kTestUserPublicKey[];
 
 // Attributes of test ClientDirective.
 extern const int32_t kTestClientDirectiveRetryAttempts;
@@ -54,10 +57,18 @@
     const std::vector<std::pair<std::string /* feature_type */,
                                 bool /* enabled */>>& feature_statuses);
 
+// The data field is set to "start_|start_time_millis|_end_|end_time_millis|".
+BeaconSeed BuildBeaconSeedForTest(int64_t start_time_millis,
+                                  int64_t end_time_millis);
+
 const ClientAppMetadata& GetClientAppMetadataForTest();
+
 const ClientDirective& GetClientDirectiveForTest();
+
 const RequestContext& GetRequestContextForTest();
 
+const BetterTogetherDeviceMetadata& GetBetterTogetherDeviceMetadataForTest();
+
 }  // namespace cryptauthv2
 
 #endif  // CHROMEOS_SERVICES_DEVICE_SYNC_PROTO_CRYPTAUTH_V2_TEST_UTIL_H_
diff --git a/chromeos/services/device_sync/remote_device_loader.cc b/chromeos/services/device_sync/remote_device_loader.cc
index b4c98ad..00167b8 100644
--- a/chromeos/services/device_sync/remote_device_loader.cc
+++ b/chromeos/services/device_sync/remote_device_loader.cc
@@ -7,7 +7,6 @@
 #include <algorithm>
 #include <utility>
 
-#include "base/base64url.h"
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "chromeos/components/multidevice/logging/logging.h"
diff --git a/chromeos/services/device_sync/value_string_encoding.cc b/chromeos/services/device_sync/value_string_encoding.cc
new file mode 100644
index 0000000..78fdf04f
--- /dev/null
+++ b/chromeos/services/device_sync/value_string_encoding.cc
@@ -0,0 +1,51 @@
+// 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 "chromeos/services/device_sync/value_string_encoding.h"
+
+#include "base/base64url.h"
+#include "chromeos/components/multidevice/logging/logging.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+namespace util {
+
+base::Value EncodeAsValueString(const std::string& unencoded_string) {
+  std::string encoded_string;
+  base::Base64UrlEncode(unencoded_string,
+                        base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+                        &encoded_string);
+
+  return base::Value(encoded_string);
+}
+
+base::Optional<std::string> DecodeFromValueString(
+    const base::Value* encoded_value_string) {
+  if (!encoded_value_string || !encoded_value_string->is_string())
+    return base::nullopt;
+
+  std::string decoded_string;
+  if (!base::Base64UrlDecode(encoded_value_string->GetString(),
+                             base::Base64UrlDecodePolicy::REQUIRE_PADDING,
+                             &decoded_string)) {
+    return base::nullopt;
+  }
+
+  return decoded_string;
+}
+
+base::Value EncodeProtoMessageAsValueString(
+    const ::google::protobuf::MessageLite* unencoded_message) {
+  DCHECK(unencoded_message);
+
+  return EncodeAsValueString(unencoded_message->SerializeAsString());
+}
+
+}  // namespace util
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/value_string_encoding.h b/chromeos/services/device_sync/value_string_encoding.h
new file mode 100644
index 0000000..c9f753a
--- /dev/null
+++ b/chromeos/services/device_sync/value_string_encoding.h
@@ -0,0 +1,63 @@
+// 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 CHROMEOS_SERVICES_DEVICE_SYNC_VALUE_STRING_ENCODING_H_
+#define CHROMEOS_SERVICES_DEVICE_SYNC_VALUE_STRING_ENCODING_H_
+
+#include <string>
+
+#include "base/optional.h"
+#include "base/values.h"
+#include "third_party/protobuf/src/google/protobuf/message_lite.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+namespace util {
+
+// NOTE: Do not change the encoding scheme because some output values are
+// persisted as preferences.
+
+// Converts input string to Base64Url-encoded base::Value string. This is
+// particularly useful when storing byte strings as preferences because
+// base::Value strings must be valid UTF-8 strings.
+base::Value EncodeAsValueString(const std::string& unencoded_string);
+
+// Inverse operation to EncodeAsValueString(). Returns null if
+// |encoded_value_string| is null or cannot be decoded.
+base::Optional<std::string> DecodeFromValueString(
+    const base::Value* encoded_value_string);
+
+// Serializes input proto message to Base64Url-encoded base::Value string.
+// |unencoded_message| cannot be null.
+base::Value EncodeProtoMessageAsValueString(
+    const ::google::protobuf::MessageLite* unencoded_message);
+
+// Inverse operation to EncodeProtoMessageAsValueString(). The template class T
+// must be a child of ::google::protobuf::MessageLite. Returns null if
+// |encoded_value_string| is null, cannot be decoded, or proto message T cannot
+// be parsed from the decoded string.
+template <class T>
+base::Optional<T> DecodeProtoMessageFromValueString(
+    const base::Value* encoded_value_string) {
+  base::Optional<std::string> decoded_string =
+      DecodeFromValueString(encoded_value_string);
+  if (!decoded_string)
+    return base::nullopt;
+
+  T decoded_message;
+  if (!decoded_message.ParseFromString(*decoded_string))
+    return base::nullopt;
+
+  return decoded_message;
+}
+
+}  // namespace util
+
+}  // namespace device_sync
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_DEVICE_SYNC_VALUE_STRING_ENCODING_H_
diff --git a/components/BUILD.gn b/components/BUILD.gn
index ae2242b..188bfd3f 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -208,8 +208,8 @@
     deps += [
       "//components/app_modal:unit_tests",
       "//components/autofill/content/browser:unit_tests",
-      "//components/autofill/content/common:unit_tests",
       "//components/autofill/content/renderer:unit_tests",
+      "//components/autofill/core/common/mojom:unit_tests",
       "//components/cast_certificate:unit_tests",
       "//components/cast_channel:unit_tests",
       "//components/certificate_transparency:unit_tests",
diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc
index 48bc282..b5d6c78 100644
--- a/components/autofill/content/browser/content_autofill_driver.cc
+++ b/components/autofill/content/browser/content_autofill_driver.cc
@@ -165,7 +165,7 @@
 void ContentAutofillDriver::PopupHidden() {
   // If the unmask prompt is showing, keep showing the preview. The preview
   // will be cleared when the prompt closes.
-  if (autofill_manager_ && !autofill_manager_->IsShowingUnmaskPrompt())
+  if (autofill_manager_ && autofill_manager_->ShouldClearPreviewedForm())
     RendererShouldClearPreviewedForm();
 }
 
diff --git a/components/autofill/content/common/BUILD.gn b/components/autofill/content/common/BUILD.gn
index 3fb742d..c4ad7b9 100644
--- a/components/autofill/content/common/BUILD.gn
+++ b/components/autofill/content/common/BUILD.gn
@@ -11,52 +11,9 @@
   ]
 
   public_deps = [
-    ":mojo_types",
+    "//components/autofill/core/common/mojom:mojo_types",
     "//mojo/public/mojom/base",
     "//ui/gfx/geometry/mojo",
     "//url/mojom:url_mojom_gurl",
   ]
 }
-
-mojom("mojo_types") {
-  sources = [
-    "autofill_types.mojom",
-  ]
-
-  public_deps = [
-    "//mojo/public/mojom/base",
-    "//ui/gfx/geometry/mojo",
-    "//url/mojom:url_mojom_gurl",
-    "//url/mojom:url_mojom_origin",
-  ]
-}
-
-mojom("mojo_test_types") {
-  sources = [
-    "test_autofill_types.mojom",
-  ]
-
-  public_deps = [
-    ":mojo_types",
-  ]
-}
-
-source_set("unit_tests") {
-  testonly = true
-  sources = [
-    "autofill_types_struct_traits_unittest.cc",
-  ]
-
-  public_deps = [
-    ":mojo_test_types",
-  ]
-
-  deps = [
-    "//base",
-    "//base/test:test_support",
-    "//components/autofill/core/browser:test_support",
-    "//components/password_manager/core/common",
-    "//mojo/public/cpp/bindings",
-    "//testing/gtest",
-  ]
-}
diff --git a/components/autofill/content/common/autofill_agent.mojom b/components/autofill/content/common/autofill_agent.mojom
index 6138f4b..391bac0 100644
--- a/components/autofill/content/common/autofill_agent.mojom
+++ b/components/autofill/content/common/autofill_agent.mojom
@@ -4,7 +4,7 @@
 
 module autofill.mojom;
 
-import "components/autofill/content/common/autofill_types.mojom";
+import "components/autofill/core/common/mojom/autofill_types.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 
 // There is one instance of this interface per render frame in the render
diff --git a/components/autofill/content/common/autofill_driver.mojom b/components/autofill/content/common/autofill_driver.mojom
index 765affb..3274079 100644
--- a/components/autofill/content/common/autofill_driver.mojom
+++ b/components/autofill/content/common/autofill_driver.mojom
@@ -4,7 +4,7 @@
 
 module autofill.mojom;
 
-import "components/autofill/content/common/autofill_types.mojom";
+import "components/autofill/core/common/mojom/autofill_types.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 import "mojo/public/mojom/base/text_direction.mojom";
 import "mojo/public/mojom/base/time.mojom";
diff --git a/components/autofill/content/common/test_autofill_types.mojom b/components/autofill/content/common/test_autofill_types.mojom
deleted file mode 100644
index c24e36bc..0000000
--- a/components/autofill/content/common/test_autofill_types.mojom
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module autofill.mojom;
-
-import "components/autofill/content/common/autofill_types.mojom";
-
-interface TypeTraitsTest {
-  PassFormData(FormData s) => (FormData passed);
-  PassFormFieldData(FormFieldData s) => (FormFieldData passed);
-  PassFormDataPredictions(FormDataPredictions s) => (FormDataPredictions passed);
-  PassFormFieldDataPredictions(FormFieldDataPredictions s) => (FormFieldDataPredictions passed);
-  PassPasswordForm(PasswordForm s) => (PasswordForm passed);
-  PassPasswordFormFillData(PasswordFormFillData s) => (PasswordFormFillData passed);
-  PassFormsPredictionsMap(FormsPredictionsMap s) => (FormsPredictionsMap passed);
-  PassPasswordFormGenerationData(PasswordFormGenerationData s) => (PasswordFormGenerationData passed);
-  PassNewPasswordFormGenerationData(NewPasswordFormGenerationData s) => (NewPasswordFormGenerationData passed);
-  PassPasswordGenerationUIData(PasswordGenerationUIData s) => (PasswordGenerationUIData passed);
-};
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index c7af09b..4d8479c 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -23,7 +23,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
-#include "components/autofill/content/common/autofill_types.mojom.h"
 #include "components/autofill/content/renderer/form_autofill_util.h"
 #include "components/autofill/content/renderer/password_form_conversion_utils.h"
 #include "components/autofill/content/renderer/password_generation_agent.h"
@@ -32,6 +31,7 @@
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/autofill_util.h"
 #include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/core/common/mojom/autofill_types.mojom.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
 #include "content/public/renderer/document_state.h"
 #include "content/public/renderer/render_frame.h"
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 6b29ee3..375d439 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -146,14 +146,16 @@
     "payments/autofill_wallet_model_type_controller.h",
     "payments/card_unmask_delegate.cc",
     "payments/card_unmask_delegate.h",
+    "payments/credit_card_access_manager.cc",
+    "payments/credit_card_access_manager.h",
+    "payments/credit_card_cvc_authenticator.cc",
+    "payments/credit_card_cvc_authenticator.h",
     "payments/credit_card_save_manager.cc",
     "payments/credit_card_save_manager.h",
     "payments/credit_card_save_strike_database.cc",
     "payments/credit_card_save_strike_database.h",
     "payments/full_card_request.cc",
     "payments/full_card_request.h",
-    "payments/legacy_strike_database.cc",
-    "payments/legacy_strike_database.h",
     "payments/legal_message_line.cc",
     "payments/legal_message_line.h",
     "payments/local_card_migration_manager.cc",
@@ -379,12 +381,12 @@
     "geo/test_region_data_loader.h",
     "mock_autocomplete_history_manager.cc",
     "mock_autocomplete_history_manager.h",
+    "payments/test_authentication_requester.cc",
+    "payments/test_authentication_requester.h",
     "payments/test_credit_card_save_manager.cc",
     "payments/test_credit_card_save_manager.h",
     "payments/test_credit_card_save_strike_database.cc",
     "payments/test_credit_card_save_strike_database.h",
-    "payments/test_legacy_strike_database.cc",
-    "payments/test_legacy_strike_database.h",
     "payments/test_local_card_migration_manager.cc",
     "payments/test_local_card_migration_manager.h",
     "payments/test_payments_client.cc",
@@ -525,9 +527,10 @@
     "geo/phone_number_i18n_unittest.cc",
     "geo/subkey_requester_unittest.cc",
     "payments/autofill_wallet_data_type_controller_unittest.cc",
+    "payments/credit_card_access_manager_unittest.cc",
+    "payments/credit_card_cvc_authenticator_unittest.cc",
     "payments/credit_card_save_manager_unittest.cc",
     "payments/full_card_request_unittest.cc",
-    "payments/legacy_strike_database_unittest.cc",
     "payments/legal_message_line_unittest.cc",
     "payments/local_card_migration_manager_unittest.cc",
     "payments/payments_client_unittest.cc",
diff --git a/components/autofill/core/browser/autofill_assistant_unittest.cc b/components/autofill/core/browser/autofill_assistant_unittest.cc
index d0177b4..f29ec920 100644
--- a/components/autofill/core/browser/autofill_assistant_unittest.cc
+++ b/components/autofill/core/browser/autofill_assistant_unittest.cc
@@ -139,6 +139,15 @@
     return form_structure;
   }
 
+  // Convenience method to cast the FullCardRequest into a CardUnmaskDelegate.
+  CardUnmaskDelegate* full_card_unmask_delegate() {
+    payments::FullCardRequest* full_card_request =
+        autofill_manager_->credit_card_access_manager_->cvc_authenticator_
+            ->full_card_request_.get();
+    DCHECK(full_card_request);
+    return static_cast<CardUnmaskDelegate*>(full_card_request);
+  }
+
   base::test::ScopedTaskEnvironment task_environment_;
   TestAutofillClient autofill_client_;
   testing::NiceMock<TestAutofillDriver> autofill_driver_;
@@ -279,9 +288,7 @@
   EXPECT_CALL(*autofill_manager_, FillCreditCardForm(_, _, _, _, _)).Times(0);
 
   autofill_assistant_->ShowAssistForCreditCard(card);
-  static_cast<CardUnmaskDelegate*>(
-      autofill_manager_->GetOrCreateFullCardRequest())
-      ->OnUnmaskPromptClosed();
+  full_card_unmask_delegate()->OnUnmaskPromptClosed();
 }
 
 TEST_F(AutofillAssistantTest, ShowAssistForCreditCard_ValidCard_SubmitCvc) {
@@ -310,9 +317,7 @@
 
   CardUnmaskDelegate::UnmaskResponse unmask_response;
   unmask_response.cvc = base::ASCIIToUTF16("123");
-  static_cast<CardUnmaskDelegate*>(
-      autofill_manager_->GetOrCreateFullCardRequest())
-      ->OnUnmaskResponse(unmask_response);
+  full_card_unmask_delegate()->OnUnmaskResponse(unmask_response);
 }
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index a60e5f6..fe4feaf 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -57,7 +57,6 @@
 class CreditCard;
 class FormDataImporter;
 class FormStructure;
-class LegacyStrikeDatabase;
 class MigratableCreditCard;
 class PersonalDataManager;
 class StrikeDatabase;
@@ -215,10 +214,6 @@
   // Gets the payments::PaymentsClient instance owned by the client.
   virtual payments::PaymentsClient* GetPaymentsClient() = 0;
 
-  // Gets the LegacyStrikeDatabase associated with the client.
-  // TODO(crbug.com/884817): Delete this once v2 of StrikeDatabase is launched.
-  virtual LegacyStrikeDatabase* GetLegacyStrikeDatabase() = 0;
-
   // Gets the StrikeDatabase associated with the client.
   virtual StrikeDatabase* GetStrikeDatabase() = 0;
 
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index fd602b1..9659ae6 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -56,6 +56,7 @@
 #include "components/autofill/core/browser/metrics/address_form_event_logger.h"
 #include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
 #include "components/autofill/core/browser/metrics/form_events.h"
+#include "components/autofill/core/browser/payments/credit_card_access_manager.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/randomized_encoder.h"
@@ -344,15 +345,15 @@
 
   DCHECK_EQ(CREDIT_CARD, type.group());
 
-  bool is_all_server_suggestions;
+  bool should_display_gpay_logo;
   auto cards =
-      GetCreditCardSuggestions(field_data, type, &is_all_server_suggestions);
+      GetCreditCardSuggestions(field_data, type, &should_display_gpay_logo);
 
   DCHECK(!cards.empty());
 
   external_delegate_->OnSuggestionsReturned(
       query_id, cards,
-      /*autoselect_first_suggestion=*/false, is_all_server_suggestions);
+      /*autoselect_first_suggestion=*/false, should_display_gpay_logo);
 }
 
 bool AutofillManager::ShouldParseForms(const std::vector<FormData>& forms,
@@ -456,7 +457,7 @@
   }
 
   const std::vector<CreditCard*>& credit_cards =
-      personal_data_->GetCreditCards();
+      credit_card_access_manager_->GetCreditCards();
   if (profiles.empty() && credit_cards.empty())
     return false;
 
@@ -661,7 +662,7 @@
   autocomplete_history_manager_->CancelPendingQueries(this);
   external_delegate_->OnSuggestionsReturned(query_id, suggestions,
                                             autoselect_first_suggestion,
-                                            context.is_all_server_suggestions);
+                                            context.should_display_gpay_logo);
 }
 
 bool AutofillManager::WillFillCreditCardNumber(const FormData& form,
@@ -692,33 +693,40 @@
     int query_id,
     const FormData& form,
     const FormFieldData& field,
-    const CreditCard& credit_card) {
+    const CreditCard* credit_card) {
   FormStructure* form_structure = nullptr;
   AutofillField* autofill_field = nullptr;
   if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field))
     return;
-  if (action == AutofillDriver::FORM_DATA_ACTION_FILL) {
-    if (credit_card.record_type() == CreditCard::MASKED_SERVER_CARD &&
-        WillFillCreditCardNumber(form, field)) {
-      unmasking_query_id_ = query_id;
-      unmasking_form_ = form;
-      unmasking_field_ = field;
-      masked_card_ = credit_card;
-      payments::FullCardRequest* full_card_request =
-          CreateFullCardRequest(form_structure->form_parsed_timestamp());
-      full_card_request->GetFullCard(
-          masked_card_, AutofillClient::UNMASK_FOR_AUTOFILL,
-          weak_ptr_factory_.GetWeakPtr(), weak_ptr_factory_.GetWeakPtr());
-      credit_card_form_event_logger_->OnDidSelectMaskedServerCardSuggestion(
-          *form_structure, sync_state_);
-      return;
-    }
+
+  credit_card_ = credit_card ? *credit_card : CreditCard();
+  bool is_preview = action != AutofillDriver::FORM_DATA_ACTION_FILL;
+  bool should_fetch_card = !is_preview && WillFillCreditCardNumber(form, field);
+
+  if (should_fetch_card) {
+    credit_card_form_event_logger_->OnDidSelectCardSuggestion(
+        credit_card_, *form_structure, sync_state_);
+
+    credit_card_action_ = action;
+    credit_card_query_id_ = query_id;
+    credit_card_form_ = form;
+    credit_card_field_ = field;
+
+    // CreditCardAccessManager::FetchCreditCard() will call
+    // OnCreditCardFetched() in this class after successfully fetching the card.
+    credit_card_access_manager_->FetchCreditCard(
+        credit_card, weak_ptr_factory_.GetWeakPtr(),
+        form_structure->form_parsed_timestamp());
+    return;
+  }
+
+  if (!is_preview) {
     credit_card_form_event_logger_->OnDidFillSuggestion(
-        credit_card, *form_structure, *autofill_field, sync_state_);
+        credit_card_, *form_structure, *autofill_field, sync_state_);
   }
 
   FillOrPreviewDataModelForm(
-      action, query_id, form, field, credit_card, /*is_credit_card=*/true,
+      action, query_id, form, field, credit_card_, /*is_credit_card=*/true,
       /*cvc=*/base::string16(), form_structure, autofill_field);
 }
 
@@ -767,11 +775,12 @@
   if (!RefreshDataModels() || !driver()->RendererIsAvailable())
     return;
 
-  const CreditCard* credit_card = nullptr;
-  const AutofillProfile* profile = nullptr;
-  if (GetCreditCard(unique_id, &credit_card))
-    FillOrPreviewCreditCardForm(action, query_id, form, field, *credit_card);
-  else if (GetProfile(unique_id, &profile))
+  const AutofillProfile* profile = GetProfile(unique_id);
+  const CreditCard* credit_card = GetCreditCard(unique_id);
+
+  if (credit_card)
+    FillOrPreviewCreditCardForm(action, query_id, form, field, credit_card);
+  else if (profile)
     FillOrPreviewProfileForm(action, query_id, form, field, *profile);
 }
 
@@ -931,22 +940,15 @@
   if (identifier < 0)
     return false;
 
-  const CreditCard* credit_card = nullptr;
-  const AutofillProfile* profile = nullptr;
-  if (GetCreditCard(identifier, &credit_card)) {
-    if (credit_card->record_type() != CreditCard::LOCAL_CARD)
-      return false;
+  const CreditCard* credit_card = GetCreditCard(identifier);
+  const AutofillProfile* profile = GetProfile(identifier);
 
-    if (title)
-      title->assign(credit_card->NetworkOrBankNameAndLastFourDigits());
-    if (body) {
-      body->assign(l10n_util::GetStringUTF16(
-          IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY));
-    }
-
-    return true;
+  if (credit_card) {
+    return credit_card_access_manager_->GetDeletionConfirmationText(
+        credit_card, title, body);
   }
-  if (GetProfile(identifier, &profile)) {
+
+  if (profile) {
     if (profile->record_type() != AutofillProfile::LOCAL_PROFILE)
       return false;
 
@@ -970,26 +972,22 @@
 }
 
 bool AutofillManager::RemoveAutofillProfileOrCreditCard(int unique_id) {
-  std::string guid;
-  const CreditCard* credit_card = nullptr;
-  const AutofillProfile* profile = nullptr;
-  if (GetCreditCard(unique_id, &credit_card)) {
-    if (credit_card->record_type() != CreditCard::LOCAL_CARD)
-      return false;
-
-    guid = credit_card->guid();
-  } else if (GetProfile(unique_id, &profile)) {
-    if (profile->record_type() != AutofillProfile::LOCAL_PROFILE)
-      return false;
-
-    guid = profile->guid();
-  } else {
-    NOTREACHED();
-    return false;
+  const CreditCard* credit_card = GetCreditCard(unique_id);
+  if (credit_card) {
+    return credit_card_access_manager_->DeleteCard(credit_card);
   }
 
-  personal_data_->RemoveByGUID(guid);
-  return true;
+  const AutofillProfile* profile = GetProfile(unique_id);
+  if (profile) {
+    bool is_local = profile->record_type() == AutofillProfile::LOCAL_PROFILE;
+    if (is_local)
+      personal_data_->RemoveByGUID(profile->guid());
+
+    return is_local;
+  }
+
+  NOTREACHED();
+  return false;
 }
 
 void AutofillManager::RemoveAutocompleteEntry(const base::string16& name,
@@ -1001,24 +999,19 @@
   autocomplete_history_manager_->OnAutocompleteEntrySelected(value);
 }
 
-bool AutofillManager::IsShowingUnmaskPrompt() {
-  return full_card_request_ && full_card_request_->IsGettingFullCard();
+bool AutofillManager::ShouldClearPreviewedForm() {
+  return credit_card_access_manager_->ShouldClearPreviewedForm();
 }
 
 payments::FullCardRequest* AutofillManager::GetOrCreateFullCardRequest() {
-  if (!full_card_request_) {
-    full_card_request_.reset(new payments::FullCardRequest(
-        client_, client_->GetPaymentsClient(), personal_data_));
-  }
-  return full_card_request_.get();
+  return credit_card_access_manager_->credit_card_cvc_authenticator()
+      ->GetFullCardRequest();
 }
 
-payments::FullCardRequest* AutofillManager::CreateFullCardRequest(
-    const base::TimeTicks& form_parsed_timestamp) {
-  full_card_request_.reset(
-      new payments::FullCardRequest(client_, client_->GetPaymentsClient(),
-                                    personal_data_, form_parsed_timestamp));
-  return full_card_request_.get();
+base::WeakPtr<payments::FullCardRequest::UIDelegate>
+AutofillManager::GetAsFullCardRequestUIDelegate() {
+  return credit_card_access_manager_->credit_card_cvc_authenticator()
+      ->GetAsFullCardRequestUIDelegate();
 }
 
 void AutofillManager::SetTestDelegate(AutofillManagerTestDelegate* delegate) {
@@ -1098,42 +1091,29 @@
   driver()->SendAutofillTypePredictionsToRenderer(queried_forms);
 }
 
-void AutofillManager::OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
-                                      const std::string& real_pan) {
-  DCHECK(full_card_request_);
-  full_card_request_->OnDidGetRealPan(result, real_pan);
-}
+void AutofillManager::OnCreditCardFetched(bool did_succeed,
+                                          const CreditCard* credit_card,
+                                          const base::string16& cvc) {
+  if (!did_succeed) {
+    driver()->RendererShouldClearPreviewedForm();
+    return;
+  }
 
-void AutofillManager::OnFullCardRequestSucceeded(
-    const payments::FullCardRequest& full_card_request,
-    const CreditCard& card,
-    const base::string16& cvc) {
   FormStructure* form_structure = nullptr;
   AutofillField* autofill_field = nullptr;
-  if (!GetCachedFormAndField(unmasking_form_, unmasking_field_, &form_structure,
-                             &autofill_field))
+  if (!GetCachedFormAndField(credit_card_form_, credit_card_field_,
+                             &form_structure, &autofill_field))
     return;
+
+  // The originally selected masked card is |credit_card_|. So we must log
+  // |credit_card_| as opposed to |credit_card| to correctly indicate that the
+  // user filled the form using a masked card suggestion.
   credit_card_form_event_logger_->OnDidFillSuggestion(
-      masked_card_, *form_structure, *autofill_field, sync_state_);
-  FillCreditCardForm(unmasking_query_id_, unmasking_form_, unmasking_field_,
-                     card, cvc);
-  masked_card_ = CreditCard();
-}
+      credit_card_, *form_structure, *autofill_field, sync_state_);
 
-void AutofillManager::OnFullCardRequestFailed() {
-  driver()->RendererShouldClearPreviewedForm();
-}
-
-void AutofillManager::ShowUnmaskPrompt(
-    const CreditCard& card,
-    AutofillClient::UnmaskCardReason reason,
-    base::WeakPtr<CardUnmaskDelegate> delegate) {
-  client_->ShowUnmaskPrompt(card, reason, delegate);
-}
-
-void AutofillManager::OnUnmaskVerificationResult(
-    AutofillClient::PaymentsRpcResult result) {
-  client_->OnUnmaskVerificationResult(result);
+  DCHECK(credit_card);
+  FillCreditCardForm(credit_card_query_id_, credit_card_form_,
+                     credit_card_field_, *credit_card, cvc);
 }
 
 void AutofillManager::OnDidEndTextFieldEditing() {
@@ -1231,6 +1211,8 @@
   credit_card_form_event_logger_.reset(new CreditCardFormEventLogger(
       driver()->IsInMainFrame(), form_interactions_ukm_logger_.get(),
       personal_data_, client_));
+  credit_card_access_manager_.reset(new CreditCardAccessManager(
+      client_, personal_data_, credit_card_form_event_logger_.get()));
 #if defined(OS_ANDROID) || defined(OS_IOS)
   autofill_assistant_.Reset();
 #endif
@@ -1241,10 +1223,11 @@
   user_did_autofill_ = false;
   user_did_edit_autofilled_field_ = false;
   enable_ablation_logging_ = false;
-  masked_card_ = CreditCard();
-  unmasking_query_id_ = -1;
-  unmasking_form_ = FormData();
-  unmasking_field_ = FormFieldData();
+  credit_card_ = CreditCard();
+  credit_card_query_id_ = -1;
+  credit_card_form_ = FormData();
+  credit_card_field_ = FormFieldData();
+  credit_card_action_ = AutofillDriver::FORM_DATA_ACTION_PREVIEW;
   initial_interaction_timestamp_ = TimeTicks();
   external_delegate_->Reset();
   filling_contexts_map_.clear();
@@ -1276,6 +1259,10 @@
               form_interactions_ukm_logger_.get(),
               personal_data_,
               client_)),
+      credit_card_access_manager_(std::make_unique<CreditCardAccessManager>(
+          client_,
+          personal_data_,
+          credit_card_form_event_logger_.get())),
 #if defined(OS_ANDROID) || defined(OS_IOS)
       autofill_assistant_(this),
 #endif
@@ -1297,26 +1284,9 @@
 
   // No autofill data to return if the profiles are empty.
   const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles();
-  const std::vector<CreditCard*>& credit_cards =
-      personal_data_->GetCreditCards();
+  credit_card_access_manager_->UpdateCreditCardFormEventLogger();
 
-  // Updating the FormEventLoggers for addresses and credit cards.
-  {
-    size_t server_record_type_count = 0;
-    size_t local_record_type_count = 0;
-    for (CreditCard* credit_card : credit_cards) {
-      if (credit_card->record_type() == CreditCard::LOCAL_CARD)
-        local_record_type_count++;
-      else
-        server_record_type_count++;
-    }
-    credit_card_form_event_logger_->set_server_record_type_count(
-        server_record_type_count);
-    credit_card_form_event_logger_->set_local_record_type_count(
-        local_record_type_count);
-    credit_card_form_event_logger_->set_is_context_secure(
-        client_->IsContextSecure());
-  }
+  // Updating the FormEventLogger for addresses.
   {
     size_t server_record_type_count = 0;
     size_t local_record_type_count = 0;
@@ -1332,34 +1302,27 @@
         local_record_type_count);
   }
 
-  if (profiles.empty() && credit_cards.empty())
-    return false;
-
-  return true;
+  return !profiles.empty() ||
+         !credit_card_access_manager_->GetCreditCards().empty();
 }
 
-bool AutofillManager::GetProfile(int unique_id,
-                                 const AutofillProfile** profile) {
+CreditCard* AutofillManager::GetCreditCard(int unique_id) {
   // Unpack the |unique_id| into component parts.
   std::string credit_card_id;
   std::string profile_id;
   SplitFrontendID(unique_id, &credit_card_id, &profile_id);
-  *profile = nullptr;
+  return credit_card_access_manager_->GetCreditCard(credit_card_id);
+}
+
+AutofillProfile* AutofillManager::GetProfile(int unique_id) {
+  // Unpack the |unique_id| into component parts.
+  std::string credit_card_id;
+  std::string profile_id;
+  SplitFrontendID(unique_id, &credit_card_id, &profile_id);
+
   if (base::IsValidGUID(profile_id))
-    *profile = personal_data_->GetProfileByGUID(profile_id);
-  return !!*profile;
-}
-
-bool AutofillManager::GetCreditCard(int unique_id,
-                                    const CreditCard** credit_card) {
-  // Unpack the |unique_id| into component parts.
-  std::string credit_card_id;
-  std::string profile_id;
-  SplitFrontendID(unique_id, &credit_card_id, &profile_id);
-  *credit_card = nullptr;
-  if (base::IsValidGUID(credit_card_id))
-    *credit_card = personal_data_->GetCreditCardByGUID(credit_card_id);
-  return !!*credit_card;
+    return personal_data_->GetProfileByGUID(profile_id);
+  return nullptr;
 }
 
 void AutofillManager::FillOrPreviewDataModelForm(
@@ -1572,7 +1535,7 @@
 std::vector<Suggestion> AutofillManager::GetCreditCardSuggestions(
     const FormFieldData& field,
     const AutofillType& type,
-    bool* is_all_server_suggestions) const {
+    bool* should_display_gpay_logo) const {
   credit_card_form_event_logger_->OnDidPollSuggestions(field, sync_state_);
 
   // The field value is sanitized before attempting to match it to the user's
@@ -1581,22 +1544,8 @@
       personal_data_->GetCreditCardSuggestions(
           type, SanitizeCreditCardFieldValue(field.value),
           client_->AreServerCardsSupported());
-  const std::vector<CreditCard*> cards_to_suggest =
-      personal_data_->GetCreditCardsToSuggest(
-          client_->AreServerCardsSupported());
-  for (const CreditCard* credit_card : cards_to_suggest) {
-    if (!credit_card->bank_name().empty()) {
-      credit_card_form_event_logger_->SetBankNameAvailable();
-      break;
-    }
-  }
-
-  // Check if all the suggestions are server cards (come from Google Payments).
-  *is_all_server_suggestions = true;
-  for (const CreditCard* credit_card : cards_to_suggest) {
-    if (credit_card->record_type() == CreditCard::LOCAL_CARD)
-      *is_all_server_suggestions = false;
-  }
+  *should_display_gpay_logo =
+      credit_card_access_manager_->ShouldDisplayGPayLogo();
 
   for (size_t i = 0; i < suggestions.size(); i++) {
     suggestions[i].frontend_id =
@@ -1702,8 +1651,7 @@
   // class' FillCreditCardForm().
   if (autofill_assistant_.CanShowCreditCardAssist()) {
     const std::vector<CreditCard*> cards =
-        personal_data_->GetCreditCardsToSuggest(
-            client_->AreServerCardsSupported());
+        credit_card_access_manager_->GetCreditCardsToSuggest();
     // Expired cards are last in the sorted order, so if the first one is
     // expired, they all are.
     if (!cards.empty() && !cards.front()->IsExpired(AutofillClock::Now()))
@@ -2127,7 +2075,7 @@
   if (context->is_filling_credit_card) {
     *suggestions =
         GetCreditCardSuggestions(field, context->focused_field->Type(),
-                                 &context->is_all_server_suggestions);
+                                 &context->should_display_gpay_logo);
 
     // Logic for disabling/ablating credit card autofill.
     if (base::FeatureList::IsEnabled(
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index 0385673..1351862 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -31,6 +31,7 @@
 #include "components/autofill/core/browser/metrics/address_form_event_logger.h"
 #include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
 #include "components/autofill/core/browser/payments/card_unmask_delegate.h"
+#include "components/autofill/core/browser/payments/credit_card_access_manager.h"
 #include "components/autofill/core/browser/payments/full_card_request.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/sync_utils.h"
@@ -69,9 +70,8 @@
 // forms. One per frame; owned by the AutofillDriver.
 class AutofillManager : public AutofillHandler,
                         public AutofillDownloadManager::Observer,
-                        public payments::FullCardRequest::ResultDelegate,
-                        public payments::FullCardRequest::UIDelegate,
-                        public AutocompleteHistoryManager::SuggestionsHandler {
+                        public AutocompleteHistoryManager::SuggestionsHandler,
+                        public CreditCardAccessManager::Accessor {
  public:
   AutofillManager(AutofillDriver* driver,
                   AutofillClient* client,
@@ -146,8 +146,8 @@
   // Invoked when the user selected |value| in the Autocomplete drop-down.
   void OnAutocompleteEntrySelected(const base::string16& value);
 
-  // Returns true when the Payments card unmask prompt is being displayed.
-  bool IsShowingUnmaskPrompt();
+  // Returns true only if the previewed form should be cleared.
+  bool ShouldClearPreviewedForm();
 
   AutofillClient* client() { return client_; }
 
@@ -157,13 +157,8 @@
 
   payments::FullCardRequest* GetOrCreateFullCardRequest();
 
-  payments::FullCardRequest* CreateFullCardRequest(
-      const base::TimeTicks& form_parsed_timestamp);
-
   base::WeakPtr<payments::FullCardRequest::UIDelegate>
-  GetAsFullCardRequestUIDelegate() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
+  GetAsFullCardRequestUIDelegate();
 
   const std::string& app_locale() const { return app_locale_; }
 
@@ -347,7 +342,7 @@
     bool is_context_secure = false;
     bool is_filling_credit_card = false;
     // Flag to indicate whether all suggestions come from Google Payments.
-    bool is_all_server_suggestions = false;
+    bool should_display_gpay_logo = false;
     SuppressReason suppress_reason = SuppressReason::kNotSuppressed;
   };
 
@@ -355,35 +350,23 @@
   void OnLoadedServerPredictions(
       std::string response,
       const std::vector<std::string>& form_signatures) override;
-  // Returns the real PAN retrieved from Payments. |real_pan| will be empty on
-  // failure.
-  void OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
-                       const std::string& real_pan);
 
-  // payments::FullCardRequest::ResultDelegate:
-  void OnFullCardRequestSucceeded(
-      const payments::FullCardRequest& full_card_request,
-      const CreditCard& card,
-      const base::string16& cvc) override;
-  void OnFullCardRequestFailed() override;
-
-  // payments::FullCardRequest::UIDelegate:
-  void ShowUnmaskPrompt(const CreditCard& card,
-                        AutofillClient::UnmaskCardReason reason,
-                        base::WeakPtr<CardUnmaskDelegate> delegate) override;
-  void OnUnmaskVerificationResult(
-      AutofillClient::PaymentsRpcResult result) override;
+  // CreditCardAccessManager::Accessor
+  void OnCreditCardFetched(
+      bool did_succeed,
+      const CreditCard* credit_card = nullptr,
+      const base::string16& cvc = base::string16()) override;
 
   // Returns false if Autofill is disabled or if no Autofill data is available.
   bool RefreshDataModels();
 
-  // Gets the profile referred by |unique_id|. Returns true if the profile
-  // exists.
-  bool GetProfile(int unique_id, const AutofillProfile** profile);
+  // Gets the card referred to by the guid |unique_id|. Returns |nullptr| if
+  // card does not exist.
+  CreditCard* GetCreditCard(int unique_id);
 
-  // Gets the credit card referred by |unique_id|. Returns true if the credit
-  // card exists.
-  bool GetCreditCard(int unique_id, const CreditCard** credit_card);
+  // Gets the profile referred to by the guid |unique_id|. Returns |nullptr| if
+  // profile does not exist.
+  AutofillProfile* GetProfile(int unique_id);
 
   // Determines whether a fill on |form| initiated from |field| will wind up
   // filling a credit card number. This is useful to determine if we will need
@@ -398,7 +381,7 @@
       int query_id,
       const FormData& form,
       const FormFieldData& field,
-      const CreditCard& credit_card);
+      const CreditCard* credit_card);
 
   // Fills or previews the profile form.
   // Assumes the form and field are valid.
@@ -446,12 +429,12 @@
 
   // Returns a list of values from the stored credit cards that match |type| and
   // the value of |field| and returns the labels of the matching credit cards.
-  // |is_all_server_suggestions| will be set to true if there is no credit card
+  // |should_display_gpay_logo| will be set to true if there is no credit card
   // suggestions or all suggestions come from Payments server.
   std::vector<Suggestion> GetCreditCardSuggestions(
       const FormFieldData& field,
       const AutofillType& type,
-      bool* is_all_server_suggestions) const;
+      bool* should_display_gpay_logo) const;
 
   // Parses the forms using heuristic matching and querying the Autofill server.
   void ParseForms(const std::vector<FormData>& forms);
@@ -574,16 +557,16 @@
   // A copy of the currently interacted form data.
   std::unique_ptr<FormData> pending_form_data_;
 
-  // Responsible for getting the full card details, including the PAN and the
-  // CVC.
-  std::unique_ptr<payments::FullCardRequest> full_card_request_;
+  // The credit card access manager, used to access local and server cards.
+  std::unique_ptr<CreditCardAccessManager> credit_card_access_manager_;
 
-  // Collected information about the autofill form where unmasked card will be
+  // Collected information about the autofill form where a credit card will be
   // filled.
-  int unmasking_query_id_ = -1;
-  FormData unmasking_form_;
-  FormFieldData unmasking_field_;
-  CreditCard masked_card_;
+  AutofillDriver::RendererFormDataAction credit_card_action_;
+  int credit_card_query_id_ = -1;
+  FormData credit_card_form_;
+  FormFieldData credit_card_field_;
+  CreditCard credit_card_;
 
   // Ablation experiment turns off autofill, but logging still has to be kept
   // for metrics analysis.
@@ -614,13 +597,15 @@
   // Tracks whether or not rich query encoding is enabled for this client.
   const bool is_rich_query_enabled_ = false;
 
-  // Used to record metrics. This shoulb be set at the beginning of the
+  // Used to record metrics. This should be set at the beginning of the
   // interaction and re-used throughout the context of this manager.
   AutofillSyncSigninState sync_state_ = AutofillSyncSigninState::kNumSyncStates;
 
   base::WeakPtrFactory<AutofillManager> weak_ptr_factory_;
 
+  friend class AutofillAssistantTest;
   friend class AutofillManagerTest;
+  friend class AutofillMetricsTest;
   friend class FormStructureBrowserTest;
   friend class GetMatchingTypesTest;
   FRIEND_TEST_ALL_PREFIXES(ProfileMatchingTypesTest,
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index cef9ffb..461ae6c 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -529,14 +529,26 @@
         .Times(AtLeast(1));
     autofill_manager_->FillOrPreviewCreditCardForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, kDefaultPageID, *form,
-        form->fields[0], *card);
+        form->fields[0], card);
+  }
+
+  void OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
+                       const std::string& real_pan) {
+    payments::FullCardRequest* full_card_request =
+        autofill_manager_->credit_card_access_manager_->cvc_authenticator_
+            ->full_card_request_.get();
+    DCHECK(full_card_request);
+    full_card_request->OnDidGetRealPan(result, real_pan);
   }
 
   // Convenience method to cast the FullCardRequest into a CardUnmaskDelegate.
   CardUnmaskDelegate* full_card_unmask_delegate() {
-    DCHECK(autofill_manager_->full_card_request_);
-    return static_cast<CardUnmaskDelegate*>(
-        autofill_manager_->full_card_request_.get());
+    payments::FullCardRequest* full_card_request =
+        autofill_manager_->credit_card_access_manager_
+            ->credit_card_cvc_authenticator()
+            ->full_card_request_.get();
+    DCHECK(full_card_request);
+    return static_cast<CardUnmaskDelegate*>(full_card_request);
   }
 
   void DisableCreditCardAutofill() {
@@ -5352,8 +5364,7 @@
   response.should_store_pan = false;
   response.cvc = ASCIIToUTF16("123");
   full_card_unmask_delegate()->OnUnmaskResponse(response);
-  autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                     "4012888888881881");
+  OnDidGetRealPan(AutofillClient::SUCCESS, "4012888888881881");
   autofill_manager_->OnFormSubmitted(form, false,
                                      SubmissionSource::FORM_SUBMISSION);
 }
@@ -5369,8 +5380,7 @@
   response.exp_month = ASCIIToUTF16("02");
   response.exp_year = ASCIIToUTF16("2018");
   full_card_unmask_delegate()->OnUnmaskResponse(response);
-  autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                     "4012888888881881");
+  OnDidGetRealPan(AutofillClient::SUCCESS, "4012888888881881");
 }
 
 TEST_F(AutofillManagerTest, ProfileDisabledDoesNotFillFormData) {
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 904d04d..6357383 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -495,7 +495,10 @@
     // Abandoned the migration because no supported local cards were left after
     // filtering out unsupported cards.
     NOT_OFFERED_NO_SUPPORTED_CARDS = 6,
-    kMaxValue = NOT_OFFERED_NO_SUPPORTED_CARDS,
+    // User used a local card and they only have a single migratable local card
+    // on file, we will offer Upstream instead.
+    NOT_OFFERED_SINGLE_LOCAL_CARD = 7,
+    kMaxValue = NOT_OFFERED_SINGLE_LOCAL_CARD,
   };
 
   // Metrics to track events when local credit card migration is offered.
diff --git a/components/autofill/core/browser/autofill_metrics_unittest.cc b/components/autofill/core/browser/autofill_metrics_unittest.cc
index b7e2dc5..b94f405 100644
--- a/components/autofill/core/browser/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/autofill_metrics_unittest.cc
@@ -262,6 +262,10 @@
                            bool include_masked_server_credit_card,
                            bool include_full_server_credit_card);
 
+  // Mocks a RPC response from payments.
+  void OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
+                       const std::string& real_pan);
+
   // Removes all existing credit cards and creates 1 masked server card with a
   // bank name.
   void RecreateMaskedServerCreditCardWithBankName();
@@ -381,6 +385,17 @@
   personal_data_->Refresh();
 }
 
+void AutofillMetricsTest::OnDidGetRealPan(
+    AutofillClient::PaymentsRpcResult result,
+    const std::string& real_pan) {
+  payments::FullCardRequest* full_card_request =
+      autofill_manager_->credit_card_access_manager_
+          ->credit_card_cvc_authenticator()
+          ->full_card_request_.get();
+  DCHECK(full_card_request);
+  full_card_request->OnDidGetRealPan(result, real_pan);
+}
+
 void AutofillMetricsTest::RecreateCreditCards(
     bool include_local_credit_card,
     bool include_masked_server_credit_card,
@@ -4051,8 +4066,7 @@
     autofill_manager_->FillOrPreviewForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.back(),
         autofill_manager_->MakeFrontendID(guid, std::string()));
-    autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                       "6011000990139424");
+    OnDidGetRealPan(AutofillClient::SUCCESS, "6011000990139424");
     autofill_manager_->OnFormSubmitted(form, false,
                                        SubmissionSource::FORM_SUBMISSION);
     histogram_tester.ExpectBucketCount(
@@ -4147,8 +4161,7 @@
     autofill_manager_->FillOrPreviewForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.back(),
         autofill_manager_->MakeFrontendID(guid, std::string()));
-    autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                       "4444333322221111");
+    OnDidGetRealPan(AutofillClient::SUCCESS, "4444333322221111");
     autofill_manager_->OnFormSubmitted(form, false,
                                        SubmissionSource::FORM_SUBMISSION);
     histogram_tester.ExpectBucketCount(
@@ -4172,8 +4185,7 @@
     autofill_manager_->FillOrPreviewForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.back(),
         autofill_manager_->MakeFrontendID(guid, std::string()));
-    autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                       "4444333322221111");
+    OnDidGetRealPan(AutofillClient::SUCCESS, "4444333322221111");
     autofill_manager_->OnFormSubmitted(form, false,
                                        SubmissionSource::FORM_SUBMISSION);
     histogram_tester.ExpectBucketCount(
@@ -4269,8 +4281,7 @@
     autofill_manager_->FillOrPreviewForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.back(),
         autofill_manager_->MakeFrontendID(guid, std::string()));
-    autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                       "6011000990139424");
+    OnDidGetRealPan(AutofillClient::SUCCESS, "6011000990139424");
     histogram_tester.ExpectTotalCount(
         "Autofill.UnmaskPrompt.GetRealPanDuration", 1);
     histogram_tester.ExpectTotalCount(
@@ -4293,8 +4304,7 @@
     autofill_manager_->FillOrPreviewForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.back(),
         autofill_manager_->MakeFrontendID(guid, std::string()));
-    autofill_manager_->OnDidGetRealPan(AutofillClient::PERMANENT_FAILURE,
-                                       std::string());
+    OnDidGetRealPan(AutofillClient::PERMANENT_FAILURE, std::string());
     histogram_tester.ExpectTotalCount(
         "Autofill.UnmaskPrompt.GetRealPanDuration", 1);
     histogram_tester.ExpectTotalCount(
@@ -4893,8 +4903,7 @@
     autofill_manager_->FillOrPreviewForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.back(),
         autofill_manager_->MakeFrontendID(guid, std::string()));
-    autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                       "6011000990139424");
+    OnDidGetRealPan(AutofillClient::SUCCESS, "6011000990139424");
     autofill_manager_->OnFormSubmitted(form, false,
                                        SubmissionSource::FORM_SUBMISSION);
     histogram_tester.ExpectBucketCount(
@@ -5287,8 +5296,7 @@
     autofill_manager_->FillOrPreviewForm(
         AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.back(),
         autofill_manager_->MakeFrontendID(guid, std::string()));
-    autofill_manager_->OnDidGetRealPan(AutofillClient::SUCCESS,
-                                       "6011000990139424");
+    OnDidGetRealPan(AutofillClient::SUCCESS, "6011000990139424");
     histogram_tester.ExpectBucketCount(
         "Autofill.FormEvents.CreditCard",
         FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_FILLED, 1);
diff --git a/components/autofill/core/browser/metrics/credit_card_form_event_logger.cc b/components/autofill/core/browser/metrics/credit_card_form_event_logger.cc
index 03aea4f..e8964ff 100644
--- a/components/autofill/core/browser/metrics/credit_card_form_event_logger.cc
+++ b/components/autofill/core/browser/metrics/credit_card_form_event_logger.cc
@@ -28,11 +28,17 @@
 
 CreditCardFormEventLogger::~CreditCardFormEventLogger() = default;
 
-void CreditCardFormEventLogger::OnDidSelectMaskedServerCardSuggestion(
+void CreditCardFormEventLogger::OnDidSelectCardSuggestion(
+    const CreditCard& credit_card,
     const FormStructure& form,
     AutofillSyncSigninState sync_state) {
   sync_state_ = sync_state;
 
+  // No need to log selections for local/full-server cards -- a selection is
+  // always followed by a form fill, which is logged separately.
+  if (credit_card.record_type() != CreditCard::MASKED_SERVER_CARD)
+    return;
+
   Log(FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_SELECTED, form);
   if (!has_logged_masked_server_card_suggestion_selected_) {
     has_logged_masked_server_card_suggestion_selected_ = true;
diff --git a/components/autofill/core/browser/metrics/credit_card_form_event_logger.h b/components/autofill/core/browser/metrics/credit_card_form_event_logger.h
index aaf867dc..c405bb6 100644
--- a/components/autofill/core/browser/metrics/credit_card_form_event_logger.h
+++ b/components/autofill/core/browser/metrics/credit_card_form_event_logger.h
@@ -45,9 +45,9 @@
     is_context_secure_ = is_context_secure;
   }
 
-  void OnDidSelectMaskedServerCardSuggestion(
-      const FormStructure& form,
-      AutofillSyncSigninState sync_state);
+  void OnDidSelectCardSuggestion(const CreditCard& credit_card,
+                                 const FormStructure& form,
+                                 AutofillSyncSigninState sync_state);
 
   void SetBankNameAvailable();
 
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
new file mode 100644
index 0000000..c422b19
--- /dev/null
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -0,0 +1,161 @@
+// 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 "components/autofill/core/browser/payments/credit_card_access_manager.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/guid.h"
+#include "base/strings/string16.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/autofill_driver.h"
+#include "components/autofill/core/browser/autofill_manager.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill {
+
+CreditCardAccessManager::CreditCardAccessManager(
+    AutofillManager* autofill_manager)
+    : CreditCardAccessManager(
+          autofill_manager->client(),
+          autofill_manager->client()->GetPersonalDataManager()) {}
+
+CreditCardAccessManager::CreditCardAccessManager(
+    AutofillClient* client,
+    PersonalDataManager* personal_data_manager,
+    CreditCardFormEventLogger* form_event_logger)
+    : client_(client),
+      payments_client_(client_->GetPaymentsClient()),
+      personal_data_manager_(personal_data_manager),
+      form_event_logger_(form_event_logger),
+      weak_ptr_factory_(this) {}
+
+CreditCardAccessManager::~CreditCardAccessManager() {}
+
+void CreditCardAccessManager::UpdateCreditCardFormEventLogger() {
+  std::vector<CreditCard*> credit_cards = GetCreditCards();
+
+  if (form_event_logger_) {
+    size_t server_record_type_count = 0;
+    size_t local_record_type_count = 0;
+    for (CreditCard* credit_card : credit_cards) {
+      if (credit_card->record_type() == CreditCard::LOCAL_CARD)
+        local_record_type_count++;
+      else
+        server_record_type_count++;
+    }
+    form_event_logger_->set_server_record_type_count(server_record_type_count);
+    form_event_logger_->set_local_record_type_count(local_record_type_count);
+    form_event_logger_->set_is_context_secure(client_->IsContextSecure());
+  }
+}
+
+std::vector<CreditCard*> CreditCardAccessManager::GetCreditCards() {
+  return personal_data_manager_->GetCreditCards();
+}
+
+std::vector<CreditCard*> CreditCardAccessManager::GetCreditCardsToSuggest() {
+  const std::vector<CreditCard*> cards_to_suggest =
+      personal_data_manager_->GetCreditCardsToSuggest(
+          client_->AreServerCardsSupported());
+
+  for (const CreditCard* credit_card : cards_to_suggest) {
+    if (form_event_logger_ && !credit_card->bank_name().empty()) {
+      form_event_logger_->SetBankNameAvailable();
+      break;
+    }
+  }
+
+  return cards_to_suggest;
+}
+
+bool CreditCardAccessManager::ShouldDisplayGPayLogo() {
+  for (const CreditCard* credit_card : GetCreditCardsToSuggest()) {
+    if (credit_card->record_type() == CreditCard::LOCAL_CARD)
+      return false;
+  }
+  return true;
+}
+
+bool CreditCardAccessManager::DeleteCard(const CreditCard* card) {
+  // Server cards cannot be deleted from within Chrome.
+  bool allowed_to_delete = IsLocalCard(card);
+
+  if (allowed_to_delete)
+    personal_data_manager_->DeleteLocalCreditCards({*card});
+
+  return allowed_to_delete;
+}
+
+bool CreditCardAccessManager::GetDeletionConfirmationText(
+    const CreditCard* card,
+    base::string16* title,
+    base::string16* body) {
+  if (!IsLocalCard(card))
+    return false;
+
+  if (title)
+    title->assign(card->NetworkOrBankNameAndLastFourDigits());
+  if (body) {
+    body->assign(l10n_util::GetStringUTF16(
+        IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY));
+  }
+
+  return true;
+}
+
+bool CreditCardAccessManager::ShouldClearPreviewedForm() {
+  return !is_authentication_in_progress_;
+}
+
+CreditCard* CreditCardAccessManager::GetCreditCard(std::string guid) {
+  if (base::IsValidGUID(guid)) {
+    return personal_data_manager_->GetCreditCardByGUID(guid);
+  }
+  return nullptr;
+}
+
+void CreditCardAccessManager::FetchCreditCard(
+    const CreditCard* card,
+    base::WeakPtr<Accessor> accessor,
+    const base::TimeTicks& timestamp) {
+  if (!card || card->record_type() != CreditCard::MASKED_SERVER_CARD) {
+    accessor->OnCreditCardFetched(/*did_succeed=*/card != nullptr, card);
+    return;
+  }
+
+  accessor_ = accessor;
+  is_authentication_in_progress_ = true;
+  credit_card_cvc_authenticator()->Authenticate(
+      card, weak_ptr_factory_.GetWeakPtr(), personal_data_manager_, timestamp);
+}
+
+CreditCardCVCAuthenticator*
+CreditCardAccessManager::credit_card_cvc_authenticator() {
+  if (!cvc_authenticator_)
+    cvc_authenticator_.reset(new CreditCardCVCAuthenticator(client_));
+  return cvc_authenticator_.get();
+}
+
+void CreditCardAccessManager::OnCVCAuthenticationComplete(
+    bool did_succeed,
+    const CreditCard* card,
+    const base::string16& cvc) {
+  is_authentication_in_progress_ = false;
+  accessor_->OnCreditCardFetched(did_succeed, card, cvc);
+}
+
+bool CreditCardAccessManager::IsLocalCard(const CreditCard* card) {
+  return card && card->record_type() == CreditCard::LOCAL_CARD;
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.h b/components/autofill/core/browser/payments/credit_card_access_manager.h
new file mode 100644
index 0000000..ec84e43
--- /dev/null
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.h
@@ -0,0 +1,125 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_CREDIT_CARD_ACCESS_MANAGER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_CREDIT_CARD_ACCESS_MANAGER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/autofill_driver.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
+#include "components/autofill/core/browser/payments/credit_card_cvc_authenticator.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+
+namespace autofill {
+
+class AutofillManager;
+
+// Manages logic for accessing credit cards either stored locally or stored
+// with Google Payments. Owned by AutofillManager.
+class CreditCardAccessManager : public CreditCardCVCAuthenticator::Requester {
+ public:
+  class Accessor {
+   public:
+    virtual ~Accessor() {}
+    virtual void OnCreditCardFetched(
+        bool did_succeed,
+        const CreditCard* credit_card = nullptr,
+        const base::string16& cvc = base::string16()) = 0;
+  };
+
+  explicit CreditCardAccessManager(AutofillManager* autofill_manager);
+  CreditCardAccessManager(
+      AutofillClient* client,
+      PersonalDataManager* personal_data_manager,
+      CreditCardFormEventLogger* credit_card_form_event_logger = nullptr);
+  ~CreditCardAccessManager() override;
+
+  // Logs information about current credit card data.
+  void UpdateCreditCardFormEventLogger();
+  // Returns all credit cards.
+  std::vector<CreditCard*> GetCreditCards();
+  // Returns credit cards in the order to be suggested to the user.
+  std::vector<CreditCard*> GetCreditCardsToSuggest();
+  // Returns true only if all cards are server cards.
+  bool ShouldDisplayGPayLogo();
+  // Returns true when deletion is allowed. Only local cards can be deleted.
+  bool DeleteCard(const CreditCard* card);
+  // Returns true if the |card| is deletable. Fills out
+  // |title| and |body| with relevant user-facing text.
+  bool GetDeletionConfirmationText(const CreditCard* card,
+                                   base::string16* title,
+                                   base::string16* body);
+
+  // Returns false only if some form of authentication is still in progress.
+  bool ShouldClearPreviewedForm();
+
+  // Retrieves instance of CreditCard with given guid.
+  CreditCard* GetCreditCard(std::string guid);
+
+  // Calls |accessor->OnCreditCardFetched()| once credit card is fetched.
+  void FetchCreditCard(const CreditCard* card,
+                       base::WeakPtr<Accessor> accessor,
+                       const base::TimeTicks& timestamp = base::TimeTicks());
+
+  CreditCardCVCAuthenticator* credit_card_cvc_authenticator();
+
+ private:
+  friend class AutofillAssistantTest;
+  friend class AutofillManagerTest;
+  friend class AutofillMetricsTest;
+  friend class CreditCardAccessManagerTest;
+
+  // CreditCardCVCAuthenticator::Requester
+  void OnCVCAuthenticationComplete(
+      bool did_succeed,
+      const CreditCard* card = nullptr,
+      const base::string16& cvc = base::string16()) override;
+
+  bool is_authentication_in_progress() {
+    return is_authentication_in_progress_;
+  }
+
+  // Returns true only if |credit_card| is a local card.
+  bool IsLocalCard(const CreditCard* credit_card);
+
+  // Is set to true only when waiting for the callback to
+  // OnCVCAuthenticationComplete() to be executed.
+  bool is_authentication_in_progress_ = false;
+
+  // The associated autofill client. Weak reference.
+  AutofillClient* const client_;
+
+  // Client to interact with Payments servers.
+  payments::PaymentsClient* payments_client_;
+
+  // The personal data manager, used to save and load personal data to/from the
+  // web database.
+  // Weak reference.
+  // May be NULL. NULL indicates OTR.
+  PersonalDataManager* personal_data_manager_;
+
+  // For logging metrics. May be NULL for tests.
+  CreditCardFormEventLogger* form_event_logger_;
+
+  // Authenticator for card requests.
+  std::unique_ptr<CreditCardCVCAuthenticator> cvc_authenticator_;
+
+  // The object attempting to access a card.
+  base::WeakPtr<Accessor> accessor_;
+
+  base::WeakPtrFactory<CreditCardAccessManager> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(CreditCardAccessManager);
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_CREDIT_CARD_ACCESS_MANAGER_H_
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
new file mode 100644
index 0000000..be9a11b8
--- /dev/null
+++ b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
@@ -0,0 +1,370 @@
+// 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 "components/autofill/core/browser/payments/credit_card_access_manager.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/autofill/core/browser/autocomplete_history_manager.h"
+#include "components/autofill/core/browser/autofill_download_manager.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/form_structure.h"
+#include "components/autofill/core/browser/metrics/form_events.h"
+#include "components/autofill/core/browser/payments/test_payments_client.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_autofill_clock.h"
+#include "components/autofill/core/browser/test_autofill_driver.h"
+#include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/autofill/core/browser/validation.h"
+#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/autofill_payments_features.h"
+#include "components/autofill/core/common/autofill_prefs.h"
+#include "components/autofill/core/common/autofill_switches.h"
+#include "components/autofill/core/common/autofill_util.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/prefs/pref_service.h"
+#include "components/security_state/core/security_state.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/sync/driver/test_sync_service.h"
+#include "components/variations/variations_associated_data.h"
+#include "components/variations/variations_params_manager.h"
+#include "components/version_info/channel.h"
+#include "net/base/url_util.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/geometry/rect.h"
+#include "url/gurl.h"
+
+using base::ASCIIToUTF16;
+
+namespace autofill {
+namespace {
+
+const char kTestGUID[] = "00000000-0000-0000-0000-000000000001";
+const char kTestNumber[] = "4234567890123456";  // Visa
+
+class TestAccessor : public CreditCardAccessManager::Accessor {
+ public:
+  TestAccessor() : weak_ptr_factory_(this) {}
+
+  base::WeakPtr<TestAccessor> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+  void OnCreditCardFetched(bool did_succeed,
+                           const CreditCard* card,
+                           const base::string16& cvc) override {
+    if (did_succeed) {
+      DCHECK(card);
+      number_ = card->number();
+      return Success();
+    }
+    return Failure();
+  }
+
+  base::string16 number() { return number_; }
+
+  MOCK_METHOD0(Success, void());
+  MOCK_METHOD0(Failure, void());
+
+ private:
+  base::string16 number_;
+  base::WeakPtrFactory<TestAccessor> weak_ptr_factory_;
+};
+
+std::string NextYear() {
+  base::Time::Exploded now;
+  base::Time::Now().LocalExplode(&now);
+  return base::NumberToString(now.year + 1);
+}
+
+std::string NextMonth() {
+  base::Time::Exploded now;
+  base::Time::Now().LocalExplode(&now);
+  return base::NumberToString(now.month % 12 + 1);
+}
+
+}  // namespace
+
+class CreditCardAccessManagerTest : public testing::Test {
+ public:
+  CreditCardAccessManagerTest() {}
+
+  void SetUp() override {
+    autofill_client_.SetPrefs(test::PrefServiceForTesting());
+    personal_data_manager_.Init(/*profile_database=*/database_,
+                                /*account_database=*/nullptr,
+                                /*pref_service=*/autofill_client_.GetPrefs(),
+                                /*identity_manager=*/nullptr,
+                                /*client_profile_validator=*/nullptr,
+                                /*history_service=*/nullptr,
+                                /*is_off_the_record=*/false);
+    personal_data_manager_.SetPrefService(autofill_client_.GetPrefs());
+
+    accessor_.reset(new TestAccessor());
+    autofill_driver_ =
+        std::make_unique<testing::NiceMock<TestAutofillDriver>>();
+    request_context_ = new net::TestURLRequestContextGetter(
+        base::ThreadTaskRunnerHandle::Get());
+    autofill_driver_->SetURLRequestContext(request_context_.get());
+
+    payments::TestPaymentsClient* payments_client =
+        new payments::TestPaymentsClient(
+            autofill_driver_->GetURLLoaderFactory(),
+            autofill_client_.GetIdentityManager(), &personal_data_manager_);
+    autofill_client_.set_test_payments_client(
+        std::unique_ptr<payments::TestPaymentsClient>(payments_client));
+    form_structure_ = new FormStructure(FormData());
+    credit_card_access_manager_ = std::make_unique<CreditCardAccessManager>(
+        &autofill_client_, &personal_data_manager_, nullptr);
+  }
+
+  void TearDown() override {
+    // Order of destruction is important as AutofillDriver relies on
+    // PersonalDataManager to be around when it gets destroyed.
+    autofill_driver_.reset();
+
+    personal_data_manager_.SetPrefService(nullptr);
+    personal_data_manager_.ClearCreditCards();
+
+    request_context_ = nullptr;
+  }
+
+  bool IsAuthenticationInProgress() {
+    return credit_card_access_manager_->is_authentication_in_progress();
+  }
+
+  void CreateLocalCard(std::string guid, std::string number = std::string()) {
+    CreditCard local_card = CreditCard();
+    test::SetCreditCardInfo(&local_card, "Elvis Presley", number.c_str(),
+                            NextMonth().c_str(), NextYear().c_str(), "1");
+    local_card.set_guid(guid);
+    local_card.set_record_type(CreditCard::LOCAL_CARD);
+
+    personal_data_manager_.ClearCreditCards();
+    personal_data_manager_.AddCreditCard(local_card);
+  }
+
+  void CreateServerCard(std::string guid, std::string number = std::string()) {
+    CreditCard masked_server_card = CreditCard();
+    test::SetCreditCardInfo(&masked_server_card, "Elvis Presley",
+                            number.c_str(), NextMonth().c_str(),
+                            NextYear().c_str(), "1");
+    masked_server_card.set_guid(guid);
+    masked_server_card.set_record_type(CreditCard::MASKED_SERVER_CARD);
+
+    personal_data_manager_.ClearCreditCards();
+    personal_data_manager_.AddServerCreditCard(masked_server_card);
+  }
+
+  void OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
+                       const std::string& real_pan) {
+    payments::FullCardRequest* full_card_request =
+        credit_card_access_manager_->credit_card_cvc_authenticator()
+            ->full_card_request_.get();
+    DCHECK(full_card_request);
+    full_card_request->OnDidGetRealPan(result, real_pan);
+  }
+
+ protected:
+  std::unique_ptr<TestAccessor> accessor_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  TestAutofillClient autofill_client_;
+  std::unique_ptr<TestAutofillDriver> autofill_driver_;
+  scoped_refptr<net::TestURLRequestContextGetter> request_context_;
+  scoped_refptr<AutofillWebDataService> database_;
+  TestPersonalDataManager personal_data_manager_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  FormStructure* form_structure_;
+  std::unique_ptr<CreditCardAccessManager> credit_card_access_manager_;
+};
+
+// Ensures GetCreditCard() successfully retrieves Card.
+TEST_F(CreditCardAccessManagerTest, GetCreditCardSuccess) {
+  CreateLocalCard(kTestGUID);
+
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+  EXPECT_NE(card, nullptr);
+}
+
+// Ensures GetCreditCard() returns nullptr for invalid GUID.
+TEST_F(CreditCardAccessManagerTest, GetCreditCardFailure) {
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+  EXPECT_EQ(card, nullptr);
+}
+
+// Ensures DeleteCard() successfully removes local cards.
+TEST_F(CreditCardAccessManagerTest, RemoveLocalCreditCard) {
+  CreateLocalCard(kTestGUID);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_TRUE(personal_data_manager_.GetCreditCardWithGUID(kTestGUID));
+  EXPECT_TRUE(credit_card_access_manager_->DeleteCard(card));
+  EXPECT_FALSE(personal_data_manager_.GetCreditCardWithGUID(kTestGUID));
+}
+
+// Ensures DeleteCard() does nothing for server cards.
+TEST_F(CreditCardAccessManagerTest, RemoveServerCreditCard) {
+  CreateServerCard(kTestGUID);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_TRUE(personal_data_manager_.GetCreditCardWithGUID(kTestGUID));
+  EXPECT_FALSE(credit_card_access_manager_->DeleteCard(card));
+
+  // Cannot delete server cards.
+  EXPECT_TRUE(personal_data_manager_.GetCreditCardWithGUID(kTestGUID));
+}
+
+// Ensures GetDeletionConfirmationText(~) returns correct values for local
+// cards.
+TEST_F(CreditCardAccessManagerTest, LocalCardGetDeletionConfirmationText) {
+  CreateLocalCard(kTestGUID);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  base::string16* title = new base::string16();
+  base::string16* body = new base::string16();
+  EXPECT_TRUE(credit_card_access_manager_->GetDeletionConfirmationText(
+      card, title, body));
+
+  // |title| and |body| should be updated appropriately.
+  EXPECT_EQ(*title, card->NetworkOrBankNameAndLastFourDigits());
+  EXPECT_EQ(*body,
+            l10n_util::GetStringUTF16(
+                IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY));
+}
+
+// Ensures GetDeletionConfirmationText(~) returns false for server cards.
+TEST_F(CreditCardAccessManagerTest, ServerCardGetDeletionConfirmationText) {
+  CreateServerCard(kTestGUID);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  base::string16* title = new base::string16();
+  base::string16* body = new base::string16();
+  EXPECT_FALSE(credit_card_access_manager_->GetDeletionConfirmationText(
+      card, title, body));
+
+  // |title| and |body| should remain unchanged.
+  EXPECT_EQ(*title, base::string16());
+  EXPECT_EQ(*body, base::string16());
+}
+
+// Tests retrieving local cards.
+TEST_F(CreditCardAccessManagerTest, FetchLocalCardSuccess) {
+  CreateLocalCard(kTestGUID, kTestNumber);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_CALL(*accessor_, Success);
+  credit_card_access_manager_->FetchCreditCard(card, accessor_->GetWeakPtr());
+
+  EXPECT_EQ(ASCIIToUTF16(kTestNumber), accessor_->number());
+}
+
+// Ensures that FetchCreditCard() reports a failure when a card does not exist.
+TEST_F(CreditCardAccessManagerTest, FetchNullptrFailure) {
+  personal_data_manager_.ClearCreditCards();
+
+  EXPECT_CALL(*accessor_, Failure());
+  credit_card_access_manager_->FetchCreditCard(nullptr,
+                                               accessor_->GetWeakPtr());
+}
+
+// Ensures that FetchCreditCard() returns the full PAN upon a successful
+// response from payments.
+TEST_F(CreditCardAccessManagerTest, FetchServerCardSuccess) {
+  CreateServerCard(kTestGUID, kTestNumber);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_CALL(*accessor_, Success);
+  credit_card_access_manager_->FetchCreditCard(card, accessor_->GetWeakPtr());
+
+  OnDidGetRealPan(AutofillClient::SUCCESS, kTestNumber);
+  EXPECT_EQ(ASCIIToUTF16(kTestNumber), accessor_->number());
+}
+
+// Ensures that FetchCreditCard() returns a failure upon a negative response
+// from the server.
+TEST_F(CreditCardAccessManagerTest, FetchServerCardNetworkError) {
+  CreateServerCard(kTestGUID, kTestNumber);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_CALL(*accessor_, Failure);
+  credit_card_access_manager_->FetchCreditCard(card, accessor_->GetWeakPtr());
+
+  OnDidGetRealPan(AutofillClient::NETWORK_ERROR, std::string());
+}
+
+// Ensures that FetchCreditCard() returns a failure upon a negative response
+// from the server.
+TEST_F(CreditCardAccessManagerTest, FetchServerCardPermanentFailure) {
+  CreateServerCard(kTestGUID, kTestNumber);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_CALL(*accessor_, Failure);
+  credit_card_access_manager_->FetchCreditCard(card, accessor_->GetWeakPtr());
+
+  OnDidGetRealPan(AutofillClient::PERMANENT_FAILURE, std::string());
+}
+
+// Ensures that a "try again" response from payments does not end the flow.
+TEST_F(CreditCardAccessManagerTest, FetchServerCardTryAgainFailure) {
+  CreateServerCard(kTestGUID, kTestNumber);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_CALL(*accessor_, Success);
+  credit_card_access_manager_->FetchCreditCard(card, accessor_->GetWeakPtr());
+
+  OnDidGetRealPan(AutofillClient::TRY_AGAIN_FAILURE, std::string());
+  OnDidGetRealPan(AutofillClient::SUCCESS, kTestNumber);
+  EXPECT_EQ(ASCIIToUTF16(kTestNumber), accessor_->number());
+}
+
+// Ensures that |is_authentication_in_progress_| is set correctly.
+TEST_F(CreditCardAccessManagerTest, AuthenticationInProgress) {
+  CreateServerCard(kTestGUID, kTestNumber);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+
+  EXPECT_FALSE(IsAuthenticationInProgress());
+
+  credit_card_access_manager_->FetchCreditCard(card, accessor_->GetWeakPtr());
+  EXPECT_TRUE(IsAuthenticationInProgress());
+
+  OnDidGetRealPan(AutofillClient::SUCCESS, kTestNumber);
+  EXPECT_FALSE(IsAuthenticationInProgress());
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/credit_card_cvc_authenticator.cc b/components/autofill/core/browser/payments/credit_card_cvc_authenticator.cc
new file mode 100644
index 0000000..a249b15
--- /dev/null
+++ b/components/autofill/core/browser/payments/credit_card_cvc_authenticator.cc
@@ -0,0 +1,75 @@
+// 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 "components/autofill/core/browser/payments/credit_card_cvc_authenticator.h"
+
+#include "base/strings/string16.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/payments/full_card_request.h"
+
+namespace autofill {
+
+CreditCardCVCAuthenticator::CreditCardCVCAuthenticator(AutofillClient* client)
+    : client_(client), weak_ptr_factory_(this) {}
+
+CreditCardCVCAuthenticator::~CreditCardCVCAuthenticator() {}
+
+void CreditCardCVCAuthenticator::Authenticate(
+    const CreditCard* card,
+    base::WeakPtr<Requester> requester,
+    PersonalDataManager* personal_data_manager,
+    const base::TimeTicks& form_parsed_timestamp) {
+  requester_ = requester;
+  if (!card)
+    OnFullCardRequestFailed();
+  full_card_request_.reset(new payments::FullCardRequest(
+      client_, client_->GetPaymentsClient(), personal_data_manager,
+      form_parsed_timestamp));
+  full_card_request_->GetFullCard(*card, AutofillClient::UNMASK_FOR_AUTOFILL,
+                                  weak_ptr_factory_.GetWeakPtr(),
+                                  weak_ptr_factory_.GetWeakPtr());
+}
+
+void CreditCardCVCAuthenticator::OnFullCardRequestSucceeded(
+    const payments::FullCardRequest& /*full_card_request*/,
+    const CreditCard& card,
+    const base::string16& cvc) {
+  requester_->OnCVCAuthenticationComplete(/*did_succeed=*/true, &card, cvc);
+}
+
+void CreditCardCVCAuthenticator::OnFullCardRequestFailed() {
+  requester_->OnCVCAuthenticationComplete(/*did_succeed=*/false);
+}
+
+void CreditCardCVCAuthenticator::ShowUnmaskPrompt(
+    const CreditCard& card,
+    AutofillClient::UnmaskCardReason reason,
+    base::WeakPtr<CardUnmaskDelegate> delegate) {
+  client_->ShowUnmaskPrompt(card, reason, delegate);
+}
+
+void CreditCardCVCAuthenticator::OnUnmaskVerificationResult(
+    AutofillClient::PaymentsRpcResult result) {
+  client_->OnUnmaskVerificationResult(result);
+}
+
+payments::FullCardRequest* CreditCardCVCAuthenticator::GetFullCardRequest() {
+  // TODO(crbug.com/951669): iOS and Android clients should use
+  // CreditCardAccessManager to retrieve cards from payments instead of calling
+  // this function directly.
+  if (!full_card_request_) {
+    full_card_request_.reset(
+        new payments::FullCardRequest(client_, client_->GetPaymentsClient(),
+                                      client_->GetPersonalDataManager()));
+  }
+  return full_card_request_.get();
+}
+
+base::WeakPtr<payments::FullCardRequest::UIDelegate>
+CreditCardCVCAuthenticator::GetAsFullCardRequestUIDelegate() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/credit_card_cvc_authenticator.h b/components/autofill/core/browser/payments/credit_card_cvc_authenticator.h
new file mode 100644
index 0000000..50c21665
--- /dev/null
+++ b/components/autofill/core/browser/payments/credit_card_cvc_authenticator.h
@@ -0,0 +1,83 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_CREDIT_CARD_CVC_AUTHENTICATOR_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_CREDIT_CARD_CVC_AUTHENTICATOR_H_
+
+#include <memory>
+
+#include "base/strings/string16.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/payments/card_unmask_delegate.h"
+#include "components/autofill/core/browser/payments/full_card_request.h"
+
+namespace autofill {
+
+// Authenticates credit card unmasking through CVC verification.
+class CreditCardCVCAuthenticator
+    : public payments::FullCardRequest::ResultDelegate,
+      public payments::FullCardRequest::UIDelegate {
+ public:
+  class Requester {
+   public:
+    virtual ~Requester() {}
+    virtual void OnCVCAuthenticationComplete(
+        bool did_succeed,
+        const CreditCard* card = nullptr,
+        const base::string16& cvc = base::string16()) = 0;
+  };
+  explicit CreditCardCVCAuthenticator(AutofillClient* client);
+  ~CreditCardCVCAuthenticator() override;
+
+  // Authentication
+  void Authenticate(const CreditCard* card,
+                    base::WeakPtr<Requester> requester,
+                    PersonalDataManager* personal_data_manager,
+                    const base::TimeTicks& form_parsed_timestamp);
+
+  // payments::FullCardRequest::ResultDelegate
+  void OnFullCardRequestSucceeded(
+      const payments::FullCardRequest& /*full_card_request*/,
+      const CreditCard& card,
+      const base::string16& cvc) override;
+  void OnFullCardRequestFailed() override;
+
+  // payments::FullCardRequest::UIDelegate
+  void ShowUnmaskPrompt(const CreditCard& card,
+                        AutofillClient::UnmaskCardReason reason,
+                        base::WeakPtr<CardUnmaskDelegate> delegate) override;
+  void OnUnmaskVerificationResult(
+      AutofillClient::PaymentsRpcResult result) override;
+
+  payments::FullCardRequest* GetFullCardRequest();
+
+  base::WeakPtr<payments::FullCardRequest::UIDelegate>
+  GetAsFullCardRequestUIDelegate();
+
+ private:
+  friend class AutofillAssistantTest;
+  friend class AutofillManagerTest;
+  friend class AutofillMetricsTest;
+  friend class CreditCardAccessManagerTest;
+  friend class CreditCardCVCAuthenticatorTest;
+
+  // The associated autofill client. Weak reference.
+  AutofillClient* const client_;
+
+  // Responsible for getting the full card details, including the PAN and the
+  // CVC.
+  std::unique_ptr<payments::FullCardRequest> full_card_request_;
+
+  // Weak pointer to object that is requesting authentication.
+  base::WeakPtr<Requester> requester_;
+
+  base::WeakPtrFactory<CreditCardCVCAuthenticator> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(CreditCardCVCAuthenticator);
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_CREDIT_CARD_CVC_AUTHENTICATOR_H_
diff --git a/components/autofill/core/browser/payments/credit_card_cvc_authenticator_unittest.cc b/components/autofill/core/browser/payments/credit_card_cvc_authenticator_unittest.cc
new file mode 100644
index 0000000..4dca916
--- /dev/null
+++ b/components/autofill/core/browser/payments/credit_card_cvc_authenticator_unittest.cc
@@ -0,0 +1,218 @@
+// 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 "components/autofill/core/browser/payments/credit_card_cvc_authenticator.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/autofill/core/browser/autocomplete_history_manager.h"
+#include "components/autofill/core/browser/autofill_download_manager.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/metrics/form_events.h"
+#include "components/autofill/core/browser/payments/test_authentication_requester.h"
+#include "components/autofill/core/browser/payments/test_payments_client.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_autofill_clock.h"
+#include "components/autofill/core/browser/test_autofill_driver.h"
+#include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/autofill/core/browser/validation.h"
+#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/autofill_payments_features.h"
+#include "components/autofill/core/common/autofill_prefs.h"
+#include "components/autofill/core/common/autofill_switches.h"
+#include "components/autofill/core/common/autofill_util.h"
+#include "components/prefs/pref_service.h"
+#include "components/security_state/core/security_state.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/sync/driver/test_sync_service.h"
+#include "components/variations/variations_associated_data.h"
+#include "components/variations/variations_params_manager.h"
+#include "components/version_info/channel.h"
+#include "net/base/url_util.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/geometry/rect.h"
+#include "url/gurl.h"
+
+using base::ASCIIToUTF16;
+
+namespace autofill {
+namespace {
+
+const char kTestGUID[] = "00000000-0000-0000-0000-000000000001";
+const char kTestNumber[] = "4234567890123456";  // Visa
+
+std::string NextYear() {
+  base::Time::Exploded now;
+  base::Time::Now().LocalExplode(&now);
+  return base::NumberToString(now.year + 1);
+}
+
+std::string NextMonth() {
+  base::Time::Exploded now;
+  base::Time::Now().LocalExplode(&now);
+  return base::NumberToString(now.month % 12 + 1);
+}
+
+}  // namespace
+
+class CreditCardCVCAuthenticatorTest : public testing::Test {
+ public:
+  CreditCardCVCAuthenticatorTest() {}
+
+  void SetUp() override {
+    autofill_client_.SetPrefs(test::PrefServiceForTesting());
+    personal_data_manager_.Init(/*profile_database=*/database_,
+                                /*account_database=*/nullptr,
+                                /*pref_service=*/autofill_client_.GetPrefs(),
+                                /*identity_manager=*/nullptr,
+                                /*client_profile_validator=*/nullptr,
+                                /*history_service=*/nullptr,
+                                /*is_off_the_record=*/false);
+    personal_data_manager_.SetPrefService(autofill_client_.GetPrefs());
+
+    requester_.reset(new TestAuthenticationRequester());
+    autofill_driver_ =
+        std::make_unique<testing::NiceMock<TestAutofillDriver>>();
+    request_context_ = new net::TestURLRequestContextGetter(
+        base::ThreadTaskRunnerHandle::Get());
+    autofill_driver_->SetURLRequestContext(request_context_.get());
+
+    payments::TestPaymentsClient* payments_client =
+        new payments::TestPaymentsClient(
+            autofill_driver_->GetURLLoaderFactory(),
+            autofill_client_.GetIdentityManager(), &personal_data_manager_);
+    autofill_client_.set_test_payments_client(
+        std::unique_ptr<payments::TestPaymentsClient>(payments_client));
+    cvc_authenticator_ =
+        std::make_unique<CreditCardCVCAuthenticator>(&autofill_client_);
+  }
+
+  void TearDown() override {
+    // Order of destruction is important as AutofillDriver relies on
+    // PersonalDataManager to be around when it gets destroyed.
+    autofill_driver_.reset();
+
+    personal_data_manager_.SetPrefService(nullptr);
+    personal_data_manager_.ClearCreditCards();
+
+    request_context_ = nullptr;
+  }
+
+  CreditCard CreateServerCard(std::string guid, std::string number) {
+    CreditCard masked_server_card = CreditCard();
+    test::SetCreditCardInfo(&masked_server_card, "Elvis Presley",
+                            number.c_str(), NextMonth().c_str(),
+                            NextYear().c_str(), "1");
+    masked_server_card.set_guid(guid);
+    masked_server_card.set_record_type(CreditCard::MASKED_SERVER_CARD);
+
+    personal_data_manager_.ClearCreditCards();
+    personal_data_manager_.AddServerCreditCard(masked_server_card);
+
+    return masked_server_card;
+  }
+
+  void OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
+                       const std::string& real_pan) {
+    payments::FullCardRequest* full_card_request =
+        cvc_authenticator_->full_card_request_.get();
+    DCHECK(full_card_request);
+    full_card_request->OnDidGetRealPan(result, real_pan);
+  }
+
+ protected:
+  std::unique_ptr<TestAuthenticationRequester> requester_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  TestAutofillClient autofill_client_;
+  std::unique_ptr<TestAutofillDriver> autofill_driver_;
+  scoped_refptr<net::TestURLRequestContextGetter> request_context_;
+  scoped_refptr<AutofillWebDataService> database_;
+  TestPersonalDataManager personal_data_manager_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<CreditCardCVCAuthenticator> cvc_authenticator_;
+};
+
+TEST_F(CreditCardCVCAuthenticatorTest, AuthenticateServerCardSuccess) {
+  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);
+
+  EXPECT_CALL(*requester_, Success);
+  cvc_authenticator_->Authenticate(&card, requester_->GetWeakPtr(),
+                                   &personal_data_manager_,
+                                   base::TimeTicks::Now());
+
+  OnDidGetRealPan(AutofillClient::SUCCESS, kTestNumber);
+  EXPECT_EQ(ASCIIToUTF16(kTestNumber), requester_->number());
+}
+
+TEST_F(CreditCardCVCAuthenticatorTest, AuthenticateServerCardNetworkError) {
+  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);
+
+  EXPECT_CALL(*requester_, Failure);
+  cvc_authenticator_->Authenticate(&card, requester_->GetWeakPtr(),
+                                   &personal_data_manager_,
+                                   base::TimeTicks::Now());
+
+  OnDidGetRealPan(AutofillClient::NETWORK_ERROR, std::string());
+}
+
+TEST_F(CreditCardCVCAuthenticatorTest, AuthenticateServerCardPermanentFailure) {
+  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);
+
+  EXPECT_CALL(*requester_, Failure);
+  cvc_authenticator_->Authenticate(&card, requester_->GetWeakPtr(),
+                                   &personal_data_manager_,
+                                   base::TimeTicks::Now());
+
+  OnDidGetRealPan(AutofillClient::PERMANENT_FAILURE, std::string());
+}
+
+TEST_F(CreditCardCVCAuthenticatorTest, AuthenticateServerCardTryAgainFailure) {
+  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);
+
+  EXPECT_CALL(*requester_, Success);
+  cvc_authenticator_->Authenticate(&card, requester_->GetWeakPtr(),
+                                   &personal_data_manager_,
+                                   base::TimeTicks::Now());
+
+  OnDidGetRealPan(AutofillClient::TRY_AGAIN_FAILURE, std::string());
+  OnDidGetRealPan(AutofillClient::SUCCESS, kTestNumber);
+  EXPECT_EQ(ASCIIToUTF16(kTestNumber), requester_->number());
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager.cc b/components/autofill/core/browser/payments/credit_card_save_manager.cc
index 0cb71b0..d992afd5 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager.cc
@@ -33,7 +33,6 @@
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/form_structure.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "components/autofill/core/browser/payments/payments_util.h"
 #include "components/autofill/core/browser/payments/strike_database.h"
@@ -92,9 +91,7 @@
           features::kAutofillSaveCreditCardUsesStrikeSystemV2) ||
       base::FeatureList::IsEnabled(
           features::kAutofillLocalCardMigrationUsesStrikeSystemV2)) {
-    // Only init when |kAutofillSaveCreditCardUsesStrikeSystemV2| is enabled. If
-    // flag is off and LegacyStrikeDatabase instead of StrikeDatabase is used,
-    // this init will cause failure on GetStrikes().
+    // Only init when |kAutofillSaveCreditCardUsesStrikeSystemV2| is enabled.
     client_->GetStrikeDatabase();
   }
 }
@@ -116,17 +113,8 @@
           features::kAutofillSaveCreditCardUsesStrikeSystemV2)) {
     OnDidGetStrikesForLocalSave(GetCreditCardSaveStrikeDatabase()->GetStrikes(
         base::UTF16ToUTF8(local_card_save_candidate_.LastFourDigits())));
-  } else if (base::FeatureList::IsEnabled(
-                 features::kAutofillSaveCreditCardUsesStrikeSystem)) {
-    LegacyStrikeDatabase* strike_database = client_->GetLegacyStrikeDatabase();
-    strike_database->GetStrikes(
-        strike_database->GetKeyForCreditCardSave(
-            base::UTF16ToUTF8(local_card_save_candidate_.LastFourDigits())),
-        base::BindRepeating(&CreditCardSaveManager::OnDidGetStrikesForLocalSave,
-                            weak_ptr_factory_.GetWeakPtr()));
   } else {
-    // Skip retrieving data from the Autofill LegacyStrikeDatabase; assume 0
-    // strikes.
+    // Skip retrieving data from the Autofill StrikeDatabase; assume 0 strikes.
     OnDidGetStrikesForLocalSave(0);
   }
 }
@@ -275,18 +263,8 @@
           features::kAutofillSaveCreditCardUsesStrikeSystemV2)) {
     OnDidGetStrikesForUploadSave(GetCreditCardSaveStrikeDatabase()->GetStrikes(
         base::UTF16ToUTF8(upload_request_.card.LastFourDigits())));
-  } else if (base::FeatureList::IsEnabled(
-                 features::kAutofillSaveCreditCardUsesStrikeSystem)) {
-    LegacyStrikeDatabase* strike_database = client_->GetLegacyStrikeDatabase();
-    strike_database->GetStrikes(
-        strike_database->GetKeyForCreditCardSave(
-            base::UTF16ToUTF8(upload_request_.card.LastFourDigits())),
-        base::BindRepeating(
-            &CreditCardSaveManager::OnDidGetStrikesForUploadSave,
-            weak_ptr_factory_.GetWeakPtr()));
   } else {
-    // Skip retrieving data from the Autofill LegacyStrikeDatabase; assume 0
-    // strikes.
+    // Skip retrieving data from the Autofill StrikeDatabase; assume 0 strikes.
     OnDidGetStrikesForUploadSave(0);
   }
 }
@@ -355,23 +333,6 @@
       // removed.
       GetCreditCardSaveStrikeDatabase()->ClearStrikes(
           base::UTF16ToUTF8(upload_request_.card.LastFourDigits()));
-    } else if (base::FeatureList::IsEnabled(
-                   features::kAutofillSaveCreditCardUsesStrikeSystem)) {
-      LegacyStrikeDatabase* strike_database =
-          client_->GetLegacyStrikeDatabase();
-      // Log how many strikes the card had when it was saved.
-      strike_database->GetStrikes(
-          strike_database->GetKeyForCreditCardSave(
-              base::UTF16ToUTF8(upload_request_.card.LastFourDigits())),
-          base::BindRepeating(
-              &CreditCardSaveManager::LogStrikesPresentWhenCardSaved,
-              weak_ptr_factory_.GetWeakPtr(),
-              /*is_local=*/false));
-      // Clear all strikes for this card, in case it is later removed.
-      strike_database->ClearAllStrikesForKey(
-          strike_database->GetKeyForCreditCardSave(
-              base::UTF16ToUTF8(upload_request_.card.LastFourDigits())),
-          base::DoNothing());
     }
   } else {
     if (show_save_prompt_.has_value() && show_save_prompt_.value()) {
@@ -383,17 +344,6 @@
             base::UTF16ToUTF8(upload_request_.card.LastFourDigits()));
         // Notify the browsertests that a strike was added.
         OnStrikeChangeComplete(nth_strike_added);
-      } else if (base::FeatureList::IsEnabled(
-                     features::kAutofillSaveCreditCardUsesStrikeSystem)) {
-        // If the upload failed and the bubble was actually shown (NOT just the
-        // icon), count that as a strike against offering upload in the future.
-        LegacyStrikeDatabase* strike_database =
-            client_->GetLegacyStrikeDatabase();
-        strike_database->AddStrike(
-            strike_database->GetKeyForCreditCardSave(
-                base::UTF16ToUTF8(upload_request_.card.LastFourDigits())),
-            base::BindRepeating(&CreditCardSaveManager::OnStrikeChangeComplete,
-                                weak_ptr_factory_.GetWeakPtr()));
       }
     }
   }
@@ -432,7 +382,7 @@
       num_strikes < kMaxStrikesToPreventPoppingUpOfferToSavePrompt;
 
   // Only offer upload once both Payments and the Autofill
-  // LegacyStrikeDatabase have returned their decisions. Use population of
+  // StrikeDatabase have returned their decisions. Use population of
   // |upload_request_.context_token| as an indicator of the Payments call
   // returning successfully.
   if (!upload_request_.context_token.empty())
@@ -469,8 +419,8 @@
     legal_message_ = base::DictionaryValue::From(std::move(legal_message));
 
     // Only offer upload once both Payments and the Autofill
-    // LegacyStrikeDatabase have returned their decisions. Use presence of
-    // |show_save_prompt_| as an indicator of LegacyStrikeDatabase retrieving
+    // StrikeDatabase have returned their decisions. Use presence of
+    // |show_save_prompt_| as an indicator of StrikeDatabase retrieving
     // its data.
     if (show_save_prompt_.has_value())
       OfferCardUploadSave();
@@ -487,11 +437,10 @@
     // be rare.)
     //
     // Note that calling AttemptToOfferCardLocalSave(~) pings the Autofill
-    // LegacyStrikeDatabase again, but A) the result should be cached so this
+    // StrikeDatabase again, but A) the result should be cached so this
     // shouldn't hit the disk, and B) the alternative would require hooking into
-    // the LegacyStrikeDatabase's GetStrikes() call already in progress, which
-    // would be hacky at worst and require additional class state variables at
-    // best.
+    // the StrikeDatabase's GetStrikes() call already in progress, which would
+    // be hacky at worst and require additional class state variables at best.
     bool found_name_and_postal_code_and_cvc =
         (upload_request_.detected_values & DetectedValue::CARDHOLDER_NAME ||
          upload_request_.detected_values & DetectedValue::ADDRESS_NAME) &&
@@ -576,7 +525,7 @@
     // Set that upload was offered.
     upload_decision_metrics_ |= AutofillMetrics::UPLOAD_OFFERED;
   } else {
-    // Set that upload was abandoned due to the Autofill LegacyStrikeDatabase
+    // Set that upload was abandoned due to the Autofill StrikeDatabase
     // returning too many strikes for a mobile infobar to be displayed.
     upload_decision_metrics_ |=
         AutofillMetrics::UPLOAD_NOT_OFFERED_MAX_STRIKES_ON_MOBILE;
@@ -606,23 +555,6 @@
         // removed.
         GetCreditCardSaveStrikeDatabase()->ClearStrikes(
             base::UTF16ToUTF8(local_card_save_candidate_.LastFourDigits()));
-      } else if (base::FeatureList::IsEnabled(
-                     features::kAutofillSaveCreditCardUsesStrikeSystem)) {
-        LegacyStrikeDatabase* strike_database =
-            client_->GetLegacyStrikeDatabase();
-        // Log how many strikes the card had when it was saved.
-        strike_database->GetStrikes(
-            strike_database->GetKeyForCreditCardSave(
-                base::UTF16ToUTF8(local_card_save_candidate_.LastFourDigits())),
-            base::BindRepeating(
-                &CreditCardSaveManager::LogStrikesPresentWhenCardSaved,
-                weak_ptr_factory_.GetWeakPtr(),
-                /*is_local=*/true));
-        // Clear all strikes for this card, in case it is later removed.
-        strike_database->ClearAllStrikesForKey(
-            strike_database->GetKeyForCreditCardSave(
-                base::UTF16ToUTF8(local_card_save_candidate_.LastFourDigits())),
-            base::DoNothing());
       }
       if (base::FeatureList::IsEnabled(
               features::kAutofillLocalCardMigrationUsesStrikeSystemV2)) {
@@ -1002,18 +934,6 @@
       int nth_strike_added = GetCreditCardSaveStrikeDatabase()->AddStrike(
           base::UTF16ToUTF8(card_last_four_digits));
       OnStrikeChangeComplete(nth_strike_added);
-    } else if (base::FeatureList::IsEnabled(
-                   features::kAutofillSaveCreditCardUsesStrikeSystem)) {
-      // If the user rejected or ignored save and the offer-to-save bubble or
-      // infobar was actually shown (NOT just the icon if on desktop), count
-      // that as a strike against offering upload in the future.
-      LegacyStrikeDatabase* strike_database =
-          client_->GetLegacyStrikeDatabase();
-      strike_database->AddStrike(
-          strike_database->GetKeyForCreditCardSave(
-              base::UTF16ToUTF8(card_last_four_digits)),
-          base::BindRepeating(&CreditCardSaveManager::OnStrikeChangeComplete,
-                              weak_ptr_factory_.GetWeakPtr()));
     }
   }
 }
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
index 1a124df..41aa4f4 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
@@ -32,7 +32,6 @@
 #include "components/autofill/core/browser/payments/payments_customer_data.h"
 #include "components/autofill/core/browser/payments/test_credit_card_save_manager.h"
 #include "components/autofill/core/browser/payments/test_credit_card_save_strike_database.h"
-#include "components/autofill/core/browser/payments/test_legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/test_payments_client.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/test_autofill_client.h"
@@ -137,12 +136,7 @@
 class CreditCardSaveManagerTest : public testing::Test {
  public:
   void SetUp() override {
-    std::unique_ptr<TestLegacyStrikeDatabase> test_legacy_strike_database =
-        std::make_unique<TestLegacyStrikeDatabase>();
-    legacy_strike_database_ = test_legacy_strike_database.get();
     autofill_client_.SetPrefs(test::PrefServiceForTesting());
-    autofill_client_.set_test_legacy_strike_database(
-        std::move(test_legacy_strike_database));
     std::unique_ptr<TestStrikeDatabase> test_strike_database =
         std::make_unique<TestStrikeDatabase>();
     strike_database_ = test_strike_database.get();
@@ -376,8 +370,6 @@
   // Ends up getting owned (and destroyed) by TestAutofillClient:
   payments::TestPaymentsClient* payments_client_;
   // Ends up getting owned (and destroyed) by TestAutofillClient:
-  TestLegacyStrikeDatabase* legacy_strike_database_;
-  // Ends up getting owned (and destroyed) by TestAutofillClient:
   TestStrikeDatabase* strike_database_;
 
  private:
@@ -4454,14 +4446,13 @@
       // Enabled
       {},
       // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem,
-       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
   credit_card_save_manager_->SetCreditCardUploadEnabled(false);
 
   // Max out strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/3);
+  TestCreditCardSaveStrikeDatabase credit_card_save_strike_database =
+      TestCreditCardSaveStrikeDatabase(strike_database_);
+  credit_card_save_strike_database.AddStrikes(3, "1111");
 
   // Set up our credit card form data.
   FormData credit_card_form;
@@ -4501,13 +4492,12 @@
       // Enabled
       {},
       // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem,
-       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
 
   // Max out strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/3);
+  TestCreditCardSaveStrikeDatabase credit_card_save_strike_database =
+      TestCreditCardSaveStrikeDatabase(strike_database_);
+  credit_card_save_strike_database.AddStrikes(3, "1111");
 
   // Create, fill and submit an address form in order to establish a recent
   // profile which can be selected for the upload request.
@@ -4549,514 +4539,6 @@
       "Autofill.StrikeDatabase.StrikesPresentWhenServerCardSaved", 0);
 }
 
-// Tests that a card with some strikes using LegacyStrikeDatabase (but not max
-// strikes) should still show the save bubble/infobar.
-TEST_F(CreditCardSaveManagerTest,
-       LocallySaveCreditCard_NotEnoughLegacyStrikesStillShowsOfferToSave) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-  credit_card_save_manager_->SetCreditCardUploadEnabled(false);
-
-  // Add a single strike for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/1);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(1 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  FormSubmitted(credit_card_form);
-  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the offer-to-save bubble was still shown because the card did
-  // not have too many strikes.
-  EXPECT_TRUE(
-      autofill_client_.get_offer_to_save_credit_card_bubble_was_shown());
-  // Verify that no histogram entry was logged.
-  histogram_tester.ExpectTotalCount(
-      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes", 0);
-}
-
-// Tests that a card with some strikes using LegacyStrikeDatabase (but not max
-// strikes) should still show the save bubble/infobar.
-TEST_F(CreditCardSaveManagerTest,
-       UploadCreditCard_NotEnoughLegacyStrikesStillShowsOfferToSave) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-
-  // Add a single strike for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/1);
-
-  // Create, fill and submit an address form in order to establish a recent
-  // profile which can be selected for the upload request.
-  FormData address_form;
-  test::CreateTestAddressFormData(&address_form);
-  FormsSeen(std::vector<FormData>(1, address_form));
-  ExpectUniqueFillableFormParsedUkm();
-
-  ManuallyFillAddressForm("Flo", "Master", "77401", "US", &address_form);
-  FormSubmitted(address_form);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(2 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the offer-to-save bubble was still shown because the card did
-  // not have too many strikes.
-  EXPECT_TRUE(
-      autofill_client_.get_offer_to_save_credit_card_bubble_was_shown());
-  // Verify that no histogram entry was logged.
-  histogram_tester.ExpectTotalCount(
-      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes", 0);
-}
-
-#if defined(OS_ANDROID) || defined(OS_IOS)
-// Tests that a card with max strikes using LegacyStrikeDatabase does not offer
-// save on mobile at all.
-TEST_F(CreditCardSaveManagerTest,
-       LocallySaveCreditCard_MaxLegacyStrikesDisallowsSave) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-  credit_card_save_manager_->SetCreditCardUploadEnabled(false);
-
-  // Max out strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/3);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(1 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  // No form of credit card save should be shown.
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the correct histogram entry was logged.
-  histogram_tester.ExpectBucketCount(
-      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
-      AutofillMetrics::SaveTypeMetric::LOCAL, 1);
-}
-
-// Tests that a card with max strikes using LegacyStrikeDatabase does not offer
-// save on mobile at all.
-TEST_F(CreditCardSaveManagerTest,
-       UploadCreditCard_MaxLegacyStrikesDisallowsSave) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-
-  // Max out strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/3);
-
-  // Create, fill and submit an address form in order to establish a recent
-  // profile which can be selected for the upload request.
-  FormData address_form;
-  test::CreateTestAddressFormData(&address_form);
-  FormsSeen(std::vector<FormData>(1, address_form));
-  ExpectUniqueFillableFormParsedUkm();
-
-  ManuallyFillAddressForm("Flo", "Master", "77401", "US", &address_form);
-  FormSubmitted(address_form);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(2 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  // No form of credit card save should be shown.
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the correct histogram entries were logged.
-  ExpectCardUploadDecision(
-      histogram_tester,
-      AutofillMetrics::UPLOAD_NOT_OFFERED_MAX_STRIKES_ON_MOBILE);
-  histogram_tester.ExpectBucketCount(
-      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
-      AutofillMetrics::SaveTypeMetric::SERVER, 1);
-  // Verify that the correct UKM was logged.
-  ExpectCardUploadDecisionUkm(
-      AutofillMetrics::UPLOAD_NOT_OFFERED_MAX_STRIKES_ON_MOBILE);
-}
-
-#else  // !defined(OS_ANDROID) && !defined(OS_IOS)
-// Tests that a card with max strikes using LegacyStrikeDatabase should still
-// offer to save on Desktop via the omnibox icon, but that the offer-to-save
-// bubble itself is not shown.
-TEST_F(CreditCardSaveManagerTest,
-       LocallySaveCreditCard_MaxLegacyStrikesStillAllowsSave) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-  credit_card_save_manager_->SetCreditCardUploadEnabled(false);
-
-  // Max out strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/3);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(1 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  FormSubmitted(credit_card_form);
-  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the offer-to-save bubble was not shown because the card had too
-  // many strikes.
-  EXPECT_FALSE(
-      autofill_client_.get_offer_to_save_credit_card_bubble_was_shown());
-  // Verify that the correct histogram entry was logged.
-  histogram_tester.ExpectBucketCount(
-      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
-      AutofillMetrics::SaveTypeMetric::LOCAL, 1);
-}
-
-// Tests that a card with max strikes using LegacyStrikeDatabase should still
-// offer to save on Desktop via the omnibox icon, but that the offer-to-save
-// bubble itself is not shown.
-TEST_F(CreditCardSaveManagerTest,
-       UploadCreditCard_MaxLegacyStrikesStillAllowsSave) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-
-  // Max out strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/3);
-
-  // Create, fill and submit an address form in order to establish a recent
-  // profile which can be selected for the upload request.
-  FormData address_form;
-  test::CreateTestAddressFormData(&address_form);
-  FormsSeen(std::vector<FormData>(1, address_form));
-  ExpectUniqueFillableFormParsedUkm();
-
-  ManuallyFillAddressForm("Flo", "Master", "77401", "US", &address_form);
-  FormSubmitted(address_form);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(2 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that the offer-to-save bubble was not shown because the card had too
-  // many strikes.
-  EXPECT_FALSE(
-      autofill_client_.get_offer_to_save_credit_card_bubble_was_shown());
-  // Verify that the correct histogram entry was logged.
-  histogram_tester.ExpectBucketCount(
-      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
-      AutofillMetrics::SaveTypeMetric::SERVER, 1);
-}
-#endif
-
-// Tests that adding a card clears all LegacyStrikeDatabase strikes for that
-// card.
-TEST_F(CreditCardSaveManagerTest,
-       LocallySaveCreditCard_ClearLegacyStrikesOnAdd) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-  credit_card_save_manager_->SetCreditCardUploadEnabled(false);
-
-  // Add a couple of strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/2);
-  EXPECT_EQ(2, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(1 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  FormSubmitted(credit_card_form);
-  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that adding the card reset the strike count for that card.
-  EXPECT_EQ(0, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-}
-
-// Tests that adding a card clears all LegacyStrikeDatabase strikes for that
-// card.
-TEST_F(CreditCardSaveManagerTest, UploadCreditCard_ClearLegacyStrikesOnAdd) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-
-  // Add a couple of strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/2);
-  EXPECT_EQ(2, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-
-  // Create, fill and submit an address form in order to establish a recent
-  // profile which can be selected for the upload request.
-  FormData address_form;
-  test::CreateTestAddressFormData(&address_form);
-  FormsSeen(std::vector<FormData>(1, address_form));
-  ExpectUniqueFillableFormParsedUkm();
-
-  ManuallyFillAddressForm("Flo", "Master", "77401", "US", &address_form);
-  FormSubmitted(address_form);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(2 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that adding the card reset the strike count for that card.
-  EXPECT_EQ(0, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-}
-
-// Tests that adding a card clears all LegacyStrikeDatabase strikes for that
-// card.
-TEST_F(CreditCardSaveManagerTest,
-       LocallySaveCreditCard_NumLegacyStrikesLoggedOnAdd) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-  credit_card_save_manager_->SetCreditCardUploadEnabled(false);
-
-  // Add a couple of strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/2);
-  EXPECT_EQ(2, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(1 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  FormSubmitted(credit_card_form);
-  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that adding the card logged the number of strikes it had previously.
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.StrikeDatabase.StrikesPresentWhenLocalCardSaved",
-      /*sample=*/2, /*count=*/1);
-}
-
-// Tests that adding a card clears all LegacyStrikeDatabase strikes for that
-// card.
-TEST_F(CreditCardSaveManagerTest,
-       UploadCreditCard_NumLegacyStrikesLoggedOnAdd) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-
-  // Add a couple of strikes for the card to be added.
-  legacy_strike_database_->AddEntryWithNumStrikes(
-      legacy_strike_database_->GetKeyForCreditCardSave("1111"),
-      /*num_strikes=*/2);
-  EXPECT_EQ(2, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-
-  // Create, fill and submit an address form in order to establish a recent
-  // profile which can be selected for the upload request.
-  FormData address_form;
-  test::CreateTestAddressFormData(&address_form);
-  FormsSeen(std::vector<FormData>(1, address_form));
-  ExpectUniqueFillableFormParsedUkm();
-
-  ManuallyFillAddressForm("Flo", "Master", "77401", "US", &address_form);
-  FormSubmitted(address_form);
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions());
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-  ExpectFillableFormParsedUkm(2 /* num_fillable_forms_parsed */);
-
-  // Edit the data, and submit.
-  credit_card_form.fields[0].value = ASCIIToUTF16("Flo Master");
-  credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111");
-  credit_card_form.fields[2].value = ASCIIToUTF16(NextMonth());
-  credit_card_form.fields[3].value = ASCIIToUTF16(NextYear());
-  credit_card_form.fields[4].value = ASCIIToUTF16("123");
-
-  base::HistogramTester histogram_tester;
-
-  FormSubmitted(credit_card_form);
-  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
-  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
-
-  // Verify that adding the card logged the number of strikes it had previously.
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.StrikeDatabase.StrikesPresentWhenServerCardSaved",
-      /*sample=*/2, /*count=*/1);
-}
-
-// Tests that one LegacyStrikeDatabase strike is added when upload failed and
-// bubble is shown.
-TEST_F(CreditCardSaveManagerTest,
-       UploadCreditCard_NumLegacyStrikesLoggedOnUploadNotSuccess) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystem},
-      // Disabled
-      {features::kAutofillSaveCreditCardUsesStrikeSystemV2});
-  const char* const server_id = "InstrumentData:1234";
-  payments_client_->SetServerIdForCardUpload(server_id);
-  EXPECT_EQ(0, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-
-  // If upload failed and the bubble was shown, strike count should increase
-  // by 1.
-  credit_card_save_manager_->set_show_save_prompt(true);
-  credit_card_save_manager_->set_upload_request_card_number(
-      ASCIIToUTF16("4111111111111111"));
-  credit_card_save_manager_->OnDidUploadCard(AutofillClient::TRY_AGAIN_FAILURE,
-                                             server_id);
-  EXPECT_EQ(1, legacy_strike_database_->GetStrikesForTesting(
-                   legacy_strike_database_->GetKeyForCreditCardSave("1111")));
-}
-
 // Tests that a card with some strikes (but not max strikes) should still show
 // the save bubble/infobar.
 TEST_F(CreditCardSaveManagerTest,
diff --git a/components/autofill/core/browser/payments/legacy_strike_database.cc b/components/autofill/core/browser/payments/legacy_strike_database.cc
deleted file mode 100644
index 5868f87..0000000
--- a/components/autofill/core/browser/payments/legacy_strike_database.cc
+++ /dev/null
@@ -1,187 +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 "components/autofill/core/browser/payments/legacy_strike_database.h"
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/task/post_task.h"
-#include "base/time/time.h"
-#include "components/autofill/core/browser/proto/strike_data.pb.h"
-#include "components/leveldb_proto/public/proto_database_provider.h"
-
-namespace autofill {
-
-namespace {
-const char kLegacyDatabaseKeyDeliminator[] = "__";
-const char kKeyPrefixForCreditCardSave[] = "creditCardSave";
-}  // namespace
-
-LegacyStrikeDatabase::LegacyStrikeDatabase(
-    leveldb_proto::ProtoDatabaseProvider* db_provider,
-    base::FilePath profile_path)
-    : weak_ptr_factory_(this) {
-  auto strike_database_path =
-      profile_path.Append(FILE_PATH_LITERAL("AutofillStrikeDatabase"));
-
-  auto database_task_runner = base::CreateSequencedTaskRunnerWithTraits(
-      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
-       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
-
-  db_ = db_provider->GetDB<StrikeData>(
-      leveldb_proto::ProtoDbType::STRIKE_DATABASE, strike_database_path,
-      database_task_runner);
-
-  db_->Init(base::BindRepeating(&LegacyStrikeDatabase::OnDatabaseInit,
-                                weak_ptr_factory_.GetWeakPtr()));
-}
-
-LegacyStrikeDatabase::~LegacyStrikeDatabase() {}
-
-void LegacyStrikeDatabase::GetStrikes(const std::string key,
-                                      const StrikesCallback& outer_callback) {
-  GetStrikeData(key, base::BindRepeating(&LegacyStrikeDatabase::OnGetStrikes,
-                                         base::Unretained(this),
-                                         std::move(outer_callback)));
-}
-
-void LegacyStrikeDatabase::AddStrike(const std::string key,
-                                     const StrikesCallback& outer_callback) {
-  GetStrikeData(key, base::BindRepeating(&LegacyStrikeDatabase::OnAddStrike,
-                                         base::Unretained(this),
-                                         std::move(outer_callback), key));
-}
-
-void LegacyStrikeDatabase::ClearAllStrikes(
-    const ClearStrikesCallback& outer_callback) {
-  // For deleting all, filter method always returns true.
-  db_->UpdateEntriesWithRemoveFilter(
-      std::make_unique<StrikeDataProto::KeyEntryVector>(),
-      base::BindRepeating([](const std::string& key) { return true; }),
-      base::BindRepeating(&LegacyStrikeDatabase::OnClearAllStrikes,
-                          base::Unretained(this), outer_callback));
-}
-
-void LegacyStrikeDatabase::ClearAllStrikesForKey(
-    const std::string& key,
-    const ClearStrikesCallback& outer_callback) {
-  std::unique_ptr<std::vector<std::string>> keys_to_remove(
-      new std::vector<std::string>());
-  keys_to_remove->push_back(key);
-  db_->UpdateEntries(
-      /*entries_to_save=*/std::make_unique<
-          leveldb_proto::ProtoDatabase<StrikeData>::KeyEntryVector>(),
-      /*keys_to_remove=*/std::move(keys_to_remove),
-      base::BindRepeating(&LegacyStrikeDatabase::OnClearAllStrikesForKey,
-                          base::Unretained(this), outer_callback));
-}
-
-std::string LegacyStrikeDatabase::GetKeyForCreditCardSave(
-    const std::string& card_last_four_digits) {
-  return CreateKey(GetKeyPrefixForCreditCardSave(), card_last_four_digits);
-}
-
-LegacyStrikeDatabase::LegacyStrikeDatabase()
-    : db_(nullptr), weak_ptr_factory_(this) {}
-
-void LegacyStrikeDatabase::OnDatabaseInit(
-    leveldb_proto::Enums::InitStatus status) {}
-
-void LegacyStrikeDatabase::GetStrikeData(const std::string key,
-                                         const GetValueCallback& callback) {
-  db_->GetEntry(key, callback);
-}
-
-void LegacyStrikeDatabase::SetStrikeData(const std::string& key,
-                                         const StrikeData& data,
-                                         const SetValueCallback& callback) {
-  std::unique_ptr<StrikeDataProto::KeyEntryVector> entries(
-      new StrikeDataProto::KeyEntryVector());
-  entries->push_back(std::make_pair(key, data));
-  db_->UpdateEntries(
-      /*entries_to_save=*/std::move(entries),
-      /*keys_to_remove=*/std::make_unique<std::vector<std::string>>(),
-      callback);
-}
-
-void LegacyStrikeDatabase::OnGetStrikes(
-    StrikesCallback callback,
-    bool success,
-    std::unique_ptr<StrikeData> strike_data) {
-  if (success && strike_data)
-    callback.Run(strike_data->num_strikes());
-  else
-    callback.Run(0);
-}
-
-void LegacyStrikeDatabase::OnAddStrike(
-    StrikesCallback callback,
-    std::string key,
-    bool success,
-    std::unique_ptr<StrikeData> strike_data) {
-  if (!success) {
-    // Failed to get strike data; abort adding strike.
-    callback.Run(0);
-    return;
-  }
-  int num_strikes = strike_data ? strike_data->num_strikes() + 1 : 1;
-  StrikeData data;
-  data.set_num_strikes(num_strikes);
-  data.set_last_update_timestamp(
-      base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
-  SetStrikeData(
-      key, data,
-      base::BindRepeating(&LegacyStrikeDatabase::OnAddStrikeComplete,
-                          base::Unretained(this), callback, num_strikes, key));
-}
-
-void LegacyStrikeDatabase::OnAddStrikeComplete(StrikesCallback callback,
-                                               int num_strikes,
-                                               std::string key,
-                                               bool success) {
-  if (success) {
-    if (GetPrefixFromKey(key) == kKeyPrefixForCreditCardSave) {
-      base::UmaHistogramCounts1000(
-          "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", num_strikes);
-    }
-    callback.Run(num_strikes);
-  } else {
-    callback.Run(0);
-  }
-}
-
-void LegacyStrikeDatabase::OnClearAllStrikes(ClearStrikesCallback callback,
-                                             bool success) {
-  callback.Run(success);
-}
-
-void LegacyStrikeDatabase::OnClearAllStrikesForKey(
-    ClearStrikesCallback callback,
-    bool success) {
-  callback.Run(success);
-}
-
-void LegacyStrikeDatabase::LoadKeys(const LoadKeysCallback& callback) {
-  db_->LoadKeys(callback);
-}
-
-std::string LegacyStrikeDatabase::CreateKey(
-    const std::string& type_prefix,
-    const std::string& identifier_suffix) {
-  return type_prefix + kLegacyDatabaseKeyDeliminator + identifier_suffix;
-}
-
-std::string LegacyStrikeDatabase::GetKeyPrefixForCreditCardSave() {
-  return kKeyPrefixForCreditCardSave;
-}
-
-std::string LegacyStrikeDatabase::GetPrefixFromKey(const std::string& key) {
-  return key.substr(0, key.find(kLegacyDatabaseKeyDeliminator));
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/legacy_strike_database.h b/components/autofill/core/browser/payments/legacy_strike_database.h
deleted file mode 100644
index 4f0f7d2..0000000
--- a/components/autofill/core/browser/payments/legacy_strike_database.h
+++ /dev/null
@@ -1,150 +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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_LEGACY_STRIKE_DATABASE_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_LEGACY_STRIKE_DATABASE_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/callback_forward.h"
-#include "base/memory/weak_ptr.h"
-#include "components/keyed_service/core/keyed_service.h"
-#include "components/leveldb_proto/public/proto_database_provider.h"
-
-namespace autofill {
-class StrikeData;
-
-// Manages data on whether different Autofill opportunities should be offered to
-// the user. Projects can earn strikes in a number of ways; for instance, if a
-// user ignores or declines a prompt, or if a user accepts a prompt but the task
-// fails.
-
-// Here's how to create a new project type:
-// 1) The keys used for this database are in form
-// <ProjectTypePrefixName>__<SomeIdentifierSuffix>. In
-// legacy_strike_database.cc, add a char[] variable called
-// kKeyPrefixFor<ProjectType>.
-// 2) In legacy_strike_database.h/cc, create the functions
-//   GetKeyFor<ProjectType>(const std::string& identifier) and
-//   GetKeyPrefixFor<ProjectType>().
-// 3) Add new project type to the if block in
-// LegacyStrikeDatabase::OnAddStrikeComplete(~).
-
-class LegacyStrikeDatabase : public KeyedService {
- public:
-  using ClearStrikesCallback = base::RepeatingCallback<void(bool success)>;
-
-  using StrikesCallback = base::RepeatingCallback<void(int num_strikes)>;
-
-  using GetValueCallback =
-      base::RepeatingCallback<void(bool success,
-                                   std::unique_ptr<StrikeData> data)>;
-
-  using SetValueCallback = base::RepeatingCallback<void(bool success)>;
-
-  using LoadKeysCallback =
-      base::RepeatingCallback<void(bool success,
-                                   std::unique_ptr<std::vector<std::string>>)>;
-
-  using StrikeDataProto = leveldb_proto::ProtoDatabase<StrikeData>;
-
-  explicit LegacyStrikeDatabase(
-      leveldb_proto::ProtoDatabaseProvider* db_provider,
-      base::FilePath profile_path);
-  ~LegacyStrikeDatabase() override;
-
-  // Passes the number of strikes for |key| to |outer_callback|. In the case
-  // that the database fails to retrieve the strike update or if no entry is
-  // found for |key|, 0 is passed.
-  virtual void GetStrikes(const std::string key,
-                          const StrikesCallback& outer_callback);
-
-  // Increments strike count by 1 and passes the updated strike count to the
-  // callback. In the case of |key| has no entry, a StrikeData entry with strike
-  // count of 1 is added to the database. If the database fails to save or
-  // retrieve the strike update, 0 is passed to |outer_callback|.
-  virtual void AddStrike(const std::string key,
-                         const StrikesCallback& outer_callback);
-
-  // Removes all database entries, which implicitly resets all strike counts to
-  // 0.
-  virtual void ClearAllStrikes(const ClearStrikesCallback& outer_callback);
-
-  // Removes database entry for |key|, which implicitly sets strike count to 0.
-  virtual void ClearAllStrikesForKey(
-      const std::string& key,
-      const ClearStrikesCallback& outer_callback);
-
-  // Returns concatenation of prefix + |card_last_four_digits| to be used as key
-  // for credit card save. Expiration date is not included for privacy reasons,
-  // as conflicting last-four should be a rare event, and it's not a huge issue
-  // if we stop showing save bubbles a little earlier than usual in rare cases.
-  std::string GetKeyForCreditCardSave(const std::string& card_last_four_digits);
-
- protected:
-  // Constructor for testing that does not initialize a ProtoDatabase.
-  LegacyStrikeDatabase();
-
-  std::unique_ptr<leveldb_proto::ProtoDatabase<StrikeData>> db_;
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(LegacyStrikeDatabaseTest, GetPrefixFromKey);
-  FRIEND_TEST_ALL_PREFIXES(ChromeBrowsingDataRemoverDelegateTest,
-                           LegacyStrikeDatabaseEmptyOnAutofillRemoveEverything);
-  friend class LegacyStrikeDatabaseTest;
-  friend class LegacyStrikeDatabaseTester;
-
-  void OnDatabaseInit(leveldb_proto::Enums::InitStatus status);
-
-  // Passes success status and StrikeData entry for |key| to |inner_callback|.
-  void GetStrikeData(const std::string key,
-                     const GetValueCallback& inner_callback);
-
-  // Sets the entry for |key| to |strike_data|. Success status is passed to the
-  // callback.
-  void SetStrikeData(const std::string& key,
-                     const StrikeData& strike_data,
-                     const SetValueCallback& inner_callback);
-
-  // Passes number of strikes to |outer_callback|.
-  void OnGetStrikes(StrikesCallback outer_callback,
-                    bool success,
-                    std::unique_ptr<StrikeData> strike_data);
-
-  // Updates database entry for |key| to increment num_strikes by 1, then passes
-  // the updated strike count to |outer_callback|.
-  void OnAddStrike(StrikesCallback outer_callback,
-                   std::string key,
-                   bool success,
-                   std::unique_ptr<StrikeData> strike_data);
-
-  void OnAddStrikeComplete(StrikesCallback outer_callback,
-                           int num_strikes,
-                           std::string key,
-                           bool success);
-
-  void OnClearAllStrikes(ClearStrikesCallback outer_callback, bool success);
-
-  void OnClearAllStrikesForKey(ClearStrikesCallback outer_callback,
-                               bool success);
-
-  // Exposed for testing purposes.
-  void LoadKeys(const LoadKeysCallback& callback);
-
-  // Concatenates type prefix and identifier suffix to create a key.
-  std::string CreateKey(const std::string& type_prefix,
-                        const std::string& identifier_suffix);
-
-  std::string GetKeyPrefixForCreditCardSave();
-
-  std::string GetPrefixFromKey(const std::string& key);
-
-  base::WeakPtrFactory<LegacyStrikeDatabase> weak_ptr_factory_;
-};
-
-}  // namespace autofill
-
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_LEGACY_STRIKE_DATABASE_H_
diff --git a/components/autofill/core/browser/payments/legacy_strike_database_unittest.cc b/components/autofill/core/browser/payments/legacy_strike_database_unittest.cc
deleted file mode 100644
index 559a516..0000000
--- a/components/autofill/core/browser/payments/legacy_strike_database_unittest.cc
+++ /dev/null
@@ -1,292 +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 "components/autofill/core/browser/payments/legacy_strike_database.h"
-
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "components/autofill/core/browser/proto/strike_data.pb.h"
-#include "components/leveldb_proto/public/proto_database.h"
-#include "components/leveldb_proto/public/proto_database_provider.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace autofill {
-namespace {
-
-// Note: This class is NOT the same as test_legacy_strike_database.h. This is an
-// actual implementation of LegacyStrikeDatabase, but with helper functions
-// added for easier test setup.
-class TestLegacyStrikeDatabase : public LegacyStrikeDatabase {
- public:
-  TestLegacyStrikeDatabase(leveldb_proto::ProtoDatabaseProvider* db_provider,
-                           base::FilePath profile_path)
-      : LegacyStrikeDatabase(db_provider, profile_path) {}
-
-  void AddEntries(
-      std::vector<std::pair<std::string, StrikeData>> entries_to_add,
-      const SetValueCallback& callback) {
-    std::unique_ptr<leveldb_proto::ProtoDatabase<StrikeData>::KeyEntryVector>
-        entries(new leveldb_proto::ProtoDatabase<StrikeData>::KeyEntryVector());
-    for (std::pair<std::string, StrikeData> entry : entries_to_add) {
-      entries->push_back(entry);
-    }
-    db_->UpdateEntries(
-        /*entries_to_save=*/std::move(entries),
-        /*keys_to_remove=*/std::make_unique<std::vector<std::string>>(),
-        callback);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TestLegacyStrikeDatabase);
-};
-
-}  // anonymous namespace
-
-// Runs tests against the actual LegacyStrikeDatabase class, complete with
-// ProtoDatabase.
-class LegacyStrikeDatabaseTest : public ::testing::Test {
- public:
-  LegacyStrikeDatabaseTest() {}
-
-  void SetUp() override {
-    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
-
-    db_provider_ = std::make_unique<leveldb_proto::ProtoDatabaseProvider>(
-        temp_dir_.GetPath());
-
-    legacy_strike_database_ = std::make_unique<TestLegacyStrikeDatabase>(
-        db_provider_.get(), temp_dir_.GetPath());
-  }
-
-  void AddEntries(
-      std::vector<std::pair<std::string, StrikeData>> entries_to_add) {
-    base::RunLoop run_loop;
-    legacy_strike_database_->AddEntries(
-        entries_to_add,
-        base::BindRepeating(&LegacyStrikeDatabaseTest::OnAddEntries,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-  }
-
-  void OnAddEntries(base::RepeatingClosure run_loop_closure, bool success) {
-    run_loop_closure.Run();
-  }
-
-  void OnGetStrikes(base::RepeatingClosure run_loop_closure, int num_strikes) {
-    num_strikes_ = num_strikes;
-    run_loop_closure.Run();
-  }
-
-  int GetStrikes(std::string key) {
-    base::RunLoop run_loop;
-    legacy_strike_database_->GetStrikes(
-        key,
-        base::BindRepeating(&LegacyStrikeDatabaseTest::OnGetStrikes,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-    return num_strikes_;
-  }
-
-  void OnAddStrike(base::RepeatingClosure run_loop_closure, int num_strikes) {
-    num_strikes_ = num_strikes;
-    run_loop_closure.Run();
-  }
-
-  int AddStrike(std::string key) {
-    base::RunLoop run_loop;
-    legacy_strike_database_->AddStrike(
-        key,
-        base::BindRepeating(&LegacyStrikeDatabaseTest::OnAddStrike,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-    return num_strikes_;
-  }
-
-  void OnClearAllStrikesForKey(base::RepeatingClosure run_loop_closure,
-                               bool success) {
-    run_loop_closure.Run();
-  }
-
-  void ClearAllStrikesForKey(const std::string key) {
-    base::RunLoop run_loop;
-    legacy_strike_database_->ClearAllStrikesForKey(
-        key,
-        base::BindRepeating(&LegacyStrikeDatabaseTest::OnClearAllStrikesForKey,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-  }
-
-  void OnClearAllStrikes(base::RepeatingClosure run_loop_closure,
-                         bool success) {
-    run_loop_closure.Run();
-  }
-
-  void ClearAllStrikes() {
-    base::RunLoop run_loop;
-    legacy_strike_database_->ClearAllStrikes(
-        base::BindRepeating(&LegacyStrikeDatabaseTest::OnClearAllStrikesForKey,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-  }
-
- protected:
-  base::HistogramTester* GetHistogramTester() { return &histogram_tester_; }
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
-  std::unique_ptr<leveldb_proto::ProtoDatabaseProvider> db_provider_;
-  std::unique_ptr<TestLegacyStrikeDatabase> legacy_strike_database_;
-  base::ScopedTempDir temp_dir_;
-
- private:
-  leveldb_proto::ProtoDatabaseProvider* GetDBProvider() {
-    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
-    db_provider_ = std::make_unique<leveldb_proto::ProtoDatabaseProvider>(
-        temp_dir_.GetPath());
-
-    return db_provider_.get();
-  }
-
-  base::HistogramTester histogram_tester_;
-  int num_strikes_;
-  std::unique_ptr<StrikeData> strike_data_;
-};
-
-TEST_F(LegacyStrikeDatabaseTest, AddStrikeTest) {
-  const std::string key = "12345";
-  int strikes = AddStrike(key);
-  EXPECT_EQ(1, strikes);
-  strikes = AddStrike(key);
-  EXPECT_EQ(2, strikes);
-}
-
-TEST_F(LegacyStrikeDatabaseTest, GetStrikeForZeroStrikesTest) {
-  const std::string key = "12345";
-  int strikes = GetStrikes(key);
-  EXPECT_EQ(0, strikes);
-}
-
-TEST_F(LegacyStrikeDatabaseTest, GetStrikeForNonZeroStrikesTest) {
-  // Set up database with 3 pre-existing strikes at |key|.
-  const std::string key = "12345";
-  std::vector<std::pair<std::string, StrikeData>> entries;
-  StrikeData data;
-  data.set_num_strikes(3);
-  entries.push_back(std::make_pair(key, data));
-  AddEntries(entries);
-
-  int strikes = GetStrikes(key);
-  EXPECT_EQ(3, strikes);
-}
-
-TEST_F(LegacyStrikeDatabaseTest, ClearStrikesForZeroStrikesTest) {
-  const std::string key = "12345";
-  ClearAllStrikesForKey(key);
-  int strikes = GetStrikes(key);
-  EXPECT_EQ(0, strikes);
-}
-
-TEST_F(LegacyStrikeDatabaseTest, ClearStrikesForNonZeroStrikesTest) {
-  // Set up database with 3 pre-existing strikes at |key|.
-  const std::string key = "12345";
-  std::vector<std::pair<std::string, StrikeData>> entries;
-  StrikeData data;
-  data.set_num_strikes(3);
-  entries.push_back(std::make_pair(key, data));
-  AddEntries(entries);
-
-  int strikes = GetStrikes(key);
-  EXPECT_EQ(3, strikes);
-  ClearAllStrikesForKey(key);
-  strikes = GetStrikes(key);
-  EXPECT_EQ(0, strikes);
-}
-
-TEST_F(LegacyStrikeDatabaseTest,
-       ClearStrikesForMultipleNonZeroStrikesEntriesTest) {
-  // Set up database with 3 pre-existing strikes at |key1|, and 5 pre-existing
-  // strikes at |key2|.
-  const std::string key1 = "12345";
-  const std::string key2 = "13579";
-  std::vector<std::pair<std::string, StrikeData>> entries;
-  StrikeData data1;
-  data1.set_num_strikes(3);
-  entries.push_back(std::make_pair(key1, data1));
-  StrikeData data2;
-  data2.set_num_strikes(5);
-  entries.push_back(std::make_pair(key2, data2));
-  AddEntries(entries);
-
-  int strikes = GetStrikes(key1);
-  EXPECT_EQ(3, strikes);
-  strikes = GetStrikes(key2);
-  EXPECT_EQ(5, strikes);
-  ClearAllStrikesForKey(key1);
-  strikes = GetStrikes(key1);
-  EXPECT_EQ(0, strikes);
-  strikes = GetStrikes(key2);
-  EXPECT_EQ(5, strikes);
-}
-
-TEST_F(LegacyStrikeDatabaseTest, ClearAllStrikesTest) {
-  // Set up database with 3 pre-existing strikes at |key1|, and 5 pre-existing
-  // strikes at |key2|.
-  const std::string key1 = "12345";
-  const std::string key2 = "13579";
-  std::vector<std::pair<std::string, StrikeData>> entries;
-  StrikeData data1;
-  data1.set_num_strikes(3);
-  entries.push_back(std::make_pair(key1, data1));
-  StrikeData data2;
-  data2.set_num_strikes(5);
-  entries.push_back(std::make_pair(key2, data2));
-  AddEntries(entries);
-
-  EXPECT_EQ(3, GetStrikes(key1));
-  EXPECT_EQ(5, GetStrikes(key2));
-  ClearAllStrikes();
-  EXPECT_EQ(0, GetStrikes(key1));
-  EXPECT_EQ(0, GetStrikes(key2));
-}
-
-TEST_F(LegacyStrikeDatabaseTest, GetKeyForCreditCardSave) {
-  const std::string last_four = "1234";
-  EXPECT_EQ("creditCardSave__1234",
-            legacy_strike_database_->GetKeyForCreditCardSave(last_four));
-}
-
-TEST_F(LegacyStrikeDatabaseTest, GetPrefixFromKey) {
-  const std::string key = "creditCardSave__1234";
-  EXPECT_EQ("creditCardSave", legacy_strike_database_->GetPrefixFromKey(key));
-}
-
-TEST_F(LegacyStrikeDatabaseTest, CreditCardSaveNthStrikeAddedHistogram) {
-  const std::string last_four1 = "1234";
-  const std::string last_four2 = "9876";
-  const std::string key1 = "NotACreditCard";
-  // 1st strike added for |last_four1|.
-  AddStrike(legacy_strike_database_->GetKeyForCreditCardSave(last_four1));
-  // 2nd strike added for |last_four1|.
-  AddStrike(legacy_strike_database_->GetKeyForCreditCardSave(last_four1));
-  // 1st strike added for |last_four2|.
-  AddStrike(legacy_strike_database_->GetKeyForCreditCardSave(last_four2));
-  // Shouldn't be counted in histogram since key doesn't have prefix for credit
-  // cards.
-  AddStrike(key1);
-  std::vector<base::Bucket> buckets = GetHistogramTester()->GetAllSamples(
-      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave");
-  // There should be two buckets, one for 1st strike, one for 2nd strike count.
-  ASSERT_EQ(2U, buckets.size());
-  // Both |last_four1| and |last_four2| have 1st strikes recorded.
-  EXPECT_EQ(2, buckets[0].count);
-  // Only |last_four1| has 2nd strike recorded.
-  EXPECT_EQ(1, buckets[1].count);
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/local_card_migration_manager.cc b/components/autofill/core/browser/payments/local_card_migration_manager.cc
index d11e814..5cf85ec 100644
--- a/components/autofill/core/browser/payments/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/payments/local_card_migration_manager.cc
@@ -49,9 +49,7 @@
           features::kAutofillSaveCreditCardUsesStrikeSystemV2) ||
       base::FeatureList::IsEnabled(
           features::kAutofillLocalCardMigrationUsesStrikeSystemV2)) {
-    // Only init when |kAutofillSaveCreditCardUsesStrikeSystemV2| is enabled. If
-    // flag is off and LegacyStrikeDatabase instead of StrikeDatabase is used,
-    // this init will cause failure on GetStrikes().
+    // Only init when |kAutofillSaveCreditCardUsesStrikeSystemV2| is enabled.
     client_->GetStrikeDatabase();
   }
 }
@@ -122,6 +120,13 @@
            FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD &&
        !migratable_credit_cards_.empty())) {
     return true;
+  } else if (imported_credit_card_record_type ==
+                 FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD &&
+             migratable_credit_cards_.size() == 1) {
+    AutofillMetrics::LogLocalCardMigrationDecisionMetric(
+        AutofillMetrics::LocalCardMigrationDecisionMetric::
+            NOT_OFFERED_SINGLE_LOCAL_CARD);
+    return false;
   } else {
     AutofillMetrics::LogLocalCardMigrationDecisionMetric(
         AutofillMetrics::LocalCardMigrationDecisionMetric::
diff --git a/components/autofill/core/browser/payments/local_card_migration_manager_unittest.cc b/components/autofill/core/browser/payments/local_card_migration_manager_unittest.cc
index f95daf3..8a153f49 100644
--- a/components/autofill/core/browser/payments/local_card_migration_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/local_card_migration_manager_unittest.cc
@@ -231,7 +231,7 @@
     // will enter below.
     AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111111", "11",
                        test::NextYear().c_str(), "1", "guid1");
-    // Add other invalid local credit cards(invalid card number or expired), so
+    // Add other invalid local credit cards (invalid card number or expired), so
     // it will not trigger migration.
     AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111112", "11",
                        test::NextYear().c_str(), "1", "guid2");
@@ -249,6 +249,37 @@
     FormSubmitted(credit_card_form);
   }
 
+  void UseServerCardWithInvalidLocalCardsOnFile() {
+    // Set the billing_customer_number to designate existence of a Payments
+    // account.
+    personal_data_.SetPaymentsCustomerData(
+        std::make_unique<PaymentsCustomerData>(/*customer_id=*/"123456"));
+
+    // Add a masked credit card whose |TypeAndLastFourDigits| matches what we
+    // will enter below.
+    CreditCard credit_card(CreditCard::MASKED_SERVER_CARD, "a123");
+    test::SetCreditCardInfo(&credit_card, "Flo Master", "1111", "11",
+                            test::NextYear().c_str(), "1");
+    credit_card.SetNetworkForMaskedCard(kVisaCard);
+    personal_data_.AddServerCreditCard(credit_card);
+    // Add other invalid local credit cards (invalid card number or expired), so
+    // it will not trigger migration.
+    AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111112", "11",
+                       test::NextYear().c_str(), "1", "guid1");
+    AddLocalCreditCard(personal_data_, "Flo Master", "5555555555554444", "11",
+                       test::LastYear().c_str(), "1", "guid2");
+
+    // Set up our credit card form data.
+    FormData credit_card_form;
+    test::CreateTestCreditCardFormData(&credit_card_form, true, false);
+    FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+    // Edit the data, and submit.
+    EditCreditCardFrom(credit_card_form, "Flo Master", "4111111111111111", "11",
+                       test::NextYear().c_str(), "123");
+    FormSubmitted(credit_card_form);
+  }
+
  protected:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
@@ -388,8 +419,8 @@
   // enter below.
   AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111111", "11",
                      test::NextYear().c_str(), "1", "guid1");
-  // Add other invalid local credit cards(invalid card number or expired), so it
-  // will not trigger migration.
+  // Add other invalid local credit cards (invalid card number or expired), so
+  // it will not trigger migration.
   AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111112", "11",
                      test::NextYear().c_str(), "1", "guid2");
   AddLocalCreditCard(personal_data_, "Flo Master", "5555555555554444", "11",
@@ -459,35 +490,9 @@
 // cards as long as the other local cards are not eligible for migration.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_UseServerCardWithNoneValidLocal) {
-  // Set the billing_customer_number to designate existence of a Payments
-  // account.
-  personal_data_.SetPaymentsCustomerData(
-      std::make_unique<PaymentsCustomerData>(/*customer_id=*/"123456"));
-
-  // Add a masked credit card whose |TypeAndLastFourDigits| matches what we will
-  // enter below.
-  CreditCard credit_card(CreditCard::MASKED_SERVER_CARD, "a123");
-  test::SetCreditCardInfo(&credit_card, "Flo Master", "1111", "11",
-                          test::NextYear().c_str(), "1");
-  credit_card.SetNetworkForMaskedCard(kVisaCard);
-  personal_data_.AddServerCreditCard(credit_card);
-  // Add other invalid local credit cards(invalid card number or expired), so it
-  // will not trigger migration.
-  AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111112", "11",
-                     test::NextYear().c_str(), "1", "guid1");
-  AddLocalCreditCard(personal_data_, "Flo Master", "5555555555554444", "11",
-                     test::LastYear().c_str(), "1", "guid2");
-
-  // Set up our credit card form data.
-  FormData credit_card_form;
-  test::CreateTestCreditCardFormData(&credit_card_form, true, false);
-  FormsSeen(std::vector<FormData>(1, credit_card_form));
-
   base::HistogramTester histogram_tester;
-  // Edit the data, and submit.
-  EditCreditCardFrom(credit_card_form, "Flo Master", "4111111111111111", "11",
-                     test::NextYear().c_str(), "123");
-  FormSubmitted(credit_card_form);
+  UseServerCardWithInvalidLocalCardsOnFile();
+
   EXPECT_FALSE(local_card_migration_manager_->LocalCardMigrationWasTriggered());
 
   // Verify that metrics are correctly logged to the UseOfServerCard
@@ -1404,7 +1409,7 @@
 }
 
 // Use new card when submit so migration was not offered. Verify the migration
-// decision metic is logged as new card used.
+// decision metric is logged as new card used.
 TEST_F(LocalCardMigrationManagerTest, LogMigrationDecisionMetric_UseNewCard) {
   base::HistogramTester histogram_tester;
   UseNewCardWithLocalCardsOnFile();
@@ -1416,7 +1421,7 @@
 
 // Use one local card with more valid local cards available but billing customer
 // number is blank, will not trigger migration. Verify the migration decision
-// metic is logged as failed enablement prerequisites.
+// metric is logged as failed enablement prerequisites.
 TEST_F(LocalCardMigrationManagerTest,
        LogMigrationDecisionMetric_FailedEnablementPrerequisites) {
   base::HistogramTester histogram_tester;
@@ -1444,7 +1449,7 @@
 }
 
 // All migration requirements are met but max strikes reached. Verify the
-// migration decision metic is logged as max strikes reached.
+// migration decision metric is logged as max strikes reached.
 TEST_F(LocalCardMigrationManagerTest,
        LogMigrationDecisionMetric_MaxStrikesReached) {
   scoped_feature_list_.InitAndEnableFeature(
@@ -1467,11 +1472,24 @@
 }
 
 // Use one local card with invalid local card so migration was not offered.
-// Verify the migration decision metic is logged as no migratable cards.
+// Verify the migration decision metric is logged as not offered single local
+// card.
+TEST_F(LocalCardMigrationManagerTest,
+       LogMigrationDecisionMetric_NotOfferedSingleLocalCard) {
+  base::HistogramTester histogram_tester;
+  UseLocalCardWithInvalidLocalCardsOnFile();
+
+  ExpectUniqueLocalCardMigrationDecision(
+      histogram_tester, AutofillMetrics::LocalCardMigrationDecisionMetric::
+                            NOT_OFFERED_SINGLE_LOCAL_CARD);
+}
+
+// Use one server card with invalid local card so migration was not offered.
+// Verify the migration decision metric is logged as no migratable cards.
 TEST_F(LocalCardMigrationManagerTest,
        LogMigrationDecisionMetric_NoMigratableCards) {
   base::HistogramTester histogram_tester;
-  UseLocalCardWithInvalidLocalCardsOnFile();
+  UseServerCardWithInvalidLocalCardsOnFile();
 
   ExpectUniqueLocalCardMigrationDecision(
       histogram_tester, AutofillMetrics::LocalCardMigrationDecisionMetric::
@@ -1479,7 +1497,7 @@
 }
 
 // All migration requirements are met but GetUploadDetails rpc fails. Verify the
-// migration decision metic is logged as get upload details failed.
+// migration decision metric is logged as get upload details failed.
 TEST_F(LocalCardMigrationManagerTest,
        LogMigrationDecisionMetric_GetUploadDetailsFails) {
   // Anything other than "en-US" will cause GetUploadDetails to return a failure
@@ -1495,7 +1513,7 @@
 }
 
 // Use one unsupported local card with more unsupported local cards will not
-// trigger migration. Verify the migration decision metic is logged as no
+// trigger migration. Verify the migration decision metric is logged as no
 // supported cards.
 TEST_F(LocalCardMigrationManagerTest,
        LogMigrationDecisionMetric_NoSupportedCards) {
@@ -1537,7 +1555,7 @@
 }
 
 // All migration requirements are met and migration was offered. Verify the
-// migration decision metic is logged as migration offered.
+// migration decision metric is logged as migration offered.
 TEST_F(LocalCardMigrationManagerTest,
        LogMigrationDecisionMetric_MigrationOffered) {
   base::HistogramTester histogram_tester;
diff --git a/components/autofill/core/browser/payments/test_authentication_requester.cc b/components/autofill/core/browser/payments/test_authentication_requester.cc
new file mode 100644
index 0000000..b9d563a
--- /dev/null
+++ b/components/autofill/core/browser/payments/test_authentication_requester.cc
@@ -0,0 +1,33 @@
+// 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 "components/autofill/core/browser/payments/test_authentication_requester.h"
+
+#include "base/strings/string16.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+
+namespace autofill {
+
+TestAuthenticationRequester::TestAuthenticationRequester()
+    : weak_ptr_factory_(this) {}
+
+TestAuthenticationRequester::~TestAuthenticationRequester() {}
+
+base::WeakPtr<TestAuthenticationRequester>
+TestAuthenticationRequester::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+void TestAuthenticationRequester::OnCVCAuthenticationComplete(
+    bool did_succeed,
+    const CreditCard* card,
+    const base::string16& cvc) {
+  if (did_succeed) {
+    number_ = card->number();
+    return Success();
+  }
+  return Failure();
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/test_authentication_requester.h b/components/autofill/core/browser/payments/test_authentication_requester.h
new file mode 100644
index 0000000..e1e7c09
--- /dev/null
+++ b/components/autofill/core/browser/payments/test_authentication_requester.h
@@ -0,0 +1,46 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_AUTHENTICATION_REQUESTER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_AUTHENTICATION_REQUESTER_H_
+
+#include <memory>
+
+#include "base/strings/string16.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/payments/credit_card_cvc_authenticator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+
+// Test class for requesting authentication from CreditCardCVCAuthenticator.
+class TestAuthenticationRequester
+    : public CreditCardCVCAuthenticator::Requester {
+ public:
+  TestAuthenticationRequester();
+  ~TestAuthenticationRequester() override;
+
+  void OnCVCAuthenticationComplete(
+      bool did_succeed,
+      const CreditCard* card = nullptr,
+      const base::string16& cvc = base::string16()) override;
+
+  base::WeakPtr<TestAuthenticationRequester> GetWeakPtr();
+
+  base::string16 number() { return number_; }
+
+  MOCK_METHOD0(Success, void());
+  MOCK_METHOD0(Failure, void());
+
+ private:
+  // The card number returned from OnCVCAuthenticationComplete.
+  base::string16 number_;
+
+  base::WeakPtrFactory<TestAuthenticationRequester> weak_ptr_factory_;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_AUTHENTICATION_REQUESTER_H_
diff --git a/components/autofill/core/browser/payments/test_credit_card_save_manager.h b/components/autofill/core/browser/payments/test_credit_card_save_manager.h
index 7a16e63e..bf13c028 100644
--- a/components/autofill/core/browser/payments/test_credit_card_save_manager.h
+++ b/components/autofill/core/browser/payments/test_credit_card_save_manager.h
@@ -45,9 +45,6 @@
   bool credit_card_upload_enabled_ = false;
   bool credit_card_was_uploaded_ = false;
 
-  FRIEND_TEST_ALL_PREFIXES(
-      CreditCardSaveManagerTest,
-      UploadCreditCard_NumLegacyStrikesLoggedOnUploadNotSuccess);
   FRIEND_TEST_ALL_PREFIXES(CreditCardSaveManagerTest,
                            UploadCreditCard_NumStrikesLoggedOnUploadNotSuccess);
 
diff --git a/components/autofill/core/browser/payments/test_legacy_strike_database.cc b/components/autofill/core/browser/payments/test_legacy_strike_database.cc
deleted file mode 100644
index 0ec6a57..0000000
--- a/components/autofill/core/browser/payments/test_legacy_strike_database.cc
+++ /dev/null
@@ -1,65 +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 "components/autofill/core/browser/payments/test_legacy_strike_database.h"
-
-#include "components/autofill/core/browser/proto/strike_data.pb.h"
-
-namespace autofill {
-
-TestLegacyStrikeDatabase::TestLegacyStrikeDatabase() {}
-
-TestLegacyStrikeDatabase::~TestLegacyStrikeDatabase() {}
-
-void TestLegacyStrikeDatabase::GetStrikes(
-    const std::string key,
-    const StrikesCallback& outer_callback) {
-  outer_callback.Run(GetStrikesForTesting(key));
-}
-
-void TestLegacyStrikeDatabase::AddStrike(
-    const std::string key,
-    const StrikesCallback& outer_callback) {
-  std::unordered_map<std::string, StrikeData>::iterator it = db_.find(key);
-  StrikeData strike_data;
-  strike_data.set_last_update_timestamp(
-      base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
-  if (it != db_.end())
-    strike_data.set_num_strikes(it->second.num_strikes() + 1);
-  else
-    strike_data.set_num_strikes(1);
-  db_[key] = strike_data;
-  outer_callback.Run(strike_data.num_strikes());
-}
-
-void TestLegacyStrikeDatabase::ClearAllStrikes(
-    const ClearStrikesCallback& outer_callback) {
-  db_.clear();
-  outer_callback.Run(/*success=*/true);
-}
-
-void TestLegacyStrikeDatabase::ClearAllStrikesForKey(
-    const std::string& key,
-    const ClearStrikesCallback& outer_callback) {
-  db_.erase(key);
-  outer_callback.Run(/*success=*/true);
-}
-
-void TestLegacyStrikeDatabase::AddEntryWithNumStrikes(const std::string& key,
-                                                      int num_strikes) {
-  StrikeData strike_data;
-  strike_data.set_num_strikes(num_strikes);
-  strike_data.set_last_update_timestamp(
-      base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
-  db_[key] = strike_data;
-}
-
-int TestLegacyStrikeDatabase::GetStrikesForTesting(const std::string& key) {
-  std::unordered_map<std::string, StrikeData>::iterator it = db_.find(key);
-  if (it != db_.end())
-    return it->second.num_strikes();
-  return 0;
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/payments/test_legacy_strike_database.h b/components/autofill/core/browser/payments/test_legacy_strike_database.h
deleted file mode 100644
index ca8ca1a7..0000000
--- a/components/autofill/core/browser/payments/test_legacy_strike_database.h
+++ /dev/null
@@ -1,45 +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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_LEGACY_STRIKE_DATABASE_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_LEGACY_STRIKE_DATABASE_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
-
-namespace autofill {
-
-// An in-memory-only test version of LegacyStrikeDatabase.
-class TestLegacyStrikeDatabase : public LegacyStrikeDatabase {
- public:
-  TestLegacyStrikeDatabase();
-  ~TestLegacyStrikeDatabase() override;
-
-  // LegacyStrikeDatabase:
-  void GetStrikes(const std::string key,
-                  const StrikesCallback& outer_callback) override;
-  void AddStrike(const std::string key,
-                 const StrikesCallback& outer_callback) override;
-  void ClearAllStrikes(const ClearStrikesCallback& outer_callback) override;
-  void ClearAllStrikesForKey(
-      const std::string& key,
-      const ClearStrikesCallback& outer_callback) override;
-
-  // TestLegacyStrikeDatabase:
-  void AddEntryWithNumStrikes(const std::string& key, int num_strikes);
-  int GetStrikesForTesting(const std::string& key);
-
- private:
-  // In-memory database of StrikeData.
-  std::unordered_map<std::string, StrikeData> db_;
-};
-
-}  // namespace autofill
-
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_LEGACY_STRIKE_DATABASE_H_
diff --git a/components/autofill/core/browser/test_autofill_client.cc b/components/autofill/core/browser/test_autofill_client.cc
index fe92b8c..30d9a9b 100644
--- a/components/autofill/core/browser/test_autofill_client.cc
+++ b/components/autofill/core/browser/test_autofill_client.cc
@@ -47,10 +47,6 @@
   return payments_client_.get();
 }
 
-LegacyStrikeDatabase* TestAutofillClient::GetLegacyStrikeDatabase() {
-  return test_legacy_strike_database_.get();
-}
-
 StrikeDatabase* TestAutofillClient::GetStrikeDatabase() {
   return test_strike_database_.get();
 }
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h
index 70217e9..a29b4d9e 100644
--- a/components/autofill/core/browser/test_autofill_client.h
+++ b/components/autofill/core/browser/test_autofill_client.h
@@ -16,7 +16,6 @@
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/mock_autocomplete_history_manager.h"
-#include "components/autofill/core/browser/payments/test_legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/test_payments_client.h"
 #include "components/autofill/core/browser/payments/test_strike_database.h"
 #include "components/autofill/core/browser/test_address_normalizer.h"
@@ -42,7 +41,6 @@
   identity::IdentityManager* GetIdentityManager() override;
   FormDataImporter* GetFormDataImporter() override;
   payments::PaymentsClient* GetPaymentsClient() override;
-  LegacyStrikeDatabase* GetLegacyStrikeDatabase() override;
   StrikeDatabase* GetStrikeDatabase() override;
   ukm::UkmRecorder* GetUkmRecorder() override;
   ukm::SourceId GetUkmSourceId() override;
@@ -125,11 +123,6 @@
     prefs_ = std::move(prefs);
   }
 
-  void set_test_legacy_strike_database(
-      std::unique_ptr<TestLegacyStrikeDatabase> test_legacy_strike_database) {
-    test_legacy_strike_database_ = std::move(test_legacy_strike_database);
-  }
-
   void set_test_strike_database(
       std::unique_ptr<TestStrikeDatabase> test_strike_database) {
     test_strike_database_ = std::move(test_strike_database);
@@ -187,7 +180,6 @@
 
   // NULL by default.
   std::unique_ptr<PrefService> prefs_;
-  std::unique_ptr<TestLegacyStrikeDatabase> test_legacy_strike_database_;
   std::unique_ptr<TestStrikeDatabase> test_strike_database_;
   std::unique_ptr<payments::PaymentsClient> payments_client_;
   std::unique_ptr<FormDataImporter> form_data_importer_;
diff --git a/components/autofill/core/common/autofill_constants.h b/components/autofill/core/common/autofill_constants.h
index ca152e01..59314c26 100644
--- a/components/autofill/core/common/autofill_constants.h
+++ b/components/autofill/core/common/autofill_constants.h
@@ -36,8 +36,8 @@
   IS_PASSWORD_FIELD = 1 << 1 /* input field is a password field */
 };
 
-// Autofill LegacyStrikeDatabase: Maximum strikes allowed for the credit card
-// save project. If the LegacyStrikeDatabase returns this many strikes for a
+// Autofill StrikeDatabase: Maximum strikes allowed for the credit card
+// save project. If the StrikeDatabase returns this many strikes for a
 // given card, it will not show the offer-to-save bubble on Desktop or infobar
 // on Android. On Desktop, however, the omnibox icon will still be available.
 // TODO(crbug.com/884817): Remove once StrikeDatabase v2 moves this constant to
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index 8917d19..0f51ced 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -86,12 +86,6 @@
     "AutofillSaveCardImprovedUserConsent", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Controls whether offering to save cards will consider data from the Autofill
-// strike database.
-const base::Feature kAutofillSaveCreditCardUsesStrikeSystem{
-    "AutofillSaveCreditCardUsesStrikeSystem",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-
-// Controls whether offering to save cards will consider data from the Autofill
 // strike database (new version).
 const base::Feature kAutofillSaveCreditCardUsesStrikeSystemV2{
     "AutofillSaveCreditCardUsesStrikeSystemV2",
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h
index df49dccc..6a29550 100644
--- a/components/autofill/core/common/autofill_payments_features.h
+++ b/components/autofill/core/common/autofill_payments_features.h
@@ -32,7 +32,6 @@
 extern const base::Feature kAutofillNoLocalSaveOnUnmaskSuccess;
 extern const base::Feature kAutofillNoLocalSaveOnUploadSuccess;
 extern const base::Feature kAutofillSaveCardImprovedUserConsent;
-extern const base::Feature kAutofillSaveCreditCardUsesStrikeSystem;
 extern const base::Feature kAutofillSaveCreditCardUsesStrikeSystemV2;
 extern const base::Feature kAutofillSendExperimentIdsInPaymentsRPCs;
 extern const base::Feature kAutofillSendOnlyCountryInGetUploadDetails;
diff --git a/components/autofill/core/common/mojom/BUILD.gn b/components/autofill/core/common/mojom/BUILD.gn
new file mode 100644
index 0000000..98e1ed0
--- /dev/null
+++ b/components/autofill/core/common/mojom/BUILD.gn
@@ -0,0 +1,48 @@
+# 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("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo_types") {
+  sources = [
+    "autofill_types.mojom",
+  ]
+
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//ui/gfx/geometry/mojo",
+    "//url/mojom:url_mojom_gurl",
+    "//url/mojom:url_mojom_origin",
+  ]
+}
+
+mojom("mojo_test_types") {
+  sources = [
+    "test_autofill_types.mojom",
+  ]
+
+  public_deps = [
+    ":mojo_types",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "autofill_types_struct_traits_unittest.cc",
+  ]
+
+  public_deps = [
+    ":mojo_test_types",
+  ]
+
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//components/autofill/core/browser:test_support",
+    "//components/password_manager/core/common",
+    "//mojo/public/cpp/bindings",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/autofill/core/common/mojom/DEPS b/components/autofill/core/common/mojom/DEPS
new file mode 100644
index 0000000..ef8ad28
--- /dev/null
+++ b/components/autofill/core/common/mojom/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+mojo/public",
+]
diff --git a/components/autofill/core/common/mojom/OWNERS b/components/autofill/core/common/mojom/OWNERS
new file mode 100644
index 0000000..2c44a46
--- /dev/null
+++ b/components/autofill/core/common/mojom/OWNERS
@@ -0,0 +1,6 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_struct_traits*.*=set noparent
+per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/components/autofill/content/common/autofill_types.mojom b/components/autofill/core/common/mojom/autofill_types.mojom
similarity index 100%
rename from components/autofill/content/common/autofill_types.mojom
rename to components/autofill/core/common/mojom/autofill_types.mojom
diff --git a/components/autofill/content/common/autofill_types.typemap b/components/autofill/core/common/mojom/autofill_types.typemap
similarity index 91%
rename from components/autofill/content/common/autofill_types.typemap
rename to components/autofill/core/common/mojom/autofill_types.typemap
index 9033346..917c37a 100644
--- a/components/autofill/content/common/autofill_types.typemap
+++ b/components/autofill/core/common/mojom/autofill_types.typemap
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-mojom = "//components/autofill/content/common/autofill_types.mojom"
+mojom = "//components/autofill/core/common/mojom/autofill_types.mojom"
 public_headers = [
   "//components/autofill/core/common/filling_status.h",
   "//components/autofill/core/common/form_data.h",
@@ -17,9 +17,9 @@
   "//components/autofill/core/common/submission_source.h",
 ]
 traits_headers =
-    [ "//components/autofill/content/common/autofill_types_struct_traits.h" ]
+    [ "//components/autofill/core/common/mojom/autofill_types_struct_traits.h" ]
 sources = [
-  "//components/autofill/content/common/autofill_types_struct_traits.cc",
+  "//components/autofill/core/common/mojom/autofill_types_struct_traits.cc",
 ]
 deps = [
   "//base",
diff --git a/components/autofill/content/common/autofill_types_struct_traits.cc b/components/autofill/core/common/mojom/autofill_types_struct_traits.cc
similarity index 99%
rename from components/autofill/content/common/autofill_types_struct_traits.cc
rename to components/autofill/core/common/mojom/autofill_types_struct_traits.cc
index f7b9a6a..adc0d4a 100644
--- a/components/autofill/content/common/autofill_types_struct_traits.cc
+++ b/components/autofill/core/common/mojom/autofill_types_struct_traits.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/autofill/content/common/autofill_types_struct_traits.h"
+#include "components/autofill/core/common/mojom/autofill_types_struct_traits.h"
 
 #include "base/i18n/rtl.h"
 #include "mojo/public/cpp/base/string16_mojom_traits.h"
diff --git a/components/autofill/content/common/autofill_types_struct_traits.h b/components/autofill/core/common/mojom/autofill_types_struct_traits.h
similarity index 98%
rename from components/autofill/content/common/autofill_types_struct_traits.h
rename to components/autofill/core/common/mojom/autofill_types_struct_traits.h
index 43a16f5..af8800b 100644
--- a/components/autofill/content/common/autofill_types_struct_traits.h
+++ b/components/autofill/core/common/mojom/autofill_types_struct_traits.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_AUTOFILL_CONTENT_COMMON_AUTOFILL_TYPES_STRUCT_TRAITS_H_
-#define COMPONENTS_AUTOFILL_CONTENT_COMMON_AUTOFILL_TYPES_STRUCT_TRAITS_H_
+#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_MOJOM_AUTOFILL_TYPES_STRUCT_TRAITS_H_
+#define COMPONENTS_AUTOFILL_CORE_COMMON_MOJOM_AUTOFILL_TYPES_STRUCT_TRAITS_H_
 
 #include <map>
 #include <string>
@@ -12,11 +12,11 @@
 
 #include "base/i18n/rtl.h"
 #include "base/strings/string16.h"
-#include "components/autofill/content/common/autofill_types.mojom.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/form_data_predictions.h"
 #include "components/autofill/core/common/form_field_data.h"
 #include "components/autofill/core/common/form_field_data_predictions.h"
+#include "components/autofill/core/common/mojom/autofill_types.mojom.h"
 #include "components/autofill/core/common/password_form.h"
 #include "components/autofill/core/common/password_form_field_prediction_map.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
@@ -735,4 +735,4 @@
 
 }  // namespace mojo
 
-#endif  // COMPONENTS_AUTOFILL_CONTENT_COMMON_AUTOFILL_TYPES_STRUCT_TRAITS_H_
+#endif  // COMPONENTS_AUTOFILL_CORE_COMMON_MOJOM_AUTOFILL_TYPES_STRUCT_TRAITS_H_
diff --git a/components/autofill/content/common/autofill_types_struct_traits_unittest.cc b/components/autofill/core/common/mojom/autofill_types_struct_traits_unittest.cc
similarity index 91%
rename from components/autofill/content/common/autofill_types_struct_traits_unittest.cc
rename to components/autofill/core/common/mojom/autofill_types_struct_traits_unittest.cc
index d750fa4..8a9c1e52 100644
--- a/components/autofill/content/common/autofill_types_struct_traits_unittest.cc
+++ b/components/autofill/core/common/mojom/autofill_types_struct_traits_unittest.cc
@@ -8,11 +8,11 @@
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_task_environment.h"
-#include "components/autofill/content/common/test_autofill_types.mojom.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/common/button_title_type.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/core/common/mojom/test_autofill_types.mojom.h"
 #include "components/autofill/core/common/password_generation_util.h"
 #include "components/autofill/core/common/signatures_util.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
@@ -276,58 +276,58 @@
 };
 
 void ExpectFormFieldData(const FormFieldData& expected,
-                         const base::Closure& closure,
+                         base::OnceClosure closure,
                          const FormFieldData& passed) {
   EXPECT_EQ(expected, passed);
   EXPECT_EQ(expected.value, passed.value);
   EXPECT_EQ(expected.typed_value, passed.typed_value);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectFormData(const FormData& expected,
-                    const base::Closure& closure,
+                    base::OnceClosure closure,
                     const FormData& passed) {
   EXPECT_EQ(expected, passed);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectFormFieldDataPredictions(const FormFieldDataPredictions& expected,
-                                    const base::Closure& closure,
+                                    base::OnceClosure closure,
                                     const FormFieldDataPredictions& passed) {
   EXPECT_EQ(expected, passed);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectFormDataPredictions(const FormDataPredictions& expected,
-                               const base::Closure& closure,
+                               base::OnceClosure closure,
                                const FormDataPredictions& passed) {
   EXPECT_EQ(expected, passed);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectPasswordFormFillData(const PasswordFormFillData& expected,
-                                const base::Closure& closure,
+                                base::OnceClosure closure,
                                 const PasswordFormFillData& passed) {
   CheckEqualPasswordFormFillData(expected, passed);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectPasswordFormGenerationData(
     const PasswordFormGenerationData& expected,
-    const base::Closure& closure,
+    base::OnceClosure closure,
     const PasswordFormGenerationData& passed) {
   CheckEqualPasswordFormGenerationData(expected, passed);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectNewPasswordFormGenerationData(
     const NewPasswordFormGenerationData& expected,
-    const base::Closure& closure,
+    base::OnceClosure closure,
     const NewPasswordFormGenerationData& passed) {
   EXPECT_EQ(expected.new_password_renderer_id, passed.new_password_renderer_id);
   EXPECT_EQ(expected.confirmation_password_renderer_id,
             passed.confirmation_password_renderer_id);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectPasswordGenerationUIData(
@@ -339,17 +339,17 @@
 }
 
 void ExpectPasswordForm(const PasswordForm& expected,
-                        const base::Closure& closure,
+                        base::OnceClosure closure,
                         const PasswordForm& passed) {
   EXPECT_EQ(expected, passed);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 void ExpectFormsPredictionsMap(const FormsPredictionsMap& expected,
-                               const base::Closure& closure,
+                               base::OnceClosure closure,
                                const FormsPredictionsMap& passed) {
   EXPECT_EQ(expected, passed);
-  closure.Run();
+  std::move(closure).Run();
 }
 
 TEST_F(AutofillTypeTraitsTestImpl, PassFormFieldData) {
@@ -376,7 +376,7 @@
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
   proxy->PassFormFieldData(
-      input, base::Bind(&ExpectFormFieldData, input, loop.QuitClosure()));
+      input, base::BindOnce(&ExpectFormFieldData, input, loop.QuitClosure()));
   loop.Run();
 }
 
@@ -390,8 +390,8 @@
 
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
-  proxy->PassFormData(input,
-                      base::Bind(&ExpectFormData, input, loop.QuitClosure()));
+  proxy->PassFormData(
+      input, base::BindOnce(&ExpectFormData, input, loop.QuitClosure()));
   loop.Run();
 }
 
@@ -402,8 +402,8 @@
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
   proxy->PassFormFieldDataPredictions(
-      input,
-      base::Bind(&ExpectFormFieldDataPredictions, input, loop.QuitClosure()));
+      input, base::BindOnce(&ExpectFormFieldDataPredictions, input,
+                            loop.QuitClosure()));
   loop.Run();
 }
 
@@ -423,7 +423,8 @@
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
   proxy->PassFormDataPredictions(
-      input, base::Bind(&ExpectFormDataPredictions, input, loop.QuitClosure()));
+      input,
+      base::BindOnce(&ExpectFormDataPredictions, input, loop.QuitClosure()));
   loop.Run();
 }
 
@@ -433,8 +434,9 @@
 
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
-  proxy->PassPasswordFormFillData(input, base::Bind(&ExpectPasswordFormFillData,
-                                                    input, loop.QuitClosure()));
+  proxy->PassPasswordFormFillData(
+      input,
+      base::BindOnce(&ExpectPasswordFormFillData, input, loop.QuitClosure()));
   loop.Run();
 }
 
@@ -452,8 +454,8 @@
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
   proxy->PassPasswordFormGenerationData(
-      input,
-      base::Bind(&ExpectPasswordFormGenerationData, input, loop.QuitClosure()));
+      input, base::BindOnce(&ExpectPasswordFormGenerationData, input,
+                            loop.QuitClosure()));
   loop.Run();
 }
 
@@ -489,7 +491,7 @@
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
   proxy->PassPasswordForm(
-      input, base::Bind(&ExpectPasswordForm, input, loop.QuitClosure()));
+      input, base::BindOnce(&ExpectPasswordForm, input, loop.QuitClosure()));
   loop.Run();
 }
 
@@ -500,7 +502,8 @@
   base::RunLoop loop;
   mojom::TypeTraitsTestPtr proxy = GetTypeTraitsTestProxy();
   proxy->PassFormsPredictionsMap(
-      input, base::Bind(&ExpectFormsPredictionsMap, input, loop.QuitClosure()));
+      input,
+      base::BindOnce(&ExpectFormsPredictionsMap, input, loop.QuitClosure()));
   loop.Run();
 }
 
diff --git a/components/autofill/core/common/mojom/test_autofill_types.mojom b/components/autofill/core/common/mojom/test_autofill_types.mojom
new file mode 100644
index 0000000..b9c40d98
--- /dev/null
+++ b/components/autofill/core/common/mojom/test_autofill_types.mojom
@@ -0,0 +1,27 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module autofill.mojom;
+
+import "components/autofill/core/common/mojom/autofill_types.mojom";
+
+interface TypeTraitsTest {
+  PassFormData(FormData s) => (FormData passed);
+  PassFormFieldData(FormFieldData s) => (FormFieldData passed);
+  PassFormDataPredictions(FormDataPredictions s) =>
+      (FormDataPredictions passed);
+  PassFormFieldDataPredictions(FormFieldDataPredictions s) =>
+      (FormFieldDataPredictions passed);
+  PassPasswordForm(PasswordForm s) => (PasswordForm passed);
+  PassPasswordFormFillData(PasswordFormFillData s) =>
+      (PasswordFormFillData passed);
+  PassFormsPredictionsMap(FormsPredictionsMap s) =>
+      (FormsPredictionsMap passed);
+  PassPasswordFormGenerationData(PasswordFormGenerationData s) =>
+      (PasswordFormGenerationData passed);
+  PassNewPasswordFormGenerationData(NewPasswordFormGenerationData s) =>
+      (NewPasswordFormGenerationData passed);
+  PassPasswordGenerationUIData(PasswordGenerationUIData s) =>
+      (PasswordGenerationUIData passed);
+};
diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc
index 74f7b0a3..78a87c71 100644
--- a/components/bookmarks/browser/bookmark_model.cc
+++ b/components/bookmarks/browser/bookmark_model.cc
@@ -550,8 +550,8 @@
 }
 
 bool BookmarkModel::HasNoUserCreatedBookmarksOrFolders() {
-  return bookmark_bar_node_->empty() && other_node_->empty() &&
-         mobile_node_->empty();
+  return bookmark_bar_node_->children().empty() &&
+         other_node_->children().empty() && mobile_node_->children().empty();
 }
 
 bool BookmarkModel::IsBookmarked(const GURL& url) {
@@ -643,8 +643,7 @@
   if (U_FAILURE(error))
     collator.reset(nullptr);
   BookmarkNode* mutable_parent = AsMutable(parent);
-  std::sort(mutable_parent->children().begin(),
-            mutable_parent->children().end(),
+  std::sort(mutable_parent->children_.begin(), mutable_parent->children_.end(),
             SortComparator(collator.get()));
 
   if (store_)
@@ -675,11 +674,11 @@
     std::vector<std::unique_ptr<BookmarkNode>> new_children(
         ordered_nodes.size());
     BookmarkNode* mutable_parent = AsMutable(parent);
-    for (auto& child : mutable_parent->children()) {
+    for (auto& child : mutable_parent->children_) {
       size_t new_location = order[child.get()];
       new_children[new_location] = std::move(child);
     }
-    mutable_parent->children().swap(new_children);
+    mutable_parent->children_.swap(new_children);
 
     if (store_)
       store_->ScheduleSave();
@@ -806,7 +805,7 @@
   index_->SetNodeSorter(std::make_unique<TypedCountSorter>(client_.get()));
   // Sorting the permanent nodes has to happen on the main thread, so we do it
   // here, after loading completes.
-  std::stable_sort(root_->children().begin(), root_->children().end(),
+  std::stable_sort(root_->children_.begin(), root_->children_.end(),
                    VisibilityComparator(client_.get()));
 
   root_->SetMetaInfoMap(details->model_meta_info_map());
diff --git a/components/bookmarks/browser/bookmark_node.cc b/components/bookmarks/browser/bookmark_node.cc
index ddc6e04..3a9b99a5 100644
--- a/components/bookmarks/browser/bookmark_node.cc
+++ b/components/bookmarks/browser/bookmark_node.cc
@@ -130,7 +130,7 @@
 BookmarkPermanentNode::~BookmarkPermanentNode() = default;
 
 bool BookmarkPermanentNode::IsVisible() const {
-  return visible_ || !empty();
+  return visible_ || !children().empty();
 }
 
 }  // namespace bookmarks
diff --git a/components/bookmarks/browser/bookmark_utils_unittest.cc b/components/bookmarks/browser/bookmark_utils_unittest.cc
index 218324e1..d750236 100644
--- a/components/bookmarks/browser/bookmark_utils_unittest.cc
+++ b/components/bookmarks/browser/bookmark_utils_unittest.cc
@@ -580,10 +580,10 @@
 
   std::unique_ptr<BookmarkModel> model(
       TestBookmarkClient::CreateModelWithClient(std::move(client)));
-  EXPECT_TRUE(model->bookmark_bar_node()->empty());
-  EXPECT_TRUE(model->other_node()->empty());
-  EXPECT_TRUE(model->mobile_node()->empty());
-  EXPECT_TRUE(extra_node->empty());
+  EXPECT_TRUE(model->bookmark_bar_node()->children().empty());
+  EXPECT_TRUE(model->other_node()->children().empty());
+  EXPECT_TRUE(model->mobile_node()->children().empty());
+  EXPECT_TRUE(extra_node->children().empty());
 
   const base::string16 title = base::ASCIIToUTF16("Title");
   const GURL url("http://google.com");
@@ -601,9 +601,9 @@
   nodes.clear();
   model->GetNodesByURL(url, &nodes);
   ASSERT_EQ(1u, nodes.size());
-  EXPECT_TRUE(model->bookmark_bar_node()->empty());
-  EXPECT_TRUE(model->other_node()->empty());
-  EXPECT_TRUE(model->mobile_node()->empty());
+  EXPECT_TRUE(model->bookmark_bar_node()->children().empty());
+  EXPECT_TRUE(model->other_node()->children().empty());
+  EXPECT_TRUE(model->mobile_node()->children().empty());
   EXPECT_EQ(1, extra_node->child_count());
 }
 
diff --git a/components/bookmarks/managed/managed_bookmark_service.cc b/components/bookmarks/managed/managed_bookmark_service.cc
index 4a619b9..96f1db3 100644
--- a/components/bookmarks/managed/managed_bookmark_service.cc
+++ b/components/bookmarks/managed/managed_bookmark_service.cc
@@ -49,7 +49,7 @@
     node_->set_id(*next_node_id);
     *next_node_id = ManagedBookmarksTracker::LoadInitial(
         node_.get(), initial_bookmarks_.get(), node_->id() + 1);
-    node_->set_visible(!node_->empty());
+    node_->set_visible(!node_->children().empty());
     node_->SetTitle(l10n_util::GetStringUTF16(title_id_));
     return std::move(node_);
   }
diff --git a/components/bookmarks/managed/managed_bookmarks_tracker.cc b/components/bookmarks/managed/managed_bookmarks_tracker.cc
index 3a5ae17..e7202a4 100644
--- a/components/bookmarks/managed/managed_bookmarks_tracker.cc
+++ b/components/bookmarks/managed/managed_bookmarks_tracker.cc
@@ -112,7 +112,7 @@
   UpdateBookmarks(managed_node_, list);
 
   // The managed bookmarks folder isn't visible when that pref isn't present.
-  managed_node_->set_visible(!managed_node_->empty());
+  managed_node_->set_visible(!managed_node_->children().empty());
 }
 
 void ManagedBookmarksTracker::UpdateBookmarks(const BookmarkNode* folder,
diff --git a/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc b/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
index 9751f6ff..0b807e3 100644
--- a/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
+++ b/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
@@ -57,7 +57,7 @@
     BookmarkPermanentNode* managed_node = new BookmarkPermanentNode(100);
     ManagedBookmarksTracker::LoadInitial(
         managed_node, prefs_.GetList(prefs::kManagedBookmarks), 101);
-    managed_node->set_visible(!managed_node->empty());
+    managed_node->set_visible(!managed_node->children().empty());
     managed_node->SetTitle(l10n_util::GetStringUTF16(
         IDS_BOOKMARK_BAR_MANAGED_FOLDER_DEFAULT_NAME));
 
@@ -181,9 +181,9 @@
 
 TEST_F(ManagedBookmarksTrackerTest, Empty) {
   CreateModel();
-  EXPECT_TRUE(model_->bookmark_bar_node()->empty());
-  EXPECT_TRUE(model_->other_node()->empty());
-  EXPECT_TRUE(managed_node()->empty());
+  EXPECT_TRUE(model_->bookmark_bar_node()->children().empty());
+  EXPECT_TRUE(model_->other_node()->children().empty());
+  EXPECT_TRUE(managed_node()->children().empty());
   EXPECT_FALSE(managed_node()->IsVisible());
 }
 
@@ -191,9 +191,9 @@
   // Set a policy before loading the model.
   prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
   CreateModel();
-  EXPECT_TRUE(model_->bookmark_bar_node()->empty());
-  EXPECT_TRUE(model_->other_node()->empty());
-  EXPECT_FALSE(managed_node()->empty());
+  EXPECT_TRUE(model_->bookmark_bar_node()->children().empty());
+  EXPECT_TRUE(model_->other_node()->children().empty());
+  EXPECT_FALSE(managed_node()->children().empty());
   EXPECT_TRUE(managed_node()->IsVisible());
 
   std::unique_ptr<base::DictionaryValue> expected(CreateExpectedTree());
@@ -207,9 +207,9 @@
   // Set a policy before loading the model.
   prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
   CreateModel();
-  EXPECT_TRUE(model_->bookmark_bar_node()->empty());
-  EXPECT_TRUE(model_->other_node()->empty());
-  EXPECT_FALSE(managed_node()->empty());
+  EXPECT_TRUE(model_->bookmark_bar_node()->children().empty());
+  EXPECT_TRUE(model_->other_node()->children().empty());
+  EXPECT_FALSE(managed_node()->children().empty());
   EXPECT_TRUE(managed_node()->IsVisible());
 
   std::unique_ptr<base::DictionaryValue> expected(
@@ -293,7 +293,7 @@
   prefs_.RemoveManagedPref(prefs::kManagedBookmarks);
   Mock::VerifyAndClearExpectations(&observer_);
 
-  EXPECT_TRUE(managed_node()->empty());
+  EXPECT_TRUE(managed_node()->children().empty());
   EXPECT_FALSE(managed_node()->IsVisible());
 }
 
diff --git a/components/browser_sync/profile_sync_service_bookmark_unittest.cc b/components/browser_sync/profile_sync_service_bookmark_unittest.cc
index 1cf5fbf..cfe2311 100644
--- a/components/browser_sync/profile_sync_service_bookmark_unittest.cc
+++ b/components/browser_sync/profile_sync_service_bookmark_unittest.cc
@@ -667,7 +667,7 @@
       EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId());
       EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId());
     }
-    if (!bnode->empty())
+    if (!bnode->children().empty())
       EXPECT_TRUE(gnode.GetFirstChildId());
   }
 
diff --git a/components/navigation_metrics/BUILD.gn b/components/navigation_metrics/BUILD.gn
index 7325fcb..1a0101e 100644
--- a/components/navigation_metrics/BUILD.gn
+++ b/components/navigation_metrics/BUILD.gn
@@ -10,7 +10,9 @@
 
   deps = [
     "//base",
+    "//base:i18n",
     "//components/dom_distiller/core:core",
+    "//components/url_formatter:url_formatter",
     "//url",
   ]
 }
diff --git a/components/navigation_metrics/DEPS b/components/navigation_metrics/DEPS
index 7b83d07..310f591 100644
--- a/components/navigation_metrics/DEPS
+++ b/components/navigation_metrics/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+components/dom_distiller/core",
+  "+components/url_formatter",
 ]
diff --git a/components/navigation_metrics/navigation_metrics.cc b/components/navigation_metrics/navigation_metrics.cc
index 38876ab..a946b1c 100644
--- a/components/navigation_metrics/navigation_metrics.cc
+++ b/components/navigation_metrics/navigation_metrics.cc
@@ -4,10 +4,12 @@
 
 #include "components/navigation_metrics/navigation_metrics.h"
 
+#include "base/i18n/rtl.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/stl_util.h"
 #include "components/dom_distiller/core/url_constants.h"
+#include "components/url_formatter/url_formatter.h"
 #include "url/gurl.h"
 
 namespace navigation_metrics {
@@ -58,8 +60,15 @@
   if (!is_same_document) {
     UMA_HISTOGRAM_ENUMERATION("Navigation.MainFrameSchemeDifferentPage", scheme,
                               Scheme::COUNT);
+    UMA_HISTOGRAM_BOOLEAN("Navigation.MainFrameHasRTLDomainDifferentPage",
+                          base::i18n::StringContainsStrongRTLChars(
+                              url_formatter::IDNToUnicode(url.host())));
   }
 
+  UMA_HISTOGRAM_BOOLEAN("Navigation.MainFrameHasRTLDomain",
+                        base::i18n::StringContainsStrongRTLChars(
+                            url_formatter::IDNToUnicode(url.host())));
+
   if (is_off_the_record) {
     UMA_HISTOGRAM_ENUMERATION("Navigation.MainFrameSchemeOTR", scheme,
                               Scheme::COUNT);
diff --git a/components/navigation_metrics/navigation_metrics_unittest.cc b/components/navigation_metrics/navigation_metrics_unittest.cc
index 793a88bf..262a28b 100644
--- a/components/navigation_metrics/navigation_metrics_unittest.cc
+++ b/components/navigation_metrics/navigation_metrics_unittest.cc
@@ -11,6 +11,8 @@
 
 namespace {
 const char* const kTestUrl = "http://www.example.com";
+// http://ab.גדהוזח.ij/kl/mn/op.html in A-label form.
+constexpr char kRtlUrl[] = "http://ab.xn--6dbcdefg.ij/kl/mn/op.html";
 const char* const kMainFrameScheme = "Navigation.MainFrameScheme";
 const char* const kMainFrameSchemeDifferentPage =
     "Navigation.MainFrameSchemeDifferentPage";
@@ -19,6 +21,9 @@
     "Navigation.MainFrameSchemeDifferentPageOTR";
 const char* const kPageLoad = "PageLoad";
 const char* const kPageLoadInIncognito = "PageLoadInIncognito";
+constexpr char kMainFrameHasRTLDomain[] = "Navigation.MainFrameHasRTLDomain";
+constexpr char kMainFrameHasRTLDomainDifferentPage[] =
+    "Navigation.MainFrameHasRTLDomainDifferentPage";
 }  // namespace
 
 namespace navigation_metrics {
@@ -84,4 +89,39 @@
   EXPECT_EQ(1, user_action_tester.GetActionCount(kPageLoadInIncognito));
 }
 
+TEST(NavigationMetrics, MainFrameDifferentDocumentHasRTLDomainFalse) {
+  base::HistogramTester test;
+  RecordMainFrameNavigation(GURL(kTestUrl), false, false);
+  test.ExpectTotalCount(kMainFrameHasRTLDomainDifferentPage, 1);
+  test.ExpectTotalCount(kMainFrameHasRTLDomain, 1);
+  test.ExpectUniqueSample(kMainFrameHasRTLDomainDifferentPage, 0 /* false */,
+                          1);
+  test.ExpectUniqueSample(kMainFrameHasRTLDomain, 0 /* false */, 1);
+}
+
+TEST(NavigationMetrics, MainFrameDifferentDocumentHasRTLDomainTrue) {
+  base::HistogramTester test;
+  RecordMainFrameNavigation(GURL(kRtlUrl), false, false);
+  test.ExpectTotalCount(kMainFrameHasRTLDomainDifferentPage, 1);
+  test.ExpectTotalCount(kMainFrameHasRTLDomain, 1);
+  test.ExpectUniqueSample(kMainFrameHasRTLDomainDifferentPage, 1 /* true */, 1);
+  test.ExpectUniqueSample(kMainFrameHasRTLDomain, 1 /* true */, 1);
+}
+
+TEST(NavigationMetrics, MainFrameSameDocumentHasRTLDomainFalse) {
+  base::HistogramTester test;
+  RecordMainFrameNavigation(GURL(kTestUrl), true, false);
+  test.ExpectTotalCount(kMainFrameHasRTLDomainDifferentPage, 0);
+  test.ExpectTotalCount(kMainFrameHasRTLDomain, 1);
+  test.ExpectUniqueSample(kMainFrameHasRTLDomain, 0 /* false */, 1);
+}
+
+TEST(NavigationMetrics, MainFrameSameDocumentHasRTLDomainTrue) {
+  base::HistogramTester test;
+  RecordMainFrameNavigation(GURL(kRtlUrl), true, false);
+  test.ExpectTotalCount(kMainFrameHasRTLDomainDifferentPage, 0);
+  test.ExpectTotalCount(kMainFrameHasRTLDomain, 1);
+  test.ExpectUniqueSample(kMainFrameHasRTLDomain, 1 /* true */, 1);
+}
+
 }  // namespace navigation_metrics
diff --git a/components/offline_pages/core/prefetch/get_operation_request.cc b/components/offline_pages/core/prefetch/get_operation_request.cc
index df86e24..426da24 100644
--- a/components/offline_pages/core/prefetch/get_operation_request.cc
+++ b/components/offline_pages/core/prefetch/get_operation_request.cc
@@ -4,6 +4,8 @@
 
 #include "components/offline_pages/core/prefetch/get_operation_request.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
@@ -53,4 +55,8 @@
                            assigned_operation_name, pages);
 }
 
+PrefetchRequestFinishedCallback GetOperationRequest::GetCallbackForTesting() {
+  return std::move(callback_);
+}
+
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/get_operation_request.h b/components/offline_pages/core/prefetch/get_operation_request.h
index 63b35d38..47f1a39 100644
--- a/components/offline_pages/core/prefetch/get_operation_request.h
+++ b/components/offline_pages/core/prefetch/get_operation_request.h
@@ -34,6 +34,10 @@
       PrefetchRequestFinishedCallback callback);
   ~GetOperationRequest();
 
+  // Returns the stored callback. Note that this moves the internal value
+  // making it null.
+  PrefetchRequestFinishedCallback GetCallbackForTesting();
+
  private:
   void OnCompleted(const std::string& operation_name,
                    PrefetchRequestStatus status,
diff --git a/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc b/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc
index a679cb6..34bd528 100644
--- a/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc
+++ b/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc
@@ -257,7 +257,7 @@
   std::unique_ptr<Task> get_operation_task = std::make_unique<GetOperationTask>(
       service_->GetPrefetchStore(),
       service_->GetPrefetchNetworkRequestFactory(),
-      base::BindOnce(
+      base::BindRepeating(
           &PrefetchDispatcherImpl::DidGenerateBundleOrGetOperationRequest,
           GetWeakPtr(), "GetOperationRequest"));
   task_queue_.AddTask(std::move(get_operation_task));
@@ -266,7 +266,7 @@
       std::make_unique<GeneratePageBundleTask>(
           this, service_->GetPrefetchStore(), service_->GetCachedGCMToken(),
           service_->GetPrefetchNetworkRequestFactory(),
-          base::BindOnce(
+          base::BindRepeating(
               &PrefetchDispatcherImpl::DidGenerateBundleOrGetOperationRequest,
               GetWeakPtr(), "GeneratePageBundleRequest"));
   task_queue_.AddTask(std::move(generate_page_bundle_task));
diff --git a/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl.cc b/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl.cc
index f82e1f51..91f4b59 100644
--- a/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl.cc
+++ b/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl.cc
@@ -4,6 +4,8 @@
 
 #include "components/offline_pages/core/prefetch/prefetch_network_request_factory_impl.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "components/offline_pages/core/offline_page_feature.h"
@@ -64,6 +66,7 @@
     const std::vector<std::string>& url_strings,
     const std::string& gcm_registration_id,
     PrefetchRequestFinishedCallback callback) {
+  DCHECK(callback);
   if (!AddConcurrentRequest())
     return;
   int max_bundle_size = prefetch_prefs::IsLimitlessPrefetchingEnabled(prefs_)
@@ -94,6 +97,7 @@
 void PrefetchNetworkRequestFactoryImpl::MakeGetOperationRequest(
     const std::string& operation_name,
     PrefetchRequestFinishedCallback callback) {
+  DCHECK(callback);
   if (!AddConcurrentRequest())
     return;
   get_operation_requests_[operation_name] =
@@ -157,7 +161,7 @@
 }
 
 void PrefetchNetworkRequestFactoryImpl::ReleaseConcurrentRequest() {
-  DCHECK(concurrent_request_count_ > 0);
+  DCHECK_GT(concurrent_request_count_, 0U);
   --concurrent_request_count_;
 }
 
diff --git a/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl_unittest.cc b/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl_unittest.cc
index 6f8d7338..2fca71c 100644
--- a/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl_unittest.cc
+++ b/components/offline_pages/core/prefetch/prefetch_network_request_factory_impl_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/offline_pages/core/prefetch/prefetch_network_request_factory_impl.h"
 
+#include "base/bind_helpers.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
 #include "base/test/test_simple_task_runner.h"
@@ -61,8 +62,7 @@
   EXPECT_TRUE(operation_names->empty());
 
   // Then, make the request and ensure we can find it by name.
-  request_factory()->MakeGetOperationRequest(operation_name,
-                                             PrefetchRequestFinishedCallback());
+  request_factory()->MakeGetOperationRequest(operation_name, base::DoNothing());
   EXPECT_TRUE(request_factory()->HasOutstandingRequests());
   request = request_factory()->FindGetOperationRequestByName(operation_name);
   EXPECT_NE(nullptr, request);
@@ -81,7 +81,7 @@
 
   // Then make the second request.
   request_factory()->MakeGetOperationRequest(operation_name_2,
-                                             PrefetchRequestFinishedCallback());
+                                             base::DoNothing());
 
   // Query for the second request, ensure it is different than the first
   // request, and ensure it didn't change the first request.
@@ -94,8 +94,7 @@
 
   // Then overwrite the first request with a new one, and make sure it's
   // different.
-  request_factory()->MakeGetOperationRequest(operation_name,
-                                             PrefetchRequestFinishedCallback());
+  request_factory()->MakeGetOperationRequest(operation_name, base::DoNothing());
   EXPECT_NE(request,
             request_factory()->FindGetOperationRequestByName(operation_name));
 }
@@ -106,8 +105,8 @@
 
   EXPECT_FALSE(request_factory()->HasOutstandingRequests());
 
-  request_factory()->MakeGeneratePageBundleRequest(
-      urls, reg_id, PrefetchRequestFinishedCallback());
+  request_factory()->MakeGeneratePageBundleRequest(urls, reg_id,
+                                                   base::DoNothing());
 
   EXPECT_TRUE(request_factory()->HasOutstandingRequests());
 
@@ -116,8 +115,8 @@
   EXPECT_THAT(*requested_urls, Contains(urls[1]));
 
   std::vector<std::string> urls2 = {"example.com/3"};
-  request_factory()->MakeGeneratePageBundleRequest(
-      urls2, reg_id, PrefetchRequestFinishedCallback());
+  request_factory()->MakeGeneratePageBundleRequest(urls2, reg_id,
+                                                   base::DoNothing());
   requested_urls = request_factory()->GetAllUrlsRequested();
   EXPECT_THAT(*requested_urls, Contains(urls[0]));
   EXPECT_THAT(*requested_urls, Contains(urls[1]));
@@ -130,14 +129,14 @@
   const int kTooManyRequests = 20;
 
   for (int i = 0; i < kTooManyRequests; ++i) {
-    request_factory()->MakeGeneratePageBundleRequest(
-        urls1, reg_id, PrefetchRequestFinishedCallback());
+    request_factory()->MakeGeneratePageBundleRequest(urls1, reg_id,
+                                                     base::DoNothing());
   }
 
   // Add one more request, over the maximum count of concurrent requests.
   std::vector<std::string> urls2 = {"example.com/2"};
-  request_factory()->MakeGeneratePageBundleRequest(
-      urls2, reg_id, PrefetchRequestFinishedCallback());
+  request_factory()->MakeGeneratePageBundleRequest(urls2, reg_id,
+                                                   base::DoNothing());
 
   auto requested_urls = request_factory()->GetAllUrlsRequested();
   EXPECT_THAT(*requested_urls, Contains(urls1[0]));
@@ -150,14 +149,14 @@
   const int kTooManyRequests = 20;
 
   for (int i = 0; i < kTooManyRequests; ++i) {
-    request_factory()->MakeGetOperationRequest(
-        operation_name1, PrefetchRequestFinishedCallback());
+    request_factory()->MakeGetOperationRequest(operation_name1,
+                                               base::DoNothing());
   }
 
   // Add one more request, over the maximum count of concurrent requests.
   std::string operation_name2 = "an operation 2";
   request_factory()->MakeGetOperationRequest(operation_name2,
-                                             PrefetchRequestFinishedCallback());
+                                             base::DoNothing());
 
   auto operation_names = request_factory()->GetAllOperationNamesRequested();
   EXPECT_THAT(*operation_names, Contains(operation_name1));
@@ -176,14 +175,14 @@
   const int kNotTooManyRequests = 6;
 
   for (int i = 0; i < kNotTooManyRequests; ++i) {
-    request_factory()->MakeGetOperationRequest(
-        operation_name1, PrefetchRequestFinishedCallback());
+    request_factory()->MakeGetOperationRequest(operation_name1,
+                                               base::DoNothing());
   }
 
   // Still possible to make more requests...
   std::string operation_name2 = "an operation 2";
   request_factory()->MakeGetOperationRequest(operation_name2,
-                                             PrefetchRequestFinishedCallback());
+                                             base::DoNothing());
 
   auto operation_names = request_factory()->GetAllOperationNamesRequested();
   EXPECT_THAT(*operation_names, Contains(operation_name1));
@@ -193,14 +192,14 @@
   std::vector<std::string> urls1 = {"example.com/1"};
   std::string reg_id = "a registration id";
   for (int i = 0; i < kNotTooManyRequests; ++i) {
-    request_factory()->MakeGeneratePageBundleRequest(
-        urls1, reg_id, PrefetchRequestFinishedCallback());
+    request_factory()->MakeGeneratePageBundleRequest(urls1, reg_id,
+                                                     base::DoNothing());
   }
 
   // Add one more request, over the maximum count of concurrent requests.
   std::string operation_name3 = "an operation 3";
   request_factory()->MakeGetOperationRequest(operation_name3,
-                                             PrefetchRequestFinishedCallback());
+                                             base::DoNothing());
 
   operation_names = request_factory()->GetAllOperationNamesRequested();
   EXPECT_THAT(*operation_names, Contains(operation_name1));
diff --git a/components/offline_pages/core/prefetch/tasks/get_operation_task.cc b/components/offline_pages/core/prefetch/tasks/get_operation_task.cc
index a58428f..3831953e 100644
--- a/components/offline_pages/core/prefetch/tasks/get_operation_task.cc
+++ b/components/offline_pages/core/prefetch/tasks/get_operation_task.cc
@@ -81,7 +81,7 @@
 GetOperationTask::GetOperationTask(
     PrefetchStore* store,
     PrefetchNetworkRequestFactory* request_factory,
-    PrefetchRequestFinishedCallback callback)
+    GetOperationFinishedCallback callback)
     : prefetch_store_(store),
       request_factory_(request_factory),
       callback_(std::move(callback)),
@@ -101,8 +101,8 @@
     OperationResultList operation_names) {
   if (operation_names) {
     for (std::string& operation : *operation_names) {
-      request_factory_->MakeGetOperationRequest(operation,
-                                                std::move(callback_));
+      request_factory_->MakeGetOperationRequest(
+          operation, PrefetchRequestFinishedCallback(callback_));
     }
   }
 
diff --git a/components/offline_pages/core/prefetch/tasks/get_operation_task.h b/components/offline_pages/core/prefetch/tasks/get_operation_task.h
index 58e55476..1344d84c 100644
--- a/components/offline_pages/core/prefetch/tasks/get_operation_task.h
+++ b/components/offline_pages/core/prefetch/tasks/get_operation_task.h
@@ -24,9 +24,16 @@
  public:
   using OperationResultList = std::unique_ptr<std::vector<std::string>>;
 
+  // This is a repeating version of PrefetchRequestFinishedCallback as it may be
+  // called more than once when multiple requests are placed.
+  using GetOperationFinishedCallback =
+      base::RepeatingCallback<void(PrefetchRequestStatus status,
+                                   const std::string& operation_name,
+                                   const std::vector<RenderPageInfo>& pages)>;
+
   GetOperationTask(PrefetchStore* store,
                    PrefetchNetworkRequestFactory* request_factory,
-                   PrefetchRequestFinishedCallback callback);
+                   GetOperationFinishedCallback callback);
   ~GetOperationTask() override;
 
   // Task implementation.
@@ -37,7 +44,7 @@
 
   PrefetchStore* prefetch_store_;
   PrefetchNetworkRequestFactory* request_factory_;
-  PrefetchRequestFinishedCallback callback_;
+  GetOperationFinishedCallback callback_;
 
   base::WeakPtrFactory<GetOperationTask> weak_factory_;
 
diff --git a/components/offline_pages/core/prefetch/tasks/get_operation_task_unittest.cc b/components/offline_pages/core/prefetch/tasks/get_operation_task_unittest.cc
index b81609ff..569e1c5 100644
--- a/components/offline_pages/core/prefetch/tasks/get_operation_task_unittest.cc
+++ b/components/offline_pages/core/prefetch/tasks/get_operation_task_unittest.cc
@@ -4,7 +4,9 @@
 
 #include "components/offline_pages/core/prefetch/tasks/get_operation_task.h"
 
+#include "base/bind_helpers.h"
 #include "base/test/mock_callback.h"
+#include "components/offline_pages/core/prefetch/get_operation_request.h"
 #include "components/offline_pages/core/prefetch/prefetch_item.h"
 #include "components/offline_pages/core/prefetch/prefetch_types.h"
 #include "components/offline_pages/core/prefetch/tasks/prefetch_task_test_base.h"
@@ -13,15 +15,13 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using testing::DoAll;
 using testing::HasSubstr;
-using testing::SaveArg;
 using testing::_;
 
 namespace offline_pages {
 namespace {
 const char kOperationName[] = "an_operation";
-const char kOtherOperationName[] = "an_operation";
+const char kOtherOperationName[] = "another_operation";
 const char kOperationShouldNotBeRequested[] = "Operation Not Found";
 }  // namespace
 
@@ -36,20 +36,18 @@
 
 TEST_F(GetOperationTaskTest, StoreFailure) {
   store_util()->SimulateInitializationError();
-  base::MockCallback<PrefetchRequestFinishedCallback> callback;
 
   RunTask(std::make_unique<GetOperationTask>(
-      store(), prefetch_request_factory(), callback.Get()));
+      store(), prefetch_request_factory(), base::DoNothing()));
 }
 
 TEST_F(GetOperationTaskTest, NormalOperationTask) {
-  base::MockCallback<PrefetchRequestFinishedCallback> callback;
   int64_t id = InsertPrefetchItemInStateWithOperation(
       kOperationName, PrefetchItemState::RECEIVED_GCM);
   ASSERT_NE(nullptr, store_util()->GetPrefetchItem(id));
 
   RunTask(std::make_unique<GetOperationTask>(
-      store(), prefetch_request_factory(), callback.Get()));
+      store(), prefetch_request_factory(), base::DoNothing()));
 
   EXPECT_NE(nullptr, prefetch_request_factory()->FindGetOperationRequestByName(
                          kOperationName));
@@ -61,7 +59,6 @@
 }
 
 TEST_F(GetOperationTaskTest, NotMatchingEntries) {
-  base::MockCallback<PrefetchRequestFinishedCallback> callback;
   // List all states that are not affected by the GetOperationTask.
   std::vector<PrefetchItemState> states = GetAllStatesExcept(
       {PrefetchItemState::SENT_GET_OPERATION, PrefetchItemState::RECEIVED_GCM});
@@ -72,7 +69,7 @@
   }
 
   RunTask(std::make_unique<GetOperationTask>(
-      store(), prefetch_request_factory(), callback.Get()));
+      store(), prefetch_request_factory(), base::DoNothing()));
 
   EXPECT_EQ(nullptr, prefetch_request_factory()->FindGetOperationRequestByName(
                          kOperationName));
@@ -85,7 +82,6 @@
 }
 
 TEST_F(GetOperationTaskTest, TwoOperations) {
-  base::MockCallback<PrefetchRequestFinishedCallback> callback;
   int64_t item1 = InsertPrefetchItemInStateWithOperation(
       kOperationName, PrefetchItemState::RECEIVED_GCM);
 
@@ -96,13 +92,26 @@
   int64_t unused_item = InsertPrefetchItemInStateWithOperation(
       kOperationShouldNotBeRequested, PrefetchItemState::SENT_GET_OPERATION);
 
+  base::MockCallback<GetOperationTask::GetOperationFinishedCallback> callback;
+  EXPECT_CALL(callback, Run(_, kOperationName, _));
+  EXPECT_CALL(callback, Run(_, kOtherOperationName, _));
   RunTask(std::make_unique<GetOperationTask>(
       store(), prefetch_request_factory(), callback.Get()));
 
-  EXPECT_NE(nullptr, prefetch_request_factory()->FindGetOperationRequestByName(
+  ASSERT_NE(nullptr, prefetch_request_factory()->FindGetOperationRequestByName(
                          kOperationName));
-  EXPECT_NE(nullptr, prefetch_request_factory()->FindGetOperationRequestByName(
+  prefetch_request_factory()
+      ->FindGetOperationRequestByName(kOperationName)
+      ->GetCallbackForTesting()
+      .Run(PrefetchRequestStatus::kSuccess, kOperationName,
+           std::vector<RenderPageInfo>());
+  ASSERT_NE(nullptr, prefetch_request_factory()->FindGetOperationRequestByName(
                          kOtherOperationName));
+  prefetch_request_factory()
+      ->FindGetOperationRequestByName(kOtherOperationName)
+      ->GetCallbackForTesting()
+      .Run(PrefetchRequestStatus::kSuccess, kOtherOperationName,
+           std::vector<RenderPageInfo>());
   EXPECT_EQ(1, store_util()->GetPrefetchItem(item1)->get_operation_attempts);
   EXPECT_EQ(1, store_util()->GetPrefetchItem(item2)->get_operation_attempts);
 
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index 550068d..9ebdf2c 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -295,7 +295,6 @@
 
   if (is_resolving_promise_passed_into_show_method) {
     if (SatisfiesSkipUIConstraints()) {
-      skipped_payment_request_ui_ = true;
       Pay();
     } else if (spec_->request_shipping()) {
       state_->SelectDefaultShippingAddressAndNotifyObservers();
@@ -450,11 +449,8 @@
 void PaymentRequest::AreRequestedMethodsSupportedCallback(
     bool methods_supported) {
   if (methods_supported) {
-    if (SatisfiesSkipUIConstraints()) {
-      skipped_payment_request_ui_ = true;
-      journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SKIPPED_SHOW);
+    if (SatisfiesSkipUIConstraints())
       Pay();
-    }
   } else {
     journey_logger_.SetNotShown(
         JourneyLogger::NOT_SHOWN_REASON_NO_SUPPORTED_PAYMENT_METHOD);
@@ -474,18 +470,26 @@
   return is_show_called_ && display_handle_ && spec_ && state_;
 }
 
-bool PaymentRequest::SatisfiesSkipUIConstraints() const {
+bool PaymentRequest::SatisfiesSkipUIConstraints() {
   // Only allowing URL base payment apps to skip the payment sheet.
-  return (spec()->url_payment_method_identifiers().size() == 1 ||
-          skip_ui_for_non_url_payment_method_identifiers_for_test_) &&
-         base::FeatureList::IsEnabled(features::kWebPaymentsSingleAppUiSkip) &&
-         base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps) &&
-         is_show_user_gesture_ && state()->IsInitialized() &&
-         spec()->IsInitialized() &&
-         state()->available_instruments().size() == 1 &&
-         spec()->stringified_method_data().size() == 1 &&
-         !spec()->request_shipping() && !spec()->request_payer_name() &&
-         !spec()->request_payer_phone() && !spec()->request_payer_email();
+  skipped_payment_request_ui_ =
+      (spec()->url_payment_method_identifiers().size() == 1 ||
+       skip_ui_for_non_url_payment_method_identifiers_for_test_) &&
+      base::FeatureList::IsEnabled(features::kWebPaymentsSingleAppUiSkip) &&
+      base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps) &&
+      is_show_user_gesture_ && state()->IsInitialized() &&
+      spec()->IsInitialized() && state()->available_instruments().size() == 1 &&
+      spec()->stringified_method_data().size() == 1 &&
+      !spec()->request_shipping() && !spec()->request_payer_name() &&
+      !spec()->request_payer_phone() && !spec()->request_payer_email();
+  if (skipped_payment_request_ui_) {
+    DCHECK(state()->IsInitialized() && spec()->IsInitialized());
+    journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SKIPPED_SHOW);
+  } else if (state()->IsInitialized() && spec()->IsInitialized()) {
+    // Set EVENT_SHOWN only after state() and spec() initialization.
+    journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
+  }
+  return skipped_payment_request_ui_;
 }
 
 void PaymentRequest::OnPaymentResponseAvailable(
@@ -615,14 +619,15 @@
   display_handle_.reset();
 }
 
-void PaymentRequest::RecordDialogShownEventInJourneyLogger() {
-  journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
-}
-
 bool PaymentRequest::IsIncognito() const {
   return delegate_->IsIncognito();
 }
 
+void PaymentRequest::SetSkipUiForNonUrlPaymentMethodIdentifiersForTest() {
+  journey_logger_.set_skip_ui_for_non_url_payment_method_identifiers_for_test();
+  skip_ui_for_non_url_payment_method_identifiers_for_test_ = true;
+}
+
 void PaymentRequest::RecordFirstAbortReason(
     JourneyLogger::AbortReason abort_reason) {
   if (!has_recorded_completion_) {
diff --git a/components/payments/content/payment_request.h b/components/payments/content/payment_request.h
index 03eb010..dde36b3 100644
--- a/components/payments/content/payment_request.h
+++ b/components/payments/content/payment_request.h
@@ -113,11 +113,12 @@
   // Hide this Payment Request if it's already showing.
   void HideIfNecessary();
 
-  // Record the "dialog shown" event in the journey logger.
-  void RecordDialogShownEventInJourneyLogger();
-
   bool IsIncognito() const;
 
+  // Allow to skip UI into payment handlers for such payment methods as
+  // "basic-card". Used only in tests.
+  void SetSkipUiForNonUrlPaymentMethodIdentifiersForTest();
+
   content::WebContents* web_contents() { return web_contents_; }
 
   bool skipped_payment_request_ui() { return skipped_payment_request_ui_; }
@@ -128,12 +129,6 @@
   PaymentRequestSpec* spec() const { return spec_.get(); }
   PaymentRequestState* state() const { return state_.get(); }
 
-  // Allow to skip UI into payment handlers for such payment methods as
-  // "basic-card". Used only in tests.
-  void set_skip_ui_for_non_url_payment_method_identifiers_for_test() {
-    skip_ui_for_non_url_payment_method_identifiers_for_test_ = true;
-  }
-
  private:
   // Returns true after init() has been called and the mojo connection has been
   // established. If the mojo connection gets later disconnected, this will
@@ -147,7 +142,7 @@
   // Returns true if this payment request supports skipping the Payment Sheet.
   // Typically, this means only one payment method is supported, it's a URL
   // based method, and no other info is requested from the user.
-  bool SatisfiesSkipUIConstraints() const;
+  bool SatisfiesSkipUIConstraints();
 
   // Only records the abort reason if it's the first completion for this Payment
   // Request. This is necessary since the aborts cascade into one another with
diff --git a/components/payments/core/journey_logger.cc b/components/payments/core/journey_logger.cc
index 8b7ed73..83a1f82d 100644
--- a/components/payments/core/journey_logger.cc
+++ b/components/payments/core/journey_logger.cc
@@ -5,6 +5,7 @@
 #include "components/payments/core/journey_logger.h"
 
 #include <algorithm>
+#include <vector>
 
 #include "base/metrics/histogram_functions.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -53,6 +54,19 @@
   return name_suffix;
 }
 
+// Returns true when exactly one boolean value in the vector is true.
+bool ValidateExclusiveBitVector(const std::vector<bool>& bit_vector) {
+  bool seen_true_bit = false;
+  for (auto bit : bit_vector) {
+    if (!bit)
+      continue;
+    if (seen_true_bit)
+      return false;
+    seen_true_bit = true;
+  }
+  return seen_true_bit;
+}
+
 }  // namespace
 
 JourneyLogger::JourneyLogger(bool is_incognito, ukm::SourceId source_id)
@@ -244,6 +258,7 @@
     events_ |= EVENT_HAD_INITIAL_FORM_OF_PAYMENT;
 
   // Record the events in UMA.
+  ValidateEventBits();
   base::UmaHistogramSparse("PaymentRequest.Events", events_);
 
   if (source_id_ == ukm::kInvalidSourceId)
@@ -256,6 +271,55 @@
       .Record(ukm::UkmRecorder::Get());
 }
 
+void JourneyLogger::ValidateEventBits() const {
+  std::vector<bool> bit_vector;
+
+  // Validate completion status.
+  bit_vector.push_back(events_ & EVENT_COMPLETED);
+  bit_vector.push_back(events_ & EVENT_OTHER_ABORTED);
+  bit_vector.push_back(events_ & EVENT_USER_ABORTED);
+  DCHECK(ValidateExclusiveBitVector(bit_vector));
+  bit_vector.clear();
+  if (events_ & EVENT_COMPLETED)
+    DCHECK(events_ & EVENT_PAY_CLICKED);
+
+  // Validate the user selected method.
+  if (events_ & EVENT_COMPLETED) {
+    bit_vector.push_back(events_ & EVENT_SELECTED_CREDIT_CARD);
+    bit_vector.push_back(events_ & EVENT_SELECTED_GOOGLE);
+    bit_vector.push_back(events_ & EVENT_SELECTED_OTHER);
+    DCHECK(ValidateExclusiveBitVector(bit_vector));
+    bit_vector.clear();
+  }
+
+  // Selected method should be requested.
+  if (events_ & EVENT_SELECTED_CREDIT_CARD) {
+    DCHECK(events_ & EVENT_REQUEST_METHOD_BASIC_CARD);
+  } else if (events_ & EVENT_SELECTED_GOOGLE) {
+    DCHECK(events_ & EVENT_REQUEST_METHOD_GOOGLE);
+  } else if (events_ & EVENT_SELECTED_OTHER) {
+    // It is possible that a service worker based app responds to "basic-card"
+    // request.
+    DCHECK(events_ & EVENT_REQUEST_METHOD_OTHER ||
+           events_ & EVENT_REQUEST_METHOD_BASIC_CARD);
+  }
+
+  // Validate UI SHOWN status.
+  if (events_ & EVENT_COMPLETED) {
+    bit_vector.push_back(events_ & EVENT_SHOWN);
+    bit_vector.push_back(events_ & EVENT_SKIPPED_SHOW);
+    DCHECK(ValidateExclusiveBitVector(bit_vector));
+    bit_vector.clear();
+  }
+
+  // Basic card flow should not skip UI show unless explicitly specified for
+  // tests.
+  if (events_ & EVENT_SELECTED_CREDIT_CARD) {
+    DCHECK((events_ & EVENT_SHOWN) ||
+           skip_ui_for_non_url_payment_method_identifiers_for_test_);
+  }
+}
+
 bool JourneyLogger::WasPaymentRequestTriggered() {
   return (events_ & EVENT_SHOWN) > 0 || (events_ & EVENT_SKIPPED_SHOW) > 0;
 }
diff --git a/components/payments/core/journey_logger.h b/components/payments/core/journey_logger.h
index 87e94f2..3c2cb2e 100644
--- a/components/payments/core/journey_logger.h
+++ b/components/payments/core/journey_logger.h
@@ -182,6 +182,10 @@
   // reason.
   void SetNotShown(NotShownReason reason);
 
+  void set_skip_ui_for_non_url_payment_method_identifiers_for_test() {
+    skip_ui_for_non_url_payment_method_identifiers_for_test_ = true;
+  }
+
  private:
   static const int NUMBER_OF_SECTIONS = 3;
 
@@ -225,12 +229,16 @@
   // Payment Request.
   void RecordEventsMetric(CompletionStatus completion_status);
 
+  // Validates the recorded event sequence during the Payment Request.
+  void ValidateEventBits() const;
+
   // Returns whether this Payment Request was triggered (shown or skipped show).
   bool WasPaymentRequestTriggered();
 
   SectionStats sections_[NUMBER_OF_SECTIONS];
   bool has_recorded_ = false;
   bool is_incognito_;
+  bool skip_ui_for_non_url_payment_method_identifiers_for_test_ = false;
 
   // Accumulates the many events that have happened during the Payment Request.
   int events_;
diff --git a/components/payments/core/journey_logger_unittest.cc b/components/payments/core/journey_logger_unittest.cc
index 5e016e2a..348f582 100644
--- a/components/payments/core/journey_logger_unittest.cc
+++ b/components/payments/core/journey_logger_unittest.cc
@@ -25,13 +25,15 @@
   base::HistogramTester histogram_tester;
   JourneyLogger logger(/*is_incognito=*/false, ukm::kInvalidSourceId);
 
-  logger.SetCompleted();
+  logger.SetEventOccurred(JourneyLogger::EVENT_SKIPPED_SHOW);
+  logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_MERCHANT);
 
   // Make sure the correct events were logged.
   std::vector<base::Bucket> buckets =
       histogram_tester.GetAllSamples("PaymentRequest.Events");
   ASSERT_EQ(1U, buckets.size());
   EXPECT_FALSE(buckets[0].min & JourneyLogger::EVENT_SHOWN);
+  EXPECT_TRUE(buckets[0].min & JourneyLogger::EVENT_SKIPPED_SHOW);
   EXPECT_FALSE(buckets[0].min & JourneyLogger::EVENT_CAN_MAKE_PAYMENT_TRUE);
   EXPECT_FALSE(buckets[0].min & JourneyLogger::EVENT_CAN_MAKE_PAYMENT_FALSE);
 }
@@ -93,6 +95,11 @@
   // user completes it.
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
   logger.SetRequestedInformation(true, false, false, false);
+  logger.SetRequestedPaymentMethodTypes(
+      /*requested_basic_card=*/true, /*requested_method_google=*/false,
+      /*requested_method_other=*/false);
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   // Make sure the correct events were logged.
@@ -205,7 +212,12 @@
   // completed.
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
   logger.SetRequestedInformation(true, false, false, false);
+  logger.SetRequestedPaymentMethodTypes(
+      /*requested_basic_card=*/true, /*requested_method_google=*/false,
+      /*requested_method_other=*/false);
   logger.SetCanMakePaymentValue(false);
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   // Make sure the correct events were logged.
@@ -277,7 +289,12 @@
   // the checkout.
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
   logger.SetRequestedInformation(true, false, false, false);
+  logger.SetRequestedPaymentMethodTypes(
+      /*requested_basic_card=*/true, /*requested_method_google=*/false,
+      /*requested_method_other=*/false);
   logger.SetCanMakePaymentValue(true);
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   // Make sure the correct events were logged.
@@ -301,7 +318,12 @@
   // the checkout.
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
   logger.SetRequestedInformation(true, false, false, false);
+  logger.SetRequestedPaymentMethodTypes(
+      /*requested_basic_card=*/true, /*requested_method_google=*/false,
+      /*requested_method_other=*/false);
   logger.SetCanMakePaymentValue(true);
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   // Make sure the correct events were logged.
@@ -337,6 +359,8 @@
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
 
   // Simulate that the user completes the checkout.
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   // Make sure the correct events were logged.
@@ -473,6 +497,8 @@
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
 
   // Simulate that the user completes the checkout.
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   // Make sure the correct events were logged.
@@ -518,6 +544,8 @@
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
 
   // Simulate that the user completes the checkout.
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   // Make sure the correct events were logged.
@@ -852,6 +880,8 @@
                                       /*has_complete_suggestion=*/false);
 
   // Simulate that the user completes one checkout and aborts the other.
+  logger1.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger1.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger1.SetCompleted();
   logger2.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER);
 
@@ -972,6 +1002,8 @@
   logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_PAYMENT_METHOD, 1,
                                      /*has_complete_suggestion=*/true);
   logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
+  logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+  logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
   logger.SetCompleted();
 
   int64_t expected_step_metric =
@@ -980,7 +1012,9 @@
       JourneyLogger::EVENT_REQUEST_METHOD_BASIC_CARD |
       JourneyLogger::EVENT_COMPLETED |
       JourneyLogger::EVENT_HAD_INITIAL_FORM_OF_PAYMENT |
-      JourneyLogger::EVENT_HAD_NECESSARY_COMPLETE_SUGGESTIONS;
+      JourneyLogger::EVENT_HAD_NECESSARY_COMPLETE_SUGGESTIONS |
+      JourneyLogger::EVENT_PAY_CLICKED |
+      JourneyLogger::EVENT_SELECTED_CREDIT_CARD;
 
   // Make sure the UKM was logged correctly.
   auto entries = ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
diff --git a/components/previews/content/previews_decider_impl_unittest.cc b/components/previews/content/previews_decider_impl_unittest.cc
index 3c5ba59..b0c7265d 100644
--- a/components/previews/content/previews_decider_impl_unittest.cc
+++ b/components/previews/content/previews_decider_impl_unittest.cc
@@ -1701,9 +1701,7 @@
 TEST_F(PreviewsDeciderImplTest, ReloadsTriggerFiveMinuteRule) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {features::kPreviews, features::kClientLoFi,
-       features::kPreviewsReloadsAreSoftOptOuts},
-      {});
+      {features::kPreviews, features::kClientLoFi}, {});
   InitializeUIService();
   ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
 
diff --git a/components/previews/core/previews_features.cc b/components/previews/core/previews_features.cc
index 5c02eff8..ac96570 100644
--- a/components/previews/core/previews_features.cc
+++ b/components/previews/core/previews_features.cc
@@ -90,11 +90,6 @@
 const base::Feature kHTTPSServerPreviewsUsingURLLoader{
     "HTTPSServerPreviewsUsingURLLoader", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// When enabled, reloading on a preview will cause the session (5 minute) rule
-// to trigger.
-const base::Feature kPreviewsReloadsAreSoftOptOuts{
-    "PreviewsReloadsAreSoftOptOuts", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables fetching optimization hints from a remote Optimization Guide Service.
 const base::Feature kOptimizationHintsFetching{
     "OptimizationHintsFetching", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/components/previews/core/previews_features.h b/components/previews/core/previews_features.h
index c2c8f34d..91f9d19 100644
--- a/components/previews/core/previews_features.h
+++ b/components/previews/core/previews_features.h
@@ -22,7 +22,6 @@
 extern const base::Feature kLitePageServerPreviews;
 extern const base::Feature kSlowPageTriggering;
 extern const base::Feature kHTTPSServerPreviewsUsingURLLoader;
-extern const base::Feature kPreviewsReloadsAreSoftOptOuts;
 extern const base::Feature kOptimizationHintsFetching;
 extern const base::Feature kOfflinePreviewsFalsePositivePrevention;
 extern const base::Feature kCoinFlipHoldback;
diff --git a/components/send_tab_to_self/proto/send_tab_to_self.proto b/components/send_tab_to_self/proto/send_tab_to_self.proto
index 61d7504..e4a3cd4 100644
--- a/components/send_tab_to_self/proto/send_tab_to_self.proto
+++ b/components/send_tab_to_self/proto/send_tab_to_self.proto
@@ -18,6 +18,8 @@
   // The Send tab to self specifics proto.
   optional sync_pb.SendTabToSelfSpecifics specifics = 1;
 
-  // Has the notification for this proto been dismissed.
-  optional bool notification_dismissed = 2;
+  // notification_dismissed has been moved into SendTabToSelfSpecifics, and is
+  // deprecated inside this proto.
+  reserved 2;
+  reserved "notification_dismissed";
 }
diff --git a/components/send_tab_to_self/send_tab_to_self_entry.cc b/components/send_tab_to_self/send_tab_to_self_entry.cc
index 67ed8a5..7a432877 100644
--- a/components/send_tab_to_self/send_tab_to_self_entry.cc
+++ b/components/send_tab_to_self/send_tab_to_self_entry.cc
@@ -114,7 +114,7 @@
   pb_entry->set_device_name(GetDeviceName());
   pb_entry->set_target_device_sync_cache_guid(GetTargetDeviceSyncCacheGuid());
   pb_entry->set_opened(IsOpened());
-  local_entry.set_notification_dismissed(GetNotificationDismissed());
+  pb_entry->set_notification_dismissed(GetNotificationDismissed());
 
   return local_entry;
 }
@@ -151,18 +151,18 @@
   if (pb_entry.opened()) {
     entry->MarkOpened();
   }
+  if (pb_entry.notification_dismissed()) {
+    entry->SetNotificationDismissed(true);
+  }
+
   return entry;
 }
 
 std::unique_ptr<SendTabToSelfEntry> SendTabToSelfEntry::FromLocalProto(
     const SendTabToSelfLocal& local_entry,
     base::Time now) {
-  std::unique_ptr<SendTabToSelfEntry> to_return =
-      FromProto(local_entry.specifics(), now);
-  if (to_return) {
-    to_return->SetNotificationDismissed(local_entry.notification_dismissed());
-  }
-  return to_return;
+  // No fields are currently read from the local proto.
+  return FromProto(local_entry.specifics(), now);
 }
 
 bool SendTabToSelfEntry::IsExpired(base::Time current_time) const {
diff --git a/components/sync/android/javatests/src/org/chromium/components/sync/notifier/InvalidationPreferencesTest.java b/components/sync/android/javatests/src/org/chromium/components/sync/notifier/InvalidationPreferencesTest.java
index 455fb63c..8bde9fb 100644
--- a/components/sync/android/javatests/src/org/chromium/components/sync/notifier/InvalidationPreferencesTest.java
+++ b/components/sync/android/javatests/src/org/chromium/components/sync/notifier/InvalidationPreferencesTest.java
@@ -10,11 +10,13 @@
 import com.google.ipc.invalidation.external.client.types.ObjectId;
 
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.CollectionUtil;
+import org.chromium.base.ContextUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -30,6 +32,11 @@
 @RunWith(BaseJUnit4ClassRunner.class)
 @RetryOnFailure
 public class InvalidationPreferencesTest {
+    @Before
+    public void setUp() {
+        // Make sure the SharedPreferences start out empty.
+        ContextUtils.getAppSharedPreferences().edit().clear().apply();
+    }
 
     @Test
     @SmallTest
diff --git a/components/sync/protocol/send_tab_to_self_specifics.proto b/components/sync/protocol/send_tab_to_self_specifics.proto
index 53d6f0cc..a4a809b 100644
--- a/components/sync/protocol/send_tab_to_self_specifics.proto
+++ b/components/sync/protocol/send_tab_to_self_specifics.proto
@@ -37,4 +37,6 @@
   optional string target_device_sync_cache_guid = 7;
   // A boolean to designate if the shared tab been opened on the target device.
   optional bool opened = 8;
+  // Whether the notification for this proto been dismissed.
+  optional bool notification_dismissed = 9;
 }
diff --git a/components/sync_bookmarks/bookmark_change_processor.cc b/components/sync_bookmarks/bookmark_change_processor.cc
index acb064f..35a73a07 100644
--- a/components/sync_bookmarks/bookmark_change_processor.cc
+++ b/components/sync_bookmarks/bookmark_change_processor.cc
@@ -611,7 +611,7 @@
 
     // Children of a deleted node should not be deleted; they may be
     // reparented by a later change record.  Move them to a temporary place.
-    if (!dst->empty()) {
+    if (!dst->children().empty()) {
       if (!foster_parent) {
         foster_parent = model->AddFolder(model->other_node(),
                                          model->other_node()->child_count(),
diff --git a/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc b/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc
index 76015ba..75e992e 100644
--- a/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc
+++ b/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc
@@ -210,7 +210,7 @@
 
   const bookmarks::BookmarkNode* bookmarkbar =
       bookmark_model()->bookmark_bar_node();
-  EXPECT_TRUE(bookmarkbar->empty());
+  EXPECT_TRUE(bookmarkbar->children().empty());
 
   processor()->OnUpdateReceived(CreateDummyModelTypeState(),
                                 std::move(updates));
diff --git a/components/typemaps.gni b/components/typemaps.gni
index e4019a5dd..0b9c513 100644
--- a/components/typemaps.gni
+++ b/components/typemaps.gni
@@ -4,7 +4,7 @@
 
 typemaps = [
   "//components/account_id/interfaces/account_id.typemap",
-  "//components/autofill/content/common/autofill_types.typemap",
+  "//components/autofill/core/common/mojom/autofill_types.typemap",
   "//components/chrome_cleaner/public/typemaps/chrome_prompt.typemap",
   "//components/content_capture/common/content_capture.typemap",
   "//components/content_settings/core/common/content_settings.typemap",
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index b683e51..8a32a05 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -74,7 +74,7 @@
 #endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_WIN)
-#include "gpu/ipc/service/direct_composition_surface_win.h"
+#include "ui/gl/direct_composition_surface_win.h"
 #endif
 
 #if defined(OS_MACOSX)
@@ -550,7 +550,7 @@
   DCHECK(main_runner_->BelongsToCurrentThread());
   bool hdr_enabled = false;
 #if defined(OS_WIN)
-  hdr_enabled = gpu::DirectCompositionSurfaceWin::IsHDRSupported();
+  hdr_enabled = gl::DirectCompositionSurfaceWin::IsHDRSupported();
 #endif
   io_runner_->PostTask(FROM_HERE,
                        base::BindOnce(std::move(callback), hdr_enabled));
diff --git a/content/browser/android/ime_adapter_android.cc b/content/browser/android/ime_adapter_android.cc
index 29149cf..12ed31d 100644
--- a/content/browser/android/ime_adapter_android.cc
+++ b/content/browser/android/ime_adapter_android.cc
@@ -184,9 +184,9 @@
       ConvertUTF16ToJavaString(env, state.value);
   Java_ImeAdapterImpl_updateState(
       env, obj, static_cast<int>(state.type), state.flags, state.mode,
-      state.show_ime_if_needed, jstring_text, state.selection_start,
-      state.selection_end, state.composition_start, state.composition_end,
-      state.reply_to_request);
+      static_cast<int>(state.action), state.show_ime_if_needed, jstring_text,
+      state.selection_start, state.selection_end, state.composition_start,
+      state.composition_end, state.reply_to_request);
 }
 
 void ImeAdapterAndroid::UpdateAfterViewSizeChanged() {
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index 1ee61943..c5d6a67 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -33,6 +33,7 @@
 #include "content/browser/cache_storage/cache_storage_cache_handle.h"
 #include "content/browser/cache_storage/cache_storage_context_impl.h"
 #include "content/browser/cache_storage/cache_storage_quota_client.h"
+#include "content/browser/cache_storage/cache_storage_scheduler.h"
 #include "content/common/background_fetch/background_fetch_types.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
@@ -124,6 +125,32 @@
   bool paused_ = true;
 };
 
+// Scheduler implementation that will invoke a callback after the
+// next operation has been started.
+class CallbackScheduler : public CacheStorageScheduler {
+ public:
+  explicit CallbackScheduler(base::OnceClosure callback)
+      : CacheStorageScheduler(CacheStorageSchedulerClient::kCache,
+                              base::ThreadTaskRunnerHandle::Get()),
+        callback_(std::move(callback)) {}
+
+ protected:
+  void DispatchOperationTask(base::OnceClosure task) override {
+    auto wrapped = base::BindOnce(&CallbackScheduler::ExecuteTask,
+                                  base::Unretained(this), std::move(task));
+    CacheStorageScheduler::DispatchOperationTask(std::move(wrapped));
+  }
+
+ private:
+  void ExecuteTask(base::OnceClosure task) {
+    std::move(task).Run();
+    if (callback_)
+      std::move(callback_).Run();
+  }
+
+  base::OnceClosure callback_;
+};
+
 class MockCacheStorageQuotaManagerProxy : public MockQuotaManagerProxy {
  public:
   MockCacheStorageQuotaManagerProxy(MockQuotaManager* quota_manager,
@@ -276,6 +303,13 @@
     run_loop->Quit();
   }
 
+  void CacheMatchAllCallback(base::RunLoop* run_loop,
+                             CacheStorageError error,
+                             std::vector<blink::mojom::FetchAPIResponsePtr>) {
+    callback_error_ = error;
+    run_loop->Quit();
+  }
+
   void CreateStorageManager() {
     ChromeBlobStorageContext* blob_storage_context(
         ChromeBlobStorageContext::GetFor(&browser_context_));
@@ -1180,6 +1214,40 @@
       << "unreferenced cache not destroyed on critical memory pressure";
 }
 
+TEST_F(CacheStorageManagerTest, DropReferenceDuringQuery) {
+  // Setup the cache and execute an operation to make sure all initialization
+  // is complete.
+  EXPECT_TRUE(Open(origin1_, "foo"));
+  base::WeakPtr<LegacyCacheStorageCache> cache =
+      LegacyCacheStorageCache::From(callback_cache_handle_)->AsWeakPtr();
+  EXPECT_FALSE(CacheMatch(callback_cache_handle_.value(),
+                          GURL("http://example.com/foo")));
+
+  // Override the cache scheduler so that we can take an action below
+  // after the query operation begins.
+  base::RunLoop scheduler_loop;
+  auto scheduler =
+      std::make_unique<CallbackScheduler>(scheduler_loop.QuitClosure());
+  cache->SetSchedulerForTesting(std::move(scheduler));
+
+  // Perform a MatchAll() operation to trigger a full query of the cache
+  // that does not hit the fast path optimization.
+  base::RunLoop match_loop;
+  cache->MatchAll(
+      nullptr, nullptr, /* trace_id = */ 0,
+      base::BindOnce(&CacheStorageManagerTest::CacheMatchAllCallback,
+                     base::Unretained(this), base::Unretained(&match_loop)));
+
+  // Wait for the MatchAll operation to begin.
+  scheduler_loop.Run();
+
+  // Clear the external cache handle.
+  callback_cache_handle_ = CacheStorageCacheHandle();
+
+  // Wait for the MatchAll operation to complete as expected.
+  match_loop.Run();
+  EXPECT_EQ(CacheStorageError::kSuccess, callback_error_);
+}
 // A cache continues to work so long as there is a handle to it. Only after the
 // last cache handle is deleted can the cache be freed.
 TEST_P(CacheStorageManagerTestP, CacheWorksAfterDelete) {
diff --git a/content/browser/cache_storage/cache_storage_scheduler.cc b/content/browser/cache_storage/cache_storage_scheduler.cc
index 3532bf01..0a0f31d 100644
--- a/content/browser/cache_storage/cache_storage_scheduler.cc
+++ b/content/browser/cache_storage/cache_storage_scheduler.cc
@@ -10,8 +10,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/single_thread_task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/sequenced_task_runner.h"
 #include "content/browser/cache_storage/cache_storage_histogram_utils.h"
 #include "content/browser/cache_storage/cache_storage_operation.h"
 
@@ -33,8 +32,7 @@
                                  pending_operations_.size());
 
   pending_operations_.push_back(std::make_unique<CacheStorageOperation>(
-      std::move(closure), client_type_, op_type,
-      base::SequencedTaskRunnerHandle::Get()));
+      std::move(closure), client_type_, op_type, task_runner_));
   RunOperationIfIdle();
 }
 
@@ -49,6 +47,10 @@
   return running_operation_ || !pending_operations_.empty();
 }
 
+void CacheStorageScheduler::DispatchOperationTask(base::OnceClosure task) {
+  task_runner_->PostTask(FROM_HERE, std::move(task));
+}
+
 void CacheStorageScheduler::RunOperationIfIdle() {
   if (!running_operation_ && !pending_operations_.empty()) {
     // TODO(jkarlin): Run multiple operations in parallel where allowed.
@@ -60,9 +62,8 @@
         running_operation_->op_type(),
         base::TimeTicks::Now() - running_operation_->creation_ticks());
 
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(&CacheStorageOperation::Run,
-                                  running_operation_->AsWeakPtr()));
+    DispatchOperationTask(base::BindOnce(&CacheStorageOperation::Run,
+                                         running_operation_->AsWeakPtr()));
   }
 }
 
diff --git a/content/browser/cache_storage/cache_storage_scheduler.h b/content/browser/cache_storage/cache_storage_scheduler.h
index eda2a8f..3f0fbf7 100644
--- a/content/browser/cache_storage/cache_storage_scheduler.h
+++ b/content/browser/cache_storage/cache_storage_scheduler.h
@@ -53,6 +53,10 @@
                           weak_ptr_factory_.GetWeakPtr(), std::move(callback));
   }
 
+ protected:
+  // virtual for testing
+  virtual void DispatchOperationTask(base::OnceClosure task);
+
  private:
   void RunOperationIfIdle();
 
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
index 943d276f..abd1706 100644
--- a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
+++ b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
@@ -901,6 +901,12 @@
   quota_manager_proxy_->NotifyOriginNoLongerInUse(origin_);
 }
 
+void LegacyCacheStorageCache::SetSchedulerForTesting(
+    std::unique_ptr<CacheStorageScheduler> scheduler) {
+  DCHECK(!scheduler_->ScheduledOperations());
+  scheduler_ = std::move(scheduler);
+}
+
 LegacyCacheStorageCache::LegacyCacheStorageCache(
     const url::Origin& origin,
     CacheStorageOwner owner,
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h
index 6610ef46..3764aab 100644
--- a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h
+++ b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.h
@@ -198,6 +198,10 @@
   void DropHandleRef() override;
   bool IsUnreferenced() const override;
 
+  // Override the default scheduler with a customized scheduler for testing.
+  // The current scheduler must be idle.
+  void SetSchedulerForTesting(std::unique_ptr<CacheStorageScheduler> scheduler);
+
   static LegacyCacheStorageCache* From(const CacheStorageCacheHandle& handle) {
     return static_cast<LegacyCacheStorageCache*>(handle.value());
   }
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 14bc2ffd..1825213 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -4227,6 +4227,9 @@
         },
         base::Unretained(this)));
   }
+
+  registry_->AddInterface(base::BindRepeating(
+      &RenderFrameHostImpl::BindIdleManagerRequest, base::Unretained(this)));
 }
 
 void RenderFrameHostImpl::ResetWaitingState() {
@@ -5909,6 +5912,17 @@
   presentation_service_->Bind(std::move(request));
 }
 
+void RenderFrameHostImpl::BindIdleManagerRequest(
+    blink::mojom::IdleManagerRequest request) {
+  if (!IsFeatureEnabled(blink::mojom::FeaturePolicyFeature::kIdleDetection)) {
+    mojo::ReportBadMessage("Feature policy blocks access to IdleDetection.");
+    return;
+  }
+  static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition())
+      ->GetIdleManager()
+      ->CreateService(std::move(request));
+}
+
 blink::mojom::FileChooserPtr RenderFrameHostImpl::BindFileChooserForTesting() {
   blink::mojom::FileChooserPtr chooser;
   FileChooserImpl::Create(this, mojo::MakeRequest(&chooser));
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index e89b40a..6a27b30 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -72,6 +72,7 @@
 #include "third_party/blink/public/mojom/frame/document_interface_broker.mojom.h"
 #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
 #include "third_party/blink/public/mojom/frame/navigation_initiator.mojom.h"
+#include "third_party/blink/public/mojom/idle/idle_manager.mojom.h"
 #include "third_party/blink/public/mojom/presentation/presentation.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
@@ -1360,6 +1361,8 @@
   void BindPresentationServiceRequest(
       blink::mojom::PresentationServiceRequest request);
 
+  void BindIdleManagerRequest(blink::mojom::IdleManagerRequest request);
+
   // service_manager::mojom::InterfaceProvider:
   void GetInterface(const std::string& interface_name,
                     mojo::ScopedMessagePipeHandle interface_pipe) override;
diff --git a/content/browser/idle/idle_manager.cc b/content/browser/idle/idle_manager.cc
index be8e84b7..5a77c238 100644
--- a/content/browser/idle/idle_manager.cc
+++ b/content/browser/idle/idle_manager.cc
@@ -75,8 +75,7 @@
   }
 }
 
-void IdleManager::CreateService(blink::mojom::IdleManagerRequest request,
-                                const url::Origin& origin) {
+void IdleManager::CreateService(blink::mojom::IdleManagerRequest request) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   bindings_.AddBinding(this, std::move(request));
diff --git a/content/browser/idle/idle_manager.h b/content/browser/idle/idle_manager.h
index 68cd45e9..81d93bf 100644
--- a/content/browser/idle/idle_manager.h
+++ b/content/browser/idle/idle_manager.h
@@ -50,9 +50,7 @@
   IdleManager();
   ~IdleManager() override;
 
-  // TODO: Origin for permission check; needed?
-  void CreateService(blink::mojom::IdleManagerRequest request,
-                     const url::Origin& origin);
+  void CreateService(blink::mojom::IdleManagerRequest request);
 
   // blink.mojom.IdleManager:
   void AddMonitor(base::TimeDelta threshold,
diff --git a/content/browser/idle/idle_manager_unittest.cc b/content/browser/idle/idle_manager_unittest.cc
index 22e0ded..772bb2f 100644
--- a/content/browser/idle/idle_manager_unittest.cc
+++ b/content/browser/idle/idle_manager_unittest.cc
@@ -77,9 +77,7 @@
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
   blink::mojom::IdleManagerPtr service_ptr;
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   blink::mojom::IdleMonitorRequest monitor_request =
@@ -119,10 +117,7 @@
   auto impl = std::make_unique<IdleManager>();
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
-
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   auto monitor_request = mojo::MakeRequest(&monitor_ptr);
@@ -185,10 +180,7 @@
   auto impl = std::make_unique<IdleManager>();
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
-
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   auto monitor_request = mojo::MakeRequest(&monitor_ptr);
@@ -237,10 +229,7 @@
   auto impl = std::make_unique<IdleManager>();
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
-
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   auto monitor_request = mojo::MakeRequest(&monitor_ptr);
@@ -289,10 +278,7 @@
   auto impl = std::make_unique<IdleManager>();
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
-
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   auto monitor_request = mojo::MakeRequest(&monitor_ptr);
@@ -364,10 +350,7 @@
   auto impl = std::make_unique<IdleManager>();
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
-
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   auto monitor_request = mojo::MakeRequest(&monitor_ptr);
@@ -378,7 +361,7 @@
   {
     base::RunLoop loop;
 
-    // Simulates a user going idle, but with the screen still unlocked.
+    // Initial state of the system.
     EXPECT_CALL(*mock, CalculateIdleTime())
         .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(0)));
     EXPECT_CALL(*mock, CheckIdleStateIsLocked())
@@ -445,9 +428,7 @@
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
 
   blink::mojom::IdleManagerPtr service_ptr;
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   blink::mojom::IdleMonitorRequest monitor_request =
@@ -487,9 +468,7 @@
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
   blink::mojom::IdleManagerPtr service_ptr;
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   blink::mojom::IdleMonitorRequest monitor_request =
@@ -519,9 +498,7 @@
   auto* mock = new NiceMock<MockIdleTimeProvider>();
   impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
   blink::mojom::IdleManagerPtr service_ptr;
-  GURL url("http://google.com");
-  impl->CreateService(mojo::MakeRequest(&service_ptr),
-                      url::Origin::Create(url));
+  impl->CreateService(mojo::MakeRequest(&service_ptr));
 
   blink::mojom::IdleMonitorPtr monitor_ptr;
   blink::mojom::IdleMonitorRequest monitor_request =
diff --git a/content/browser/native_file_system/file_system_chooser.cc b/content/browser/native_file_system/file_system_chooser.cc
index 31ad1c93..2c9ca2e 100644
--- a/content/browser/native_file_system/file_system_chooser.cc
+++ b/content/browser/native_file_system/file_system_chooser.cc
@@ -5,6 +5,7 @@
 #include "content/browser/native_file_system/file_system_chooser.h"
 
 #include "base/bind.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
@@ -153,25 +154,6 @@
   auto* isolated_context = storage::IsolatedContext::GetInstance();
   DCHECK(isolated_context);
 
-  std::vector<IsolatedFileSystemEntry> result;
-  result.reserve(files.size());
-  for (const auto& path : files) {
-    std::string base_name;
-    storage::IsolatedContext::ScopedFSHandle file_system =
-        isolated_context->RegisterFileSystemForPath(
-            storage::kFileSystemTypeNativeLocal, std::string(), path,
-            &base_name);
-
-    // TODO(https://crbug.com/955185): Properly refcount file system in handle
-    // implementations, rather than just leaking them like this.
-    storage::IsolatedContext::GetInstance()->AddReference(file_system.id());
-
-    base::FilePath root_path =
-        isolated_context->CreateVirtualRootPath(file_system.id());
-    base::FilePath isolated_path = root_path.AppendASCII(base_name);
-    result.push_back({file_system.id(), isolated_path});
-  }
-
   if (type_ == blink::mojom::ChooseFileSystemEntryType::kSaveFile) {
     // Create files if they don't yet exist.
     // TODO(mek): If we change FileSystemFileHandle to be able to represent a
@@ -181,7 +163,6 @@
         FROM_HERE, {base::TaskPriority::USER_BLOCKING, base::MayBlock()},
         base::BindOnce(
             [](const std::vector<base::FilePath>& files,
-               std::vector<IsolatedFileSystemEntry> result,
                scoped_refptr<base::TaskRunner> callback_runner,
                ResultCallback callback) {
               for (const auto& path : files) {
@@ -203,7 +184,7 @@
                       base::BindOnce(std::move(callback),
                                      blink::mojom::NativeFileSystemError::New(
                                          base::File::FILE_ERROR_FAILED),
-                                     std::vector<IsolatedFileSystemEntry>()));
+                                     std::vector<base::FilePath>()));
                   return;
                 }
               }
@@ -212,9 +193,9 @@
                   base::BindOnce(std::move(callback),
                                  blink::mojom::NativeFileSystemError::New(
                                      base::File::FILE_OK),
-                                 std::move(result)));
+                                 std::move(files)));
             },
-            files, std::move(result), callback_runner_, std::move(callback_)));
+            files, callback_runner_, std::move(callback_)));
     delete this;
     return;
   }
@@ -222,7 +203,7 @@
       FROM_HERE, base::BindOnce(std::move(callback_),
                                 blink::mojom::NativeFileSystemError::New(
                                     base::File::FILE_OK),
-                                std::move(result)));
+                                std::move(files)));
   delete this;
 }
 
@@ -231,7 +212,7 @@
       FROM_HERE, base::BindOnce(std::move(callback_),
                                 blink::mojom::NativeFileSystemError::New(
                                     base::File::FILE_ERROR_ABORT),
-                                std::vector<IsolatedFileSystemEntry>()));
+                                std::vector<base::FilePath>()));
   delete this;
 }
 
diff --git a/content/browser/native_file_system/file_system_chooser.h b/content/browser/native_file_system/file_system_chooser.h
index cc73c34..efa9bd1 100644
--- a/content/browser/native_file_system/file_system_chooser.h
+++ b/content/browser/native_file_system/file_system_chooser.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_NATIVE_FILE_SYSTEM_FILE_SYSTEM_CHOOSER_H_
 
 #include "base/files/file.h"
+#include "base/files/file_path.h"
 #include "base/task_runner.h"
 #include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/native_file_system/native_file_system_manager.mojom.h"
@@ -20,17 +21,9 @@
 // All of this class has to be called on the UI thread.
 class CONTENT_EXPORT FileSystemChooser : public ui::SelectFileDialog::Listener {
  public:
-  struct IsolatedFileSystemEntry {
-    std::string file_system_id;
-    // Path to the file as it would appear in the file system URL for this
-    // entry. I.e. this includes the file_system_id and relative path to the
-    // file within that file system.
-    base::FilePath isolated_file_path;
-  };
-
   using ResultCallback =
       base::OnceCallback<void(blink::mojom::NativeFileSystemErrorPtr,
-                              std::vector<IsolatedFileSystemEntry>)>;
+                              std::vector<base::FilePath>)>;
 
   static void CreateAndShow(
       int render_process_id,
diff --git a/content/browser/native_file_system/file_system_chooser_unittest.cc b/content/browser/native_file_system/file_system_chooser_unittest.cc
index b9b08457..0c30b8e3 100644
--- a/content/browser/native_file_system/file_system_chooser_unittest.cc
+++ b/content/browser/native_file_system/file_system_chooser_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/native_file_system/file_system_chooser.h"
 
+#include "base/files/file_path.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -31,9 +32,7 @@
         std::move(accepts), include_accepts_all,
         base::BindLambdaForTesting(
             [&](blink::mojom::NativeFileSystemErrorPtr,
-                std::vector<FileSystemChooser::IsolatedFileSystemEntry>) {
-              loop.Quit();
-            }),
+                std::vector<base::FilePath>) { loop.Quit(); }),
         base::SequencedTaskRunnerHandle::Get());
     loop.Run();
   }
diff --git a/content/browser/native_file_system/native_file_system_manager_impl.cc b/content/browser/native_file_system/native_file_system_manager_impl.cc
index b324551..3f7e20fb 100644
--- a/content/browser/native_file_system/native_file_system_manager_impl.cc
+++ b/content/browser/native_file_system/native_file_system_manager_impl.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/native_file_system/native_file_system_manager_impl.h"
 
+#include "base/files/file_path.h"
 #include "base/task/post_task.h"
 #include "content/browser/native_file_system/file_system_chooser.h"
 #include "content/browser/native_file_system/native_file_system_directory_handle_impl.h"
@@ -14,9 +15,12 @@
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "storage/browser/fileapi/file_system_context.h"
 #include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/isolated_context.h"
 #include "storage/common/fileapi/file_system_util.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/native_file_system/native_file_system_error.mojom.h"
+#include "url/origin.h"
 
 using blink::mojom::NativeFileSystemError;
 
@@ -104,6 +108,40 @@
   return result;
 }
 
+blink::mojom::NativeFileSystemEntryPtr
+NativeFileSystemManagerImpl::CreateFileEntryFromPath(
+    const url::Origin& origin,
+    const base::FilePath& file_path) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  std::string name;
+  auto url = CreateFileSystemURLFromPath(origin, file_path, &name);
+
+  // TODO(https://crbug.com/955185): Pass file system ID to the handle
+  // implementations so they can properly ref and unref the filesystem.
+  // Right now the isolated file system will just be leaked and never freed.
+  return blink::mojom::NativeFileSystemEntry::New(
+      blink::mojom::NativeFileSystemHandle::NewFile(
+          CreateFileHandle(url).PassInterface()),
+      name);
+}
+
+blink::mojom::NativeFileSystemEntryPtr
+NativeFileSystemManagerImpl::CreateDirectoryEntryFromPath(
+    const url::Origin& origin,
+    const base::FilePath& directory_path) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  std::string name;
+  auto url = CreateFileSystemURLFromPath(origin, directory_path, &name);
+
+  // TODO(https://crbug.com/955185): Pass file system ID to the handle
+  // implementations so they can properly ref and unref the filesystem.
+  // Right now the isolated file system will just be leaked and never freed.
+  return blink::mojom::NativeFileSystemEntry::New(
+      blink::mojom::NativeFileSystemHandle::NewDirectory(
+          CreateDirectoryHandle(url).PassInterface()),
+      name);
+}
+
 void NativeFileSystemManagerImpl::CreateTransferToken(
     const NativeFileSystemFileHandleImpl& file,
     blink::mojom::NativeFileSystemTransferTokenRequest request) {
@@ -159,7 +197,7 @@
     const url::Origin& origin,
     ChooseEntriesCallback callback,
     blink::mojom::NativeFileSystemErrorPtr result,
-    std::vector<FileSystemChooser::IsolatedFileSystemEntry> entries) {
+    std::vector<base::FilePath> entries) {
   std::vector<blink::mojom::NativeFileSystemEntryPtr> result_entries;
   if (result->error_code != base::File::FILE_OK) {
     std::move(callback).Run(std::move(result), std::move(result_entries));
@@ -167,24 +205,10 @@
   }
   result_entries.reserve(entries.size());
   for (const auto& entry : entries) {
-    auto url = context()->CreateCrackedFileSystemURL(
-        origin.GetURL(), storage::kFileSystemTypeIsolated,
-        entry.isolated_file_path);
-    std::string name = storage::FilePathToString(
-        storage::VirtualPath::BaseName(entry.isolated_file_path));
-    // TODO(https://crbug.com/955185): Pass file system ID to the handle
-    // implementations so they can properly ref and unref the filesystem.
-    // Right now the isolated file system will just be leaked and never freed.
     if (type == blink::mojom::ChooseFileSystemEntryType::kOpenDirectory) {
-      result_entries.push_back(blink::mojom::NativeFileSystemEntry::New(
-          blink::mojom::NativeFileSystemHandle::NewDirectory(
-              CreateDirectoryHandle(url).PassInterface()),
-          name));
+      result_entries.push_back(CreateDirectoryEntryFromPath(origin, entry));
     } else {
-      result_entries.push_back(blink::mojom::NativeFileSystemEntry::New(
-          blink::mojom::NativeFileSystemHandle::NewFile(
-              CreateFileHandle(url).PassInterface()),
-          name));
+      result_entries.push_back(CreateFileEntryFromPath(origin, entry));
     }
   }
   std::move(callback).Run(std::move(result), std::move(result_entries));
@@ -234,4 +258,29 @@
   }
 }
 
+storage::FileSystemURL NativeFileSystemManagerImpl::CreateFileSystemURLFromPath(
+    const url::Origin& origin,
+    const base::FilePath& path,
+    std::string* name) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  auto* isolated_context = storage::IsolatedContext::GetInstance();
+  DCHECK(isolated_context);
+
+  storage::IsolatedContext::ScopedFSHandle file_system =
+      isolated_context->RegisterFileSystemForPath(
+          storage::kFileSystemTypeNativeLocal, std::string(), path, name);
+
+  // TODO(https://crbug.com/955185): Properly refcount file system in handle
+  // implementations, rather than just leaking them like this.
+  storage::IsolatedContext::GetInstance()->AddReference(file_system.id());
+
+  base::FilePath root_path =
+      isolated_context->CreateVirtualRootPath(file_system.id());
+  base::FilePath isolated_path = root_path.AppendASCII(*name);
+
+  return context()->CreateCrackedFileSystemURL(
+      origin.GetURL(), storage::kFileSystemTypeIsolated, isolated_path);
+}
+
 }  // namespace content
\ No newline at end of file
diff --git a/content/browser/native_file_system/native_file_system_manager_impl.h b/content/browser/native_file_system/native_file_system_manager_impl.h
index c8ef21b0..acada09 100644
--- a/content/browser/native_file_system/native_file_system_manager_impl.h
+++ b/content/browser/native_file_system/native_file_system_manager_impl.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_MANAGER_IMPL_H_
 #define CONTENT_BROWSER_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_MANAGER_IMPL_H_
 
+#include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
@@ -69,6 +70,18 @@
   blink::mojom::NativeFileSystemDirectoryHandlePtr CreateDirectoryHandle(
       const storage::FileSystemURL& url);
 
+  // Creates a new NativeFileSystemEntryPtr from the path to a file. Assumes the
+  // passed in path is valid and represents a file.
+  blink::mojom::NativeFileSystemEntryPtr CreateFileEntryFromPath(
+      const url::Origin& origin,
+      const base::FilePath& file_path);
+
+  // Creates a new NativeFileSystemEntryPtr from the path to a directory.
+  // Assumes the passed in path is valid and represents a directory.
+  blink::mojom::NativeFileSystemEntryPtr CreateDirectoryEntryFromPath(
+      const url::Origin& origin,
+      const base::FilePath& directory_path);
+
   // Create a transfer token for a specific file or directory.
   void CreateTransferToken(
       const NativeFileSystemFileHandleImpl& file,
@@ -105,12 +118,11 @@
                                   const std::string& filesystem_name,
                                   base::File::Error result);
 
-  void DidChooseEntries(
-      blink::mojom::ChooseFileSystemEntryType type,
-      const url::Origin& origin,
-      ChooseEntriesCallback callback,
-      blink::mojom::NativeFileSystemErrorPtr result,
-      std::vector<FileSystemChooser::IsolatedFileSystemEntry> entries);
+  void DidChooseEntries(blink::mojom::ChooseFileSystemEntryType type,
+                        const url::Origin& origin,
+                        ChooseEntriesCallback callback,
+                        blink::mojom::NativeFileSystemErrorPtr result,
+                        std::vector<base::FilePath> entries);
 
   void CreateTransferTokenImpl(
       const storage::FileSystemURL& url,
@@ -121,6 +133,11 @@
                               ResolvedTokenCallback callback,
                               const base::UnguessableToken& token);
 
+  // Creates a FileSystemURL which corresponds to a FilePath and Origin.
+  storage::FileSystemURL CreateFileSystemURLFromPath(const url::Origin& origin,
+                                                     const base::FilePath& path,
+                                                     std::string* name);
+
   const scoped_refptr<storage::FileSystemContext> context_;
   const scoped_refptr<ChromeBlobStorageContext> blob_context_;
   std::unique_ptr<storage::FileSystemOperationRunner> operation_runner_;
diff --git a/content/browser/oop_browsertest.cc b/content/browser/oop_browsertest.cc
index d020fbdd..0b7c5bf 100644
--- a/content/browser/oop_browsertest.cc
+++ b/content/browser/oop_browsertest.cc
@@ -50,12 +50,7 @@
 
 // This test calls into system GL which is not instrumented with MSAN.
 #if !defined(MEMORY_SANITIZER)
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
-#define MAYBE_Basic DISABLED_Basic
-#else
-#define MAYBE_Basic Basic
-#endif
-IN_PROC_BROWSER_TEST_F(OOPBrowserTest, MAYBE_Basic) {
+IN_PROC_BROWSER_TEST_F(OOPBrowserTest, Basic) {
   // Create a div to ensure we don't use solid color quads.
   GURL url = GURL(
       "data:text/html,"
diff --git a/content/browser/renderer_interface_binders.cc b/content/browser/renderer_interface_binders.cc
index 8a1d31e7..71fbf94 100644
--- a/content/browser/renderer_interface_binders.cc
+++ b/content/browser/renderer_interface_binders.cc
@@ -203,13 +203,6 @@
             ->GetLockManager()
             ->CreateService(std::move(request), origin);
       }));
-  parameterized_binder_registry_.AddInterface(base::BindRepeating(
-      [](blink::mojom::IdleManagerRequest request, RenderProcessHost* host,
-         const url::Origin& origin) {
-        static_cast<StoragePartitionImpl*>(host->GetStoragePartition())
-            ->GetIdleManager()
-            ->CreateService(std::move(request), origin);
-      }));
   if (base::FeatureList::IsEnabled(features::kSmsReceiver) &&
       base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kEnableExperimentalWebPlatformFeatures)) {
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index dcdeb5a..e6c49ef6 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -824,7 +824,7 @@
     device::test::ScopedVirtualFidoDevice scoped_virtual_device;
     scoped_virtual_device.SetSupportedProtocol(
         u2f_authenticator ? device::ProtocolVersion::kU2f
-                          : device::ProtocolVersion::kCtap);
+                          : device::ProtocolVersion::kCtap2);
 
     PublicKeyCredentialCreationOptionsPtr options =
         GetTestPublicKeyCredentialCreationOptions();
@@ -913,7 +913,7 @@
     device::test::ScopedVirtualFidoDevice scoped_virtual_device;
     scoped_virtual_device.SetSupportedProtocol(
         u2f_authenticator ? device::ProtocolVersion::kU2f
-                          : device::ProtocolVersion::kCtap);
+                          : device::ProtocolVersion::kCtap2);
 
     PublicKeyCredentialCreationOptionsPtr options =
         GetTestPublicKeyCredentialCreationOptions();
@@ -1930,7 +1930,7 @@
 // behavior of the Touch ID platform authenticator.
 TEST_F(AuthenticatorContentBrowserClientTest,
        PlatformAuthenticatorAttestation) {
-  virtual_device_.SetSupportedProtocol(device::ProtocolVersion::kCtap);
+  virtual_device_.SetSupportedProtocol(device::ProtocolVersion::kCtap2);
   virtual_device_.mutable_state()->transport =
       device::FidoTransportProtocol::kInternal;
   virtual_device_.mutable_state()->self_attestation = true;
@@ -1977,7 +1977,7 @@
 }
 
 TEST_F(AuthenticatorContentBrowserClientTest, Ctap2SelfAttestation) {
-  virtual_device_.SetSupportedProtocol(device::ProtocolVersion::kCtap);
+  virtual_device_.SetSupportedProtocol(device::ProtocolVersion::kCtap2);
   virtual_device_.mutable_state()->self_attestation = true;
   NavigateAndCommit(GURL("https://example.com"));
 
@@ -2019,7 +2019,7 @@
 
 TEST_F(AuthenticatorContentBrowserClientTest,
        Ctap2SelfAttestationNonZeroAaguid) {
-  virtual_device_.SetSupportedProtocol(device::ProtocolVersion::kCtap);
+  virtual_device_.SetSupportedProtocol(device::ProtocolVersion::kCtap2);
   virtual_device_.mutable_state()->self_attestation = true;
   virtual_device_.mutable_state()->non_zero_aaguid_with_self_attestation = true;
   NavigateAndCommit(GURL("https://example.com"));
@@ -2518,7 +2518,7 @@
   NavigateAndCommit(GURL(kTestOrigin1));
 
   for (auto protocol :
-       {device::ProtocolVersion::kU2f, device::ProtocolVersion::kCtap}) {
+       {device::ProtocolVersion::kU2f, device::ProtocolVersion::kCtap2}) {
     SCOPED_TRACE(static_cast<int>(protocol));
 
     device::test::ScopedVirtualFidoDevice scoped_virtual_device;
@@ -2551,7 +2551,7 @@
     SCOPED_TRACE(include_extension);
 
     device::test::ScopedVirtualFidoDevice scoped_virtual_device;
-    scoped_virtual_device.SetSupportedProtocol(device::ProtocolVersion::kCtap);
+    scoped_virtual_device.SetSupportedProtocol(device::ProtocolVersion::kCtap2);
 
     AuthenticatorPtr authenticator = ConnectToAuthenticator();
     PublicKeyCredentialCreationOptionsPtr options =
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc
index 3e147cf..1218e70 100644
--- a/content/browser/webauth/webauth_browsertest.cc
+++ b/content/browser/webauth/webauth_browsertest.cc
@@ -735,7 +735,7 @@
 };
 
 constexpr device::ProtocolVersion kAllProtocols[] = {
-    device::ProtocolVersion::kCtap, device::ProtocolVersion::kU2f};
+    device::ProtocolVersion::kCtap2, device::ProtocolVersion::kU2f};
 
 // Tests that when navigator.credentials.create() is called with an invalid
 // relying party id, we get a SecurityError.
diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc
index dfe2495..2061462 100644
--- a/content/browser/worker_host/dedicated_worker_host.cc
+++ b/content/browser/worker_host/dedicated_worker_host.cc
@@ -117,6 +117,8 @@
         &DedicatedWorkerHost::CreateWebUsbService, base::Unretained(this)));
     registry_.AddInterface(base::BindRepeating(
         &DedicatedWorkerHost::CreateDedicatedWorker, base::Unretained(this)));
+    registry_.AddInterface(base::BindRepeating(
+        &DedicatedWorkerHost::CreateIdleManager, base::Unretained(this)));
   }
 
   // Called from WorkerScriptFetchInitiator. Continues starting the dedicated
@@ -252,6 +254,21 @@
                                      origin_, std::move(request));
   }
 
+  void CreateIdleManager(blink::mojom::IdleManagerRequest request) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    auto* host =
+        RenderFrameHostImpl::FromID(process_id_, ancestor_render_frame_id_);
+    if (!host->IsFeatureEnabled(
+            blink::mojom::FeaturePolicyFeature::kIdleDetection)) {
+      mojo::ReportBadMessage("Feature policy blocks access to IdleDetection.");
+      return;
+    }
+    static_cast<StoragePartitionImpl*>(
+        host->GetProcess()->GetStoragePartition())
+        ->GetIdleManager()
+        ->CreateService(std::move(request));
+  }
+
   const int process_id_;
   // ancestor_render_frame_id_ is the id of the frame that owns this worker,
   // either directly, or (in the case of nested workers) indirectly via a tree
diff --git a/content/common/text_input_state.cc b/content/common/text_input_state.cc
index f6044bf..ac32684 100644
--- a/content/common/text_input_state.cc
+++ b/content/common/text_input_state.cc
@@ -9,6 +9,7 @@
 TextInputState::TextInputState()
     : type(ui::TEXT_INPUT_TYPE_NONE),
       mode(ui::TEXT_INPUT_MODE_DEFAULT),
+      action(ui::TextInputAction::kDefault),
       flags(0),
       selection_start(0),
       selection_end(0),
diff --git a/content/common/text_input_state.h b/content/common/text_input_state.h
index 454529e..f2a1523c 100644
--- a/content/common/text_input_state.h
+++ b/content/common/text_input_state.h
@@ -7,6 +7,7 @@
 
 #include "base/strings/string16.h"
 #include "content/common/content_export.h"
+#include "ui/base/ime/text_input_action.h"
 #include "ui/base/ime/text_input_mode.h"
 #include "ui/base/ime/text_input_type.h"
 
@@ -25,6 +26,9 @@
   // The mode of input field.
   ui::TextInputMode mode;
 
+  // The action of the input field.
+  ui::TextInputAction action;
+
   // The flags of input field (autocorrect, autocomplete, etc.)
   int flags;
 
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index 31a3cfe4..7ca006c8 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -44,7 +44,6 @@
 #include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
 #include "third_party/blink/public/web/web_plugin_action.h"
 #include "third_party/blink/public/web/web_text_direction.h"
-#include "ui/base/ime/text_input_mode.h"
 #include "ui/base/ime/text_input_type.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/point.h"
diff --git a/content/common/widget_messages.h b/content/common/widget_messages.h
index 39e2b6b..c30f95c 100644
--- a/content/common/widget_messages.h
+++ b/content/common/widget_messages.h
@@ -24,6 +24,8 @@
 #include "third_party/blink/public/platform/web_intrinsic_sizing_info.h"
 #include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/public/web/web_text_direction.h"
+#include "ui/base/ime/text_input_action.h"
+#include "ui/base/ime/text_input_mode.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
@@ -111,11 +113,13 @@
 IPC_STRUCT_END()
 
 // Traits for TextInputState.
+IPC_ENUM_TRAITS_MAX_VALUE(ui::TextInputAction, ui::TextInputAction::kMax)
 IPC_ENUM_TRAITS_MAX_VALUE(ui::TextInputMode, ui::TEXT_INPUT_MODE_MAX)
 
 IPC_STRUCT_TRAITS_BEGIN(content::TextInputState)
   IPC_STRUCT_TRAITS_MEMBER(type)
   IPC_STRUCT_TRAITS_MEMBER(mode)
+  IPC_STRUCT_TRAITS_MEMBER(action)
   IPC_STRUCT_TRAITS_MEMBER(flags)
   IPC_STRUCT_TRAITS_MEMBER(value)
   IPC_STRUCT_TRAITS_MEMBER(selection_start)
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 2697e90..b43789e 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -533,6 +533,7 @@
     "javatests/src/org/chromium/content/browser/input/CursorAnchorInfoControllerTest.java",
     "javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java",
     "javatests/src/org/chromium/content/browser/input/ImeAutocapitalizeTest.java",
+    "javatests/src/org/chromium/content/browser/input/ImeInputActionTest.java",
     "javatests/src/org/chromium/content/browser/input/ImeInputModeTest.java",
     "javatests/src/org/chromium/content/browser/input/ImeLollipopTest.java",
     "javatests/src/org/chromium/content/browser/input/ImePasswordTest.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnection.java b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnection.java
index 77061a1..f413bcf 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnection.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnection.java
@@ -21,8 +21,8 @@
      */
     public interface Factory {
         ChromiumBaseInputConnection initializeAndGet(View view, ImeAdapterImpl imeAdapter,
-                int inputType, int inputFlags, int inputMode, int selectionStart, int selectionEnd,
-                EditorInfo outAttrs);
+                int inputType, int inputFlags, int inputMode, int inputAction, int selectionStart,
+                int selectionEnd, EditorInfo outAttrs);
 
         @VisibleForTesting
         Handler getHandler();
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
index 8fcf56d..fbe4ed43 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
@@ -51,6 +51,7 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.ViewUtils;
+import org.chromium.ui.base.ime.TextInputAction;
 import org.chromium.ui.base.ime.TextInputType;
 
 import java.lang.ref.WeakReference;
@@ -117,6 +118,7 @@
     private int mTextInputType = TextInputType.NONE;
     private int mTextInputFlags;
     private int mTextInputMode = WebTextInputMode.DEFAULT;
+    private int mTextInputAction = TextInputAction.DEFAULT;
     private boolean mNodeEditable;
     private boolean mNodePassword;
 
@@ -310,8 +312,8 @@
         if (mInputConnectionFactory == null) return null;
         View containerView = getContainerView();
         setInputConnection(mInputConnectionFactory.initializeAndGet(containerView, this,
-                mTextInputType, mTextInputFlags, mTextInputMode, mLastSelectionStart,
-                mLastSelectionEnd, outAttrs));
+                mTextInputType, mTextInputFlags, mTextInputMode, mTextInputAction,
+                mLastSelectionStart, mLastSelectionEnd, outAttrs));
         if (DEBUG_LOGS) Log.i(TAG, "onCreateInputConnection: " + mInputConnection);
 
         if (mCursorAnchorInfoController != null) {
@@ -398,6 +400,7 @@
      * @param textInputType Text input type for the currently focused field in renderer.
      * @param textInputFlags Text input flags.
      * @param textInputMode Text input mode.
+     * @param textInputAction Text input mode action.
      * @param showIfNeeded Whether the keyboard should be shown if it is currently hidden.
      * @param text The String contents of the field being edited.
      * @param selectionStart The character offset of the selection start, or the caret position if
@@ -412,13 +415,15 @@
      */
     @CalledByNative
     private void updateState(int textInputType, int textInputFlags, int textInputMode,
-            boolean showIfNeeded, String text, int selectionStart, int selectionEnd,
-            int compositionStart, int compositionEnd, boolean replyToRequest) {
+            int textInputAction, boolean showIfNeeded, String text, int selectionStart,
+            int selectionEnd, int compositionStart, int compositionEnd, boolean replyToRequest) {
         TraceEvent.begin("ImeAdapter.updateState");
         try {
             if (DEBUG_LOGS) {
-                Log.i(TAG, "updateState: type [%d->%d], flags [%d], mode[%d], show [%b], ",
-                        mTextInputType, textInputType, textInputFlags, textInputMode, showIfNeeded);
+                Log.i(TAG,
+                        "updateState: type [%d->%d], flags [%d], mode[%d], action[%d] show [%b], ",
+                        mTextInputType, textInputType, textInputFlags, textInputMode,
+                        textInputAction, showIfNeeded);
             }
             boolean needsRestart = false;
             boolean hide = false;
@@ -443,6 +448,10 @@
             } else if (textInputType == TextInputType.NONE) {
                 hide = true;
             }
+            if (mTextInputAction != textInputAction) {
+                mTextInputAction = textInputAction;
+                needsRestart = true;
+            }
 
             boolean editable = focusedNodeEditable();
             boolean password = textInputType == TextInputType.PASSWORD;
@@ -750,19 +759,24 @@
 
     boolean performEditorAction(int actionCode) {
         if (!isValid()) return false;
-        switch (actionCode) {
-            case EditorInfo.IME_ACTION_NEXT:
-                advanceFocusInForm(WebFocusType.FORWARD);
-                break;
-            case EditorInfo.IME_ACTION_PREVIOUS:
-                advanceFocusInForm(WebFocusType.BACKWARD);
-                break;
-            default:
-                sendSyntheticKeyPress(KeyEvent.KEYCODE_ENTER,
-                        KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
-                                | KeyEvent.FLAG_EDITOR_ACTION);
-                break;
+
+        // If mTextInputAction has been specified (indicating an enterKeyHint
+        // has been specified in the HTML) then we do will send the enter key
+        // events. Otherwise we fallback to having the enter key move focus
+        // between the elements.
+        if (mTextInputAction == TextInputAction.DEFAULT) {
+            switch (actionCode) {
+                case EditorInfo.IME_ACTION_NEXT:
+                    advanceFocusInForm(WebFocusType.FORWARD);
+                    return true;
+                case EditorInfo.IME_ACTION_PREVIOUS:
+                    advanceFocusInForm(WebFocusType.BACKWARD);
+                    return true;
+            }
         }
+        sendSyntheticKeyPress(KeyEvent.KEYCODE_ENTER,
+                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+                        | KeyEvent.FLAG_EDITOR_ACTION);
         return true;
     }
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeUtils.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeUtils.java
index 0897a23a..7bd2b805 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ImeUtils.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeUtils.java
@@ -16,6 +16,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.blink_public.web.WebTextInputFlags;
 import org.chromium.blink_public.web.WebTextInputMode;
+import org.chromium.ui.base.ime.TextInputAction;
 import org.chromium.ui.base.ime.TextInputType;
 
 import java.util.Locale;
@@ -31,12 +32,13 @@
      * @param inputType Type defined in {@link TextInputType}.
      * @param inputFlags Flags defined in {@link WebTextInputFlags}.
      * @param inputMode Flags defined in {@link WebTextInputMode}.
+     * @param inputAction Flags defined in {@link TextInputAction}.
      * @param initialSelStart The initial selection start position.
      * @param initialSelEnd The initial selection end position.
      * @param outAttrs An instance of {@link EditorInfo} that we are going to change.
      */
     public static void computeEditorInfo(int inputType, int inputFlags, int inputMode,
-            int initialSelStart, int initialSelEnd, EditorInfo outAttrs) {
+            int inputAction, int initialSelStart, int initialSelEnd, EditorInfo outAttrs) {
         outAttrs.inputType =
                 EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
 
@@ -109,7 +111,7 @@
             }
         }
 
-        outAttrs.imeOptions |= getImeAction(inputType, inputFlags, inputMode,
+        outAttrs.imeOptions |= getImeAction(inputType, inputFlags, inputMode, inputAction,
                 (outAttrs.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0);
 
         // Handling of autocapitalize. Blink will send the flag taking into account the element's
@@ -133,21 +135,47 @@
         outAttrs.initialSelEnd = initialSelEnd;
     }
 
-    private static int getImeAction(
-            int inputType, int inputFlags, int inputMode, boolean isMultiLineInput) {
+    private static int getImeAction(int inputType, int inputFlags, int inputMode, int inputAction,
+            boolean isMultiLineInput) {
         int imeAction = 0;
-        if (inputMode == WebTextInputMode.DEFAULT && inputType == TextInputType.SEARCH) {
-            imeAction |= EditorInfo.IME_ACTION_SEARCH;
-        } else if (isMultiLineInput) {
-            // For textarea that sends you to another webpage on enter key press using
-            // JavaScript, we will only show ENTER.
-            imeAction |= EditorInfo.IME_ACTION_NONE;
-        } else if ((inputFlags & WebTextInputFlags.HAVE_NEXT_FOCUSABLE_ELEMENT) != 0) {
-            imeAction |= EditorInfo.IME_ACTION_NEXT;
+        if (inputAction == TextInputAction.DEFAULT) {
+            if (inputMode == WebTextInputMode.DEFAULT && inputType == TextInputType.SEARCH) {
+                imeAction |= EditorInfo.IME_ACTION_SEARCH;
+            } else if (isMultiLineInput) {
+                // For textarea that sends you to another webpage on enter key press using
+                // JavaScript, we will only show ENTER.
+                imeAction |= EditorInfo.IME_ACTION_NONE;
+            } else if ((inputFlags & WebTextInputFlags.HAVE_NEXT_FOCUSABLE_ELEMENT) != 0) {
+                imeAction |= EditorInfo.IME_ACTION_NEXT;
+            } else {
+                // For last element inside form, we should give preference to GO key as PREVIOUS
+                // has less importance in those cases.
+                imeAction |= EditorInfo.IME_ACTION_GO;
+            }
         } else {
-            // For last element inside form, we should give preference to GO key as PREVIOUS
-            // has less importance in those cases.
-            imeAction |= EditorInfo.IME_ACTION_GO;
+            switch (inputAction) {
+                case TextInputAction.ENTER:
+                    imeAction |= EditorInfo.IME_ACTION_NONE;
+                    break;
+                case TextInputAction.GO:
+                    imeAction |= EditorInfo.IME_ACTION_GO;
+                    break;
+                case TextInputAction.DONE:
+                    imeAction |= EditorInfo.IME_ACTION_DONE;
+                    break;
+                case TextInputAction.NEXT:
+                    imeAction |= EditorInfo.IME_ACTION_NEXT;
+                    break;
+                case TextInputAction.PREVIOUS:
+                    imeAction |= EditorInfo.IME_ACTION_PREVIOUS;
+                    break;
+                case TextInputAction.SEARCH:
+                    imeAction |= EditorInfo.IME_ACTION_SEARCH;
+                    break;
+                case TextInputAction.SEND:
+                    imeAction |= EditorInfo.IME_ACTION_SEND;
+                    break;
+            }
         }
         return imeAction;
     }
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java
index 72336d1..7cd6b9e 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java
@@ -109,14 +109,14 @@
 
     @Override
     public ThreadedInputConnection initializeAndGet(View view, ImeAdapterImpl imeAdapter,
-            int inputType, int inputFlags, int inputMode, int selectionStart, int selectionEnd,
-            EditorInfo outAttrs) {
+            int inputType, int inputFlags, int inputMode, int inputAction, int selectionStart,
+            int selectionEnd, EditorInfo outAttrs) {
         ImeUtils.checkOnUiThread();
 
         // Compute outAttrs early in case we early out to prevent reentrancy. (crbug.com/636197)
         // TODO(changwan): move this up to ImeAdapter once ReplicaInputConnection is deprecated.
-        ImeUtils.computeEditorInfo(
-                inputType, inputFlags, inputMode, selectionStart, selectionEnd, outAttrs);
+        ImeUtils.computeEditorInfo(inputType, inputFlags, inputMode, inputAction, selectionStart,
+                selectionEnd, outAttrs);
         if (DEBUG_LOGS) {
             Log.i(TAG, "initializeAndGet. outAttr: " + ImeUtils.getEditorInfoDebugString(outAttrs));
         }
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
index 998e9eb..e7a6bee 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
@@ -53,6 +53,7 @@
     static final String INPUT_FORM_HTML = "content/test/data/android/input/input_forms.html";
     static final String PASSWORD_FORM_HTML = "content/test/data/android/input/password_form.html";
     static final String INPUT_MODE_HTML = "content/test/data/android/input/input_mode.html";
+    static final String INPUT_ACTION_HTML = "content/test/data/android/input/input_action.html";
 
     private SelectionPopupControllerImpl mSelectionPopupController;
     private TestCallbackHelperContainer mCallbackContainer;
@@ -160,6 +161,28 @@
                 JavaScriptUtils.executeJavaScriptAndWaitForResult(getWebContents(), code));
     }
 
+    void waitForEventLogState(String expectedLogs) {
+        final String code = "getEventLogs()";
+        final String sanitizedExpectedLogs = "\"" + expectedLogs + "\"";
+        CriteriaHelper.pollInstrumentationThread(
+                Criteria.equals(sanitizedExpectedLogs, new Callable<String>() {
+                    @Override
+                    public String call() throws Exception {
+                        return JavaScriptUtils.executeJavaScriptAndWaitForResult(
+                                getWebContents(), code);
+                    }
+                }));
+    }
+
+    void waitForFocusedElement(String id) {
+        CriteriaHelper.pollInstrumentationThread(Criteria.equals(id, new Callable<String>() {
+            @Override
+            public String call() throws Exception {
+                return DOMUtils.getFocusedNode(getWebContents());
+            }
+        }));
+    }
+
     void assertTextsAroundCursor(CharSequence before, CharSequence selected, CharSequence after)
             throws Exception {
         Assert.assertEquals(before, getTextBeforeCursor(100, 0));
@@ -169,11 +192,11 @@
 
     void waitForKeyboardStates(int show, int hide, int restart, Integer[] textInputTypeHistory) {
         final String expected =
-                stringifyKeyboardStates(show, hide, restart, textInputTypeHistory, null);
+                stringifyKeyboardStates(show, hide, restart, textInputTypeHistory, null, null);
         CriteriaHelper.pollUiThread(Criteria.equals(expected, new Callable<String>() {
             @Override
             public String call() {
-                return getKeyboardStates(false);
+                return getKeyboardStates(false, false);
             }
         }));
     }
@@ -181,11 +204,23 @@
     void waitForKeyboardStates(int show, int hide, int restart, Integer[] textInputTypeHistory,
             Integer[] textInputModeHistory) {
         final String expected = stringifyKeyboardStates(
-                show, hide, restart, textInputTypeHistory, textInputModeHistory);
+                show, hide, restart, textInputTypeHistory, textInputModeHistory, null);
         CriteriaHelper.pollUiThread(Criteria.equals(expected, new Callable<String>() {
             @Override
             public String call() {
-                return getKeyboardStates(true);
+                return getKeyboardStates(true, false);
+            }
+        }));
+    }
+
+    void waitForKeyboardInputActionStates(int show, int hide, int restart,
+            Integer[] textInputTypeHistory, Integer[] textInputActionHistory) {
+        final String expected = stringifyKeyboardStates(
+                show, hide, restart, textInputTypeHistory, null, textInputActionHistory);
+        CriteriaHelper.pollUiThread(Criteria.equals(expected, new Callable<String>() {
+            @Override
+            public String call() {
+                return getKeyboardStates(false, true);
             }
         }));
     }
@@ -195,22 +230,38 @@
         mConnectionFactory.resetAllStates();
     }
 
-    String getKeyboardStates(boolean includeInputMode) {
+    String getKeyboardStates(boolean includeInputMode, boolean includeInputAction) {
         int showCount = mInputMethodManagerWrapper.getShowSoftInputCounter();
         int hideCount = mInputMethodManagerWrapper.getHideSoftInputCounter();
         int restartCount = mInputMethodManagerWrapper.getRestartInputCounter();
         Integer[] textInputTypeHistory = mConnectionFactory.getTextInputTypeHistory();
         Integer[] textInputModeHistory = null;
+        Integer[] textInputActionHistory = null;
         if (includeInputMode) textInputModeHistory = mConnectionFactory.getTextInputModeHistory();
-        return stringifyKeyboardStates(
-                showCount, hideCount, restartCount, textInputTypeHistory, textInputModeHistory);
+        if (includeInputAction)
+            textInputActionHistory = mConnectionFactory.getTextInputActionHistory();
+        return stringifyKeyboardStates(showCount, hideCount, restartCount, textInputTypeHistory,
+                textInputModeHistory, textInputActionHistory);
     }
 
     String stringifyKeyboardStates(int show, int hide, int restart, Integer[] inputTypeHistory,
-            Integer[] inputModeHistory) {
+            Integer[] inputModeHistory, Integer[] inputActionHistory) {
         return "show count: " + show + ", hide count: " + hide + ", restart count: " + restart
                 + ", input type history: " + Arrays.deepToString(inputTypeHistory)
-                + ", input mode history: " + Arrays.deepToString(inputModeHistory);
+                + ", input mode history: " + Arrays.deepToString(inputModeHistory)
+                + ", input action history: " + Arrays.deepToString(inputActionHistory);
+    }
+
+    void waitForEditorAction(final int expectedAction) {
+        CriteriaHelper.pollUiThread(Criteria.equals(expectedAction, new Callable<Integer>() {
+            @Override
+            public Integer call() {
+                EditorInfo editorInfo = mConnectionFactory.getOutAttrs();
+                return editorInfo.actionId != 0
+                        ? editorInfo.actionId
+                        : editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
+            }
+        }));
     }
 
     void performEditorAction(final int action) {
@@ -547,12 +598,7 @@
             throws InterruptedException, TimeoutException {
         DOMUtils.focusNode(getWebContents(), id);
         assertWaitForKeyboardStatus(shouldShowKeyboard);
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(id, new Callable<String>() {
-            @Override
-            public String call() throws Exception {
-                return DOMUtils.getFocusedNode(getWebContents());
-            }
-        }));
+        waitForFocusedElement(id);
         // When we focus another element, the connection may be recreated.
         mConnection = getInputConnection();
     }
@@ -562,6 +608,7 @@
 
         private final List<Integer> mTextInputTypeList = new ArrayList<>();
         private final List<Integer> mTextInputModeList = new ArrayList<>();
+        private final List<Integer> mTextInputActionList = new ArrayList<>();
         private EditorInfo mOutAttrs;
 
         public TestInputConnectionFactory(ChromiumBaseInputConnection.Factory factory) {
@@ -570,13 +617,14 @@
 
         @Override
         public ChromiumBaseInputConnection initializeAndGet(View view, ImeAdapterImpl imeAdapter,
-                int inputType, int inputFlags, int inputMode, int selectionStart, int selectionEnd,
-                EditorInfo outAttrs) {
+                int inputType, int inputFlags, int inputMode, int inputAction, int selectionStart,
+                int selectionEnd, EditorInfo outAttrs) {
             mTextInputTypeList.add(inputType);
             mTextInputModeList.add(inputMode);
+            mTextInputActionList.add(inputAction);
             mOutAttrs = outAttrs;
             return mFactory.initializeAndGet(view, imeAdapter, inputType, inputFlags, inputMode,
-                    selectionStart, selectionEnd, outAttrs);
+                    inputAction, selectionStart, selectionEnd, outAttrs);
         }
 
         @Override
@@ -593,6 +641,7 @@
         public void resetAllStates() {
             mTextInputTypeList.clear();
             mTextInputModeList.clear();
+            mTextInputActionList.clear();
         }
 
         public Integer[] getTextInputModeHistory() {
@@ -601,6 +650,12 @@
             return result;
         }
 
+        public Integer[] getTextInputActionHistory() {
+            Integer[] result = new Integer[mTextInputActionList.size()];
+            mTextInputActionList.toArray(result);
+            return result;
+        }
+
         public EditorInfo getOutAttrs() {
             return mOutAttrs;
         }
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeInputActionTest.java b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeInputActionTest.java
new file mode 100644
index 0000000..7e2bcec
--- /dev/null
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeInputActionTest.java
@@ -0,0 +1,121 @@
+// 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.
+
+package org.chromium.content.browser.input;
+
+import android.support.test.filters.SmallTest;
+import android.view.inputmethod.EditorInfo;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Feature;
+import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
+import org.chromium.ui.base.ime.TextInputAction;
+import org.chromium.ui.base.ime.TextInputType;
+
+/**
+ * IME (input method editor) and text input tests for enterkeyhint attribute.
+ */
+@RunWith(ContentJUnit4ClassRunner.class)
+@CommandLineFlags.Add({"enable-experimental-web-platform-features"})
+public class ImeInputActionTest {
+    @Rule
+    public ImeActivityTestRule mRule = new ImeActivityTestRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mRule.setUpForUrl(ImeActivityTestRule.INPUT_ACTION_HTML);
+    }
+
+    private void checkInputAction(String elementId, int type, int textAction, int editorAction)
+            throws Throwable {
+        mRule.focusElement(elementId);
+        mRule.waitAndVerifyUpdateSelection(0, 0, 0, -1, -1);
+        mRule.waitForKeyboardInputActionStates(
+                1, 0, 1, new Integer[] {type}, new Integer[] {textAction});
+        mRule.waitForEventLogs("selectionchange");
+        mRule.clearEventLogs();
+        mRule.waitForEditorAction(editorAction);
+        mRule.performEditorAction(editorAction);
+        mRule.waitForEventLogState(type == TextInputType.TEXT
+                        ? "keydown(13),keypress(13),keyup(13)"
+                        : "keydown(13),keypress(13),keyup(13),selectionchange");
+        mRule.clearEventLogs();
+        mRule.resetAllStates();
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"TextInput"})
+    public void testShowAndHideInputAction() throws Throwable {
+        Assert.assertNotNull(mRule.getInputMethodManagerWrapper().getInputConnection());
+        checkInputAction("contenteditable_default", TextInputType.CONTENT_EDITABLE,
+                TextInputAction.DEFAULT, EditorInfo.IME_ACTION_NONE);
+        checkInputAction("contenteditable_enter", TextInputType.CONTENT_EDITABLE,
+                TextInputAction.ENTER, EditorInfo.IME_ACTION_NONE);
+        checkInputAction("contenteditable_go", TextInputType.CONTENT_EDITABLE, TextInputAction.GO,
+                EditorInfo.IME_ACTION_GO);
+        checkInputAction("contenteditable_done", TextInputType.CONTENT_EDITABLE,
+                TextInputAction.DONE, EditorInfo.IME_ACTION_DONE);
+        checkInputAction("contenteditable_next", TextInputType.CONTENT_EDITABLE,
+                TextInputAction.NEXT, EditorInfo.IME_ACTION_NEXT);
+        checkInputAction("contenteditable_previous", TextInputType.CONTENT_EDITABLE,
+                TextInputAction.PREVIOUS, EditorInfo.IME_ACTION_PREVIOUS);
+        checkInputAction("contenteditable_search", TextInputType.CONTENT_EDITABLE,
+                TextInputAction.SEARCH, EditorInfo.IME_ACTION_SEARCH);
+        checkInputAction("contenteditable_send", TextInputType.CONTENT_EDITABLE,
+                TextInputAction.SEND, EditorInfo.IME_ACTION_SEND);
+        checkInputAction("textarea_default", TextInputType.TEXT_AREA, TextInputAction.DEFAULT,
+                EditorInfo.IME_ACTION_NONE);
+        checkInputAction("textarea_enter", TextInputType.TEXT_AREA, TextInputAction.ENTER,
+                EditorInfo.IME_ACTION_NONE);
+        checkInputAction("textarea_go", TextInputType.TEXT_AREA, TextInputAction.GO,
+                EditorInfo.IME_ACTION_GO);
+        checkInputAction("textarea_done", TextInputType.TEXT_AREA, TextInputAction.DONE,
+                EditorInfo.IME_ACTION_DONE);
+        checkInputAction("textarea_next", TextInputType.TEXT_AREA, TextInputAction.NEXT,
+                EditorInfo.IME_ACTION_NEXT);
+        checkInputAction("textarea_previous", TextInputType.TEXT_AREA, TextInputAction.PREVIOUS,
+                EditorInfo.IME_ACTION_PREVIOUS);
+        checkInputAction("textarea_search", TextInputType.TEXT_AREA, TextInputAction.SEARCH,
+                EditorInfo.IME_ACTION_SEARCH);
+        checkInputAction("textarea_send", TextInputType.TEXT_AREA, TextInputAction.SEND,
+                EditorInfo.IME_ACTION_SEND);
+        checkInputAction("input_enter", TextInputType.TEXT, TextInputAction.ENTER,
+                EditorInfo.IME_ACTION_NONE);
+        checkInputAction(
+                "input_go", TextInputType.TEXT, TextInputAction.GO, EditorInfo.IME_ACTION_GO);
+        checkInputAction(
+                "input_done", TextInputType.TEXT, TextInputAction.DONE, EditorInfo.IME_ACTION_DONE);
+        checkInputAction(
+                "input_next", TextInputType.TEXT, TextInputAction.NEXT, EditorInfo.IME_ACTION_NEXT);
+        checkInputAction("input_previous", TextInputType.TEXT, TextInputAction.PREVIOUS,
+                EditorInfo.IME_ACTION_PREVIOUS);
+        checkInputAction("input_search", TextInputType.TEXT, TextInputAction.SEARCH,
+                EditorInfo.IME_ACTION_SEARCH);
+        checkInputAction(
+                "input_send", TextInputType.TEXT, TextInputAction.SEND, EditorInfo.IME_ACTION_SEND);
+
+        // Now verify that focusing a default input shows next action and doesn't generate key
+        // presses and focus moves to next node.
+        mRule.focusElement("input_text");
+        mRule.waitAndVerifyUpdateSelection(0, 0, 0, -1, -1);
+        mRule.waitForKeyboardInputActionStates(1, 0, 1, new Integer[] {TextInputType.TEXT},
+                new Integer[] {TextInputAction.DEFAULT});
+        mRule.waitForEventLogs("selectionchange");
+        mRule.clearEventLogs();
+        mRule.waitForEditorAction(EditorInfo.IME_ACTION_NEXT);
+        mRule.performEditorAction(EditorInfo.IME_ACTION_NEXT);
+        mRule.waitForEventLogState("selectionchange");
+        mRule.clearEventLogs();
+        mRule.resetAllStates();
+
+        mRule.waitForFocusedElement("input_enter");
+    }
+}
diff --git a/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java
index f5e3154..9ff5320 100644
--- a/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java
+++ b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java
@@ -154,7 +154,7 @@
             @Override
             public InputConnection call() throws Exception {
                 return mFactory.initializeAndGet(
-                        mContainerView, mImeAdapter, 1, 0, 0, 0, 0, mEditorInfo);
+                        mContainerView, mImeAdapter, 1, 0, 0, 0, 0, 0, mEditorInfo);
             }
         };
         when(mProxyView.onCreateInputConnection(any(EditorInfo.class)))
@@ -197,7 +197,7 @@
             @Override
             public void run() {
                 assertNull(mFactory.initializeAndGet(
-                        mContainerView, mImeAdapter, 1, 0, 0, 0, 0, mEditorInfo));
+                        mContainerView, mImeAdapter, 1, 0, 0, 0, 0, 0, mEditorInfo));
             }
         });
     }
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 046e138..3efeb92f 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -1466,6 +1466,7 @@
     TextInputState params;
     params.type = new_type;
     params.mode = new_mode;
+    params.action = new_info.action;
     params.flags = new_info.flags;
 #if defined(OS_ANDROID)
     if (next_previous_flags_ == kInvalidNextPreviousFlagsValue) {
diff --git a/content/test/data/android/input/input_action.html b/content/test/data/android/input/input_action.html
new file mode 100644
index 0000000..0e7b044c
--- /dev/null
+++ b/content/test/data/android/input/input_action.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<!-- Note: if this page gets long (or wide) enough that it can't fit entirely on
+     the phone's display without scrolling, the test will start being flaky and
+     it will be very difficult to debug :(. -->
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width">
+  </head>
+  <body>
+    <form action="about:blank">
+      <input id="input_text" type="text" size="10">
+      <input id="input_enter" type="text" size="10" enterKeyHint="enter">
+      <input id="input_go" type="text" size="10" enterKeyHint="go">
+      <input id="input_done" type="text" size="10" enterKeyHint="done">
+      <input id="input_next" type="text" size="10" enterKeyHint="next">
+      <input id="input_previous" type="text" size="10" enterKeyHint="previous">
+      <input id="input_search" type="text" size="10" enterKeyHint="search">
+      <input id="input_send" type="text" size="10" enterKeyHint="send">
+    </form>
+
+    <form action="about:blank">
+      <textarea id="textarea_default" rows="2" cols="10"></textarea>
+      <textarea id="textarea_enter" rows="2" cols="10" enterKeyHint="enter"></textarea>
+      <textarea id="textarea_go" rows="2" cols="10" enterKeyHint="go"></textarea>
+      <textarea id="textarea_done" rows="2" cols="10" enterKeyHint="done"></textarea>
+      <textarea id="textarea_next" rows="2" cols="10" enterKeyHint="next"></textarea>
+      <textarea id="textarea_previous" rows="2" cols="10" enterKeyHint="previous"></textarea>
+      <textarea id="textarea_search" rows="2" cols="10" enterKeyHint="search"></textarea>
+      <textarea id="textarea_send" rows="2" cols="10" enterKeyHint="send"></textarea>
+    </form>
+
+    <div id="contenteditable_default" contenteditable></div>
+    <div id="contenteditable_enter" contenteditable enterKeyHint="enter"></div>
+    <div id="contenteditable_go" contenteditable enterKeyHint="go"></div>
+    <div id="contenteditable_done" contenteditable enterKeyHint="done"></div>
+    <div id="contenteditable_next" contenteditable enterKeyHint="next"></div>
+    <div id="contenteditable_previous" contenteditable enterKeyHint="previous"></div>
+    <div id="contenteditable_search" contenteditable enterKeyHint="search"></div>
+    <div id="contenteditable_send" contenteditable enterKeyHint="send"></div>
+  </body>
+
+    <script>
+      var selectionChangeEventLog = "";
+      var otherEventLog = "";
+
+      function addOtherEventLog(type, detail) {
+        if (otherEventLog.length > 0) {
+          otherEventLog += ',';
+        }
+        if (detail == null) {
+          otherEventLog += type;
+        } else {
+          otherEventLog += type + '(' + detail + ')';
+        }
+      }
+
+      function addSelectionChangeEventLog(type, detail) {
+        if (selectionChangeEventLog.length > 0) {
+          selectionChangeEventLog += ',';
+        }
+        if (detail == null) {
+          selectionChangeEventLog += type;
+        } else {
+          selectionChangeEventLog += type + '(' + detail + ')';
+        }
+      }
+
+      // selectionchange event is queued, so it races with the other events.
+      // crbug.com/628964
+      function getEventLogs() {
+        if (otherEventLog.length > 0 && selectionChangeEventLog.length > 0)
+          return otherEventLog + ',' + selectionChangeEventLog;
+        return otherEventLog + selectionChangeEventLog;
+      }
+
+      function clearEventLogs() {
+        selectionChangeEventLog = '';
+        otherEventLog = '';
+      }
+
+      function addKeyEventListener(element, event_name) {
+        element.addEventListener(event_name, function (e) { addOtherEventLog(event_name, e.keyCode); });
+      }
+
+      function addSelectionEventListener(event_name) {
+        // Note that listeners added to the element are not effective for now.
+        document.addEventListener(event_name, function (e) { addSelectionChangeEventLog(event_name, e.data); });
+      }
+
+      function registerListeners(element) {
+        addKeyEventListener(element, "keydown");
+        addKeyEventListener(element, "keypress");
+        addKeyEventListener(element, "keyup");
+      }
+
+      // SelectionEventListener should be outside registerListenersAndObserver() to avoid duplication.
+      addSelectionEventListener("selectionchange");
+      registerListeners(document.body);
+    </script>
+</html>
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 48498ec..c32bf62 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -117,15 +117,16 @@
 crbug.com/782254 [ d3d11 win nvidia-0x1cb3 ] conformance2/rendering/attrib-type-match.html [ RetryOnFailure ]
 
 # WIN / OpenGL / NVIDIA failures
-crbug.com/781668 [ opengl win nvidia-0x1cb3 ] conformance2/textures/canvas_sub_rectangle/tex-2d-rgb565-rgb-unsigned_byte.html [ Failure ]
+crbug.com/715001 [ opengl win nvidia                    ] conformance/limits/gl-max-texture-dimensions.html [ Failure ]
+crbug.com/703779 [ opengl win nvidia                    ] conformance/textures/misc/texture-size.html [ Failure ]
+crbug.com/830046 [ opengl win passthrough nvidia        ] conformance2/rendering/blitframebuffer-size-overflow.html [ Skip ]
+crbug.com/781668 [ opengl win nvidia-0x1cb3             ] conformance2/textures/canvas_sub_rectangle/tex-2d-rgb565-rgb-unsigned_byte.html [ Failure ]
 crbug.com/921055 [ opengl win passthrough nvidia-0x1cb3 ] conformance2/textures/image_bitmap_from_image_data/tex-2d-rgb9_e5-rgb-float.html [ RetryOnFailure ]
-crbug.com/715001 [ opengl win nvidia ] conformance/limits/gl-max-texture-dimensions.html [ Failure ]
-crbug.com/703779 [ opengl win nvidia ] conformance/textures/misc/texture-size.html [ Failure ]
-crbug.com/830046 [ opengl win passthrough nvidia ] conformance2/rendering/blitframebuffer-size-overflow.html [ Skip ]
-crbug.com/832238 [ opengl win no-passthrough nvidia ] conformance2/transform_feedback/switching-objects.html [ RetryOnFailure ]
-crbug.com/887578 [ opengl win passthrough nvidia ] deqp/data/gles3/shaders/conversions.html [ RetryOnFailure ]
-crbug.com/822733 [ opengl win nvidia-0x1cb3 ] deqp/functional/gles3/transformfeedback/* [ RetryOnFailure ]
-crbug.com/905003 [ opengl win passthrough nvidia ] conformance2/textures/misc/integer-cubemap-specification-order-bug.html [ Failure ]
+crbug.com/905003 [ opengl win passthrough nvidia        ] conformance2/textures/misc/integer-cubemap-specification-order-bug.html [ Failure ]
+crbug.com/832238 [ opengl win no-passthrough nvidia     ] conformance2/transform_feedback/switching-objects.html [ RetryOnFailure ]
+crbug.com/887578 [ opengl win passthrough nvidia        ] deqp/data/gles3/shaders/conversions.html [ RetryOnFailure ]
+crbug.com/965648 [ opengl win passthrough nvidia-0x1cb3 ] deqp/functional/gles3/shaderstruct.html [ RetryOnFailure ]
+crbug.com/822733 [ opengl win nvidia-0x1cb3             ] deqp/functional/gles3/transformfeedback/* [ RetryOnFailure ]
 
 # Win / AMD
 
@@ -156,6 +157,7 @@
 crbug.com/828984 [ d3d11 win amd ] conformance2/textures/canvas_sub_rectangle/tex-2d-rgb9_e5-rgb-half_float.html [ RetryOnFailure ]
 crbug.com/828984 [ d3d11 win amd ] conformance2/textures/canvas_sub_rectangle/tex-2d-rgb9_e5-rgb-float.html [ RetryOnFailure ]
 crbug.com/828984 [ d3d11 win amd ] conformance2/textures/canvas_sub_rectangle/tex-2d-rgb32f-rgb-float.html [ RetryOnFailure ]
+crbug.com/844483 [ d3d11 win amd ] conformance2/textures/canvas_sub_rectangle/tex-2d-srgb8_alpha8-rgba-unsigned_byte.html [ RetryOnFailure ]
 crbug.com/828984 [ d3d11 win amd ] conformance2/textures/misc/copy-texture-image-webgl-specific.html [ RetryOnFailure ]
 crbug.com/878780 [ win amd ] conformance2/textures/webgl_canvas/* [ RetryOnFailure ]
 
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 e2b9ad3..8e13b88f 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
@@ -123,7 +123,11 @@
 crbug.com/751849 [ win7 debug nvidia d3d11 passthrough ] conformance/extensions/oes-texture-half-float-with-video.html [ RetryOnFailure ]
 # crbug.com/737016 [ win nvidia d3d11 passthrough ] conformance/programs/program-test.html [ RetryOnFailure ]
 
-# Win failures
+
+####################
+# Win failures     #
+####################
+
 # TODO(kbr): re-enable suppression for same test below once fixed.
 crbug.com/angleproject/2103 [ win ] conformance/glsl/bugs/sampler-struct-function-arg.html [ Skip ]
 
@@ -327,7 +331,11 @@
 crbug.com/957644 [ win passthrough amd vulkan ] conformance/glsl/samplers/glsl-function-texture2dprojlod.html [ Failure ]
 crbug.com/957644 [ win passthrough amd vulkan ] conformance/textures/misc/texture-corner-case-videos.html [ Failure ]
 
-# Mac failures
+
+####################
+# Mac failures     #
+####################
+
 crbug.com/844311 [ mac ] conformance/glsl/misc/fragcolor-fragdata-invariant.html [ Failure ]
 crbug.com/599272 [ mac no-passthrough ] conformance/extensions/oes-texture-float-with-video.html [ RetryOnFailure ]
 crbug.com/928926 [ mac ] conformance/ogles/GL/abs/abs_001_to_006.html [ RetryOnFailure ]
@@ -356,6 +364,11 @@
 crbug.com/871352 [ mac debug nvidia-0xfe9 ] conformance/uniforms/uniform-samplers-test.html [ Failure ]
 crbug.com/948218 [ mac debug nvidia-0xfe9 ] conformance/glsl/misc/shader-with-non-reserved-words.html [ Failure ]
 
+
+####################
+# Linux failures   #
+####################
+
 # Already fixed with Mesa 17.1.6
 crbug.com/680675 [ linux intel ] conformance/extensions/webgl-compressed-texture-astc.html [ Failure ]
 
@@ -390,6 +403,10 @@
 crbug.com/906066 [ linux amd passthrough ] conformance/rendering/draw-webgl-to-canvas-2d-repeatedly.html [ Failure ]
 crbug.com/960808 [ linux amd passthrough ] conformance/glsl/misc/shader-with-non-reserved-words.html [ Failure ]
 
+# Linux passthrough AMD OpenGL
+crbug.com/965594 [ linux amd opengl passthrough ] conformance/more/conformance/quickCheckAPI-S_V.html [ RetryOnFailure ]
+crbug.com/965594 [ linux amd opengl passthrough ] conformance/more/conformance/webGLArrays.html [ RetryOnFailure ]
+
 # The following two tests only fail on Linux/Intel with Mesa 18.0.5,
 # not on Mesa 17.1.4 with the same Intel HD 630 GPU.
 crbug.com/928530 [ linux intel no-passthrough ] conformance/programs/program-test.html [ Failure ]
diff --git a/device/fido/authenticator_get_info_response.cc b/device/fido/authenticator_get_info_response.cc
index 345e195..3e6be2d 100644
--- a/device/fido/authenticator_get_info_response.cc
+++ b/device/fido/authenticator_get_info_response.cc
@@ -44,8 +44,8 @@
     const AuthenticatorGetInfoResponse& response) {
   cbor::Value::ArrayValue version_array;
   for (const auto& version : response.versions) {
-    version_array.emplace_back(version == ProtocolVersion::kCtap ? kCtap2Version
-                                                                 : kU2fVersion);
+    version_array.emplace_back(
+        version == ProtocolVersion::kCtap2 ? kCtap2Version : kU2fVersion);
   }
   cbor::Value::MapValue device_info_map;
   device_info_map.emplace(1, std::move(version_array));
diff --git a/device/fido/ble/fido_ble_device_unittest.cc b/device/fido/ble/fido_ble_device_unittest.cc
index 17e8f30e..83697c93 100644
--- a/device/fido/ble/fido_ble_device_unittest.cc
+++ b/device/fido/ble/fido_ble_device_unittest.cc
@@ -199,7 +199,7 @@
 TEST_F(FidoBleDeviceTest, CancelDuringTransmission) {
   // Simulate a cancelation request that occurs while a multi-fragment message
   // is still being transmitted.
-  device()->set_supported_protocol(ProtocolVersion::kCtap);
+  device()->set_supported_protocol(ProtocolVersion::kCtap2);
   ConnectWithLength(kControlPointLength);
 
   ::testing::Sequence sequence;
@@ -233,7 +233,7 @@
 
 TEST_F(FidoBleDeviceTest, CancelAfterTransmission) {
   // Simulate a cancelation request that occurs after the request has been sent.
-  device()->set_supported_protocol(ProtocolVersion::kCtap);
+  device()->set_supported_protocol(ProtocolVersion::kCtap2);
   ConnectWithLength(kControlPointLength);
 
   ::testing::Sequence sequence;
diff --git a/device/fido/credential_management_handler.cc b/device/fido/credential_management_handler.cc
index 46ac8413..aeec57a 100644
--- a/device/fido/credential_management_handler.cc
+++ b/device/fido/credential_management_handler.cc
@@ -51,7 +51,7 @@
   state_ = State::kGettingRetries;
   CancelActiveAuthenticators(authenticator->GetId());
 
-  if (authenticator->SupportedProtocol() != ProtocolVersion::kCtap ||
+  if (authenticator->SupportedProtocol() != ProtocolVersion::kCtap2 ||
       !authenticator->Options() ||
       !(authenticator->Options()->supports_credential_management ||
         authenticator->Options()->supports_credential_management_preview)) {
diff --git a/device/fido/credential_management_handler_unittest.cc b/device/fido/credential_management_handler_unittest.cc
index b0c837a..2e068fa 100644
--- a/device/fido/credential_management_handler_unittest.cc
+++ b/device/fido/credential_management_handler_unittest.cc
@@ -68,7 +68,7 @@
   ctap_config.credential_management_support = true;
   ctap_config.resident_credential_storage = 100;
   virtual_device.SetCtap2Config(ctap_config);
-  virtual_device.SetSupportedProtocol(device::ProtocolVersion::kCtap);
+  virtual_device.SetSupportedProtocol(device::ProtocolVersion::kCtap2);
   virtual_device.mutable_state()->pin = kPIN;
   virtual_device.mutable_state()->retries = 8;
   ASSERT_TRUE(virtual_device.mutable_state()->InjectResidentKey(
diff --git a/device/fido/ctap_response_unittest.cc b/device/fido/ctap_response_unittest.cc
index 9e3f3eae..c3e607e5 100644
--- a/device/fido/ctap_response_unittest.cc
+++ b/device/fido/ctap_response_unittest.cc
@@ -580,7 +580,7 @@
   ASSERT_TRUE(get_info_response->max_msg_size);
   EXPECT_EQ(*get_info_response->max_msg_size, 1200u);
   EXPECT_TRUE(
-      base::ContainsKey(get_info_response->versions, ProtocolVersion::kCtap));
+      base::ContainsKey(get_info_response->versions, ProtocolVersion::kCtap2));
   EXPECT_TRUE(
       base::ContainsKey(get_info_response->versions, ProtocolVersion::kU2f));
   EXPECT_TRUE(get_info_response->options.is_platform_device);
@@ -625,7 +625,7 @@
 
 TEST(CTAPResponseTest, TestSerializeGetInfoResponse) {
   AuthenticatorGetInfoResponse response(
-      {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, kTestDeviceAaguid);
+      {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid);
   response.extensions.emplace({std::string("uvm"), std::string("hmac-secret")});
   AuthenticatorSupportedOptions options;
   options.supports_resident_key = true;
diff --git a/device/fido/device_response_converter.cc b/device/fido/device_response_converter.cc
index d351270..8076a1a 100644
--- a/device/fido/device_response_converter.cc
+++ b/device/fido/device_response_converter.cc
@@ -31,7 +31,7 @@
 
 ProtocolVersion ConvertStringToProtocolVersion(base::StringPiece version) {
   if (version == kCtap2Version)
-    return ProtocolVersion::kCtap;
+    return ProtocolVersion::kCtap2;
   if (version == kU2fVersion)
     return ProtocolVersion::kU2f;
 
diff --git a/device/fido/fido_constants.h b/device/fido/fido_constants.h
index 7f862e41..a48c13a0 100644
--- a/device/fido/fido_constants.h
+++ b/device/fido/fido_constants.h
@@ -44,7 +44,7 @@
 };
 
 enum class ProtocolVersion {
-  kCtap,
+  kCtap2,
   kU2f,
   kUnknown,
 };
diff --git a/device/fido/fido_device.cc b/device/fido/fido_device.cc
index 4f8cb03..e4c0bc6 100644
--- a/device/fido/fido_device.cc
+++ b/device/fido/fido_device.cc
@@ -36,7 +36,7 @@
   // Set the protocol version to CTAP2 for the purpose of sending the GetInfo
   // request. The correct value will be set in the callback based on the
   // device response.
-  supported_protocol_ = ProtocolVersion::kCtap;
+  supported_protocol_ = ProtocolVersion::kCtap2;
   FIDO_LOG(DEBUG)
       << "Sending CTAP2 AuthenticatorGetInfo request to authenticator.";
   DeviceTransact(AuthenticatorGetInfoRequest().Serialize(),
@@ -46,7 +46,7 @@
 
 bool FidoDevice::SupportedProtocolIsInitialized() {
   return (supported_protocol_ == ProtocolVersion::kU2f && !device_info_) ||
-         (supported_protocol_ == ProtocolVersion::kCtap && device_info_);
+         (supported_protocol_ == ProtocolVersion::kCtap2 && device_info_);
 }
 
 void FidoDevice::OnDeviceInfoReceived(
@@ -59,12 +59,12 @@
   state_ = FidoDevice::State::kReady;
   base::Optional<AuthenticatorGetInfoResponse> get_info_response =
       response ? ReadCTAPGetInfoResponse(*response) : base::nullopt;
-  if (!get_info_response ||
-      !base::ContainsKey(get_info_response->versions, ProtocolVersion::kCtap)) {
+  if (!get_info_response || !base::ContainsKey(get_info_response->versions,
+                                               ProtocolVersion::kCtap2)) {
     supported_protocol_ = ProtocolVersion::kU2f;
     FIDO_LOG(DEBUG) << "The device only supports the U2F protocol.";
   } else {
-    supported_protocol_ = ProtocolVersion::kCtap;
+    supported_protocol_ = ProtocolVersion::kCtap2;
     device_info_ = std::move(*get_info_response);
     FIDO_LOG(DEBUG) << "The device supports the CTAP2 protocol.";
   }
diff --git a/device/fido/fido_device_authenticator.cc b/device/fido/fido_device_authenticator.cc
index 1b6acf1..a266b035 100644
--- a/device/fido/fido_device_authenticator.cc
+++ b/device/fido/fido_device_authenticator.cc
@@ -46,7 +46,7 @@
     case ProtocolVersion::kU2f:
       options_ = AuthenticatorSupportedOptions();
       break;
-    case ProtocolVersion::kCtap:
+    case ProtocolVersion::kCtap2:
       DCHECK(device_->device_info()) << "uninitialized device";
       options_ = device_->device_info()->options;
       break;
diff --git a/device/fido/get_assertion_handler_unittest.cc b/device/fido/get_assertion_handler_unittest.cc
index c50d87c..6bcb58a 100644
--- a/device/fido/get_assertion_handler_unittest.cc
+++ b/device/fido/get_assertion_handler_unittest.cc
@@ -770,7 +770,7 @@
   base::test::ScopedTaskEnvironment scoped_task_environment{
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
   device::test::ScopedVirtualFidoDevice virtual_device;
-  virtual_device.SetSupportedProtocol(device::ProtocolVersion::kCtap);
+  virtual_device.SetSupportedProtocol(device::ProtocolVersion::kCtap2);
   ASSERT_TRUE(virtual_device.mutable_state()->InjectRegistration(
       fido_parsing_utils::Materialize(test_data::kTestGetAssertionCredentialId),
       test_data::kRelyingPartyId));
diff --git a/device/fido/get_assertion_task.cc b/device/fido/get_assertion_task.cc
index 97d7690..a60447b8f 100644
--- a/device/fido/get_assertion_task.cc
+++ b/device/fido/get_assertion_task.cc
@@ -72,7 +72,7 @@
 }
 
 void GetAssertionTask::StartTask() {
-  if (device()->supported_protocol() == ProtocolVersion::kCtap) {
+  if (device()->supported_protocol() == ProtocolVersion::kCtap2) {
     GetAssertion();
   } else {
     U2fSign();
diff --git a/device/fido/hid/fido_hid_device.cc b/device/fido/hid/fido_hid_device.cc
index caafe5f..edd84036 100644
--- a/device/fido/hid/fido_hid_device.cc
+++ b/device/fido/hid/fido_hid_device.cc
@@ -56,7 +56,7 @@
     // cause the request to complete in the usual way. U2F doesn't have a cancel
     // message, but U2F devices are not expected to block on requests and also
     // no U2F command alters state in a meaningful way, as CTAP2 commands do.
-    if (supported_protocol() != ProtocolVersion::kCtap) {
+    if (supported_protocol() != ProtocolVersion::kCtap2) {
       return;
     }
 
@@ -118,7 +118,7 @@
 
       // Write message to the device.
       current_token_ = pending_transactions_.front().token;
-      const auto command_type = supported_protocol() == ProtocolVersion::kCtap
+      const auto command_type = supported_protocol() == ProtocolVersion::kCtap2
                                     ? FidoHidDeviceCommand::kCbor
                                     : FidoHidDeviceCommand::kMsg;
       auto maybe_message(FidoHidMessage::Create(
@@ -349,7 +349,7 @@
 
   // If received HID packet is a keep-alive message then reset the timeout and
   // read again.
-  if (supported_protocol() == ProtocolVersion::kCtap &&
+  if (supported_protocol() == ProtocolVersion::kCtap2 &&
       message->cmd() == FidoHidDeviceCommand::kKeepAlive) {
     timeout_callback_.Cancel();
     ArmTimeout();
diff --git a/device/fido/hid/fido_hid_device_unittest.cc b/device/fido/hid/fido_hid_device_unittest.cc
index 9fd5003..39fe23a 100644
--- a/device/fido/hid/fido_hid_device_unittest.cc
+++ b/device/fido/hid/fido_hid_device_unittest.cc
@@ -344,7 +344,7 @@
   auto& device = u2f_devices.front();
 
   // Keep alive message handling is only supported for CTAP HID device.
-  device->set_supported_protocol(ProtocolVersion::kCtap);
+  device->set_supported_protocol(ProtocolVersion::kCtap2);
   TestDeviceCallbackReceiver cb;
   device->DeviceTransact(GetMockDeviceRequest(), cb.callback());
   cb.WaitForCallback();
@@ -390,7 +390,7 @@
   auto& device = u2f_devices.front();
 
   // Keep alive message handling is only supported for CTAP HID device.
-  device->set_supported_protocol(ProtocolVersion::kCtap);
+  device->set_supported_protocol(ProtocolVersion::kCtap2);
   TestDeviceCallbackReceiver cb;
   device->DeviceTransact(GetMockDeviceRequest(), cb.callback());
   cb.WaitForCallback();
@@ -435,7 +435,7 @@
   auto& device = u2f_devices.front();
 
   // Keep alive message handling is only supported for CTAP HID device.
-  device->set_supported_protocol(ProtocolVersion::kCtap);
+  device->set_supported_protocol(ProtocolVersion::kCtap2);
   TestDeviceCallbackReceiver cb;
   auto token = device->DeviceTransact(GetMockDeviceRequest(), cb.callback());
   auto delay_before_cancel = base::TimeDelta::FromSeconds(1);
@@ -500,7 +500,7 @@
   device = u2f_devices.front().get();
 
   // Keep alive message handling is only supported for CTAP HID device.
-  device->set_supported_protocol(ProtocolVersion::kCtap);
+  device->set_supported_protocol(ProtocolVersion::kCtap2);
   TestDeviceCallbackReceiver cb;
   // The size of |dummy_request| needs only to make the request need two USB
   // frames.
@@ -568,7 +568,7 @@
   device = u2f_devices.front().get();
 
   // Cancelation is only supported for CTAP HID device.
-  device->set_supported_protocol(ProtocolVersion::kCtap);
+  device->set_supported_protocol(ProtocolVersion::kCtap2);
   TestDeviceCallbackReceiver cb;
   std::vector<uint8_t> dummy_request(1);
   token = device->DeviceTransact(std::move(dummy_request), cb.callback());
@@ -633,7 +633,7 @@
   device = u2f_devices.front().get();
 
   // Cancelation is only supported for CTAP HID device.
-  device->set_supported_protocol(ProtocolVersion::kCtap);
+  device->set_supported_protocol(ProtocolVersion::kCtap2);
   TestDeviceCallbackReceiver cb;
   std::vector<uint8_t> dummy_request(1);
   token = device->DeviceTransact(std::move(dummy_request), cb.callback());
diff --git a/device/fido/make_credential_task.cc b/device/fido/make_credential_task.cc
index 053f60f..ee15ae5 100644
--- a/device/fido/make_credential_task.cc
+++ b/device/fido/make_credential_task.cc
@@ -107,7 +107,7 @@
 }
 
 void MakeCredentialTask::StartTask() {
-  if (device()->supported_protocol() == ProtocolVersion::kCtap &&
+  if (device()->supported_protocol() == ProtocolVersion::kCtap2 &&
       !request_.is_u2f_only &&
       !ShouldUseU2fBecauseCtapRequiresClientPin(device(), request_)) {
     MakeCredential();
@@ -115,7 +115,7 @@
     // |device_info| should be present iff the device is CTAP2. This will be
     // used in |MaybeRevertU2fFallback| to restore the protocol of CTAP2 devices
     // once this task is complete.
-    DCHECK((device()->supported_protocol() == ProtocolVersion::kCtap) ==
+    DCHECK((device()->supported_protocol() == ProtocolVersion::kCtap2) ==
            static_cast<bool>(device()->device_info()));
     device()->set_supported_protocol(ProtocolVersion::kU2f);
     U2fRegister();
@@ -258,7 +258,7 @@
     // This was actually a CTAP2 device, but the protocol version was set to U2F
     // because it had a PIN set and so, in order to make a credential, the U2F
     // interface was used.
-    device()->set_supported_protocol(ProtocolVersion::kCtap);
+    device()->set_supported_protocol(ProtocolVersion::kCtap2);
   }
 
   std::move(callback_).Run(status, std::move(response));
diff --git a/device/fido/make_credential_task_unittest.cc b/device/fido/make_credential_task_unittest.cc
index ded1476..3e7e1d5 100644
--- a/device/fido/make_credential_task_unittest.cc
+++ b/device/fido/make_credential_task_unittest.cc
@@ -78,7 +78,7 @@
   EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
             make_credential_callback_receiver().status());
   EXPECT_TRUE(make_credential_callback_receiver().value());
-  EXPECT_EQ(device->supported_protocol(), ProtocolVersion::kCtap);
+  EXPECT_EQ(device->supported_protocol(), ProtocolVersion::kCtap2);
   EXPECT_TRUE(device->device_info());
 }
 
@@ -116,7 +116,7 @@
 
 TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) {
   AuthenticatorGetInfoResponse device_info(
-      {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, kTestDeviceAaguid);
+      {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid);
   AuthenticatorSupportedOptions options;
   options.client_pin_availability =
       AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet;
@@ -136,7 +136,7 @@
 
 TEST_F(FidoMakeCredentialTaskTest, EnforceClientPinWhenUserVerificationSet) {
   AuthenticatorGetInfoResponse device_info(
-      {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, kTestDeviceAaguid);
+      {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid);
   AuthenticatorSupportedOptions options;
   options.client_pin_availability =
       AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet;
diff --git a/device/fido/mock_fido_device.cc b/device/fido/mock_fido_device.cc
index 99af57b..92c2370 100644
--- a/device/fido/mock_fido_device.cc
+++ b/device/fido/mock_fido_device.cc
@@ -36,7 +36,7 @@
   if (!device_info) {
     device_info = DefaultAuthenticatorInfo();
   }
-  return std::make_unique<MockFidoDevice>(ProtocolVersion::kCtap,
+  return std::make_unique<MockFidoDevice>(ProtocolVersion::kCtap2,
                                           std::move(*device_info));
 }
 
diff --git a/device/fido/reset_request_handler.cc b/device/fido/reset_request_handler.cc
index 873b9232..2ca1720 100644
--- a/device/fido/reset_request_handler.cc
+++ b/device/fido/reset_request_handler.cc
@@ -47,7 +47,7 @@
   processed_touch_ = true;
   CancelActiveAuthenticators(authenticator->GetId());
 
-  if (authenticator->SupportedProtocol() != ProtocolVersion::kCtap) {
+  if (authenticator->SupportedProtocol() != ProtocolVersion::kCtap2) {
     std::move(finished_callback_)
         .Run(CtapDeviceResponseCode::kCtap1ErrInvalidCommand);
     return;
diff --git a/device/fido/scoped_virtual_fido_device.cc b/device/fido/scoped_virtual_fido_device.cc
index 145afb4..6b8b17a 100644
--- a/device/fido/scoped_virtual_fido_device.cc
+++ b/device/fido/scoped_virtual_fido_device.cc
@@ -37,7 +37,7 @@
  protected:
   void StartInternal() override {
     std::unique_ptr<FidoDevice> device;
-    if (supported_protocol_ == ProtocolVersion::kCtap) {
+    if (supported_protocol_ == ProtocolVersion::kCtap2) {
       device = std::make_unique<VirtualCtap2Device>(state_, ctap2_config_);
     } else {
       device = std::make_unique<VirtualU2fDevice>(state_);
@@ -73,7 +73,7 @@
 
 void ScopedVirtualFidoDevice::SetCtap2Config(
     const VirtualCtap2Device::Config& config) {
-  supported_protocol_ = ProtocolVersion::kCtap;
+  supported_protocol_ = ProtocolVersion::kCtap2;
   ctap2_config_ = config;
 }
 
diff --git a/device/fido/virtual_ctap2_device.cc b/device/fido/virtual_ctap2_device.cc
index 821df8f..be2c76b 100644
--- a/device/fido/virtual_ctap2_device.cc
+++ b/device/fido/virtual_ctap2_device.cc
@@ -442,7 +442,7 @@
 VirtualCtap2Device::VirtualCtap2Device()
     : VirtualFidoDevice(), weak_factory_(this) {
   device_info_ =
-      AuthenticatorGetInfoResponse({ProtocolVersion::kCtap}, kDeviceAaguid);
+      AuthenticatorGetInfoResponse({ProtocolVersion::kCtap2}, kDeviceAaguid);
 }
 
 VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
@@ -450,7 +450,7 @@
     : VirtualFidoDevice(std::move(state)),
       config_(config),
       weak_factory_(this) {
-  std::vector<ProtocolVersion> versions = {ProtocolVersion::kCtap};
+  std::vector<ProtocolVersion> versions = {ProtocolVersion::kCtap2};
   if (config.u2f_support) {
     versions.emplace_back(ProtocolVersion::kU2f);
     u2f_device_.reset(new VirtualU2fDevice(NewReferenceToState()));
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 0e5aa71..79742a0 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -190,10 +190,20 @@
         "windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.h",
         "windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.cc",
         "windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.h",
+        "windows_mixed_reality/wrappers/test/mock_wmr_input_location.cc",
+        "windows_mixed_reality/wrappers/test/mock_wmr_input_location.h",
         "windows_mixed_reality/wrappers/test/mock_wmr_input_manager.cc",
         "windows_mixed_reality/wrappers/test/mock_wmr_input_manager.h",
+        "windows_mixed_reality/wrappers/test/mock_wmr_input_source.cc",
+        "windows_mixed_reality/wrappers/test/mock_wmr_input_source.h",
+        "windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.cc",
+        "windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.h",
         "windows_mixed_reality/wrappers/test/mock_wmr_origins.cc",
         "windows_mixed_reality/wrappers/test/mock_wmr_origins.h",
+        "windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.cc",
+        "windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.h",
+        "windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.cc",
+        "windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.h",
         "windows_mixed_reality/wrappers/test/mock_wmr_rendering.cc",
         "windows_mixed_reality/wrappers/test/mock_wmr_rendering.h",
         "windows_mixed_reality/wrappers/test/mock_wmr_timestamp.cc",
diff --git a/device/vr/openvr/openvr_gamepad_helper.cc b/device/vr/openvr/openvr_gamepad_helper.cc
index 63389f20..5cb1b9e 100644
--- a/device/vr/openvr/openvr_gamepad_helper.cc
+++ b/device/vr/openvr/openvr_gamepad_helper.cc
@@ -316,7 +316,13 @@
         GetOpenVRString(vr_system, vr::Prop_ModelNumber_String, controller_id);
     std::string manufacturer = GetOpenVRString(
         vr_system, vr::Prop_ManufacturerName_String, controller_id);
-    return (model == "Vive Controller MV") && (manufacturer == "HTC");
+
+    // OpenVR reports different model strings for developer vs released versions
+    // of Vive controllers. In the future, there could be additional iterations
+    // of the Vive controller that we also want to catch here. That's why we
+    // check if the model string contains "Vive" instead of doing an exact match
+    // against specific string(s).
+    return (manufacturer == "HTC") && (model.find("Vive") != std::string::npos);
   }
 
   // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue
diff --git a/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc b/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc
index 868abe9..7be8aeb3 100644
--- a/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc
+++ b/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc
@@ -136,27 +136,27 @@
   return gamepad_vector;
 }
 
-GamepadPose GetGamepadPose(const WMRInputLocation& location) {
+GamepadPose GetGamepadPose(const WMRInputLocation* location) {
   GamepadPose gamepad_pose;
 
   WFN::Quaternion quat;
-  if (location.TryGetOrientation(&quat)) {
+  if (location->TryGetOrientation(&quat)) {
     gamepad_pose.not_null = true;
     gamepad_pose.orientation = ConvertToGamepadQuaternion(quat);
   }
 
   WFN::Vector3 vec3;
-  if (location.TryGetPosition(&vec3)) {
+  if (location->TryGetPosition(&vec3)) {
     gamepad_pose.not_null = true;
     gamepad_pose.position = ConvertToGamepadVector(vec3);
   }
 
-  if (location.TryGetVelocity(&vec3)) {
+  if (location->TryGetVelocity(&vec3)) {
     gamepad_pose.not_null = true;
     gamepad_pose.linear_velocity = ConvertToGamepadVector(vec3);
   }
 
-  if (location.TryGetAngularVelocity(&vec3)) {
+  if (location->TryGetAngularVelocity(&vec3)) {
     gamepad_pose.not_null = true;
     gamepad_pose.angular_velocity = ConvertToGamepadVector(vec3);
   }
@@ -214,20 +214,20 @@
 // Note that since this is built by polling, and so eventing changes are not
 // accounted for here.
 std::unordered_map<ButtonName, GamepadBuilder::ButtonData> ParseButtonState(
-    const WMRInputSourceState& source_state) {
+    const WMRInputSourceState* source_state) {
   std::unordered_map<ButtonName, GamepadBuilder::ButtonData> button_map;
 
   // Add the select button
   GamepadBuilder::ButtonData data = button_map[ButtonName::kSelect];
-  data.pressed = source_state.IsSelectPressed();
+  data.pressed = source_state->IsSelectPressed();
   data.touched = data.pressed;
-  data.value = source_state.SelectPressedValue();
+  data.value = source_state->SelectPressedValue();
   data.has_both_axes = false;
   button_map[ButtonName::kSelect] = data;
 
   // Add the grip button
   data = button_map[ButtonName::kGrip];
-  data.pressed = source_state.IsGrasped();
+  data.pressed = source_state->IsGrasped();
   data.touched = data.pressed;
   data.value = data.pressed ? 1.0 : 0.0;
   data.has_both_axes = false;
@@ -235,31 +235,31 @@
 
   // Select and grip are the only two required buttons, if we can't get the
   // others, we can safely return just them.
-  if (!source_state.SupportsControllerProperties())
+  if (!source_state->SupportsControllerProperties())
     return button_map;
 
   // Add the thumbstick
   data = button_map[ButtonName::kThumbstick];
-  data.pressed = source_state.IsThumbstickPressed();
+  data.pressed = source_state->IsThumbstickPressed();
   data.touched = data.pressed;
   data.value = data.pressed ? 1.0 : 0.0;
 
   data.has_both_axes = true;
-  data.x_axis = source_state.ThumbstickX();
-  data.y_axis = source_state.ThumbstickY();
+  data.x_axis = source_state->ThumbstickX();
+  data.y_axis = source_state->ThumbstickY();
   button_map[ButtonName::kThumbstick] = data;
 
   // Add the touchpad
   data = button_map[ButtonName::kTouchpad];
-  data.pressed = source_state.IsTouchpadPressed();
-  data.touched = source_state.IsTouchpadTouched() || data.pressed;
+  data.pressed = source_state->IsTouchpadPressed();
+  data.touched = source_state->IsTouchpadTouched() || data.pressed;
   data.value = data.pressed ? 1.0 : 0.0;
 
   // The Touchpad does have Axes, but if it's not touched, they are 0.
   data.has_both_axes = true;
   if (data.touched) {
-    data.x_axis = source_state.TouchpadX();
-    data.y_axis = source_state.TouchpadY();
+    data.x_axis = source_state->TouchpadX();
+    data.y_axis = source_state->TouchpadY();
   } else {
     data.x_axis = 0;
     data.y_axis = 0;
@@ -282,8 +282,8 @@
   return gfx::ComposeTransform(decomposed_transform);
 }
 
-bool TryGetPointerOffset(const WMRInputSourceState& state,
-                         const WMRInputSource& source,
+bool TryGetPointerOffset(const WMRInputSourceState* state,
+                         const WMRInputSource* source,
                          const WMRCoordinateSystem* origin,
                          gfx::Transform origin_from_grip,
                          gfx::Transform* grip_from_pointer) {
@@ -300,23 +300,25 @@
   if (!origin_from_grip.GetInverse(&grip_from_origin))
     return false;
 
-  bool pointing_supported = source.IsPointingSupported();
+  bool pointing_supported = source->IsPointingSupported();
 
-  WMRPointerPose pointer_pose(nullptr);
-  if (!state.TryGetPointerPose(origin, &pointer_pose))
+  std::unique_ptr<WMRPointerPose> pointer_pose =
+      state->TryGetPointerPose(origin);
+  if (!pointer_pose)
     return false;
 
   WFN::Vector3 pos;
   WFN::Quaternion rot;
   if (pointing_supported) {
-    WMRPointerSourcePose pointer_source_pose(nullptr);
-    if (!pointer_pose.TryGetInteractionSourcePose(source, &pointer_source_pose))
+    std::unique_ptr<WMRPointerSourcePose> pointer_source_pose =
+        pointer_pose->TryGetInteractionSourcePose(source);
+    if (!pointer_source_pose)
       return false;
 
-    pos = pointer_source_pose.Position();
-    rot = pointer_source_pose.Orientation();
+    pos = pointer_source_pose->Position();
+    rot = pointer_source_pose->Orientation();
   } else {
-    pos = pointer_pose.HeadForward();
+    pos = pointer_pose->HeadForward();
   }
 
   gfx::Transform origin_from_pointer = CreateTransform(
@@ -336,8 +338,8 @@
   }
 }
 
-uint32_t GetSourceId(const WMRInputSource& source) {
-  uint32_t id = source.Id();
+uint32_t GetSourceId(const WMRInputSource* source) {
+  uint32_t id = source->Id();
 
   // Voice's ID seems to be coming through as 0, which will cause a DCHECK in
   // the hash table used on the blink side.  To ensure that we don't have any
@@ -386,11 +388,15 @@
   if (!timestamp || !origin || !EnsureSpatialInteractionManager())
     return input_states;
 
-  base::AutoLock scoped_lock(lock_);
   auto source_states =
       input_manager_->GetDetectedSourcesAtTimestamp(timestamp->GetRawPtr());
-  for (auto state : source_states) {
-    auto parsed_source_state = LockedParseWindowsSourceState(state, origin);
+  // This can't be acquired until after GetDetectedSourcesAtTimestamp() because
+  // otherwise the tests will deadlock when triggering pressed/released
+  // callbacks.
+  base::AutoLock scoped_lock(lock_);
+  for (const auto& state : source_states) {
+    auto parsed_source_state =
+        LockedParseWindowsSourceState(state.get(), origin);
 
     if (parsed_source_state.source_state) {
       parsed_source_state.source_state->gamepad =
@@ -399,9 +405,9 @@
     }
   }
 
-  for (unsigned int i = 0; i < pending_voice_states_.size(); i++) {
+  for (const auto& state : pending_voice_states_) {
     auto parsed_source_state =
-        LockedParseWindowsSourceState(pending_voice_states_[i], origin);
+        LockedParseWindowsSourceState(state.get(), origin);
 
     if (parsed_source_state.source_state)
       input_states.push_back(std::move(parsed_source_state.source_state));
@@ -420,11 +426,15 @@
   if (!timestamp || !origin || !EnsureSpatialInteractionManager())
     return ret;
 
-  base::AutoLock scoped_lock(lock_);
   auto source_states =
       input_manager_->GetDetectedSourcesAtTimestamp(timestamp->GetRawPtr());
-  for (auto state : source_states) {
-    auto parsed_source_state = LockedParseWindowsSourceState(state, origin);
+  // This can't be acquired until after GetDetectedSourcesAtTimestamp() because
+  // otherwise the tests will deadlock when triggering pressed/released
+  // callbacks.
+  base::AutoLock scoped_lock(lock_);
+  for (const auto& state : source_states) {
+    auto parsed_source_state =
+        LockedParseWindowsSourceState(state.get(), origin);
 
     // If we have a grip, then we should have enough data.
     if (parsed_source_state.source_state &&
@@ -436,14 +446,14 @@
 }
 
 ParsedInputState MixedRealityInputHelper::LockedParseWindowsSourceState(
-    const WMRInputSourceState& state,
+    const WMRInputSourceState* state,
     const WMRCoordinateSystem* origin) {
   ParsedInputState input_state;
   if (!origin)
     return input_state;
 
-  WMRInputSource source = state.GetSource();
-  SourceKind source_kind = source.Kind();
+  std::unique_ptr<WMRInputSource> source = state->GetSource();
+  SourceKind source_kind = source->Kind();
 
   bool is_controller =
       (source_kind == SourceKind::SpatialInteractionSourceKind_Controller);
@@ -460,11 +470,12 @@
   gfx::Transform origin_from_grip;
   if (is_controller) {
     input_state.button_data = ParseButtonState(state);
-    WMRInputLocation location_in_origin(nullptr);
-    if (!state.TryGetLocation(origin, &location_in_origin))
+    std::unique_ptr<WMRInputLocation> location_in_origin =
+        state->TryGetLocation(origin);
+    if (!location_in_origin)
       return input_state;
 
-    auto gamepad_pose = GetGamepadPose(location_in_origin);
+    auto gamepad_pose = GetGamepadPose(location_in_origin.get());
     if (!(gamepad_pose.not_null && gamepad_pose.position.not_null &&
           gamepad_pose.orientation.not_null))
       return input_state;
@@ -475,7 +486,7 @@
   }
 
   gfx::Transform grip_from_pointer;
-  if (!TryGetPointerOffset(state, source, origin, origin_from_grip,
+  if (!TryGetPointerOffset(state, source.get(), origin, origin_from_grip,
                            &grip_from_pointer))
     return input_state;
 
@@ -485,7 +496,7 @@
 
   // Hands may not have the same id especially if they are lost but since we
   // are only tracking controllers/voice, this id should be consistent.
-  uint32_t id = GetSourceId(source);
+  uint32_t id = GetSourceId(source.get());
 
   source_state->source_id = id;
   source_state->primary_input_pressed = controller_states_[id].pressed;
@@ -509,7 +520,7 @@
     description->handedness = device::mojom::XRHandedness::NONE;
   } else if (is_controller) {
     description->target_ray_mode = device::mojom::XRTargetRayMode::POINTING;
-    description->handedness = WindowsToMojoHandedness(source.Handedness());
+    description->handedness = WindowsToMojoHandedness(source->Handedness());
   } else {
     NOTREACHED();
   }
@@ -541,16 +552,16 @@
   if (press_kind != PressKind::SpatialInteractionPressKind_Select)
     return;
 
-  WMRInputSourceState state = args.State();
-  WMRInputSource source = state.GetSource();
+  std::unique_ptr<WMRInputSourceState> state = args.State();
+  std::unique_ptr<WMRInputSource> source = state->GetSource();
 
-  SourceKind source_kind = source.Kind();
+  SourceKind source_kind = source->Kind();
 
   if (source_kind != SourceKind::SpatialInteractionSourceKind_Controller &&
       source_kind != SourceKind::SpatialInteractionSourceKind_Voice)
     return;
 
-  uint32_t id = GetSourceId(source);
+  uint32_t id = GetSourceId(source.get());
 
   bool wasPressed = controller_states_[id].pressed;
   bool wasClicked = controller_states_[id].clicked;
diff --git a/device/vr/windows_mixed_reality/mixed_reality_input_helper.h b/device/vr/windows_mixed_reality/mixed_reality_input_helper.h
index 723ebf5..070d3cde 100644
--- a/device/vr/windows_mixed_reality/mixed_reality_input_helper.h
+++ b/device/vr/windows_mixed_reality/mixed_reality_input_helper.h
@@ -59,7 +59,7 @@
   bool EnsureSpatialInteractionManager();
 
   ParsedInputState LockedParseWindowsSourceState(
-      const WMRInputSourceState& state,
+      const WMRInputSourceState* state,
       const WMRCoordinateSystem* origin);
 
   void OnSourcePressed(const WMRInputSourceEventArgs& args);
@@ -84,7 +84,7 @@
   std::unordered_map<uint32_t, ControllerState> controller_states_;
   HWND hwnd_;
 
-  std::vector<WMRInputSourceState> pending_voice_states_;
+  std::vector<std::unique_ptr<WMRInputSourceState>> pending_voice_states_;
 
   base::Lock lock_;
 
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.cc
index 6582ab7..d0afcec 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.cc
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.cc
@@ -4,6 +4,8 @@
 
 #include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.h"
 
+#include "device/vr/test/test_hook.h"
+#include "device/vr/windows_mixed_reality/mixed_reality_statics.h"
 #include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.h"
 #include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_timestamp.h"
 #include "device/vr/windows_mixed_reality/wrappers/wmr_rendering.h"
@@ -29,7 +31,9 @@
 }
 
 // MockWMRHolographicFrame
-MockWMRHolographicFrame::MockWMRHolographicFrame() {}
+MockWMRHolographicFrame::MockWMRHolographicFrame(
+    const Microsoft::WRL::ComPtr<ID3D11Device>& device)
+    : d3d11_device_(device) {}
 
 MockWMRHolographicFrame::~MockWMRHolographicFrame() = default;
 
@@ -40,10 +44,16 @@
 
 std::unique_ptr<WMRRenderingParameters>
 MockWMRHolographicFrame::TryGetRenderingParameters(const WMRCameraPose* pose) {
-  return std::make_unique<MockWMRRenderingParameters>();
+  return std::make_unique<MockWMRRenderingParameters>(d3d11_device_);
 }
 
 bool MockWMRHolographicFrame::TryPresentUsingCurrentPrediction() {
+  // TODO(https://crbug.com/926048): Actually pass in correct data.
+  SubmittedFrameData data;
+  auto locked_hook = MixedRealityDeviceStatics::GetLockedTestHook();
+  if (locked_hook.GetHook()) {
+    locked_hook.GetHook()->OnFrameSubmitted(data);
+  }
   return true;
 }
 
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.h
index f0a8044..8c51e76 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.h
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_frame.h
@@ -4,6 +4,7 @@
 #ifndef DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_HOLOGRAPHIC_FRAME_H_
 #define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_HOLOGRAPHIC_FRAME_H_
 
+#include <d3d11.h>
 #include "device/vr/windows_mixed_reality/wrappers/wmr_holographic_frame.h"
 
 namespace device {
@@ -22,7 +23,7 @@
 
 class MockWMRHolographicFrame : public WMRHolographicFrame {
  public:
-  MockWMRHolographicFrame();
+  MockWMRHolographicFrame(const Microsoft::WRL::ComPtr<ID3D11Device>& device);
   ~MockWMRHolographicFrame() override;
 
   std::unique_ptr<WMRHolographicFramePrediction> CurrentPrediction() override;
@@ -31,6 +32,7 @@
   bool TryPresentUsingCurrentPrediction() override;
 
  private:
+  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_ = nullptr;
   DISALLOW_COPY_AND_ASSIGN(MockWMRHolographicFrame);
 };
 
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.cc
index c250e18..a017cb1 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.cc
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.cc
@@ -5,6 +5,7 @@
 #include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.h"
 
 #include <D3D11_1.h>
+#include <Windows.Graphics.DirectX.Direct3D11.interop.h>
 #include <dxgi.h>
 #include <wrl.h>
 
@@ -64,12 +65,19 @@
 
 std::unique_ptr<WMRHolographicFrame>
 MockWMRHolographicSpace::TryCreateNextFrame() {
-  return std::make_unique<MockWMRHolographicFrame>();
+  return std::make_unique<MockWMRHolographicFrame>(d3d11_device_);
 }
 
 bool MockWMRHolographicSpace::TrySetDirect3D11Device(
     const Microsoft::WRL::ComPtr<
         ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>& device) {
+  Microsoft::WRL::ComPtr<
+      Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
+      dxgi_interface_access;
+  if (FAILED(device.As(&dxgi_interface_access)))
+    return false;
+  if (FAILED(dxgi_interface_access->GetInterface(IID_PPV_ARGS(&d3d11_device_))))
+    return false;
   return true;
 }
 
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.h
index 2e9a354..b1cba29 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.h
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_holographic_space.h
@@ -4,6 +4,7 @@
 #ifndef DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_HOLOGRAPHIC_SPACE_H_
 #define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_HOLOGRAPHIC_SPACE_H_
 
+#include <d3d11.h>
 #include "device/vr/windows_mixed_reality/wrappers/wmr_holographic_space.h"
 
 namespace device {
@@ -22,6 +23,7 @@
       override;
 
  private:
+  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_ = nullptr;
   DISALLOW_COPY_AND_ASSIGN(MockWMRHolographicSpace);
 };
 
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.cc
new file mode 100644
index 0000000..955b38b
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.cc
@@ -0,0 +1,57 @@
+// 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 "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.h"
+
+#include "base/logging.h"
+
+namespace device {
+
+MockWMRInputLocation::MockWMRInputLocation(ControllerFrameData data)
+    : data_(data) {}
+
+MockWMRInputLocation::~MockWMRInputLocation() = default;
+
+bool MockWMRInputLocation::TryGetPosition(
+    ABI::Windows::Foundation::Numerics::Vector3* position) const {
+  DCHECK(position);
+  // TODO(https://crbug.com/926048): Properly implement.
+  position->X = 0;
+  position->Y = 0;
+  position->Z = 0;
+  return true;
+}
+
+bool MockWMRInputLocation::TryGetVelocity(
+    ABI::Windows::Foundation::Numerics::Vector3* velocity) const {
+  DCHECK(velocity);
+  // TODO(https://crbug.com/926048): Properly implement.
+  velocity->X = 0;
+  velocity->Y = 0;
+  velocity->Z = 0;
+  return true;
+}
+
+bool MockWMRInputLocation::TryGetOrientation(
+    ABI::Windows::Foundation::Numerics::Quaternion* orientation) const {
+  DCHECK(orientation);
+  // TODO(https://crbug.com/926048): Properly implement.
+  orientation->X = 0;
+  orientation->Y = 0;
+  orientation->Z = 0;
+  orientation->W = 1;
+  return true;
+}
+
+bool MockWMRInputLocation::TryGetAngularVelocity(
+    ABI::Windows::Foundation::Numerics::Vector3* angular_velocity) const {
+  DCHECK(angular_velocity);
+  // TODO(https://crbug.com/926048): Properly implement.
+  angular_velocity->X = 0;
+  angular_velocity->Y = 0;
+  angular_velocity->Z = 0;
+  return true;
+}
+
+}  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.h
new file mode 100644
index 0000000..69ddd97
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.h
@@ -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.
+#ifndef DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_LOCATION_H_
+#define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_LOCATION_H_
+
+#include "device/vr/test/test_hook.h"
+#include "device/vr/windows_mixed_reality/wrappers/wmr_input_location.h"
+
+namespace device {
+
+class MockWMRInputLocation : public WMRInputLocation {
+ public:
+  MockWMRInputLocation(ControllerFrameData data);
+  ~MockWMRInputLocation() override;
+
+  bool TryGetPosition(
+      ABI::Windows::Foundation::Numerics::Vector3* position) const override;
+  bool TryGetVelocity(
+      ABI::Windows::Foundation::Numerics::Vector3* velocity) const override;
+  bool TryGetOrientation(ABI::Windows::Foundation::Numerics::Quaternion*
+                             orientation) const override;
+  bool TryGetAngularVelocity(ABI::Windows::Foundation::Numerics::Vector3*
+                                 angular_velocity) const override;
+
+ private:
+  ControllerFrameData data_;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_LOCATION_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.cc
index 0283753..0299adb 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.cc
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.cc
@@ -3,20 +3,60 @@
 // found in the LICENSE file.
 
 #include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.h"
-#include "device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.h"
+
+#include "base/logging.h"
+#include "device/vr/test/test_hook.h"
+#include "device/vr/windows_mixed_reality/mixed_reality_statics.h"
+#include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.h"
 
 namespace device {
 
+// MockWMRInputSourceEventArgs
+MockWMRInputSourceEventArgs::MockWMRInputSourceEventArgs(
+    ControllerFrameData data,
+    unsigned int id,
+    ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind kind)
+    : data_(data), id_(id), kind_(kind) {}
+
+MockWMRInputSourceEventArgs::~MockWMRInputSourceEventArgs() = default;
+
+ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind
+MockWMRInputSourceEventArgs::PressKind() const {
+  return kind_;
+}
+
+std::unique_ptr<WMRInputSourceState> MockWMRInputSourceEventArgs::State()
+    const {
+  return std::make_unique<MockWMRInputSourceState>(data_, id_);
+}
+
 // MockWMRInputManager
 MockWMRInputManager::MockWMRInputManager() {}
 
 MockWMRInputManager::~MockWMRInputManager() = default;
 
-std::vector<WMRInputSourceState>
+std::vector<std::unique_ptr<WMRInputSourceState>>
 MockWMRInputManager::GetDetectedSourcesAtTimestamp(
     Microsoft::WRL::ComPtr<ABI::Windows::Perception::IPerceptionTimestamp>
-        timestamp) const {
-  return {};
+        timestamp) {
+  std::vector<std::unique_ptr<WMRInputSourceState>> ret;
+  auto locked_hook = MixedRealityDeviceStatics::GetLockedTestHook();
+  if (!locked_hook.GetHook())
+    return ret;
+
+  // Index 0 should always be the headset, so start at 1 and keep going until
+  // we stop getting valid controller data.
+  for (unsigned int i = 1; i < kMaxTrackedDevices; ++i) {
+    auto controller_data = locked_hook.GetHook()->WaitGetControllerData(i);
+    if (!controller_data.is_valid)
+      break;
+    ret.push_back(
+        std::make_unique<MockWMRInputSourceState>(controller_data, i));
+    MaybeNotifyCallbacks(controller_data, i);
+    last_frame_data_[i] = controller_data;
+  }
+
+  return ret;
 }
 
 std::unique_ptr<WMRInputManager::InputEventCallbackList::Subscription>
@@ -31,4 +71,62 @@
   return released_callback_list_.Add(cb);
 }
 
+void MockWMRInputManager::MaybeNotifyCallbacks(const ControllerFrameData& data,
+                                               unsigned int id) {
+  DCHECK(id < kMaxTrackedDevices);
+  // Determine if anything changed.
+  ControllerFrameData last_frame_data = last_frame_data_[id];
+  uint64_t changed_buttons =
+      data.buttons_pressed ^ last_frame_data.buttons_pressed;
+  if (changed_buttons == 0)
+    return;
+
+  // Determine which buttons changed - if more than one changed, we'll have to
+  // fire the callbacks multiple times.
+  // Select/trigger.
+  if (changed_buttons & kSelectButton) {
+    HandleCallback(
+        data, id, kSelectButton,
+        ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind_Select);
+  }
+  // Menu.
+  if (changed_buttons & kMenuButton) {
+    HandleCallback(
+        data, id, kMenuButton,
+        ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind_Menu);
+  }
+  // Grasp/grip.
+  if (changed_buttons & kGripButton) {
+    HandleCallback(
+        data, id, kGripButton,
+        ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind_Grasp);
+  }
+  // Touchpad.
+  if (changed_buttons & kTrackpadButton) {
+    HandleCallback(
+        data, id, kTrackpadButton,
+        ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind_Touchpad);
+  }
+  // Joystick.
+  if (changed_buttons & kJoystickButton) {
+    HandleCallback(data, id, kJoystickButton,
+                   ABI::Windows::UI::Input::Spatial::
+                       SpatialInteractionPressKind_Thumbstick);
+  }
+}
+
+void MockWMRInputManager::HandleCallback(
+    const ControllerFrameData& data,
+    unsigned int id,
+    uint64_t mask,
+    ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind kind) {
+  bool now_pressed = ((data.buttons_pressed & mask) > 0);
+  MockWMRInputSourceEventArgs args(data, id, kind);
+  if (now_pressed) {
+    pressed_callback_list_.Notify(args);
+  } else {
+    released_callback_list_.Notify(args);
+  }
+}
+
 }  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.h
index 1c07433..22943e4 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.h
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_manager.h
@@ -4,18 +4,38 @@
 #ifndef DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_MANAGER_H_
 #define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_MANAGER_H_
 
+#include "device/vr/test/test_hook.h"
 #include "device/vr/windows_mixed_reality/wrappers/wmr_input_manager.h"
 
 namespace device {
 
+class MockWMRInputSourceEventArgs : public WMRInputSourceEventArgs {
+ public:
+  MockWMRInputSourceEventArgs(
+      ControllerFrameData data,
+      unsigned int id,
+      ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind kind);
+  ~MockWMRInputSourceEventArgs() override;
+
+  ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind PressKind()
+      const override;
+  std::unique_ptr<WMRInputSourceState> State() const override;
+
+ private:
+  ControllerFrameData data_;
+  unsigned int id_;
+  ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind kind_;
+};
+
 class MockWMRInputManager : public WMRInputManager {
  public:
   MockWMRInputManager();
   ~MockWMRInputManager() override;
 
-  std::vector<WMRInputSourceState> GetDetectedSourcesAtTimestamp(
+  std::vector<std::unique_ptr<WMRInputSourceState>>
+  GetDetectedSourcesAtTimestamp(
       Microsoft::WRL::ComPtr<ABI::Windows::Perception::IPerceptionTimestamp>
-          timestamp) const override;
+          timestamp) override;
 
   std::unique_ptr<InputEventCallbackList::Subscription> AddPressedCallback(
       const InputEventCallback& cb) override;
@@ -24,6 +44,14 @@
       const InputEventCallback& cb) override;
 
  private:
+  void MaybeNotifyCallbacks(const ControllerFrameData& data, unsigned int id);
+  void HandleCallback(
+      const ControllerFrameData& data,
+      unsigned int id,
+      uint64_t mask,
+      ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind kind);
+  ControllerFrameData last_frame_data_[kMaxTrackedDevices];
+
   InputEventCallbackList pressed_callback_list_;
   InputEventCallbackList released_callback_list_;
   DISALLOW_COPY_AND_ASSIGN(MockWMRInputManager);
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.cc
new file mode 100644
index 0000000..538e23d0
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.cc
@@ -0,0 +1,44 @@
+// 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 "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.h"
+
+namespace device {
+
+MockWMRInputSource::MockWMRInputSource(ControllerFrameData data,
+                                       unsigned int id)
+    : data_(data), id_(id) {}
+
+MockWMRInputSource::~MockWMRInputSource() = default;
+
+uint32_t MockWMRInputSource::Id() const {
+  return id_;
+}
+
+ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceKind
+MockWMRInputSource::Kind() const {
+  return ABI::Windows::UI::Input::Spatial::
+      SpatialInteractionSourceKind_Controller;
+}
+
+bool MockWMRInputSource::IsPointingSupported() const {
+  return true;
+}
+
+ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceHandedness
+MockWMRInputSource::Handedness() const {
+  switch (data_.role) {
+    case ControllerRole::kControllerRoleLeft:
+      return ABI::Windows::UI::Input::Spatial::
+          SpatialInteractionSourceHandedness_Left;
+    case ControllerRole::kControllerRoleRight:
+      return ABI::Windows::UI::Input::Spatial::
+          SpatialInteractionSourceHandedness_Right;
+    default:
+      return ABI::Windows::UI::Input::Spatial::
+          SpatialInteractionSourceHandedness_Unspecified;
+  }
+}
+
+}  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.h
new file mode 100644
index 0000000..54cdca4b
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.h
@@ -0,0 +1,31 @@
+// 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 DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_SOURCE_H_
+#define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_SOURCE_H_
+
+#include "device/vr/test/test_hook.h"
+#include "device/vr/windows_mixed_reality/wrappers/wmr_input_source.h"
+
+namespace device {
+
+class MockWMRInputSource : public WMRInputSource {
+ public:
+  MockWMRInputSource(ControllerFrameData data, unsigned int id);
+  ~MockWMRInputSource() override;
+
+  uint32_t Id() const override;
+  ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceKind Kind()
+      const override;
+  bool IsPointingSupported() const override;
+  ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceHandedness
+  Handedness() const override;
+
+ private:
+  ControllerFrameData data_;
+  unsigned int id_;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_SOURCE_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.cc
new file mode 100644
index 0000000..cfff5def
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.cc
@@ -0,0 +1,105 @@
+// 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 "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.h"
+
+#include "base/logging.h"
+#include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_location.h"
+#include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source.h"
+#include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.h"
+
+namespace device {
+
+MockWMRInputSourceState::MockWMRInputSourceState(ControllerFrameData data,
+                                                 unsigned int id)
+    : data_(data), id_(id) {}
+
+MockWMRInputSourceState::~MockWMRInputSourceState() = default;
+
+std::unique_ptr<WMRPointerPose> MockWMRInputSourceState::TryGetPointerPose(
+    const WMRCoordinateSystem* origin) const {
+  return std::make_unique<MockWMRPointerPose>();
+}
+
+std::unique_ptr<WMRInputSource> MockWMRInputSourceState::GetSource() const {
+  return std::make_unique<MockWMRInputSource>(data_, id_);
+}
+
+bool MockWMRInputSourceState::IsGrasped() const {
+  return IsButtonPressed(kGripButton);
+}
+
+bool MockWMRInputSourceState::IsSelectPressed() const {
+  return IsButtonPressed(kSelectButton);
+}
+
+double MockWMRInputSourceState::SelectPressedValue() const {
+  double val = data_.axis_data[kSelectAxis].x;
+  // Should only be in [0, 1] for triggers.
+  DCHECK(val <= 1);
+  DCHECK(val >= 0);
+  return val;
+}
+
+bool MockWMRInputSourceState::SupportsControllerProperties() const {
+  return true;
+}
+
+bool MockWMRInputSourceState::IsThumbstickPressed() const {
+  return IsButtonPressed(kJoystickButton);
+}
+
+bool MockWMRInputSourceState::IsTouchpadPressed() const {
+  return IsButtonPressed(kTrackpadButton);
+}
+
+bool MockWMRInputSourceState::IsTouchpadTouched() const {
+  auto touched =
+      data_.supported_buttons & data_.buttons_touched & kTrackpadButton;
+  return touched != 0;
+}
+
+double MockWMRInputSourceState::ThumbstickX() const {
+  double val = data_.axis_data[kJoystickAxis].x;
+  // Should be in [-1, 1] for joysticks.
+  DCHECK(val <= 1);
+  DCHECK(val >= -1);
+  return val;
+}
+
+double MockWMRInputSourceState::ThumbstickY() const {
+  double val = data_.axis_data[kJoystickAxis].y;
+  // Should be in [-1, 1] for joysticks.
+  DCHECK(val <= 1);
+  DCHECK(val >= -1);
+  return val;
+}
+
+double MockWMRInputSourceState::TouchpadX() const {
+  double val = data_.axis_data[kTrackpadAxis].x;
+  // Should be in [-1, 1] for touchpads.
+  DCHECK(val <= 1);
+  DCHECK(val >= -1);
+  return val;
+}
+
+double MockWMRInputSourceState::TouchpadY() const {
+  double val = data_.axis_data[kTrackpadAxis].y;
+  // Should be in [-1, 1] for touchpads.
+  DCHECK(val <= 1);
+  DCHECK(val >= -1);
+  return val;
+}
+
+std::unique_ptr<WMRInputLocation> MockWMRInputSourceState::TryGetLocation(
+    const WMRCoordinateSystem* origin) const {
+  return std::make_unique<MockWMRInputLocation>(data_);
+}
+
+bool MockWMRInputSourceState::IsButtonPressed(uint64_t button_mask) const {
+  auto pressed = data_.supported_buttons & data_.buttons_pressed & button_mask;
+  return pressed != 0;
+}
+
+}  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.h
new file mode 100644
index 0000000..1cad57ad7
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_input_source_state.h
@@ -0,0 +1,63 @@
+// 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 DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_SOURCE_STATE_H_
+#define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_SOURCE_STATE_H_
+
+#include "device/vr/test/test_hook.h"
+#include "device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.h"
+
+namespace device {
+
+// These are copies of various OpenVR mask constants that are used for
+// specifying controller input. However, since we won't necessarily have OpenVR
+// support compiled in, we can't use the constants directly.
+// k_EButton_ApplicationMenu.
+static constexpr uint64_t kMenuButton = 1ull << 1;
+// k_EButton_Grip.
+static constexpr uint64_t kGripButton = 1ull << 2;
+// k_EButton_SteamVR_Touchpad.
+static constexpr uint64_t kTrackpadButton = 1ull << 32;
+// k_EButton_SteamVR_Trigger.
+static constexpr uint64_t kSelectButton = 1ull << 33;
+// k_EButton_Axis2.
+static constexpr uint64_t kJoystickButton = 1ull << 34;
+static constexpr uint64_t kTrackpadAxis = 0;
+static constexpr uint64_t kSelectAxis = 1;
+static constexpr uint64_t kJoystickAxis = 2;
+
+class MockWMRInputSourceState : public WMRInputSourceState {
+ public:
+  MockWMRInputSourceState(ControllerFrameData data, unsigned int id);
+  ~MockWMRInputSourceState() override;
+
+  std::unique_ptr<WMRPointerPose> TryGetPointerPose(
+      const WMRCoordinateSystem* origin) const override;
+  std::unique_ptr<WMRInputSource> GetSource() const override;
+
+  bool IsGrasped() const override;
+  bool IsSelectPressed() const override;
+  double SelectPressedValue() const override;
+
+  bool SupportsControllerProperties() const override;
+
+  bool IsThumbstickPressed() const override;
+  bool IsTouchpadPressed() const override;
+  bool IsTouchpadTouched() const override;
+  double ThumbstickX() const override;
+  double ThumbstickY() const override;
+  double TouchpadX() const override;
+  double TouchpadY() const override;
+
+  std::unique_ptr<WMRInputLocation> TryGetLocation(
+      const WMRCoordinateSystem* origin) const override;
+
+ private:
+  bool IsButtonPressed(uint64_t button_mask) const;
+  ControllerFrameData data_;
+  unsigned int id_;
+};
+
+}  // namespace device
+
+#endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_INPUT_SOURCE_STATE_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.cc
new file mode 100644
index 0000000..c9aacbaa
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.cc
@@ -0,0 +1,31 @@
+// 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 "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.h"
+
+#include "base/logging.h"
+#include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.h"
+
+namespace device {
+
+MockWMRPointerPose::MockWMRPointerPose() {}
+
+MockWMRPointerPose::~MockWMRPointerPose() = default;
+
+bool MockWMRPointerPose::IsValid() const {
+  return true;
+}
+
+std::unique_ptr<WMRPointerSourcePose>
+MockWMRPointerPose::TryGetInteractionSourcePose(
+    const WMRInputSource* source) const {
+  return std::make_unique<MockWMRPointerSourcePose>();
+}
+
+ABI::Windows::Foundation::Numerics::Vector3 MockWMRPointerPose::HeadForward()
+    const {
+  return {1, 0, 0};
+}
+
+}  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.h
new file mode 100644
index 0000000..d543040
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_pose.h
@@ -0,0 +1,27 @@
+// 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 DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_POINTER_POSE_H_
+#define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_POINTER_POSE_H_
+
+#include "device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.h"
+
+namespace device {
+
+class MockWMRPointerPose : public WMRPointerPose {
+ public:
+  MockWMRPointerPose();
+  ~MockWMRPointerPose() override;
+
+  bool IsValid() const override;
+  std::unique_ptr<WMRPointerSourcePose> TryGetInteractionSourcePose(
+      const WMRInputSource* source) const override;
+  ABI::Windows::Foundation::Numerics::Vector3 HeadForward() const override;
+
+ private:
+  DISALLOW_COPY(MockWMRPointerPose);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_POINTER_POSE_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.cc
new file mode 100644
index 0000000..7e5d149
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.cc
@@ -0,0 +1,30 @@
+// 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 "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.h"
+
+namespace device {
+
+MockWMRPointerSourcePose::MockWMRPointerSourcePose() {}
+
+MockWMRPointerSourcePose::~MockWMRPointerSourcePose() = default;
+
+bool MockWMRPointerSourcePose::IsValid() const {
+  return true;
+}
+
+ABI::Windows::Foundation::Numerics::Vector3 MockWMRPointerSourcePose::Position()
+    const {
+  // TODO(https://crbug.com/926048): Actually implement.
+  return {1, 1, 1};
+}
+
+ABI::Windows::Foundation::Numerics::Quaternion
+MockWMRPointerSourcePose::Orientation() const {
+  // TODO(https://crbug.com/926048): Actually implement.
+  // For whatever reason, W is first?
+  return {1, 0, 0, 0};
+}
+
+}  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.h
new file mode 100644
index 0000000..a63c611c
--- /dev/null
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_pointer_source_pose.h
@@ -0,0 +1,26 @@
+// 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 DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_POINTER_SOURCE_POSE_H_
+#define DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_POINTER_SOURCE_POSE_H_
+
+#include "device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.h"
+
+namespace device {
+
+class MockWMRPointerSourcePose : public WMRPointerSourcePose {
+ public:
+  MockWMRPointerSourcePose();
+  ~MockWMRPointerSourcePose() override;
+
+  bool IsValid() const override;
+  ABI::Windows::Foundation::Numerics::Vector3 Position() const override;
+  ABI::Windows::Foundation::Numerics::Quaternion Orientation() const override;
+
+ private:
+  DISALLOW_COPY(MockWMRPointerSourcePose);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_TEST_MOCK_WMR_POINTER_SOURCE_POSE_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.cc b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.cc
index 4baba390..711c3cfd 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.cc
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.cc
@@ -4,7 +4,11 @@
 
 #include "device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.h"
 
-#include "base/debug/debugger.h"
+#include <Windows.Graphics.DirectX.Direct3D11.interop.h>
+#include <d3d11.h>
+#include <dxgi.h>
+#include <dxgiformat.h>
+
 #include "base/logging.h"
 #include "device/vr/test/test_hook.h"
 #include "device/vr/windows_mixed_reality/mixed_reality_statics.h"
@@ -145,13 +149,32 @@
   return true;
 }
 
-MockWMRRenderingParameters::MockWMRRenderingParameters() {}
+MockWMRRenderingParameters::MockWMRRenderingParameters(
+    const Microsoft::WRL::ComPtr<ID3D11Device>& device)
+    : d3d11_device_(device) {}
 
 MockWMRRenderingParameters::~MockWMRRenderingParameters() = default;
 
 Microsoft::WRL::ComPtr<ID3D11Texture2D>
 MockWMRRenderingParameters::TryGetBackbufferAsTexture2D() {
-  return nullptr;
+  if (!d3d11_device_)
+    return nullptr;
+  auto desc = CD3D11_TEXTURE2D_DESC();
+  desc.ArraySize = 2;
+  desc.Width = kDefaultWmrRenderWidth;
+  desc.Height = kDefaultWmrRenderHeight;
+  desc.SampleDesc = {1, 0};
+  desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+  desc.Usage = D3D11_USAGE_DEFAULT;
+  desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+  desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
+
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = nullptr;
+  auto hr = d3d11_device_->CreateTexture2D(&desc, nullptr, &texture);
+  if (FAILED(hr))
+    return nullptr;
+
+  return texture;
 }
 
 }  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.h b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.h
index 32893fe..7dd9785 100644
--- a/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.h
+++ b/device/vr/windows_mixed_reality/wrappers/test/mock_wmr_rendering.h
@@ -43,13 +43,15 @@
 
 class MockWMRRenderingParameters : public WMRRenderingParameters {
  public:
-  MockWMRRenderingParameters();
+  MockWMRRenderingParameters(
+      const Microsoft::WRL::ComPtr<ID3D11Device>& device);
   ~MockWMRRenderingParameters() override;
 
   Microsoft::WRL::ComPtr<ID3D11Texture2D> TryGetBackbufferAsTexture2D()
       override;
 
  private:
+  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_ = nullptr;
   DISALLOW_COPY_AND_ASSIGN(MockWMRRenderingParameters);
 };
 
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_location.cc b/device/vr/windows_mixed_reality/wrappers/wmr_input_location.cc
index cd76c2a..8f2b7714 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_location.cc
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_location.cc
@@ -31,18 +31,17 @@
 
 namespace device {
 
-WMRInputLocation::WMRInputLocation(
+WMRInputLocationImpl::WMRInputLocationImpl(
     ComPtr<ISpatialInteractionSourceLocation> location)
     : location_(location) {
-  if (location_) {
-    location_.As(&location2_);
-    location_.As(&location3_);
-  }
+  DCHECK(location_);
+  location_.As(&location2_);
+  location_.As(&location3_);
 }
 
-WMRInputLocation::~WMRInputLocation() = default;
+WMRInputLocationImpl::~WMRInputLocationImpl() = default;
 
-bool WMRInputLocation::TryGetPosition(WFN::Vector3* position) const {
+bool WMRInputLocationImpl::TryGetPosition(WFN::Vector3* position) const {
   DCHECK(position);
   if (!location_)
     return false;
@@ -52,7 +51,7 @@
   return TryGetValue(ref, position);
 }
 
-bool WMRInputLocation::TryGetVelocity(WFN::Vector3* velocity) const {
+bool WMRInputLocationImpl::TryGetVelocity(WFN::Vector3* velocity) const {
   DCHECK(velocity);
   if (!location_)
     return false;
@@ -62,7 +61,8 @@
   return TryGetValue(ref, velocity);
 }
 
-bool WMRInputLocation::TryGetOrientation(WFN::Quaternion* orientation) const {
+bool WMRInputLocationImpl::TryGetOrientation(
+    WFN::Quaternion* orientation) const {
   DCHECK(orientation);
   if (!location2_)
     return false;
@@ -72,7 +72,7 @@
   return TryGetValue(ref, orientation);
 }
 
-bool WMRInputLocation::TryGetAngularVelocity(
+bool WMRInputLocationImpl::TryGetAngularVelocity(
     WFN::Vector3* angular_velocity) const {
   DCHECK(angular_velocity);
   if (!location3_)
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_location.h b/device/vr/windows_mixed_reality/wrappers/wmr_input_location.h
index e4d9d4c..7052cc1 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_location.h
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_location.h
@@ -12,25 +12,41 @@
 namespace device {
 class WMRInputLocation {
  public:
-  explicit WMRInputLocation(
+  virtual ~WMRInputLocation() = default;
+
+  virtual bool TryGetPosition(
+      ABI::Windows::Foundation::Numerics::Vector3* position) const = 0;
+  virtual bool TryGetVelocity(
+      ABI::Windows::Foundation::Numerics::Vector3* velocity) const = 0;
+
+  virtual bool TryGetOrientation(
+      ABI::Windows::Foundation::Numerics::Quaternion* orientation) const = 0;
+
+  virtual bool TryGetAngularVelocity(
+      ABI::Windows::Foundation::Numerics::Vector3* angular_velocity) const = 0;
+};
+
+class WMRInputLocationImpl : public WMRInputLocation {
+ public:
+  explicit WMRInputLocationImpl(
       Microsoft::WRL::ComPtr<
           ABI::Windows::UI::Input::Spatial::ISpatialInteractionSourceLocation>
           location);
-  virtual ~WMRInputLocation();
+  ~WMRInputLocationImpl() override;
 
   // Uses ISpatialInteractionSourceLocation.
   bool TryGetPosition(
-      ABI::Windows::Foundation::Numerics::Vector3* position) const;
+      ABI::Windows::Foundation::Numerics::Vector3* position) const override;
   bool TryGetVelocity(
-      ABI::Windows::Foundation::Numerics::Vector3* velocity) const;
+      ABI::Windows::Foundation::Numerics::Vector3* velocity) const override;
 
   // Uses ISpatialInteractionSourceLocation2.
-  bool TryGetOrientation(
-      ABI::Windows::Foundation::Numerics::Quaternion* orientation) const;
+  bool TryGetOrientation(ABI::Windows::Foundation::Numerics::Quaternion*
+                             orientation) const override;
 
   // Uses ISpatialInteractionSourceLocation3.
-  bool TryGetAngularVelocity(
-      ABI::Windows::Foundation::Numerics::Vector3* angular_velocity) const;
+  bool TryGetAngularVelocity(ABI::Windows::Foundation::Numerics::Vector3*
+                                 angular_velocity) const override;
 
  private:
   Microsoft::WRL::ComPtr<
@@ -43,8 +59,9 @@
       ABI::Windows::UI::Input::Spatial::ISpatialInteractionSourceLocation3>
       location3_;
 
-  DISALLOW_COPY(WMRInputLocation);
+  DISALLOW_COPY(WMRInputLocationImpl);
 };
+
 }  // namespace device
 
 #endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_WMR_INPUT_LOCATION_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.cc b/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.cc
index ecd6dc1d..8b4e1e9a 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.cc
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.cc
@@ -40,7 +40,7 @@
     SpatialInteractionSourceEventHandler;
 
 namespace device {
-WMRInputSourceEventArgs::WMRInputSourceEventArgs(
+WMRInputSourceEventArgsImpl::WMRInputSourceEventArgsImpl(
     ComPtr<ISpatialInteractionSourceEventArgs> args)
     : args_(args) {
   DCHECK(args_);
@@ -48,20 +48,21 @@
   DCHECK(SUCCEEDED(hr));
 }
 
-WMRInputSourceEventArgs::~WMRInputSourceEventArgs() = default;
+WMRInputSourceEventArgsImpl::~WMRInputSourceEventArgsImpl() = default;
 
-WMRPressKind WMRInputSourceEventArgs::PressKind() const {
+WMRPressKind WMRInputSourceEventArgsImpl::PressKind() const {
   WMRPressKind press_kind;
   HRESULT hr = args2_->get_PressKind(&press_kind);
   DCHECK(SUCCEEDED(hr));
   return press_kind;
 }
 
-WMRInputSourceState WMRInputSourceEventArgs::State() const {
+std::unique_ptr<WMRInputSourceState> WMRInputSourceEventArgsImpl::State()
+    const {
   ComPtr<ISpatialInteractionSourceState> wmr_state;
   HRESULT hr = args_->get_State(&wmr_state);
   DCHECK(SUCCEEDED(hr));
-  return WMRInputSourceState(wmr_state);
+  return std::make_unique<WMRInputSourceStateImpl>(wmr_state);
 }
 
 WMRInputManagerImpl::WMRInputManagerImpl(
@@ -77,10 +78,10 @@
   UnsubscribeEvents();
 }
 
-std::vector<WMRInputSourceState>
+std::vector<std::unique_ptr<WMRInputSourceState>>
 WMRInputManagerImpl::GetDetectedSourcesAtTimestamp(
-    ComPtr<IPerceptionTimestamp> timestamp) const {
-  std::vector<WMRInputSourceState> input_states;
+    ComPtr<IPerceptionTimestamp> timestamp) {
+  std::vector<std::unique_ptr<WMRInputSourceState>> input_states;
   ComPtr<IVectorView<SpatialInteractionSourceState*>> source_states;
   if (FAILED(manager_->GetDetectedSourcesAtTimestamp(timestamp.Get(),
                                                      &source_states)))
@@ -94,7 +95,8 @@
     ComPtr<ISpatialInteractionSourceState> source_state_wmr;
     hr = source_states->GetAt(i, &source_state_wmr);
     DCHECK(SUCCEEDED(hr));
-    input_states.emplace_back(source_state_wmr);
+    input_states.push_back(
+        std::make_unique<WMRInputSourceStateImpl>(source_state_wmr));
   }
 
   return input_states;
@@ -114,7 +116,7 @@
     ISpatialInteractionManager* sender,
     ISpatialInteractionSourceEventArgs* raw_args) {
   ComPtr<ISpatialInteractionSourceEventArgs> wmr_args(raw_args);
-  WMRInputSourceEventArgs args(wmr_args);
+  WMRInputSourceEventArgsImpl args(wmr_args);
   pressed_callback_list_.Notify(args);
   return S_OK;
 }
@@ -123,7 +125,7 @@
     ISpatialInteractionManager* sender,
     ISpatialInteractionSourceEventArgs* raw_args) {
   ComPtr<ISpatialInteractionSourceEventArgs> wmr_args(raw_args);
-  WMRInputSourceEventArgs args(wmr_args);
+  WMRInputSourceEventArgsImpl args(wmr_args);
   released_callback_list_.Notify(args);
   return S_OK;
 }
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.h b/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.h
index dcfda96..d4cff01 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.h
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_manager.h
@@ -18,15 +18,24 @@
 class WMRInputSourceState;
 class WMRInputSourceEventArgs {
  public:
-  explicit WMRInputSourceEventArgs(
+  virtual ~WMRInputSourceEventArgs() = default;
+
+  virtual ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind
+  PressKind() const = 0;
+  virtual std::unique_ptr<WMRInputSourceState> State() const = 0;
+};
+
+class WMRInputSourceEventArgsImpl : public WMRInputSourceEventArgs {
+ public:
+  explicit WMRInputSourceEventArgsImpl(
       Microsoft::WRL::ComPtr<
           ABI::Windows::UI::Input::Spatial::ISpatialInteractionSourceEventArgs>
           args);
-  virtual ~WMRInputSourceEventArgs();
+  ~WMRInputSourceEventArgsImpl() override;
 
   ABI::Windows::UI::Input::Spatial::SpatialInteractionPressKind PressKind()
-      const;
-  WMRInputSourceState State() const;
+      const override;
+  std::unique_ptr<WMRInputSourceState> State() const override;
 
  private:
   Microsoft::WRL::ComPtr<
@@ -46,9 +55,10 @@
 
   virtual ~WMRInputManager() = default;
 
-  virtual std::vector<WMRInputSourceState> GetDetectedSourcesAtTimestamp(
+  virtual std::vector<std::unique_ptr<WMRInputSourceState>>
+  GetDetectedSourcesAtTimestamp(
       Microsoft::WRL::ComPtr<ABI::Windows::Perception::IPerceptionTimestamp>
-          timestamp) const = 0;
+          timestamp) = 0;
 
   virtual std::unique_ptr<InputEventCallbackList::Subscription>
   AddPressedCallback(const InputEventCallback& cb) = 0;
@@ -70,9 +80,10 @@
           manager);
   ~WMRInputManagerImpl() override;
 
-  std::vector<WMRInputSourceState> GetDetectedSourcesAtTimestamp(
+  std::vector<std::unique_ptr<WMRInputSourceState>>
+  GetDetectedSourcesAtTimestamp(
       Microsoft::WRL::ComPtr<ABI::Windows::Perception::IPerceptionTimestamp>
-          timestamp) const override;
+          timestamp) override;
 
   std::unique_ptr<InputEventCallbackList::Subscription> AddPressedCallback(
       const InputEventCallback& cb) override;
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_source.cc b/device/vr/windows_mixed_reality/wrappers/wmr_input_source.cc
index 58ffb43..b2718e94 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_source.cc
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_source.cc
@@ -21,33 +21,42 @@
 using Microsoft::WRL::ComPtr;
 
 namespace device {
-WMRInputSource::WMRInputSource(ComPtr<ISpatialInteractionSource> source)
-    : source_(source) {
-  if (source_) {
-    source_.As(&source2_);
-    source_.As(&source3_);
-  }
+
+ABI::Windows::UI::Input::Spatial::ISpatialInteractionSource*
+WMRInputSource::GetRawPtr() const {
+  // This should only ever be used by the real implementation, so by default
+  // make sure it's not called.
+  NOTREACHED();
+  return nullptr;
 }
 
-WMRInputSource::~WMRInputSource() = default;
+WMRInputSourceImpl::WMRInputSourceImpl(ComPtr<ISpatialInteractionSource> source)
+    : source_(source) {
+  DCHECK(source_);
+  source_.As(&source2_);
+  source_.As(&source3_);
+}
 
-WMRInputSource::WMRInputSource(const WMRInputSource& other) = default;
+WMRInputSourceImpl::~WMRInputSourceImpl() = default;
 
-uint32_t WMRInputSource::Id() const {
+WMRInputSourceImpl::WMRInputSourceImpl(const WMRInputSourceImpl& other) =
+    default;
+
+uint32_t WMRInputSourceImpl::Id() const {
   uint32_t val;
   HRESULT hr = source_->get_Id(&val);
   DCHECK(SUCCEEDED(hr));
   return val;
 }
 
-SourceKind WMRInputSource::Kind() const {
+SourceKind WMRInputSourceImpl::Kind() const {
   SourceKind val;
   HRESULT hr = source_->get_Kind(&val);
   DCHECK(SUCCEEDED(hr));
   return val;
 }
 
-bool WMRInputSource::IsPointingSupported() const {
+bool WMRInputSourceImpl::IsPointingSupported() const {
   if (!source2_)
     return false;
   boolean val;
@@ -56,7 +65,7 @@
   return val;
 }
 
-SourceHandedness WMRInputSource::Handedness() const {
+SourceHandedness WMRInputSourceImpl::Handedness() const {
   if (!source3_)
     return SourceHandedness::SpatialInteractionSourceHandedness_Unspecified;
   SourceHandedness val;
@@ -65,7 +74,7 @@
   return val;
 }
 
-ISpatialInteractionSource* WMRInputSource::GetRawPtr() const {
+ISpatialInteractionSource* WMRInputSourceImpl::GetRawPtr() const {
   return source_.Get();
 }
 }  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_source.h b/device/vr/windows_mixed_reality/wrappers/wmr_input_source.h
index 93b6a6d..d667152 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_source.h
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_source.h
@@ -12,25 +12,46 @@
 namespace device {
 class WMRInputSource {
  public:
-  explicit WMRInputSource(
-      Microsoft::WRL::ComPtr<
-          ABI::Windows::UI::Input::Spatial::ISpatialInteractionSource> source);
-  WMRInputSource(const WMRInputSource& other);
-  virtual ~WMRInputSource();
+  virtual ~WMRInputSource() = default;
 
   // Uses ISpatialInteractionSource.
-  uint32_t Id() const;
-  ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceKind Kind() const;
+  virtual uint32_t Id() const = 0;
+  virtual ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceKind Kind()
+      const = 0;
 
   // Uses ISpatialInteractionSource2.
-  bool IsPointingSupported() const;
+  virtual bool IsPointingSupported() const = 0;
+
+  // Uses ISpatialInteractionSource3.
+  virtual ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceHandedness
+  Handedness() const = 0;
+
+  virtual ABI::Windows::UI::Input::Spatial::ISpatialInteractionSource*
+  GetRawPtr() const;
+};
+
+class WMRInputSourceImpl : public WMRInputSource {
+ public:
+  explicit WMRInputSourceImpl(
+      Microsoft::WRL::ComPtr<
+          ABI::Windows::UI::Input::Spatial::ISpatialInteractionSource> source);
+  WMRInputSourceImpl(const WMRInputSourceImpl& other);
+  ~WMRInputSourceImpl() override;
+
+  // Uses ISpatialInteractionSource.
+  uint32_t Id() const override;
+  ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceKind Kind()
+      const override;
+
+  // Uses ISpatialInteractionSource2.
+  bool IsPointingSupported() const override;
 
   // Uses ISpatialInteractionSource3.
   ABI::Windows::UI::Input::Spatial::SpatialInteractionSourceHandedness
-  Handedness() const;
+  Handedness() const override;
 
   ABI::Windows::UI::Input::Spatial::ISpatialInteractionSource* GetRawPtr()
-      const;
+      const override;
 
  private:
   Microsoft::WRL::ComPtr<
@@ -43,6 +64,7 @@
       ABI::Windows::UI::Input::Spatial::ISpatialInteractionSource3>
       source3_;
 };
+
 }  // namespace device
 
 #endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_WMR_INPUT_SOURCE_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.cc b/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.cc
index 32273218..23313dc 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.cc
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.cc
@@ -25,7 +25,8 @@
 using Microsoft::WRL::ComPtr;
 
 namespace device {
-WMRInputSourceState::WMRInputSourceState(
+
+WMRInputSourceStateImpl::WMRInputSourceStateImpl(
     ComPtr<ISpatialInteractionSourceState> source_state)
     : source_state_(source_state) {
   DCHECK(source_state_);
@@ -38,60 +39,58 @@
   source_state2_->get_ControllerProperties(&controller_properties_);
 }
 
-WMRInputSourceState::WMRInputSourceState(const WMRInputSourceState& other) =
-    default;
+WMRInputSourceStateImpl::WMRInputSourceStateImpl(
+    const WMRInputSourceStateImpl& other) = default;
 
-WMRInputSourceState::~WMRInputSourceState() = default;
+WMRInputSourceStateImpl::~WMRInputSourceStateImpl() = default;
 
 // ISpatialInteractionSourceState
-bool WMRInputSourceState::TryGetPointerPose(
-    const WMRCoordinateSystem* origin,
-    WMRPointerPose* pointer_pose) const {
+std::unique_ptr<WMRPointerPose> WMRInputSourceStateImpl::TryGetPointerPose(
+    const WMRCoordinateSystem* origin) const {
   ComPtr<ISpatialPointerPose> pointer_pose_wmr;
   HRESULT hr =
       source_state_->TryGetPointerPose(origin->GetRawPtr(), &pointer_pose_wmr);
 
   if (SUCCEEDED(hr) && pointer_pose_wmr) {
-    *pointer_pose = WMRPointerPose(pointer_pose_wmr);
-    return true;
+    return std::make_unique<WMRPointerPoseImpl>(pointer_pose_wmr);
   }
 
-  return false;
+  return nullptr;
 }
 
-WMRInputSource WMRInputSourceState::GetSource() const {
+std::unique_ptr<WMRInputSource> WMRInputSourceStateImpl::GetSource() const {
   ComPtr<ISpatialInteractionSource> source;
   HRESULT hr = source_state_->get_Source(&source);
   DCHECK(SUCCEEDED(hr));
-  return WMRInputSource(source);
+  return std::make_unique<WMRInputSourceImpl>(source);
 }
 
-bool WMRInputSourceState::IsGrasped() const {
+bool WMRInputSourceStateImpl::IsGrasped() const {
   boolean val;
   HRESULT hr = source_state2_->get_IsGrasped(&val);
   DCHECK(SUCCEEDED(hr));
   return val;
 }
 
-bool WMRInputSourceState::IsSelectPressed() const {
+bool WMRInputSourceStateImpl::IsSelectPressed() const {
   boolean val;
   HRESULT hr = source_state2_->get_IsSelectPressed(&val);
   DCHECK(SUCCEEDED(hr));
   return val;
 }
 
-double WMRInputSourceState::SelectPressedValue() const {
+double WMRInputSourceStateImpl::SelectPressedValue() const {
   DOUBLE val;
   HRESULT hr = source_state2_->get_SelectPressedValue(&val);
   DCHECK(SUCCEEDED(hr));
   return val;
 }
 
-bool WMRInputSourceState::SupportsControllerProperties() const {
+bool WMRInputSourceStateImpl::SupportsControllerProperties() const {
   return controller_properties_ != nullptr;
 }
 
-bool WMRInputSourceState::IsThumbstickPressed() const {
+bool WMRInputSourceStateImpl::IsThumbstickPressed() const {
   DCHECK(SupportsControllerProperties());
   boolean val;
   HRESULT hr = controller_properties_->get_IsThumbstickPressed(&val);
@@ -99,7 +98,7 @@
   return val;
 }
 
-bool WMRInputSourceState::IsTouchpadPressed() const {
+bool WMRInputSourceStateImpl::IsTouchpadPressed() const {
   DCHECK(SupportsControllerProperties());
   boolean val;
   HRESULT hr = controller_properties_->get_IsTouchpadPressed(&val);
@@ -107,7 +106,7 @@
   return val;
 }
 
-bool WMRInputSourceState::IsTouchpadTouched() const {
+bool WMRInputSourceStateImpl::IsTouchpadTouched() const {
   DCHECK(SupportsControllerProperties());
   boolean val;
   HRESULT hr = controller_properties_->get_IsTouchpadTouched(&val);
@@ -115,7 +114,7 @@
   return val;
 }
 
-double WMRInputSourceState::ThumbstickX() const {
+double WMRInputSourceStateImpl::ThumbstickX() const {
   DCHECK(SupportsControllerProperties());
   DOUBLE val;
   HRESULT hr = controller_properties_->get_ThumbstickX(&val);
@@ -123,7 +122,7 @@
   return val;
 }
 
-double WMRInputSourceState::ThumbstickY() const {
+double WMRInputSourceStateImpl::ThumbstickY() const {
   DCHECK(SupportsControllerProperties());
   DOUBLE val;
   HRESULT hr = controller_properties_->get_ThumbstickY(&val);
@@ -131,7 +130,7 @@
   return val;
 }
 
-double WMRInputSourceState::TouchpadX() const {
+double WMRInputSourceStateImpl::TouchpadX() const {
   DCHECK(SupportsControllerProperties());
   DOUBLE val;
   HRESULT hr = controller_properties_->get_TouchpadX(&val);
@@ -139,7 +138,7 @@
   return val;
 }
 
-double WMRInputSourceState::TouchpadY() const {
+double WMRInputSourceStateImpl::TouchpadY() const {
   DCHECK(SupportsControllerProperties());
   DOUBLE val;
   HRESULT hr = controller_properties_->get_TouchpadY(&val);
@@ -147,16 +146,14 @@
   return val;
 }
 
-bool WMRInputSourceState::TryGetLocation(const WMRCoordinateSystem* origin,
-                                         WMRInputLocation* location) const {
-  DCHECK(location);
+std::unique_ptr<WMRInputLocation> WMRInputSourceStateImpl::TryGetLocation(
+    const WMRCoordinateSystem* origin) const {
   ComPtr<ISpatialInteractionSourceLocation> location_wmr;
   if (FAILED(properties_->TryGetLocation(origin->GetRawPtr(), &location_wmr)) ||
       !location_wmr)
-    return false;
+    return nullptr;
 
-  *location = WMRInputLocation(location_wmr);
-  return true;
+  return std::make_unique<WMRInputLocationImpl>(location_wmr);
 }
 
 }  // namespace device
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.h b/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.h
index 8f3ee2a..b4ddf33 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.h
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_input_source_state.h
@@ -7,45 +7,77 @@
 #include <windows.perception.spatial.h>
 #include <windows.ui.input.spatial.h>
 #include <wrl.h>
+#include <memory>
 
 namespace device {
 class WMRCoordinateSystem;
 class WMRInputLocation;
 class WMRInputSource;
 class WMRPointerPose;
+
 class WMRInputSourceState {
  public:
-  explicit WMRInputSourceState(
+  virtual ~WMRInputSourceState() = default;
+
+  // Uses ISpatialInteractionSourceState.
+  virtual std::unique_ptr<WMRPointerPose> TryGetPointerPose(
+      const WMRCoordinateSystem* origin) const = 0;
+  virtual std::unique_ptr<WMRInputSource> GetSource() const = 0;
+
+  // Uses ISpatialInteractionSourceState2.
+  virtual bool IsGrasped() const = 0;
+  virtual bool IsSelectPressed() const = 0;
+  virtual double SelectPressedValue() const = 0;
+
+  virtual bool SupportsControllerProperties() const = 0;
+
+  // Uses SpatialInteractionControllerProperties.
+  virtual bool IsThumbstickPressed() const = 0;
+  virtual bool IsTouchpadPressed() const = 0;
+  virtual bool IsTouchpadTouched() const = 0;
+  virtual double ThumbstickX() const = 0;
+  virtual double ThumbstickY() const = 0;
+  virtual double TouchpadX() const = 0;
+  virtual double TouchpadY() const = 0;
+
+  // Uses SpatialInteractionSourceProperties.
+  virtual std::unique_ptr<WMRInputLocation> TryGetLocation(
+      const WMRCoordinateSystem* origin) const = 0;
+};
+
+class WMRInputSourceStateImpl : public WMRInputSourceState {
+ public:
+  explicit WMRInputSourceStateImpl(
       Microsoft::WRL::ComPtr<
           ABI::Windows::UI::Input::Spatial::ISpatialInteractionSourceState>
           source_state);
-  WMRInputSourceState(const WMRInputSourceState& other);
-  virtual ~WMRInputSourceState();
+  WMRInputSourceStateImpl(const WMRInputSourceStateImpl& other);
+  ~WMRInputSourceStateImpl() override;
 
   // Uses ISpatialInteractionSourceState.
-  bool TryGetPointerPose(const WMRCoordinateSystem* origin,
-                         WMRPointerPose* pointer_pose) const;
-  WMRInputSource GetSource() const;
+  std::unique_ptr<WMRPointerPose> TryGetPointerPose(
+      const WMRCoordinateSystem* origin) const override;
+  std::unique_ptr<WMRInputSource> GetSource() const override;
 
   // Uses ISpatialInteractionSourceState2.
-  bool IsGrasped() const;
-  bool IsSelectPressed() const;
-  double SelectPressedValue() const;
+  bool IsGrasped() const override;
+  bool IsSelectPressed() const override;
+  double SelectPressedValue() const override;
 
-  bool SupportsControllerProperties() const;
+  bool SupportsControllerProperties() const override;
 
   // Uses SpatialInteractionControllerProperties.
-  bool IsThumbstickPressed() const;
-  bool IsTouchpadPressed() const;
-  bool IsTouchpadTouched() const;
-  double ThumbstickX() const;
-  double ThumbstickY() const;
-  double TouchpadX() const;
-  double TouchpadY() const;
+  bool IsThumbstickPressed() const override;
+  bool IsTouchpadPressed() const override;
+  bool IsTouchpadTouched() const override;
+  double ThumbstickX() const override;
+  double ThumbstickY() const override;
+  double TouchpadX() const override;
+  double TouchpadY() const override;
 
   // Uses SpatialInteractionSourceProperties.
-  bool TryGetLocation(const WMRCoordinateSystem* origin,
-                      WMRInputLocation* location) const;
+  std::unique_ptr<WMRInputLocation> TryGetLocation(
+      const WMRCoordinateSystem* origin) const override;
 
  private:
   Microsoft::WRL::ComPtr<
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.cc b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.cc
index 5516e607..0b759e8 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.cc
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.cc
@@ -22,41 +22,37 @@
 using Microsoft::WRL::ComPtr;
 
 namespace device {
-WMRPointerPose::WMRPointerPose(ComPtr<ISpatialPointerPose> pointer_pose)
+WMRPointerPoseImpl::WMRPointerPoseImpl(ComPtr<ISpatialPointerPose> pointer_pose)
     : pointer_pose_(pointer_pose) {
-  if (pointer_pose_) {
-    pointer_pose_.As(&pointer_pose2_);
+  DCHECK(pointer_pose_);
+  pointer_pose_.As(&pointer_pose2_);
 
-    HRESULT hr = pointer_pose_->get_Head(&head_);
-    DCHECK(SUCCEEDED(hr));
-  }
+  HRESULT hr = pointer_pose_->get_Head(&head_);
+  DCHECK(SUCCEEDED(hr));
 }
 
-WMRPointerPose::~WMRPointerPose() = default;
+WMRPointerPoseImpl::~WMRPointerPoseImpl() = default;
 
-bool WMRPointerPose::IsValid() const {
+bool WMRPointerPoseImpl::IsValid() const {
   return pointer_pose_ != nullptr;
 }
 
-bool WMRPointerPose::TryGetInteractionSourcePose(
-    const WMRInputSource& source,
-    WMRPointerSourcePose* pointer_source_pose) const {
-  DCHECK(pointer_source_pose);
+std::unique_ptr<WMRPointerSourcePose>
+WMRPointerPoseImpl::TryGetInteractionSourcePose(
+    const WMRInputSource* source) const {
   if (!pointer_pose2_)
-    return false;
+    return nullptr;
 
   ComPtr<ISpatialPointerInteractionSourcePose> psp_wmr;
-  HRESULT hr =
-      pointer_pose2_->TryGetInteractionSourcePose(source.GetRawPtr(), &psp_wmr);
-  if (SUCCEEDED(hr) && psp_wmr) {
-    *pointer_source_pose = WMRPointerSourcePose(psp_wmr);
-    return true;
-  }
+  HRESULT hr = pointer_pose2_->TryGetInteractionSourcePose(source->GetRawPtr(),
+                                                           &psp_wmr);
+  if (SUCCEEDED(hr) && psp_wmr)
+    return std::make_unique<WMRPointerSourcePoseImpl>(psp_wmr);
 
-  return false;
+  return nullptr;
 }
 
-WFN::Vector3 WMRPointerPose::HeadForward() const {
+WFN::Vector3 WMRPointerPoseImpl::HeadForward() const {
   DCHECK(IsValid());
   WFN::Vector3 val;
   HRESULT hr = head_->get_ForwardDirection(&val);
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.h b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.h
index 25089e3..ec396af 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.h
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_pose.h
@@ -7,6 +7,7 @@
 #include <windows.perception.spatial.h>
 #include <windows.ui.input.spatial.h>
 #include <wrl.h>
+#include <memory>
 
 #include "base/macros.h"
 
@@ -15,20 +16,29 @@
 class WMRPointerSourcePose;
 class WMRPointerPose {
  public:
-  explicit WMRPointerPose(
+  virtual ~WMRPointerPose() = default;
+
+  virtual bool IsValid() const = 0;
+  virtual std::unique_ptr<WMRPointerSourcePose> TryGetInteractionSourcePose(
+      const WMRInputSource* source) const = 0;
+  virtual ABI::Windows::Foundation::Numerics::Vector3 HeadForward() const = 0;
+};
+
+class WMRPointerPoseImpl : public WMRPointerPose {
+ public:
+  explicit WMRPointerPoseImpl(
       Microsoft::WRL::ComPtr<
           ABI::Windows::UI::Input::Spatial::ISpatialPointerPose> pointer_pose);
-  virtual ~WMRPointerPose();
+  ~WMRPointerPoseImpl() override;
 
-  bool IsValid() const;
+  bool IsValid() const override;
 
   // Uses ISpatialPointerPose2.
-  bool TryGetInteractionSourcePose(
-      const WMRInputSource& source,
-      WMRPointerSourcePose* pointer_source_pose) const;
+  std::unique_ptr<WMRPointerSourcePose> TryGetInteractionSourcePose(
+      const WMRInputSource* source) const override;
 
   // Uses IHeadPose.
-  ABI::Windows::Foundation::Numerics::Vector3 HeadForward() const;
+  ABI::Windows::Foundation::Numerics::Vector3 HeadForward() const override;
 
  private:
   Microsoft::WRL::ComPtr<ABI::Windows::UI::Input::Spatial::ISpatialPointerPose>
@@ -39,8 +49,9 @@
   // This is a simple interface, so expose it directly rather than create
   // a new wrapper class.
   Microsoft::WRL::ComPtr<ABI::Windows::Perception::People::IHeadPose> head_;
-  DISALLOW_COPY(WMRPointerPose);
+  DISALLOW_COPY(WMRPointerPoseImpl);
 };
+
 }  // namespace device
 
 #endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_WMR_POINTER_POSE_H_
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.cc b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.cc
index a39827b..bf56d6b 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.cc
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.cc
@@ -16,22 +16,21 @@
 using Microsoft::WRL::ComPtr;
 
 namespace device {
-WMRPointerSourcePose::WMRPointerSourcePose(
+WMRPointerSourcePoseImpl::WMRPointerSourcePoseImpl(
     ComPtr<ISpatialPointerInteractionSourcePose> pointer_source_pose)
     : pointer_source_pose_(pointer_source_pose) {
-  if (pointer_source_pose_) {
-    HRESULT hr = pointer_source_pose_.As(&pointer_source_pose2_);
-    DCHECK(SUCCEEDED(hr));
-  }
+  DCHECK(pointer_source_pose_);
+  HRESULT hr = pointer_source_pose_.As(&pointer_source_pose2_);
+  DCHECK(SUCCEEDED(hr));
 }
 
-WMRPointerSourcePose::~WMRPointerSourcePose() = default;
+WMRPointerSourcePoseImpl::~WMRPointerSourcePoseImpl() = default;
 
-bool WMRPointerSourcePose::IsValid() const {
+bool WMRPointerSourcePoseImpl::IsValid() const {
   return pointer_source_pose_ != nullptr;
 }
 
-WFN::Vector3 WMRPointerSourcePose::Position() const {
+WFN::Vector3 WMRPointerSourcePoseImpl::Position() const {
   DCHECK(IsValid());
   WFN::Vector3 val;
   HRESULT hr = pointer_source_pose_->get_Position(&val);
@@ -39,7 +38,7 @@
   return val;
 }
 
-WFN::Quaternion WMRPointerSourcePose::Orientation() const {
+WFN::Quaternion WMRPointerSourcePoseImpl::Orientation() const {
   DCHECK(IsValid());
   WFN::Quaternion val;
   HRESULT hr = pointer_source_pose2_->get_Orientation(&val);
diff --git a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.h b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.h
index caae4bfe..a310fed 100644
--- a/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.h
+++ b/device/vr/windows_mixed_reality/wrappers/wmr_pointer_source_pose.h
@@ -12,19 +12,29 @@
 namespace device {
 class WMRPointerSourcePose {
  public:
-  explicit WMRPointerSourcePose(
+  virtual ~WMRPointerSourcePose() = default;
+
+  virtual bool IsValid() const = 0;
+  virtual ABI::Windows::Foundation::Numerics::Vector3 Position() const = 0;
+  virtual ABI::Windows::Foundation::Numerics::Quaternion Orientation()
+      const = 0;
+};
+
+class WMRPointerSourcePoseImpl : public WMRPointerSourcePose {
+ public:
+  explicit WMRPointerSourcePoseImpl(
       Microsoft::WRL::ComPtr<ABI::Windows::UI::Input::Spatial::
                                  ISpatialPointerInteractionSourcePose>
           pointer_pose);
-  virtual ~WMRPointerSourcePose();
+  ~WMRPointerSourcePoseImpl() override;
 
-  bool IsValid() const;
+  bool IsValid() const override;
 
   // Uses ISpatialPointerInteractionSourcePose.
-  ABI::Windows::Foundation::Numerics::Vector3 Position() const;
+  ABI::Windows::Foundation::Numerics::Vector3 Position() const override;
 
   // Uses ISpatialPointerInteractionSourcePose2.
-  ABI::Windows::Foundation::Numerics::Quaternion Orientation() const;
+  ABI::Windows::Foundation::Numerics::Quaternion Orientation() const override;
 
  private:
   Microsoft::WRL::ComPtr<
@@ -34,8 +44,9 @@
       ABI::Windows::UI::Input::Spatial::ISpatialPointerInteractionSourcePose2>
       pointer_source_pose2_;
 
-  DISALLOW_COPY(WMRPointerSourcePose);
+  DISALLOW_COPY(WMRPointerSourcePoseImpl);
 };
+
 }  // namespace device
 
 #endif  // DEVICE_VR_WINDOWS_MIXED_REALITY_WRAPPERS_WMR_POINTER_SOURCE_POSE_H_
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc
index ab502152..8c58d75 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc
@@ -38,13 +38,11 @@
 namespace {
 
 // TODO(ekaramad): Make this a proper resource (https://crbug.com/659750).
-// TODO(ekaramad): Should we use an <embed>? Verify if this causes issues with
-// post messaging support for PDF viewer (https://crbug.com/659750).
 const char kFullPageMimeHandlerViewHTML[] =
     "<!doctype html><html><body style='height: 100%%; width: 100%%; overflow: "
     "hidden; margin:0px; background-color: rgb(%d, %d, %d);'><embed "
     "style='position:absolute; left: 0; top: 0;'width='100%%' height='100%%'"
-    " src='about:blank' type='text/html' "
+    " src='about:blank' type='%s' "
     "internalid='%s'></embed></body></html>";
 const uint32_t kFullPageMimeHandlerViewDataPipeSize = 512U;
 
@@ -99,9 +97,9 @@
     return false;
   auto color = GetBackgroundColorStringForMimeType(resource_url, mime_type);
   std::string token = base::UnguessableToken::Create().ToString();
-  auto html_str =
-      base::StringPrintf(kFullPageMimeHandlerViewHTML, SkColorGetR(color),
-                         SkColorGetG(color), SkColorGetB(color), token.c_str());
+  auto html_str = base::StringPrintf(
+      kFullPageMimeHandlerViewHTML, SkColorGetR(color), SkColorGetG(color),
+      SkColorGetB(color), mime_type.c_str(), token.c_str());
   payload->assign(html_str);
   *data_pipe_size = kFullPageMimeHandlerViewDataPipeSize;
   base::PostTaskWithTraitsAndReply(
diff --git a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc
index 8bbdbc7..ef81a97 100644
--- a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc
+++ b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc
@@ -82,6 +82,12 @@
   auto* manager = Get(content::RenderFrame::FromWebFrame(
                           plugin_element.GetDocument().GetFrame()),
                       true /* create_if_does_not_exist */);
+  if (manager->IsManagedByContainerManager(plugin_element)) {
+    // This is the one injected by HTML string. Return true so that the
+    // HTMLPlugInElement creates a child frame to be used as the outer
+    // WebContents frame.
+    return true;
+  }
   if (auto* old_frame_container = GetFrameContainer(plugin_element)) {
     if (old_frame_container->resource_url().EqualsIgnoringRef(resource_url) &&
         old_frame_container->mime_type() == mime_type) {
@@ -104,9 +110,7 @@
 v8::Local<v8::Object> MimeHandlerViewContainerManager::GetScriptableObject(
     const blink::WebElement& plugin_element,
     v8::Isolate* isolate) {
-  if (plugin_element.HasAttribute("internalid") &&
-      base::ToUpperASCII(plugin_element.GetAttribute("internalid").Utf8()) ==
-          internal_id_) {
+  if (IsManagedByContainerManager(plugin_element)) {
     return GetPostMessageSupport()->GetScriptableObject(isolate);
   }
   if (auto* frame_container = GetFrameContainer(plugin_element)) {
@@ -131,6 +135,7 @@
   // files happens in the same RenderFrame (e.g., some of
   // PDFExtensionLoadTest tests).
   post_message_support_.reset();
+  plugin_element_ = blink::WebElement();
 }
 
 void MimeHandlerViewContainerManager::OnDestruct() {
@@ -281,4 +286,15 @@
 bool MimeHandlerViewContainerManager::IsResourceAccessibleBySource() const {
   return true;
 }
+
+bool MimeHandlerViewContainerManager::IsManagedByContainerManager(
+    const blink::WebElement& plugin_element) {
+  if (plugin_element_.IsNull() && plugin_element.HasAttribute("internalid") &&
+      base::ToUpperASCII(plugin_element.GetAttribute("internalid").Utf8()) ==
+          internal_id_) {
+    plugin_element_ = plugin_element;
+  }
+  return plugin_element_ == plugin_element;
+}
+
 }  // namespace extensions
diff --git a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h
index b3d3984d..8843442 100644
--- a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h
+++ b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h
@@ -15,6 +15,7 @@
 #include "extensions/common/mojo/guest_view.mojom.h"
 #include "extensions/renderer/guest_view/mime_handler_view/post_message_support.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "third_party/blink/public/web/web_element.h"
 #include "url/gurl.h"
 
 namespace blink {
@@ -110,6 +111,11 @@
 
   void RecordInteraction(MimeHandlerViewUMATypes::Type type);
 
+  // Returns true if the |element| is managed by
+  // MimeHandlerViewContainerManager; this would be the element that is added by
+  // the HTML string injected at MimeHandlerViewAttachHelper.
+  bool IsManagedByContainerManager(const blink::WebElement& plugin_element);
+
   // Instantiated if this MHVFC is for a full-page MHV. This means MHV is
   // created when a frame was navigated to MHV resource by means other than
   // HTMLPlugInElement::RequestObjectInternal (e.g., omnibox). Note: the
@@ -125,6 +131,8 @@
   // MimeHandlerViewAttachHelper (and hence requires a scriptable object to for
   // postMessage purposes).
   std::string internal_id_;
+  // The plugin element that is managed by MimeHandlerViewContainerManager.
+  blink::WebElement plugin_element_;
 
   mojo::BindingSet<mojom::MimeHandlerViewContainerManager> bindings_;
   mojo::Binding<mime_handler::BeforeUnloadControl>
diff --git a/fuchsia/engine/browser/web_engine_url_request_context_getter.cc b/fuchsia/engine/browser/web_engine_url_request_context_getter.cc
index 0eec8886..91a97a6 100644
--- a/fuchsia/engine/browser/web_engine_url_request_context_getter.cc
+++ b/fuchsia/engine/browser/web_engine_url_request_context_getter.cc
@@ -52,11 +52,8 @@
           data_dir_path_.Append(FILE_PATH_LITERAL("Cookies")), false, false,
           NULL);
 
-      // Platform encryption support is not yet implemented, so store cookies in
-      // plaintext for now.
-      // TODO(crbug.com/884355): Add OSCrypt impl for Fuchsia and encrypt the
-      // cookie store with it.
-      NOTIMPLEMENTED() << "Persistent cookie store is NOT encrypted!";
+      // Fuchsia protects the local data at rest so there is no need to encrypt
+      // cookie store.
       builder.SetCookieStore(
           content::CreateCookieStore(cookie_config, nullptr));
     }
diff --git a/google_apis/BUILD.gn b/google_apis/BUILD.gn
index 6098f3b..3c605a4 100644
--- a/google_apis/BUILD.gn
+++ b/google_apis/BUILD.gn
@@ -180,7 +180,6 @@
     "gaia/fake_oauth2_token_service.h",
     "gaia/fake_oauth2_token_service_delegate.cc",
     "gaia/fake_oauth2_token_service_delegate.h",
-    "gaia/mock_url_fetcher_factory.h",
     "gaia/oauth2_token_service_test_util.cc",
     "gaia/oauth2_token_service_test_util.h",
   ]
diff --git a/google_apis/gaia/gaia_auth_fetcher_unittest.cc b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
index 1744893..7e74ac8 100644
--- a/google_apis/gaia/gaia_auth_fetcher_unittest.cc
+++ b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
@@ -23,7 +23,6 @@
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/gaia/google_service_auth_error.h"
-#include "google_apis/gaia/mock_url_fetcher_factory.h"
 #include "google_apis/gaia/oauth_multilogin_result.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/load_flags.h"
diff --git a/google_apis/gaia/mock_url_fetcher_factory.h b/google_apis/gaia/mock_url_fetcher_factory.h
deleted file mode 100644
index 663ed9f..0000000
--- a/google_apis/gaia/mock_url_fetcher_factory.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2012 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.
-//
-// A collection of classes that are useful when testing things that use a
-// GaiaAuthFetcher.
-
-#ifndef GOOGLE_APIS_GAIA_MOCK_URL_FETCHER_FACTORY_H_
-#define GOOGLE_APIS_GAIA_MOCK_URL_FETCHER_FACTORY_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "google_apis/gaia/gaia_auth_fetcher.h"
-#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
-#include "net/url_request/test_url_fetcher_factory.h"
-#include "net/url_request/url_request_status.h"
-
-// Responds as though ClientLogin returned from the server.
-class MockFetcher : public net::TestURLFetcher {
- public:
-  MockFetcher(bool success,
-              const GURL& url,
-              const std::string& results,
-              net::URLFetcher::RequestType request_type,
-              net::URLFetcherDelegate* d);
-
-  MockFetcher(const GURL& url,
-              const net::URLRequestStatus& status,
-              int response_code,
-              const std::string& results,
-              net::URLFetcher::RequestType request_type,
-              net::URLFetcherDelegate* d);
-
-  ~MockFetcher() override;
-
-  void Start() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MockFetcher);
-};
-
-template<typename T>
-class MockURLFetcherFactory : public net::URLFetcherFactory,
-                              public net::ScopedURLFetcherFactory {
- public:
-  MockURLFetcherFactory()
-      : net::ScopedURLFetcherFactory(this),
-        success_(true) {
-  }
-  ~MockURLFetcherFactory() {}
-  std::unique_ptr<net::URLFetcher> CreateURLFetcher(
-      int id,
-      const GURL& url,
-      net::URLFetcher::RequestType request_type,
-      net::URLFetcherDelegate* d,
-      net::NetworkTrafficAnnotationTag traffic_annotation) override {
-    return std::unique_ptr<net::URLFetcher>(
-        new T(success_, url, results_, request_type, d));
-  }
-  void set_success(bool success) {
-    success_ = success;
-  }
-  void set_results(const std::string& results) {
-    results_ = results;
-  }
- private:
-  bool success_;
-  std::string results_;
-  DISALLOW_COPY_AND_ASSIGN(MockURLFetcherFactory);
-};
-
-#endif  // GOOGLE_APIS_GAIA_MOCK_URL_FETCHER_FACTORY_H_
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index f1c0c2c..d868bed 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -256,7 +256,6 @@
     "ipc/client/gpu_context_tests.h",
     "ipc/client/gpu_in_process_context_tests.cc",
     "ipc/client/raster_in_process_context_tests.cc",
-    "ipc/service/direct_composition_surface_win_unittest.cc",
   ]
 
   if (use_dawn) {
diff --git a/gpu/ipc/service/BUILD.gn b/gpu/ipc/service/BUILD.gn
index 9ee8e62..075753d 100644
--- a/gpu/ipc/service/BUILD.gn
+++ b/gpu/ipc/service/BUILD.gn
@@ -81,25 +81,10 @@
   ldflags = []
   if (is_win) {
     sources += [
-      "child_window_win.cc",
-      "child_window_win.h",
-      "dc_layer_tree.cc",
-      "dc_layer_tree.h",
-      "direct_composition_child_surface_win.cc",
-      "direct_composition_child_surface_win.h",
-      "direct_composition_surface_win.cc",
-      "direct_composition_surface_win.h",
       "gpu_memory_buffer_factory_dxgi.cc",
       "gpu_memory_buffer_factory_dxgi.h",
       "image_transport_surface_win.cc",
-      "swap_chain_presenter.cc",
-      "swap_chain_presenter.h",
     ]
-    libs += [
-      "dxgi.lib",
-      "dwmapi.lib",
-    ]
-    ldflags += [ "/DELAYLOAD:dxgi.dll" ]
   }
   if (is_mac) {
     sources += [
diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc
index 82b62aa3..1b3c1086 100644
--- a/gpu/ipc/service/gpu_init.cc
+++ b/gpu/ipc/service/gpu_init.cc
@@ -36,7 +36,7 @@
 #endif
 
 #if defined(OS_WIN)
-#include "gpu/ipc/service/direct_composition_surface_win.h"
+#include "ui/gl/direct_composition_surface_win.h"
 #include "ui/gl/gl_surface_egl.h"
 #endif
 
@@ -80,18 +80,18 @@
   if (gl::GetGLImplementation() == gl::kGLImplementationEGLGLES2) {
     DCHECK(gpu_info);
     gpu_info->direct_composition =
-        DirectCompositionSurfaceWin::IsDirectCompositionSupported();
+        gl::DirectCompositionSurfaceWin::IsDirectCompositionSupported();
     gpu_info->supports_overlays =
-        DirectCompositionSurfaceWin::AreOverlaysSupported();
+        gl::DirectCompositionSurfaceWin::AreOverlaysSupported();
     bool supports_scaling = false;
-    if (DirectCompositionSurfaceWin::SupportsOverlayFormat(DXGI_FORMAT_YUY2,
-                                                           &supports_scaling)) {
+    if (gl::DirectCompositionSurfaceWin::SupportsOverlayFormat(
+            DXGI_FORMAT_YUY2, &supports_scaling)) {
       gpu_info->yuy2_overlay_support = supports_scaling
                                            ? gpu::OverlaySupport::kScaling
                                            : gpu::OverlaySupport::kDirect;
     }
-    if (DirectCompositionSurfaceWin::SupportsOverlayFormat(DXGI_FORMAT_NV12,
-                                                           &supports_scaling)) {
+    if (gl::DirectCompositionSurfaceWin::SupportsOverlayFormat(
+            DXGI_FORMAT_NV12, &supports_scaling)) {
       gpu_info->nv12_overlay_support = supports_scaling
                                            ? gpu::OverlaySupport::kScaling
                                            : gpu::OverlaySupport::kDirect;
diff --git a/gpu/ipc/service/image_transport_surface_win.cc b/gpu/ipc/service/image_transport_surface_win.cc
index b88bc55..0b1526c 100644
--- a/gpu/ipc/service/image_transport_surface_win.cc
+++ b/gpu/ipc/service/image_transport_surface_win.cc
@@ -9,9 +9,9 @@
 #include "base/win/windows_version.h"
 #include "gpu/command_buffer/service/feature_info.h"
 #include "gpu/config/gpu_preferences.h"
-#include "gpu/ipc/service/direct_composition_surface_win.h"
 #include "gpu/ipc/service/pass_through_image_transport_surface.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/gl/direct_composition_surface_win.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_implementation.h"
 #include "ui/gl/gl_surface_egl.h"
@@ -35,15 +35,15 @@
     auto vsync_provider =
         std::make_unique<gl::VSyncProviderWin>(surface_handle);
 
-    if (DirectCompositionSurfaceWin::IsDirectCompositionSupported()) {
+    if (gl::DirectCompositionSurfaceWin::IsDirectCompositionSupported()) {
       const auto& workarounds = delegate->GetFeatureInfo()->workarounds();
-      DirectCompositionSurfaceWin::Settings settings;
+      gl::DirectCompositionSurfaceWin::Settings settings;
       settings.disable_nv12_dynamic_textures =
           workarounds.disable_nv12_dynamic_textures;
       settings.disable_larger_than_screen_overlays =
           workarounds.disable_larger_than_screen_overlays;
       auto vsync_callback = delegate->GetGpuVSyncCallback();
-      auto dc_surface = base::MakeRefCounted<DirectCompositionSurfaceWin>(
+      auto dc_surface = base::MakeRefCounted<gl::DirectCompositionSurfaceWin>(
           std::move(vsync_provider), std::move(vsync_callback), surface_handle,
           settings);
       if (!dc_surface->Initialize(gl::GLSurfaceFormat()))
diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc
index 57eb874c..6e67e58 100644
--- a/headless/app/headless_shell.cc
+++ b/headless/app/headless_shell.cc
@@ -372,11 +372,9 @@
 
 void HeadlessShell::OnReadyState(
     std::unique_ptr<runtime::EvaluateResult> result) {
-  // result->GetResult() can be nullptr if
-  // HeadlessDevToolsClientImpl::DispatchMessageReply sees an error.
-  // It shouldn't, because the result is actually non-optional according to
-  // js_protocol.pdl; but there is no good way to graft that into here. Sigh.
-  if (result->GetResult() && result->GetResult()->GetValue()->is_string()) {
+  // |result| can be nullptr if HeadlessDevToolsClientImpl::DispatchMessageReply
+  // sees an error.
+  if (result && result->GetResult()->GetValue()->is_string()) {
     std::stringstream stream(result->GetResult()->GetValue()->GetString());
     std::string ready_state;
     std::string url;
diff --git a/headless/lib/browser/headless_devtools_client_impl.cc b/headless/lib/browser/headless_devtools_client_impl.cc
index a61b18d7..aefe06e 100644
--- a/headless/lib/browser/headless_devtools_client_impl.cc
+++ b/headless/lib/browser/headless_devtools_client_impl.cc
@@ -226,6 +226,7 @@
       }
     } else if (message_dict.GetDictionary("error", &result_dict)) {
       auto null_value = std::make_unique<base::Value>();
+      base::Value* null_value_ptr = null_value.get();
       DLOG(ERROR) << "Error in method call result: " << *result_dict;
       if (browser_main_thread_) {
         browser_main_thread_->PostTask(
@@ -233,7 +234,7 @@
             base::BindOnce(
                 &HeadlessDevToolsClientImpl::DispatchMessageReplyWithResultTask,
                 weak_ptr_factory_.GetWeakPtr(), std::move(null_value),
-                std::move(callback.callback_with_result), null_value.get()));
+                std::move(callback.callback_with_result), null_value_ptr));
       } else {
         std::move(callback.callback_with_result).Run(*null_value);
       }
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 9d39f98..a50c8abd 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -2071,6 +2071,23 @@
 }
 
 - (void)finishDismissingTabSwitcher {
+  // In real world devices, it is possible to have an empty tab model at the
+  // finishing block of a BVC presentation animation. This can happen when the
+  // following occur: a) There is JS that closes the last incognito tab, b) that
+  // JS was paused while the user was in the tab switcher, c) the user enters
+  // the tab, activating the JS while the tab is being presented. Effectively,
+  // the BVC finishes the presentation animation, but there are no tabs to
+  // display. The only appropriate action is to dismiss the BVC and return the
+  // user to the tab switcher.
+  if (self.currentTabModel.count == 0U) {
+    _tabSwitcherIsActive = NO;
+    _dismissingTabSwitcher = NO;
+    _modeToDisplayOnTabSwitcherDismissal = TabSwitcherDismissalMode::NONE;
+    self.NTPActionAfterTabSwitcherDismissal = NO_ACTION;
+    [self showTabSwitcher];
+    return;
+  }
+
   // The tab switcher dismissal animation runs
   // as part of the BVC presentation process.  The BVC is presented before the
   // animations begin, so it should be the current active VC at this point.
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index 54014c8..10f0f4d 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -262,13 +262,6 @@
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillDownstreamUseGooglePayBrandingOniOS)},
-    {"enable-autofill-save-credit-card-uses-strike-system",
-     flag_descriptions::kEnableAutofillSaveCreditCardUsesStrikeSystemName,
-     flag_descriptions::
-         kEnableAutofillSaveCreditCardUsesStrikeSystemDescription,
-     flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(
-         autofill::features::kAutofillSaveCreditCardUsesStrikeSystem)},
     {"enable-autofill-save-credit-card-uses-strike-system-v2",
      flag_descriptions::kEnableAutofillSaveCreditCardUsesStrikeSystemV2Name,
      flag_descriptions::
diff --git a/ios/chrome/browser/autofill/BUILD.gn b/ios/chrome/browser/autofill/BUILD.gn
index ffda190..bb49548 100644
--- a/ios/chrome/browser/autofill/BUILD.gn
+++ b/ios/chrome/browser/autofill/BUILD.gn
@@ -25,8 +25,6 @@
     "form_suggestion_tab_helper.mm",
     "form_suggestion_view.h",
     "form_suggestion_view.mm",
-    "legacy_strike_database_factory.cc",
-    "legacy_strike_database_factory.h",
     "personal_data_manager_factory.cc",
     "personal_data_manager_factory.h",
     "strike_database_factory.cc",
diff --git a/ios/chrome/browser/autofill/legacy_strike_database_factory.cc b/ios/chrome/browser/autofill/legacy_strike_database_factory.cc
deleted file mode 100644
index d2fe4b6..0000000
--- a/ios/chrome/browser/autofill/legacy_strike_database_factory.cc
+++ /dev/null
@@ -1,54 +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 "ios/chrome/browser/autofill/legacy_strike_database_factory.h"
-
-#include <utility>
-
-#include "base/no_destructor.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
-#include "components/keyed_service/ios/browser_state_dependency_manager.h"
-#include "ios/chrome/browser/application_context.h"
-#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
-#include "ios/chrome/browser/leveldb_proto/proto_database_provider_factory.h"
-
-namespace autofill {
-
-// static
-LegacyStrikeDatabase* LegacyStrikeDatabaseFactory::GetForBrowserState(
-    ios::ChromeBrowserState* browser_state) {
-  return static_cast<LegacyStrikeDatabase*>(
-      GetInstance()->GetServiceForBrowserState(browser_state, true));
-}
-
-// static
-LegacyStrikeDatabaseFactory* LegacyStrikeDatabaseFactory::GetInstance() {
-  static base::NoDestructor<LegacyStrikeDatabaseFactory> instance;
-  return instance.get();
-}
-
-LegacyStrikeDatabaseFactory::LegacyStrikeDatabaseFactory()
-    : BrowserStateKeyedServiceFactory(
-          "AutofillLegacyStrikeDatabase",
-          BrowserStateDependencyManager::GetInstance()) {
-  DependsOn(leveldb_proto::ProtoDatabaseProviderFactory::GetInstance());
-}
-
-LegacyStrikeDatabaseFactory::~LegacyStrikeDatabaseFactory() {}
-
-std::unique_ptr<KeyedService>
-LegacyStrikeDatabaseFactory::BuildServiceInstanceFor(
-    web::BrowserState* context) const {
-  ios::ChromeBrowserState* chrome_browser_state =
-      ios::ChromeBrowserState::FromBrowserState(context);
-
-  leveldb_proto::ProtoDatabaseProvider* db_provider =
-      leveldb_proto::ProtoDatabaseProviderFactory::GetInstance()
-          ->GetForBrowserState(chrome_browser_state);
-
-  return std::make_unique<autofill::LegacyStrikeDatabase>(
-      db_provider, chrome_browser_state->GetStatePath());
-}
-
-}  // namespace autofill
diff --git a/ios/chrome/browser/autofill/legacy_strike_database_factory.h b/ios/chrome/browser/autofill/legacy_strike_database_factory.h
deleted file mode 100644
index 56ca6f46..0000000
--- a/ios/chrome/browser/autofill/legacy_strike_database_factory.h
+++ /dev/null
@@ -1,45 +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 IOS_CHROME_BROWSER_AUTOFILL_LEGACY_STRIKE_DATABASE_FACTORY_H_
-#define IOS_CHROME_BROWSER_AUTOFILL_LEGACY_STRIKE_DATABASE_FACTORY_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/no_destructor.h"
-#include "components/keyed_service/ios/browser_state_keyed_service_factory.h"
-
-namespace ios {
-class ChromeBrowserState;
-}
-
-namespace autofill {
-
-class LegacyStrikeDatabase;
-
-// Singleton that owns all LegacyStrikeDatabases and associates them with
-// ios::ChromeBrowserState.
-class LegacyStrikeDatabaseFactory : public BrowserStateKeyedServiceFactory {
- public:
-  static LegacyStrikeDatabase* GetForBrowserState(
-      ios::ChromeBrowserState* browser_state);
-  static LegacyStrikeDatabaseFactory* GetInstance();
-
- private:
-  friend class base::NoDestructor<LegacyStrikeDatabaseFactory>;
-
-  LegacyStrikeDatabaseFactory();
-  ~LegacyStrikeDatabaseFactory() override;
-
-  // BrowserStateKeyedServiceFactory implementation.
-  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
-      web::BrowserState* context) const override;
-
-  DISALLOW_COPY_AND_ASSIGN(LegacyStrikeDatabaseFactory);
-};
-
-}  // namespace autofill
-
-#endif  // IOS_CHROME_BROWSER_AUTOFILL_LEGACY_STRIKE_DATABASE_FACTORY_H_
diff --git a/ios/chrome/browser/bookmarks/bookmarks_utils.cc b/ios/chrome/browser/bookmarks/bookmarks_utils.cc
index 996e4a09..f3bb3f003 100644
--- a/ios/chrome/browser/bookmarks/bookmarks_utils.cc
+++ b/ios/chrome/browser/bookmarks/bookmarks_utils.cc
@@ -35,7 +35,7 @@
     if (!bookmark_model->client()->CanBeEditedByUser(
             bookmark_model->root_node()->GetChild(i)))
       continue;
-    if (!bookmark_model->root_node()->GetChild(i)->empty())
+    if (!bookmark_model->root_node()->GetChild(i)->children().empty())
       return false;
   }
 
diff --git a/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm b/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
index 70df4af7..51bf4e6 100644
--- a/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
+++ b/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
@@ -22,7 +22,6 @@
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/post_task.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
@@ -39,7 +38,6 @@
 #include "components/signin/core/browser/signin_pref_names.h"
 #include "components/signin/ios/browser/account_consistency_service.h"
 #include "ios/chrome/browser/application_context.h"
-#include "ios/chrome/browser/autofill/legacy_strike_database_factory.h"
 #include "ios/chrome/browser/autofill/personal_data_manager_factory.h"
 #include "ios/chrome/browser/autofill/strike_database_factory.h"
 #include "ios/chrome/browser/bookmarks/bookmark_remover_helper.h"
@@ -437,23 +435,10 @@
               autofill::features::
                   kAutofillLocalCardMigrationUsesStrikeSystemV2)) {
         // Clear out the Autofill StrikeDatabase in its entirety.
-        // Both StrikeDatabase and LegacyStrikeDatabase use data from the same
-        // ProtoDatabase, so only one of them needs to call ClearAllStrikes(~).
         autofill::StrikeDatabase* strike_database =
             autofill::StrikeDatabaseFactory::GetForBrowserState(browser_state_);
         if (strike_database)
           strike_database->ClearAllStrikes();
-      } else if (base::FeatureList::IsEnabled(
-                     autofill::features::
-                         kAutofillSaveCreditCardUsesStrikeSystem)) {
-        // Clear out the Autofill LegacyStrikeDatabase in its entirety.
-        autofill::LegacyStrikeDatabase* legacy_strike_database =
-            autofill::LegacyStrikeDatabaseFactory::GetForBrowserState(
-                browser_state_);
-        if (legacy_strike_database) {
-          legacy_strike_database->ClearAllStrikes(AdaptCallbackForRepeating(
-              IgnoreArgument<bool>(CreatePendingTaskCompletionClosure())));
-        }
       }
 
       // Ask for a call back when the above calls are finished.
diff --git a/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm b/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm
index ca510471..36fa3236 100644
--- a/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm
+++ b/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm
@@ -14,6 +14,7 @@
 #include "components/password_manager/core/browser/password_form_manager_for_ui.h"
 #include "components/password_manager/core/browser/password_form_metrics_recorder.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
+#include "components/password_manager/core/browser/password_ui_utils.h"
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/infobars/infobar.h"
 #import "ios/chrome/browser/passwords/update_password_infobar_controller.h"
@@ -106,12 +107,10 @@
 
 bool IOSChromeUpdatePasswordInfoBarDelegate::Accept() {
   DCHECK(form_to_save());
-  if (ShowMultipleAccounts()) {
-    form_to_save()->Update(
-        *form_to_save()->GetBestMatches().at(selected_account_));
-  } else {
-    form_to_save()->Update(form_to_save()->GetPendingCredentials());
-  }
+  UpdatePasswordFormUsernameAndPassword(
+      selected_account_, form_to_save()->GetPendingCredentials().password_value,
+      form_to_save());
+  form_to_save()->Save();
   set_infobar_response(password_manager::metrics_util::CLICKED_SAVE);
   return true;
 }
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
index d9d392d..cbc41361 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
@@ -14,7 +14,6 @@
 #include "components/autofill/core/browser/autocomplete_history_manager.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/payments/card_unmask_delegate.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h"
@@ -53,7 +52,6 @@
   identity::IdentityManager* GetIdentityManager() override;
   FormDataImporter* GetFormDataImporter() override;
   payments::PaymentsClient* GetPaymentsClient() override;
-  LegacyStrikeDatabase* GetLegacyStrikeDatabase() override;
   StrikeDatabase* GetStrikeDatabase() override;
   ukm::UkmRecorder* GetUkmRecorder() override;
   ukm::SourceId GetUkmSourceId() override;
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
index f16813e..6b2a9c3 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
@@ -25,7 +25,6 @@
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/autofill/address_normalizer_factory.h"
 #include "ios/chrome/browser/autofill/autocomplete_history_manager_factory.h"
-#include "ios/chrome/browser/autofill/legacy_strike_database_factory.h"
 #include "ios/chrome/browser/autofill/personal_data_manager_factory.h"
 #include "ios/chrome/browser/autofill/strike_database_factory.h"
 #include "ios/chrome/browser/infobars/infobar.h"
@@ -137,11 +136,6 @@
   return payments_client_.get();
 }
 
-LegacyStrikeDatabase* ChromeAutofillClientIOS::GetLegacyStrikeDatabase() {
-  return LegacyStrikeDatabaseFactory::GetForBrowserState(
-      browser_state_->GetOriginalChromeBrowserState());
-}
-
 StrikeDatabase* ChromeAutofillClientIOS::GetStrikeDatabase() {
   return StrikeDatabaseFactory::GetForBrowserState(
       browser_state_->GetOriginalChromeBrowserState());
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm b/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm
index 0d6ee537..1033b38 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm
@@ -162,7 +162,7 @@
   // Add "Bookmarks Bar" and "Other Bookmarks" only when they are not empty.
   const BookmarkNode* bookmarkBar =
       self.sharedState.bookmarkModel->bookmark_bar_node();
-  if (!bookmarkBar->empty()) {
+  if (!bookmarkBar->children().empty()) {
     BookmarkHomeNodeItem* barItem =
         [[BookmarkHomeNodeItem alloc] initWithType:BookmarkHomeItemTypeBookmark
                                       bookmarkNode:bookmarkBar];
@@ -173,7 +173,7 @@
 
   const BookmarkNode* otherBookmarks =
       self.sharedState.bookmarkModel->other_node();
-  if (!otherBookmarks->empty()) {
+  if (!otherBookmarks->children().empty()) {
     BookmarkHomeNodeItem* otherItem =
         [[BookmarkHomeNodeItem alloc] initWithType:BookmarkHomeItemTypeBookmark
                                       bookmarkNode:otherBookmarks];
@@ -450,7 +450,7 @@
 
 - (BOOL)hasBookmarksOrFolders {
   return self.sharedState.tableViewDisplayedRootNode &&
-         !self.sharedState.tableViewDisplayedRootNode->empty();
+         !self.sharedState.tableViewDisplayedRootNode->children().empty();
 }
 
 // Delete all items for the given |sectionIdentifier| section, or create it
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
index 99a59e7..eff5712 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
@@ -274,7 +274,8 @@
         bookmark_utils_ios::FindFolderById(self.bookmarks, nodeID);
     DCHECK(node);
     // if node is an empty permanent node, stop.
-    if (node->empty() && IsPrimaryPermanentNode(node, self.bookmarks)) {
+    if (node->children().empty() &&
+        IsPrimaryPermanentNode(node, self.bookmarks)) {
       break;
     }
 
@@ -1226,7 +1227,7 @@
     return [self
         hasItemsInSectionIdentifier:BookmarkHomeSectionIdentifierBookmarks];
   } else {
-    return !self.sharedState.tableViewDisplayedRootNode->empty();
+    return !self.sharedState.tableViewDisplayedRootNode->children().empty();
   }
 }
 
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 b6d9c7e..609e4e5 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -1627,7 +1627,12 @@
     // case, display the Long Press InProductHelp if needed.
     auto completion =
         ^(id<UIViewControllerTransitionCoordinatorContext> context) {
-          [self.bubblePresenter presentLongPressBubbleIfEligible];
+          // Do not attempt to use the browserState if |-shutdown| was called
+          // during the BVC presentation animation. Attempting to present
+          // bubbles will crash since bubblePresenter requires a valid
+          // BrowserState.
+          if (!_isShutdown)
+            [self.bubblePresenter presentLongPressBubbleIfEligible];
         };
 
     [self.transitionCoordinator animateAlongsideTransition:nil
@@ -1693,6 +1698,12 @@
 
 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
   [super traitCollectionDidChange:previousTraitCollection];
+
+  // After |-shutdown| is called, |_browserState| is invalid and will cause a
+  // crash.
+  if (!_browserState || _isShutdown)
+    return;
+
   FullscreenControllerFactory::GetInstance()
       ->GetForBrowserState(_browserState)
       ->BrowserTraitCollectionChangedBegin();
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index 0869aa0..61b3a55 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -105,8 +105,6 @@
   "internal/autofill/web_view_autofill_client_ios.mm",
   "internal/autofill/web_view_autocomplete_history_manager_factory.h",
   "internal/autofill/web_view_autocomplete_history_manager_factory.mm",
-  "internal/autofill/web_view_legacy_strike_database_factory.h",
-  "internal/autofill/web_view_legacy_strike_database_factory.mm",
   "internal/autofill/web_view_personal_data_manager_factory.h",
   "internal/autofill/web_view_personal_data_manager_factory.mm",
   "internal/autofill/web_view_strike_database_factory.h",
diff --git a/ios/web_view/internal/autofill/cwv_autofill_controller.mm b/ios/web_view/internal/autofill/cwv_autofill_controller.mm
index 3af0f60..52128d0 100644
--- a/ios/web_view/internal/autofill/cwv_autofill_controller.mm
+++ b/ios/web_view/internal/autofill/cwv_autofill_controller.mm
@@ -37,7 +37,6 @@
 #import "ios/web_view/internal/autofill/cwv_credit_card_verifier_internal.h"
 #include "ios/web_view/internal/autofill/web_view_autocomplete_history_manager_factory.h"
 #import "ios/web_view/internal/autofill/web_view_autofill_client_ios.h"
-#include "ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.h"
 #include "ios/web_view/internal/autofill/web_view_personal_data_manager_factory.h"
 #include "ios/web_view/internal/autofill/web_view_strike_database_factory.h"
 #import "ios/web_view/internal/passwords/cwv_password_controller.h"
@@ -137,8 +136,6 @@
         _webState, self,
         ios_web_view::WebViewIdentityManagerFactory::GetForBrowserState(
             browserState->GetRecordingBrowserState()),
-        ios_web_view::WebViewLegacyStrikeDatabaseFactory::GetForBrowserState(
-            browserState->GetRecordingBrowserState()),
         ios_web_view::WebViewStrikeDatabaseFactory::GetForBrowserState(
             browserState->GetRecordingBrowserState()),
         ios_web_view::WebViewWebDataServiceWrapperFactory::
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
index 856e752..e8bb3c0 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
@@ -14,7 +14,6 @@
 #include "components/autofill/core/browser/autocomplete_history_manager.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/payments/card_unmask_delegate.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
 #include "components/autofill/core/browser/payments/strike_database.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
@@ -35,7 +34,6 @@
       web::WebState* web_state,
       id<CWVAutofillClientIOSBridge> bridge,
       identity::IdentityManager* identity_manager,
-      LegacyStrikeDatabase* legacy_strike_database,
       StrikeDatabase* strike_database,
       scoped_refptr<AutofillWebDataService> autofill_web_data_service,
       syncer::SyncService* sync_service);
@@ -49,7 +47,6 @@
   identity::IdentityManager* GetIdentityManager() override;
   FormDataImporter* GetFormDataImporter() override;
   payments::PaymentsClient* GetPaymentsClient() override;
-  LegacyStrikeDatabase* GetLegacyStrikeDatabase() override;
   StrikeDatabase* GetStrikeDatabase() override;
   ukm::UkmRecorder* GetUkmRecorder() override;
   ukm::SourceId GetUkmSourceId() override;
@@ -121,7 +118,6 @@
   identity::IdentityManager* identity_manager_;
   std::unique_ptr<payments::PaymentsClient> payments_client_;
   std::unique_ptr<FormDataImporter> form_data_importer_;
-  LegacyStrikeDatabase* legacy_strike_database_;
   StrikeDatabase* strike_database_;
   scoped_refptr<AutofillWebDataService> autofill_web_data_service_;
   syncer::SyncService* sync_service_ = nullptr;
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
index b593199..2272e0ba 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
@@ -31,7 +31,6 @@
     web::WebState* web_state,
     id<CWVAutofillClientIOSBridge> bridge,
     identity::IdentityManager* identity_manager,
-    LegacyStrikeDatabase* legacy_strike_database,
     StrikeDatabase* strike_database,
     scoped_refptr<AutofillWebDataService> autofill_web_data_service,
     syncer::SyncService* sync_service)
@@ -53,7 +52,6 @@
           personal_data_manager_,
           ios_web_view::ApplicationContext::GetInstance()
               ->GetApplicationLocale())),
-      legacy_strike_database_(legacy_strike_database),
       strike_database_(strike_database),
       autofill_web_data_service_(autofill_web_data_service),
       sync_service_(sync_service) {}
@@ -91,10 +89,6 @@
   return payments_client_.get();
 }
 
-LegacyStrikeDatabase* WebViewAutofillClientIOS::GetLegacyStrikeDatabase() {
-  return legacy_strike_database_;
-}
-
 StrikeDatabase* WebViewAutofillClientIOS::GetStrikeDatabase() {
   return strike_database_;
 }
diff --git a/ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.h b/ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.h
deleted file mode 100644
index 8f0914b..0000000
--- a/ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.h
+++ /dev/null
@@ -1,45 +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 IOS_WEB_VIEW_INTERNAL_AUTOFILL_WEB_VIEW_LEGACY_STRIKE_DATABASE_FACTORY_H_
-#define IOS_WEB_VIEW_INTERNAL_AUTOFILL_WEB_VIEW_LEGACY_STRIKE_DATABASE_FACTORY_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/no_destructor.h"
-#include "components/keyed_service/ios/browser_state_keyed_service_factory.h"
-
-namespace autofill {
-class LegacyStrikeDatabase;
-}
-
-namespace ios_web_view {
-class WebViewBrowserState;
-
-// Singleton that owns all LegacyStrikeDatabases and associates them with
-// ios_web_view::WebViewBrowserState.
-class WebViewLegacyStrikeDatabaseFactory
-    : public BrowserStateKeyedServiceFactory {
- public:
-  static autofill::LegacyStrikeDatabase* GetForBrowserState(
-      WebViewBrowserState* browser_state);
-  static WebViewLegacyStrikeDatabaseFactory* GetInstance();
-
- private:
-  friend class base::NoDestructor<WebViewLegacyStrikeDatabaseFactory>;
-
-  WebViewLegacyStrikeDatabaseFactory();
-  ~WebViewLegacyStrikeDatabaseFactory() override;
-
-  // BrowserStateKeyedServiceFactory implementation.
-  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
-      web::BrowserState* context) const override;
-
-  DISALLOW_COPY_AND_ASSIGN(WebViewLegacyStrikeDatabaseFactory);
-};
-
-}  // namespace ios_web_view
-
-#endif  // IOS_WEB_VIEW_INTERNAL_AUTOFILL_WEB_VIEW_LEGACY_STRIKE_DATABASE_FACTORY_H_
diff --git a/ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.mm b/ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.mm
deleted file mode 100644
index ddb91cc..0000000
--- a/ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.mm
+++ /dev/null
@@ -1,60 +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 "ios/web_view/internal/autofill/web_view_legacy_strike_database_factory.h"
-
-#include <utility>
-
-#include "base/no_destructor.h"
-#include "components/autofill/core/browser/payments/legacy_strike_database.h"
-#include "components/keyed_service/ios/browser_state_dependency_manager.h"
-#include "ios/web_view/internal/app/application_context.h"
-#include "ios/web_view/internal/leveldb_proto/web_view_proto_database_provider_factory.h"
-#include "ios/web_view/internal/web_view_browser_state.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace ios_web_view {
-
-// static
-autofill::LegacyStrikeDatabase*
-WebViewLegacyStrikeDatabaseFactory::GetForBrowserState(
-    WebViewBrowserState* browser_state) {
-  return static_cast<autofill::LegacyStrikeDatabase*>(
-      GetInstance()->GetServiceForBrowserState(browser_state, true));
-}
-
-// static
-WebViewLegacyStrikeDatabaseFactory*
-WebViewLegacyStrikeDatabaseFactory::GetInstance() {
-  static base::NoDestructor<WebViewLegacyStrikeDatabaseFactory> instance;
-  return instance.get();
-}
-
-WebViewLegacyStrikeDatabaseFactory::WebViewLegacyStrikeDatabaseFactory()
-    : BrowserStateKeyedServiceFactory(
-          "AutofillLegacyStrikeDatabase",
-          BrowserStateDependencyManager::GetInstance()) {
-  DependsOn(leveldb_proto::WebViewProtoDatabaseProviderFactory::GetInstance());
-}
-
-WebViewLegacyStrikeDatabaseFactory::~WebViewLegacyStrikeDatabaseFactory() {}
-
-std::unique_ptr<KeyedService>
-WebViewLegacyStrikeDatabaseFactory::BuildServiceInstanceFor(
-    web::BrowserState* context) const {
-  WebViewBrowserState* browser_state =
-      WebViewBrowserState::FromBrowserState(context);
-
-  leveldb_proto::ProtoDatabaseProvider* db_provider =
-      leveldb_proto::WebViewProtoDatabaseProviderFactory::GetInstance()
-          ->GetForBrowserState(browser_state);
-
-  return std::make_unique<autofill::LegacyStrikeDatabase>(
-      db_provider, browser_state->GetStatePath());
-}
-
-}  // namespace ios_web_view
diff --git a/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java b/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
index 9f21233..15415a50 100644
--- a/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
+++ b/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
@@ -522,11 +522,9 @@
         assert mMediaDrm != null;
         assert !securityLevel.isEmpty();
 
-        String currentSecurityLevel;
-        try {
-            currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL);
-        } catch (java.lang.IllegalStateException e) {
-            Log.e(TAG, "Failed to get current security level", e);
+        String currentSecurityLevel = getSecurityLevel();
+        if (currentSecurityLevel.equals("")) {
+            // Failure logged by getSecurityLevel().
             return false;
         }
 
@@ -1147,9 +1145,21 @@
             return "";
         }
 
+        // Any failure in getPropertyString() means we don't know what the current security level
+        // is.
         try {
             return mMediaDrm.getPropertyString(SECURITY_LEVEL);
         } catch (java.lang.IllegalStateException e) {
+            // getPropertyString() may fail with android.media.MediaDrmResetException or
+            // android.media.MediaDrm.MediaDrmStateException. As MediaDrmStateException was added in
+            // API 21, we can't use it directly. However, both of these are IllegalStateExceptions,
+            // so both will be handled here.
+            Log.e(TAG, "Failed to get current security level", e);
+            return "";
+        } catch (Exception e) {
+            // getPropertyString() has been failing with android.media.ResourceBusyException on some
+            // devices. ResourceBusyException is not mentioned as a possible exception nor a runtime
+            // exception and thus can not be listed, so catching all exceptions to handle it here.
             Log.e(TAG, "Failed to get current security level", e);
             return "";
         }
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 16fa56b..706ec87 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -199,9 +199,19 @@
 #endif
 };
 
-// Only decode preload=metadata elements upon visibility?
-const base::Feature kPreloadMetadataLazyLoad{"PreloadMetadataLazyLoad",
-                                             base::FEATURE_DISABLED_BY_DEFAULT};
+// Only decode preload=metadata elements upon visibility. Experiment results
+// vary across platforms and are still being analyzed on macOS and CrOS.
+//
+// Android, Windows, and Linux all saw improvements without regressions, so
+// enable by default there.
+const base::Feature kPreloadMetadataLazyLoad {
+  "PreloadMetadataLazyLoad",
+#if defined(OS_MACOSX) || defined(OS_CHROMEOS)
+      base::FEATURE_DISABLED_BY_DEFAULT
+#else
+      base::FEATURE_ENABLED_BY_DEFAULT
+#endif
+};
 
 // Let videos be resumed via remote controls (for example, the notification)
 // when in background.
diff --git a/media/blink/video_frame_compositor.cc b/media/blink/video_frame_compositor.cc
index 09bdae9..ede81f7 100644
--- a/media/blink/video_frame_compositor.cc
+++ b/media/blink/video_frame_compositor.cc
@@ -193,7 +193,7 @@
   callback_ = callback;
   task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&VideoFrameCompositor::OnRendererStateUpdate,
-                                base::Unretained(this), true));
+                                weak_ptr_factory_.GetWeakPtr(), true));
 }
 
 void VideoFrameCompositor::Stop() {
@@ -205,7 +205,7 @@
   callback_ = nullptr;
   task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&VideoFrameCompositor::OnRendererStateUpdate,
-                                base::Unretained(this), false));
+                                weak_ptr_factory_.GetWeakPtr(), false));
 }
 
 void VideoFrameCompositor::PaintSingleFrame(scoped_refptr<VideoFrame> frame,
@@ -213,8 +213,8 @@
   if (!task_runner_->BelongsToCurrentThread()) {
     task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&VideoFrameCompositor::PaintSingleFrame,
-                                  base::Unretained(this), std::move(frame),
-                                  repaint_duplicate_frame));
+                                  weak_ptr_factory_.GetWeakPtr(),
+                                  std::move(frame), repaint_duplicate_frame));
     return;
   }
   if (ProcessNewFrame(std::move(frame), repaint_duplicate_frame) &&
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 5d956d8..4cc7bd0 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -1148,7 +1148,7 @@
   // TODO(sandersd): Replace with |highest_ready_state_since_seek_| if we need
   // to ensure that preroll always gets a chance to complete.
   // See http://crbug.com/671525.
-  if (highest_ready_state_ >= ReadyState::kReadyStateHaveFutureData)
+  if (highest_ready_state_ >= ReadyState::kReadyStateHaveMetadata)
     return false;
 
   // To suspend before we reach kReadyStateHaveCurrentData is only ok
@@ -1528,19 +1528,9 @@
   // is seeking.
   UpdateBackgroundVideoOptimizationState();
 
-  // If we successfully completed a suspended startup, lie about our buffering
-  // state for the time being. While ultimately we want to avoid lying about the
-  // buffering state, for the initial test of true preload=metadata, signal
-  // BUFFERING_HAVE_ENOUGH so that canplay and canplaythrough fire correctly.
-  //
-  // Later we can experiment with the impact of removing this lie; initial data
-  // suggests high disruption since we've also made preload=metadata the
-  // default. Most sites are not prepared for a lack of canplay; even many of
-  // our own tests don't function correctly. See https://crbug.com/694855.
-  //
-  // Note: This call is dual purpose, it is also responsible for triggering an
-  // UpdatePlayState() call which may need to resume the pipeline once Blink
-  // has been told about the ReadyState change.
+  // If we successfully completed a suspended startup, we need to make a call to
+  // UpdatePlayState() in case any events which should trigger a resume have
+  // occurred during startup.
   if (attempting_suspended_start_ &&
       pipeline_controller_->IsPipelineSuspended()) {
     did_lazy_load_ = !has_poster_ && HasVideo();
@@ -1548,6 +1538,15 @@
       DCHECK(base::FeatureList::IsEnabled(kPreloadMetadataLazyLoad));
 
     skip_metrics_due_to_startup_suspend_ = true;
+
+    // If we successfully completed a suspended startup, signal that we have
+    // reached BUFFERING_HAVE_ENOUGH so that canplay and canplaythrough fire
+    // correctly. We must unfortunately always do this because it's valid for
+    // elements to play while not visible nor even in the DOM.
+    //
+    // Note: This call is dual purpose, it is also responsible for triggering an
+    // UpdatePlayState() call which may need to resume the pipeline once Blink
+    // has been told about the ReadyState change.
     OnBufferingStateChangeInternal(BUFFERING_HAVE_ENOUGH, true);
 
     // If |skip_metrics_due_to_startup_suspend_| is unset by a resume started by
@@ -1935,7 +1934,7 @@
 
 void WebMediaPlayerImpl::OnProgress() {
   DVLOG(4) << __func__;
-  if (highest_ready_state_ < ReadyState::kReadyStateHaveFutureData) {
+  if (highest_ready_state_ < ReadyState::kReadyStateHaveMetadata) {
     // Reset the preroll attempt clock.
     preroll_attempt_pending_ = true;
     preroll_attempt_start_time_ = base::TimeTicks();
@@ -2853,19 +2852,19 @@
   // errors.
   bool has_error = IsNetworkStateError(network_state_);
 
-  // After HaveFutureData, Blink will call play() if the state is not paused;
-  // prior to this point |paused_| is not accurate.
-  bool have_future_data =
-      highest_ready_state_ >= WebMediaPlayer::kReadyStateHaveFutureData;
+  // After kReadyStateHaveMetadata, Blink will call play() if the state is not
+  // paused; prior to this point |paused_| is not accurate.
+  bool have_metadata =
+      highest_ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata;
 
   // Background suspend is only enabled for paused players.
   // In the case of players with audio the session should be kept.
   bool background_suspended =
-      can_auto_suspend && is_backgrounded && paused_ && have_future_data;
+      can_auto_suspend && is_backgrounded && paused_ && have_metadata;
 
-  // Idle suspension is allowed prior to have future data since there exist
-  // mechanisms to exit the idle state when the player is capable of reaching
-  // the have future data state; see didLoadingProgress().
+  // Idle suspension is allowed prior to kReadyStateHaveMetadata since there
+  // exist mechanisms to exit the idle state when the player is capable of
+  // reaching the kReadyStateHaveMetadata state; see didLoadingProgress().
   //
   // TODO(sandersd): Make the delegate suspend idle players immediately when
   // hidden.
@@ -2873,9 +2872,10 @@
                         !overlay_enabled_ && !needs_first_frame_;
 
   // If we're already suspended, see if we can wait for user interaction. Prior
-  // to HaveFutureData, we require |is_stale| to remain suspended. |is_stale|
-  // will be cleared when we receive data which may take us to HaveFutureData.
-  bool can_stay_suspended = (is_stale || have_future_data) && is_suspended &&
+  // to kReadyStateHaveMetadata, we require |is_stale| to remain suspended.
+  // |is_stale| will be cleared when we receive data which may take us to
+  // kReadyStateHaveMetadata.
+  bool can_stay_suspended = (is_stale || have_metadata) && is_suspended &&
                             paused_ && !seeking_ && !needs_first_frame_;
 
   // Combined suspend state.
@@ -2886,8 +2886,7 @@
            << ", idle_suspended=" << idle_suspended
            << ", background_suspended=" << background_suspended
            << ", can_stay_suspended=" << can_stay_suspended
-           << ", is_stale=" << is_stale
-           << ", have_future_data=" << have_future_data
+           << ", is_stale=" << is_stale << ", have_metadata=" << have_metadata
            << ", paused_=" << paused_ << ", seeking_=" << seeking_;
 
   // We do not treat |playback_rate_| == 0 as paused. For the media session,
@@ -2905,14 +2904,14 @@
   // suspension does not destroy the media session, because we expect that the
   // notification controls (and audio focus) remain. With some exceptions for
   // background videos, the player only needs to have audio to have controls
-  // (requires |have_future_data|).
+  // (requires |have_current_data|).
   //
   // |alive| indicates if the player should be present (not |GONE|) to the
   // delegate, either paused or playing. The following must be true for the
   // player:
-  //   - |have_future_data|, since we need to know whether we are paused to
-  //     correctly configure the session and also because the tracks and
-  //     duration are passed to DidPlay(),
+  //   - |have_current_data|, since playback can't begin before that point, we
+  //     need to know whether we are paused to correctly configure the session,
+  //     and also because the tracks and duration are passed to DidPlay(),
   //   - |is_flinging| is false (RemotePlayback is not handled by the delegate)
   //   - |has_error| is false as player should have no errors,
   //   - |background_suspended| is false, otherwise |has_remote_controls| must
@@ -2927,7 +2926,8 @@
   bool backgrounded_video_has_no_remote_controls =
       IsBackgroundSuspendEnabled(this) && !IsResumeBackgroundVideosEnabled() &&
       is_backgrounded && HasVideo();
-  bool can_play = !has_error && have_future_data;
+  bool have_current_data = highest_ready_state_ >= kReadyStateHaveCurrentData;
+  bool can_play = !has_error && have_current_data;
   bool has_remote_controls =
       HasAudio() && !backgrounded_video_has_no_remote_controls;
   bool alive = can_play && !is_flinging && !must_suspend &&
diff --git a/media/blink/webmediaplayer_impl_unittest.cc b/media/blink/webmediaplayer_impl_unittest.cc
index bdbc748b..562f9ef 100644
--- a/media/blink/webmediaplayer_impl_unittest.cc
+++ b/media/blink/webmediaplayer_impl_unittest.cc
@@ -634,14 +634,14 @@
       client->DidFinishLoading();
   }
 
-  void LoadAndWaitForMetadata(std::string data_file) {
+  // This runs until we reach the |ready_state_|. Attempting to wait for ready
+  // states < kReadyStateHaveCurrentData in non-startup-suspend test cases is
+  // unreliable due to asynchronous execution of tasks on the
+  // base::test:ScopedTaskEnvironment.
+  void LoadAndWaitForReadyState(std::string data_file,
+                                blink::WebMediaPlayer::ReadyState ready_state) {
     Load(data_file);
-
-    // This runs until we reach the have current data state. Attempting to wait
-    // for states < kReadyStateHaveCurrentData is unreliable due to asynchronous
-    // execution of tasks on the base::test:ScopedTaskEnvironment.
-    while (wmpi_->GetReadyState() <
-           blink::WebMediaPlayer::kReadyStateHaveCurrentData) {
+    while (wmpi_->GetReadyState() < ready_state) {
       base::RunLoop loop;
       EXPECT_CALL(client_, ReadyStateChanged())
           .WillRepeatedly(RunClosure(loop.QuitClosure()));
@@ -654,7 +654,14 @@
     // Verify we made it through pipeline startup.
     EXPECT_TRUE(wmpi_->data_source_);
     EXPECT_TRUE(wmpi_->demuxer_);
-    EXPECT_FALSE(wmpi_->seeking_);
+
+    if (ready_state > blink::WebMediaPlayer::kReadyStateHaveCurrentData)
+      EXPECT_FALSE(wmpi_->seeking_);
+  }
+
+  void LoadAndWaitForCurrentData(std::string data_file) {
+    LoadAndWaitForReadyState(data_file,
+                             blink::WebMediaPlayer::kReadyStateHaveCurrentData);
   }
 
   void CycleThreads() {
@@ -727,12 +734,12 @@
   EXPECT_FALSE(IsSuspended());
 }
 
-// Verify LoadAndWaitForMetadata() functions without issue.
+// Verify LoadAndWaitForCurrentData() functions without issue.
 TEST_F(WebMediaPlayerImplTest, LoadAndDestroy) {
   InitializeWebMediaPlayerImpl();
   EXPECT_FALSE(IsSuspended());
   wmpi_->SetPreload(blink::WebMediaPlayer::kPreloadAuto);
-  LoadAndWaitForMetadata(kAudioOnlyTestFile);
+  LoadAndWaitForCurrentData(kAudioOnlyTestFile);
   EXPECT_FALSE(IsSuspended());
   CycleThreads();
 
@@ -743,7 +750,7 @@
   EXPECT_GT(reported_memory_ - data_source_size, 0);
 }
 
-// Verify LoadAndWaitForMetadata() functions without issue.
+// Verify LoadAndWaitForCurrentData() functions without issue.
 TEST_F(WebMediaPlayerImplTest, LoadAndDestroyDataUrl) {
   InitializeWebMediaPlayerImpl();
   EXPECT_FALSE(IsSuspended());
@@ -831,7 +838,8 @@
   InitializeWebMediaPlayerImpl();
   EXPECT_CALL(client_, CouldPlayIfEnoughData()).WillRepeatedly(Return(false));
   wmpi_->SetPreload(blink::WebMediaPlayer::kPreloadMetaData);
-  LoadAndWaitForMetadata(kAudioOnlyTestFile);
+  LoadAndWaitForReadyState(kAudioOnlyTestFile,
+                           blink::WebMediaPlayer::kReadyStateHaveMetadata);
   testing::Mock::VerifyAndClearExpectations(&client_);
   EXPECT_CALL(client_, ReadyStateChanged()).Times(AnyNumber());
   CycleThreads();
@@ -855,11 +863,9 @@
   constexpr char kLargeAudioOnlyTestFile[] = "bear_192kHz.wav";
   Load(kLargeAudioOnlyTestFile, LoadType::kStreaming);
 
-  // This runs until we reach the have current data state. Attempting to wait
-  // for states < kReadyStateHaveCurrentData is unreliable due to asynchronous
-  // execution of tasks on the base::test:ScopedTaskEnvironment.
+  // This runs until we reach the metadata state.
   while (wmpi_->GetReadyState() <
-         blink::WebMediaPlayer::kReadyStateHaveCurrentData) {
+         blink::WebMediaPlayer::kReadyStateHaveMetadata) {
     base::RunLoop loop;
     EXPECT_CALL(client_, ReadyStateChanged())
         .WillRepeatedly(RunClosure(loop.QuitClosure()));
@@ -896,7 +902,8 @@
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   }
 
-  LoadAndWaitForMetadata(kVideoOnlyTestFile);
+  LoadAndWaitForReadyState(kVideoOnlyTestFile,
+                           blink::WebMediaPlayer::kReadyStateHaveMetadata);
   testing::Mock::VerifyAndClearExpectations(&client_);
   EXPECT_CALL(client_, ReadyStateChanged()).Times(AnyNumber());
   CycleThreads();
@@ -930,7 +937,8 @@
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   }
 
-  LoadAndWaitForMetadata(kVideoOnlyTestFile);
+  LoadAndWaitForReadyState(kVideoOnlyTestFile,
+                           blink::WebMediaPlayer::kReadyStateHaveMetadata);
   testing::Mock::VerifyAndClearExpectations(&client_);
   EXPECT_CALL(client_, ReadyStateChanged()).Times(AnyNumber());
   CycleThreads();
@@ -951,7 +959,7 @@
   InitializeWebMediaPlayerImpl();
   EXPECT_CALL(client_, CouldPlayIfEnoughData()).WillRepeatedly(Return(true));
   wmpi_->SetPreload(blink::WebMediaPlayer::kPreloadMetaData);
-  LoadAndWaitForMetadata(kAudioOnlyTestFile);
+  LoadAndWaitForCurrentData(kAudioOnlyTestFile);
   testing::Mock::VerifyAndClearExpectations(&client_);
   EXPECT_CALL(client_, ReadyStateChanged()).Times(AnyNumber());
   base::RunLoop().RunUntilIdle();
@@ -1373,6 +1381,7 @@
 
 TEST_F(WebMediaPlayerImplTest, Waiting_NoDecryptionKey) {
   InitializeWebMediaPlayerImpl();
+  wmpi_->SetPreload(blink::WebMediaPlayer::kPreloadAuto);
 
   scoped_refptr<cc::Layer> layer = cc::Layer::Create();
   EXPECT_CALL(*surface_layer_bridge_ptr_, GetCcLayer())
@@ -1394,7 +1403,7 @@
 
   // Use non-encrypted file here since we don't have a CDM. Otherwise pipeline
   // initialization will stall waiting for a CDM to be set.
-  LoadAndWaitForMetadata(kVideoOnlyTestFile);
+  LoadAndWaitForCurrentData(kVideoOnlyTestFile);
 
   EXPECT_CALL(encrypted_client_, DidBlockPlaybackWaitingForKey());
   EXPECT_CALL(encrypted_client_, DidResumePlaybackBlockedForKey());
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index 432d7d1..fcc672f 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -1270,19 +1270,16 @@
 
 scoped_refptr<VASurface> VaapiWrapper::CreateVASurfaceForPixmap(
     const scoped_refptr<gfx::NativePixmap>& pixmap) {
-  // Create a VASurface for a NativePixmap by importing the underlying dmabufs.
-  VASurfaceAttribExternalBuffers va_attrib_extbuf;
-  memset(&va_attrib_extbuf, 0, sizeof(va_attrib_extbuf));
+  const gfx::BufferFormat buffer_format = pixmap->GetBufferFormat();
 
-  va_attrib_extbuf.pixel_format =
-      BufferFormatToVAFourCC(pixmap->GetBufferFormat());
-  gfx::Size size = pixmap->GetBufferSize();
+  // Create a VASurface for a NativePixmap by importing the underlying dmabufs.
+  const gfx::Size size = pixmap->GetBufferSize();
+  VASurfaceAttribExternalBuffers va_attrib_extbuf{};
+  va_attrib_extbuf.pixel_format = BufferFormatToVAFourCC(buffer_format);
   va_attrib_extbuf.width = size.width();
   va_attrib_extbuf.height = size.height();
 
-  size_t num_planes =
-      gfx::NumberOfPlanesForBufferFormat(pixmap->GetBufferFormat());
-  std::vector<uintptr_t> fds(num_planes);
+  const size_t num_planes = gfx::NumberOfPlanesForBufferFormat(buffer_format);
   for (size_t i = 0; i < num_planes; ++i) {
     va_attrib_extbuf.pitches[i] = pixmap->GetDmaBufPitch(i);
     va_attrib_extbuf.offsets[i] = pixmap->GetDmaBufOffset(i);
@@ -1301,8 +1298,8 @@
   va_attrib_extbuf.buffers = &fd;
   va_attrib_extbuf.num_buffers = 1u;
 
-  va_attrib_extbuf.flags = 0;
-  va_attrib_extbuf.private_data = NULL;
+  DCHECK_EQ(va_attrib_extbuf.flags, 0u);
+  DCHECK_EQ(va_attrib_extbuf.private_data, nullptr);
 
   std::vector<VASurfaceAttrib> va_attribs(2);
 
@@ -1316,8 +1313,7 @@
   va_attribs[1].value.type = VAGenericValueTypePointer;
   va_attribs[1].value.value.p = &va_attrib_extbuf;
 
-  const unsigned int va_format =
-      BufferFormatToVARTFormat(pixmap->GetBufferFormat());
+  const unsigned int va_format = BufferFormatToVARTFormat(buffer_format);
 
   VASurfaceID va_surface_id = VA_INVALID_ID;
   {
@@ -1328,11 +1324,8 @@
     VA_SUCCESS_OR_RETURN(va_res, "Failed to create unowned VASurface", nullptr);
   }
 
-  // It's safe to use Unretained() here, because the caller takes care of the
-  // destruction order. All the surfaces will be destroyed before VaapiWrapper.
-  return new VASurface(
-      va_surface_id, size, va_format,
-      base::Bind(&VaapiWrapper::DestroySurface, base::Unretained(this)));
+  return new VASurface(va_surface_id, size, va_format,
+                       base::BindOnce(&VaapiWrapper::DestroySurface, this));
 }
 
 bool VaapiWrapper::SubmitBuffer(VABufferType va_buffer_type,
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index b7dbe0e..3eb3b9d1 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -441,7 +441,9 @@
 void ClientSession::OnLocalPointerMoved(const webrtc::DesktopVector& position,
                                         ui::EventType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  remote_input_filter_.LocalPointerMoved(position, type);
+  bool is_local = remote_input_filter_.LocalPointerMoved(position, type);
+  if (is_local && desktop_environment_options_.terminate_upon_input())
+    DisconnectSession(protocol::OK);
 }
 
 void ClientSession::SetDisableInputs(bool disable_inputs) {
diff --git a/remoting/host/client_session_unittest.cc b/remoting/host/client_session_unittest.cc
index d98940d0..43d9e7a3 100644
--- a/remoting/host/client_session_unittest.cc
+++ b/remoting/host/client_session_unittest.cc
@@ -186,6 +186,8 @@
   protocol::FakeConnectionToClient* connection_;
 
   std::unique_ptr<FakeDesktopEnvironmentFactory> desktop_environment_factory_;
+
+  DesktopEnvironmentOptions desktop_environment_options_;
 };
 
 void ClientSessionTest::SetUp() {
@@ -195,11 +197,13 @@
 
   desktop_environment_factory_.reset(
       new FakeDesktopEnvironmentFactory(message_loop_.task_runner()));
+  desktop_environment_options_ = DesktopEnvironmentOptions::CreateDefault();
 }
 
 void ClientSessionTest::TearDown() {
   if (client_session_) {
-    client_session_->DisconnectSession(protocol::OK);
+    if (connection_->is_connected())
+      client_session_->DisconnectSession(protocol::OK);
     client_session_.reset();
     desktop_environment_factory_.reset();
   }
@@ -223,9 +227,8 @@
 
   client_session_.reset(new ClientSession(
       &session_event_handler_, std::move(connection),
-      desktop_environment_factory_.get(),
-      DesktopEnvironmentOptions::CreateDefault(), base::TimeDelta(), nullptr,
-      extensions_));
+      desktop_environment_factory_.get(), desktop_environment_options_,
+      base::TimeDelta(), nullptr, extensions_));
 }
 
 void ClientSessionTest::CreateClientSession() {
@@ -470,10 +473,24 @@
   EXPECT_THAT(mouse_events[0], EqualsMouseMoveEvent(100, 101));
   EXPECT_THAT(mouse_events[1], EqualsMouseMoveEvent(200, 201));
 
+  // Verify that we're still connected.
+  EXPECT_TRUE(connection_->is_connected());
+
   // TODO(jamiewalch): Verify that remote inputs are re-enabled
   // eventually (via dependency injection, not sleep!)
 }
 
+TEST_F(ClientSessionTest, DisconnectOnLocalInputTest) {
+  desktop_environment_options_.set_terminate_upon_input(true);
+  CreateClientSession();
+  ConnectClientSession();
+  SetupSingleDisplay();
+
+  client_session_->OnLocalPointerMoved(webrtc::DesktopVector(100, 101),
+                                       ui::ET_MOUSE_MOVED);
+  EXPECT_FALSE(connection_->is_connected());
+}
+
 TEST_F(ClientSessionTest, RestoreEventState) {
   CreateClientSession();
   ConnectClientSession();
diff --git a/remoting/host/desktop_environment_options.cc b/remoting/host/desktop_environment_options.cc
index 5eef6c9..0b08dfa 100644
--- a/remoting/host/desktop_environment_options.cc
+++ b/remoting/host/desktop_environment_options.cc
@@ -79,6 +79,14 @@
   enable_user_interface_ = enabled;
 }
 
+bool DesktopEnvironmentOptions::terminate_upon_input() const {
+  return terminate_upon_input_;
+}
+
+void DesktopEnvironmentOptions::set_terminate_upon_input(bool enabled) {
+  terminate_upon_input_ = enabled;
+}
+
 bool DesktopEnvironmentOptions::enable_file_transfer() const {
   return enable_file_transfer_;
 }
diff --git a/remoting/host/desktop_environment_options.h b/remoting/host/desktop_environment_options.h
index ab978ee..bc12703 100644
--- a/remoting/host/desktop_environment_options.h
+++ b/remoting/host/desktop_environment_options.h
@@ -36,6 +36,9 @@
   bool enable_user_interface() const;
   void set_enable_user_interface(bool enabled);
 
+  bool terminate_upon_input() const;
+  void set_terminate_upon_input(bool enabled);
+
   bool enable_file_transfer() const;
   void set_enable_file_transfer(bool enabled);
 
@@ -56,6 +59,9 @@
   // True if a user-interactive window is showing up in it2me scenario.
   bool enable_user_interface_ = true;
 
+  // True if the session should be terminated when local input is detected.
+  bool terminate_upon_input_ = false;
+
   // True if this host has file transfer enabled.
   bool enable_file_transfer_ = false;
 
diff --git a/remoting/host/it2me/it2me_host.cc b/remoting/host/it2me/it2me_host.cc
index 4b93df3f..959e407 100644
--- a/remoting/host/it2me/it2me_host.cc
+++ b/remoting/host/it2me/it2me_host.cc
@@ -73,6 +73,15 @@
 #endif
 }
 
+void It2MeHost::set_terminate_upon_input(bool terminate_upon_input) {
+#if defined(OS_CHROMEOS) || !defined(NDEBUG)
+  terminate_upon_input_ = terminate_upon_input;
+#else
+  NOTREACHED()
+      << "It2MeHost::set_terminate_upon_input is only supported on ChromeOS";
+#endif
+}
+
 void It2MeHost::Connect(
     std::unique_ptr<ChromotingHostContext> host_context,
     std::unique_ptr<base::DictionaryValue> policies,
@@ -190,6 +199,7 @@
   // Create the host.
   DesktopEnvironmentOptions options(DesktopEnvironmentOptions::CreateDefault());
   options.set_enable_user_interface(enable_dialogs_);
+  options.set_terminate_upon_input(terminate_upon_input_);
   host_.reset(new ChromotingHost(
       desktop_environment_factory_.get(), std::move(session_manager),
       transport_context, host_context_->audio_task_runner(),
diff --git a/remoting/host/it2me/it2me_host.h b/remoting/host/it2me/it2me_host.h
index c16ccaf..420a0e1 100644
--- a/remoting/host/it2me/it2me_host.h
+++ b/remoting/host/it2me/it2me_host.h
@@ -74,6 +74,10 @@
   void set_enable_dialogs(bool enable);
   bool enable_dialogs() const { return enable_dialogs_; }
 
+  // Enable or disable whether or not the session should be terminated if local
+  // input is detected.
+  void set_terminate_upon_input(bool terminate_upon_input);
+
   // Methods called by the script object, from the plugin thread.
 
   // Creates It2Me host structures and starts the host.
@@ -192,6 +196,7 @@
   std::string connecting_jid_;
 
   bool enable_dialogs_ = true;
+  bool terminate_upon_input_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(It2MeHost);
 };
diff --git a/remoting/host/it2me/it2me_native_messaging_host.cc b/remoting/host/it2me/it2me_native_messaging_host.cc
index 382520d..6022a64 100644
--- a/remoting/host/it2me/it2me_native_messaging_host.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host.cc
@@ -270,6 +270,9 @@
   bool no_dialogs = false;
   message->GetBoolean("noDialogs", &no_dialogs);
 
+  bool terminate_upon_input = false;
+  message->GetBoolean("terminateUponInput", &terminate_upon_input);
+
   std::string directory_bot_jid =
       ServiceUrls::GetInstance()->directory_bot_jid();
 
@@ -327,6 +330,7 @@
   it2me_host_ = factory_->CreateIt2MeHost();
 #if defined(OS_CHROMEOS) || !defined(NDEBUG)
   it2me_host_->set_enable_dialogs(!no_dialogs);
+  it2me_host_->set_terminate_upon_input(terminate_upon_input);
 #endif
   it2me_host_->Connect(host_context_->Copy(), std::move(policies),
                        std::make_unique<It2MeConfirmationDialogFactory>(),
diff --git a/remoting/host/remote_input_filter.cc b/remoting/host/remote_input_filter.cc
index 4eface7..388ce13 100644
--- a/remoting/host/remote_input_filter.cc
+++ b/remoting/host/remote_input_filter.cc
@@ -32,7 +32,7 @@
 
 RemoteInputFilter::~RemoteInputFilter() = default;
 
-void RemoteInputFilter::LocalPointerMoved(const webrtc::DesktopVector& pos,
+bool RemoteInputFilter::LocalPointerMoved(const webrtc::DesktopVector& pos,
                                           ui::EventType type) {
   // If this is a genuine local input event (rather than an echo of a remote
   // input event that we've just injected), then ignore remote inputs for a
@@ -57,13 +57,14 @@
       // These spurious positions should therefore be discarded.
       injected_mouse_positions_.erase(injected_mouse_positions_.begin(),
                                       ++found_position);
-      return;
+      return false;
     }
   }
 
   // Release all pressed buttons or keys, disable inputs, and note the time.
   event_tracker_->ReleaseAll();
   latest_local_input_time_ = base::TimeTicks::Now();
+  return true;
 }
 
 void RemoteInputFilter::SetExpectLocalEcho(bool expect_local_echo) {
diff --git a/remoting/host/remote_input_filter.h b/remoting/host/remote_input_filter.h
index 13a22f4..99592b2 100644
--- a/remoting/host/remote_input_filter.h
+++ b/remoting/host/remote_input_filter.h
@@ -27,8 +27,9 @@
 
   // Informs the filter that local mouse or touch activity has been detected.
   // If the activity does not match events we injected then we assume that it
-  // is local, and block remote input for a short while.
-  void LocalPointerMoved(const webrtc::DesktopVector& pos, ui::EventType type);
+  // is local, and block remote input for a short while. Returns true if the
+  // input was local, or false if it was rejected as an echo.
+  bool LocalPointerMoved(const webrtc::DesktopVector& pos, ui::EventType type);
 
   // Informs the filter that injecting input causes an echo.
   void SetExpectLocalEcho(bool expect_local_echo);
diff --git a/services/service_manager/sandbox/mac/gpu_v2.sb b/services/service_manager/sandbox/mac/gpu_v2.sb
index c0bf7e9..aeca837 100644
--- a/services/service_manager/sandbox/mac/gpu_v2.sb
+++ b/services/service_manager/sandbox/mac/gpu_v2.sb
@@ -11,6 +11,7 @@
 
 ; Allow communication between the GPU process and the UI server.
 (allow mach-lookup
+  (global-name "com.apple.cfprefsd.daemon")
   (global-name "com.apple.CoreServices.coreservicesd")
   (global-name "com.apple.coreservices.launchservicesd")
   (global-name "com.apple.cvmsServ")
diff --git a/services/service_manager/sandbox/win/sandbox_win.cc b/services/service_manager/sandbox/win/sandbox_win.cc
index 7f4b3a6..779a153 100644
--- a/services/service_manager/sandbox/win/sandbox_win.cc
+++ b/services/service_manager/sandbox/win/sandbox_win.cc
@@ -71,6 +71,7 @@
     L"airfoilinject3.dll",         // Airfoil.
     L"akinsofthook32.dll",         // Akinsoft Software Engineering.
     L"assistant_x64.dll",          // Unknown.
+    L"atcuf64.dll",                // Bit Defender Internet Security x64.
     L"avcuf64.dll",                // Bit Defender Internet Security x64.
     L"avgrsstx.dll",               // AVG 8.
     L"babylonchromepi.dll",        // Babylon translator.
diff --git a/styleguide/c++/c++-dos-and-donts.md b/styleguide/c++/c++-dos-and-donts.md
index 9f5f4bf..2e342677 100644
--- a/styleguide/c++/c++-dos-and-donts.md
+++ b/styleguide/c++/c++-dos-and-donts.md
@@ -6,245 +6,17 @@
 can always deviate from something on this page, if the relevant
 author/reviewer/OWNERS agree that another course is better.
 
-
 ## Minimize Code in Headers
 
-### Don't include unneeded headers
-
-If a file isn't using the symbols from some header, remove the header. It turns
-out that this happens frequently in the Chromium codebase due to refactoring.
-
-### Move helper / inner classes into the implementation
-
-You can also forward declare classes inside a class:
-
-```cpp
-class Whatever {
- public:
-  /* ... */
- private:
-  struct DataStruct;
-  std::vector<DataStruct> data_;
-};
-```
-
-Any headers that DataStruct needed no longer need to be included in the header
-file and only need to be included in the implementation. This will often let you
-pull includes out of the header. For reference, the syntax in the implementation
-file is:
-
-```cpp
-struct Whatever::DataStruct {
-};
-```
-
-Note that sometimes you can't do this because certain STL data structures
-require the full definition at declaration time (most notably, std::deque and
-the STL adapters that wrap it).
-
-## Stop inlining code in headers
-
-*** note
-**BACKGROUND**: Unless something is a cheap accessor or you truly need it to be
-inlined, don't ask for it to be inlined.  Remember that definitions inside class
-declarations are implicitly requested to be inlined.
-***
-
-DON'T:
-
-```cpp
-class InlinedMethods {
-  InlinedMethods() {
-    // This constructor is equivalent to having the inline keyword in front
-    // of it!
-  }
-  void Method() {
-    // Same here!
-  }
-};
-```
-
-### Stop inlining complex methods
-
-DON'T:
-
-```cpp
-class DontDoThis {
- public:
-  int ComputeSomething() {
-    int sum = 0;
-    for (int i = 0; i < limit; ++i) {
-      sum += OtherMethod(i, ... );
-    }
-    return sum;
-  }
-};
-```
-
-A request to inline is merely a suggestion to the compiler, and anything more
-than a few operations on integral data types will probably not be inlined.
-However, every file that has to use an inline method will also emit a function
-version in the resulting .o, even if the method was inlined. (This is to support
-function pointer behavior properly.) Therefore, by requesting an inline in this
-case, you're likely just adding crud to the .o files which the linker will need
-to do work to resolve.
-
-If the method has significant implementation, there's also a good chance that by
-not inlining it, you could eliminate some includes.
-
-### Stop inlining virtual methods
-
-You can't inline virtual methods under most circumstances, even if the method
-would otherwise be inlined because it's very short. The compiler must do runtime
-dispatch on any virtual method where the compiler doesn't know the object's
-complete type, which rules out the majority of cases where you have an object.
-
-### Stop inlining constructors and destructors
-
-Constructors and destructors are often significantly more complex than you think
-they are, especially if your class has any non-POD data members. Many STL
-classes have inlined constructors/destructors which may be copied into your
-function body. Because the bodies of these appear to be empty, they often seem
-like trivial functions that can safely be inlined.  Don't give in to this
-temptation.  Define them in the implementation file unless you really _need_
-them to be inlined.  Even if they do nothing now, someone could later add
-something seemingly-trivial to the class and make your hundreds of inlined
-destructors much more complex.
-
-Even worse, inlining constructors/destructors prevents you from using forward
-declared variables.
-
-DON'T:
-
-```cpp
-class Forward;
-class WontCompile {
- public:
-   // THIS WON'T COMPILE, BUT IT WOULD HAVE IF WE PUT THESE IN THE
-   // IMPLEMENTATION FILE!
-   //
-   // The compiler needs the definition of Forward to call the
-   // vector/scoped_ptr ctors/dtors.
-   Example() { }
-   ~Example() { }
-
- private:
-  std::vector<Forward> examples_;
-  scoped_ptr<Forward> super_example_;
-};
-```
-
-For more information, read Item 30 in Effective C++.
-
-### When you CAN inline constructors and destructors
-
-C++ has the concept of a
-[trivial destructor](http://publib.boulder.ibm.com/infocenter/macxhelp/v6v81/index.jsp?topic=/com.ibm.vacpp6m.doc/language/ref/clrc15cplr380.htm).
-If your class has only POD types and does not explicitly declare a destructor,
-then the compiler will not bother to generate or run a destructor.
-
-```cpp
-struct Data {
-  Data() : count_one(0), count_two(0) {}
-  // No explicit destructor, thus no implicit destructor either.
-
-  // The members must all be POD for this trick to work.
-  int count_one;
-  int count_two;
-};
-```
-
-In this example, since there is no inheritance and only a few POD members, the
-constructor will be only a few trivial integer operations, and thus OK to
-inline.
-
-For abstract base classes with no members, it's safe to define the (trivial)
-destructor inline:
-
-```cpp
-class Interface {
- public:
-  virtual ~Interface() {}
-
-  virtual void DoSomething(int parameter) = 0;
-  virtual int GetAValue() = 0;
-};
-```
-But be careful; these two "interfaces" don't count.
-
-DON'T:
-
-```cpp
-class ClaimsToBeAnInterface : public base::RefCounted<ClaimsToBeAnInterface> {
- public:
-  virtual ~ClaimsToBeAnInterface() { /* But derives from a template! */ }
-};
-
-class HasARealMember {
- public:
-  virtual void InterfaceMethod() = 0;
-  virtual ~HasARealMember() {}
-
- protected:
-  vector<string> some_data_;
-};
-```
-
-If in doubt, don't rely on these sorts of exceptions.  Err on the side of not
-inlining.
-
-### Be careful about your accessors
-
-Not all accessors are light weight. Compare:
-
-```cpp
-class Foo {
- public:
-  int count() const { return count_; }
-
- private:
-  int count_;
-};
-```
-
-Here the accessor is trivial and safe to inline.  But the following code is
-probably not, even though it also looks simple.
-
-DON'T:
-
-```cpp
-struct MyData {
-  vector<GURL> urls_;
-  base::Time last_access_;
-};
-
-class Manager {
- public:
-  MyData get_data() { return my_data_; }
-
- private:
-  MyData my_data_;
-};
-```
-
-The underlying copy constructor calls for MyData are going to be complex. (Also,
-they're going to be synthesized, which is bad.)
-
-### What about code outside of headers?
-
-For classes declared in .cc files, there's no risk of bloating several .o files
-with the definitions of the same "inlined" function.  While there are other,
-weaker arguments to continue to avoid inlining, the primary remaining
-consideration is simply what would make code most readable.
-
-This is especially true in testing code.  Test framework classes don't tend to
-be instantiated separately and passed around as objects; they're effectively
-just bundles of translation-unit-scope functionality coupled with a mechanism
-to reset state between tests.  In these cases, defining the test functions
-inline at their declaration sites has little negative effect and can reduce the
-amount of "boilerplate" in the test file.
-
-Different reviewers may have different opinions here; use good judgment.
+* Remove #includes you don't use.  Unfortunately, Chromium lacks
+  include-what-you-use ("IWYU") support, so there's no tooling to do this
+  automatically.  Look carefully when refactoring.
+* Where possible, forward-declare nested classes, then give the full declaration
+  (and definition) in the .cc file.
+* Defining a class method in the declaration is an implicit request to inline
+  it.  Avoid this in header files except for cheap non-virtual getters and
+  setters.  Note that constructors and destructors can be more expensive than
+  they appear and should also generally not be inlined.
 
 ## Static variables
 
@@ -258,10 +30,10 @@
 
 ```cpp
 void foo() {
-    static int ok_count = ComputeTheCount();  // OK; a problem pre-2017.
-    static int good_count = 42;  // C++03 3.6.2 says this is done before dynamic initialization, so probably thread-safe.
-    static constexpr int better_count = 42;  // Even better, as this will likely be inlined at compile time.
-    static auto& object = *new Object;  // For class types.
+  static int ok_count = ComputeTheCount();  // OK; a problem pre-2017.
+  static int good_count = 42;               // Done before dynamic initialization.
+  static constexpr int better_count = 42;   // Even better (likely inlined at compile time).
+  static auto& object = *new Object;        // For class types.
 }
 ```
 
@@ -269,7 +41,6 @@
 
 There are myriad ways to initialize variables in C++11.  Prefer the following
 general rules:
-
 1. Use assignment syntax when performing "simple" initialization with one or
    more literal values which will simply be composed into the object:
 
@@ -286,7 +57,6 @@
    happening.  Note that "{}" are allowed on the right side of the '=' here
    (e.g. when you're merely passing a set of initial values to a "simple"
    struct/   container constructor; see below items for contrast).
-
 2. Use constructor syntax when construction performs significant logic, uses an
    explicit constructor, or in some other way is not intuitively "simple" to the
    reader:
@@ -295,7 +65,6 @@
    MyClass c(1.7, false, "test");
    std::vector<double> v(500, 0.97);  // Creates 50 copies of the provided initializer
    ```
-
 3. Use C++11 "uniform init" syntax ("{}" without '=') only when neither of the
    above work:
 
@@ -321,7 +90,6 @@
      ...
    }
    ```
-
 4. Never mix uniform init syntax with auto, since what it deduces is unlikely
    to be what was intended:
 
@@ -357,17 +125,12 @@
 }
 ```
 
-In exchange for a longer definition, callers can now refer to a named type and
-named fields, and returning an instance of the struct is still (usually) terse.
-
 A good rule of thumb for when to prefer a struct is whenever you'd find
 declaring a type alias for the pair or tuple beneficial, which is usually
 whenever it's used more than just as a local one-off.
 
 ## Use `std::make_unique` and `base::MakeRefCounted` instead of bare `new`
 
-Most Chromium contributors already understand the benefits of
-[using smart pointers to indicate ownership](http://google.github.io/styleguide/cppguide.html#Ownership_and_Smart_Pointers).
 When possible, avoid bare `new` by using
 [`std::make_unique<T>(...)`](http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique)
 and
@@ -429,7 +192,6 @@
      // ...
    };
    ```
-
 2. `base::WrapUnique(new Foo)` and `base::WrapUnique(new Foo())` mean something
    different if `Foo` does not have a user-defined constructor. Don't make
    future maintainers guess whether you left off the '()' on purpose. Use
@@ -448,10 +210,8 @@
 
 ## Do not use `auto` to deduce a raw pointer
 
-The use of the `auto` keyword to deduce the type from the initializing
-expression is encouraged when it improves readability. However, do not use
-`auto` when the type would be deduced to be a pointer type. This can cause
-confusion. Instead, prefer specifying the "pointer" part outside of `auto`:
+Do not use `auto` when the type would be deduced to be a pointer type; this can
+cause confusion. Instead, specify the "pointer" part outside of `auto`:
 
 ```cpp
 auto item = new Item();  // BAD: auto deduces to Item*, type of |item| is Item*
@@ -460,159 +220,12 @@
 
 ## Use `const` correctly
 
-*** promo
-**TLDR:** For safety and simplicity, **don't return pointers or references to
-non-const objects from const methods**. Within that constraint, **mark methods
-as const where possible**.  **Avoid `const_cast` to remove const**, except when
+For safety and simplicity, **don't return pointers or references to non-const
+objects from const methods**. Within that constraint, **mark methods as const
+where possible**.  **Avoid `const_cast` to remove const**, except when
 implementing non-const getters in terms of const getters.
-***
 
-### A brief primer on const
-
-To the compiler, the `const` qualifier on a method refers to _physical
-constness_: calling this method does not change the bits in this object.  What
-we want is _logical constness_, which is only partly overlapping: calling this
-method does not affect the object in ways callers will notice, nor does it give
-you a handle with the ability to do so.
-
-Mismatches between these concepts can occur in both directions.  When something
-is logically but not physically const, C++ provides the `mutable` keyword to
-silence compiler complaints.  This is valuable for e.g. cached calculations,
-where the cache is an implementation detail callers do not care about.  When
-something is physically but not logically const, however, the compiler will
-happily accept it, and there are no tools that will automatically save you.
-This discrepancy usually involves pointers.  For example,
-
-```cpp
-void T::Cleanup() const { delete pointer_member_; }
-```
-
-Deleting a member is a change callers are likely to care about, so this is
-probably not logically const.  But because `delete` does not affect the pointer
-itself, but only the memory it points to, this code is physically const, so it
-will compile.
-
-Or, more subtly, consider this pseudocode from a node in a tree:
-
-```cpp
-class Node {
- public:
-  void RemoveSelf() { parent_->RemoveChild(this); }
-  void RemoveChild(Node* node) {
-    if (node == left_child_)
-      left_child_ = nullptr;
-    else if (node == right_child_)
-      right_child_ = nullptr;
-  }
-  Node* left_child() const { return left_child_; }
-  Node* right_child() const { return right_child_; }
-
- private:
-  Node* parent_;
-  Node* left_child_;
-  Node* right_child_;
-};
-```
-
-The `left_child()` and `right_child()` methods don't change anything about
-`|this|`, so making them `const` seems fine.  But they allow code like this:
-
-```cpp
-void SignatureAppearsHarmlessToCallers(const Node& node) {
-  node.left_child()->RemoveSelf();
-  // Now |node| has no |left_child_|, despite having been passed in by const ref.
-}
-```
-
-The original class definition compiles, and looks locally fine, but it's a
-timebomb: a const method returning a handle that can be used to change the
-system in ways that affect the original object.  Eventually, someone will
-actually modify the object, potentially far away from where the handle is
-obtained.
-
-These modifications can be difficult to spot in practice.  As we see in the
-previous example, splitting related concepts or state (like "a tree") across
-several objects means a change to one object affects the behavior of others.
-And if this tree is in turn referred to by yet more objects (e.g. the DOM of a
-web page, which influences all sorts of other data structures relating to the
-page), then small changes can have visible ripples across the entire system.  In
-a codebase as complex as Chromium, it can be almost impossible to reason about
-what sorts of local changes could ultimately impact the behavior of distant
-objects, and vice versa.
-
-"Logically const correct" code assures readers that const methods will not
-change the system, directly or indirectly, nor allow callers to easily do so.
-They make it easier to reason about large-scale behavior.  But since the
-compiler verifies physical constness, it will not guarantee that code is
-actually logically const.  Hence the recommendations here.
-
-### Classes of const (in)correctness
-
-In a
-[larger discussion of this issue](https://groups.google.com/a/chromium.org/d/topic/platform-architecture-dev/C2Szi07dyQo/discussion),
-Matt Giuca
-[postulated three classes of const(in)correctness](https://groups.google.com/a/chromium.org/d/msg/platform-architecture-dev/C2Szi07dyQo/lbHMUQHMAgAJ):
-
-* **Const correct:** All code marked "const" is logically const; all code that
-  is logically const is marked "const".
-* **Const okay:** All code marked "const" is logically const, but not all code
-  that is logically const is marked "const".  (Basically, if you see "const" you
-  can trust it, but sometimes it's missing.)
-* **Const broken:** Some code marked "const" is not logically const.
-
-The Chromium codebase currently varies. A significant amount of Blink code is
-"const broken". A great deal of Chromium code is "const okay". A minority of
-code is "const correct".
-
-While "const correct" is ideal, it can take a great deal of work to achieve.
-Const (in)correctness is viral, so fixing one API often requires a yak shave.
-(On the plus side, this same property helps prevent regressions when people
-actually use const objects to access the const APIs.)
-
-At the least, strive to convert code that is "const broken" to be "const okay".
-A simple rule of thumb that will prevent most cases of "const brokenness" is for
-const methods to never return pointers to non-const objects.  This is overly
-conservative, but less than you might think, due to how objects can transitively
-affect distant, seemingly-unrelated parts of the system.  The discussion thread
-linked above has more detail, but in short, it's hard for readers and reviewers
-to prove that returning pointers-to-non-const is truly safe, and will stay safe
-through later refactorings and modifications.  Following this rule is easier
-than debating about whether individual cases are exceptions.
-
-One way to ensure code is "const okay" would be to never mark anything const.
-This is suboptimal for the same reason we don't choose to "never write comments,
-so they can never be wrong".  Marking a method "const" provides the reader
-useful information about the system behavior.  Also, despite physical constness
-being different than logical constness, using "const" correctly still does catch
-certain classes of errors at compile time. Accordingly, the
-[Google style guide requests the use of const where possible](http://google.github.io/styleguide/cppguide.html#Use_of_const),
-so mark methods const when they are logically const.
-
-Making code more const correct leads to cases where duplicate const and non-const getters are required:
-
-```cpp
-const T* Foo::GetT() const { return t_; }
-T* Foo::GetT() { return t_; }
-```
-
-If the implementation of GetT() is complex, there's a
-[trick to implement the non-const getter in terms of the const one](https://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func/123995#123995),
-courtesy of _Effective C++_:
-
-```cpp
-T* Foo::GetT() { return const_cast<T*>(static_cast<const Foo*>(this)->GetT()); }
-```
-
-While this is a mouthful, it does guarantee the implementations won't get out of
-sync and no const-incorrectness will occur. And once you've seen it a few times,
-it's a recognizable pattern.
-
-This is probably the only case where you should see `const_cast` used to remove
-constness.  Its use anywhere else is generally indicative of either "const
-broken" code, or a boundary between "const correct" and "const okay" code that
-could change to "const broken" at any future time without warning from the
-compiler.  Both cases should be fixed.
-
+For more information, see [Using Const Correctly](const.md).
 
 ## Prefer to use `=default`
 
@@ -640,23 +253,9 @@
 Good::Good() = default;
 ```
 
-### What are the advantages of `=default`?
-
-* Compiler-defined copy and move operations don't need maintenance every time
-  members are added or removed.
-* Compiler-provided special member functions can be "trivial" (if defaulted in
-  the class), and can be better optimized by the compiler and library.
-* Types with defaulted constructors can be aggregates (if defaulted in the
-  class), and hence support aggregate initialization. User provided constructors
-  disqualify a class from being an aggregate.
-* Defaulted functions are constexpr if the implicit version would have been (and
-  if defaulted in the class).
-* Using `=default` consistently helps readers identify customized operations.
-
 ## Comment style
 
 The common ways to represent names in comments are as follows:
-
 * Class and type names: `FooClass`
 * Function name: `FooFunction()`. The trailing parens disambiguate against
   class names, and, occasionally, English words.
diff --git a/styleguide/c++/c++.md b/styleguide/c++/c++.md
index 09b10ae0..6b4aaf1 100644
--- a/styleguide/c++/c++.md
+++ b/styleguide/c++/c++.md
@@ -35,43 +35,13 @@
     with the `ForTesting` suffix. This is checked at presubmit time to ensure
     these functions are only called by test files.
 
-  * Test-only constructors cannot have the `ForTesting` suffix. Instead, they
-    should be declared protected with a test-only subclass, or private with a
-    test-only friend class. They should be commented as `For testing only`.
-
-  * Test-only free functions should generally live within a test_support
-    target.
-
-  * `#if defined(UNIT_TEST)` is problematic and discouraged.
-
 ## Code formatting
 
   * Put `*` and `&` by the type rather than the variable name.
-
-  * When you derive from a base class, group any overriding functions in your
-    header file in one labeled section. Use the override specifier on all these
-    functions.
-
+  * In class declarations, group function overrides together within each access
+    control section, with one labeled group per parent class.
   * Prefer `(foo == 0)` to `(0 == foo)`.
 
-  * Prefer putting delegate classes in their own header files. Implementors of
-    the delegate interface will often be included elsewhere, which will often
-    cause more coupling with the header of the main class.
-
-  * Don't use else after return. So use:
-    ```c++
-      if (foo)
-        return 1;
-      return 2;
-    ```
-    instead of:
-    ```c++
-      if (foo)
-        return 1;
-      else
-        return 2;
-    ```
-
 ## Unnamed namespaces
 
 Items local to a .cc file should be wrapped in an unnamed namespace. While some
@@ -82,13 +52,10 @@
 
 ## Exporting symbols
 
-When building shared libraries and DLLs, we need to indicate which functions
-and classes should be visible outside of the library, and which should only be
-visible inside the library.
-
-Symbols can be exported by annotating with a `<COMPONENT>_EXPORT` macro name
-(where `<COMPONENT>` is the name of the component being built, e.g. BASE, NET,
-CONTENT, etc.). Class annotations should precede the class name:
+Symbols can be exported (made visible outside of a shared library/DLL) by
+annotating with a `<COMPONENT>_EXPORT` macro name (where `<COMPONENT>` is the
+name of the component being built, e.g. BASE, NET, CONTENT, etc.). Class
+annotations should precede the class name:
 ```c++
 class FOO_EXPORT Foo {
   void Bar();
@@ -102,19 +69,10 @@
 class FooSingleton {
   FOO_EXPORT Foo& GetFoo();
   FOO_EXPORT Foo& SetFooForTesting(Foo* foo);
-  void SetFoo(Foo* foo);
+  void SetFoo(Foo* foo);  // Not exported.
 };
 ```
 
-These examples result in `Foo::Bar()`, `Foo::Baz()`, `FooSingleton::GetFoo()`,
-and `FooSingleton::SetFooForTesting()` all being available outside of the DLL,
-but not `FooSingleton::SetFoo()`.
-
-Whether something is exported is distinct from whether it is public or private,
-or even whether it would normally be considered part of the external API. For
-example, if part of the external API is an inlined function that calls a
-private function, that private function must be exported as well.
-
 ## Multiple inheritance
 
 Multiple inheritance and virtual inheritance are permitted in Chromium code,
@@ -125,9 +83,7 @@
 ## Inline functions
 
 Simple accessors should generally be the only inline functions. These should be
-named `unix_hacker_style()`. Virtual functions should never be declared this way.
-For more detail, consult the [C++ Dos and Don'ts](c++-dos-and-donts.md) section
-on inlining.
+named using `snake_case()`. Virtual functions should never be declared this way.
 
 ## Logging
 
@@ -140,10 +96,8 @@
 prefer `DVLOG(1)` to other logging methods. This avoids bloating the release
 executable and in debug can be selectively enabled at runtime by command-line
 arguments:
-
-  * `--v=n` sets the global log level to n (default 0). All log statements with a
-    log level less than or equal to the global level will be printed.
-
+  * `--v=n` sets the global log level to n (default 0). All log statements with
+    a log level less than or equal to the global level will be printed.
   * `--vmodule=mod=n[,mod=n,...]` overrides the global log level for the module
     mod. Supplying the string foo for mod will affect all files named foo.cc,
     while supplying a wildcard like `*bar/baz*` will affect all files with
@@ -178,46 +132,27 @@
 ## Types
 
   * Use `size_t` for object and allocation sizes, object counts, array and
-    pointer offsets, vector indices, and so on. The signed types are
-    incorrect and unsafe for these purposes (e.g. integer overflow behavior for
-    signed types is undefined in the C and C++ standards, while the behavior is
-    defined for unsigned types). The C++ STL and libc are good guides here: they
-    use `size_t` and `foo::size_type` for very good reasons.
-
-  * Use `size_t` directly in preference to `std::string::size_type` and similar.
-
-  * Occasionally classes may have a good reason to use a type other than `size_t`
-    for one of these concepts, e.g. as a storage space optimization. In these
-    cases, continue to use `size_t` in public-facing function declarations,
-    and continue to use unsigned types internally (e.g. `uint32_t`).
-
-  * Be aware that `size_t` (object sizes and indices), `off_t` (file offsets),
-    `ptrdiff_t` (the difference between two pointer values), `intptr_t` (an
-    integer type large enough to hold the value of a pointer), `uint32_t`,
-    `uint64_t`, and so on are not necessarily the same. Use the right type for
-    your purpose. `CheckedNumeric` is an ergonomic way to perform safe
-    arithmetic and casting with and between these different types.
-
+    pointer offsets, vector indices, and so on. This prevents casts when
+    dealing with STL APIs, and if followed consistently across the codebase,
+    minimizes casts elsewhere.
+  * Occasionally classes may have a good reason to use a type other than
+    `size_t` for one of these concepts, e.g. as a storage space optimization. In
+    these cases, continue to use `size_t` in public-facing function
+    declarations, and continue to use unsigned types internally (e.g.
+    `uint32_t`).
   * Follow [Google C++ casting
     conventions](https://google.github.io/styleguide/cppguide.html#Casting)
     to convert arithmetic types when you know the conversion is safe. Use
     `checked_cast<T>` (from `base/numerics/safe_conversions.h`) when you need to
     `CHECK` that the source value is in range for the destination type. Use
     `saturated_cast<T>` if you instead wish to clamp out-of-range values.
-
-  * Do not use unsigned types to mean "this value should never be < 0". For
-    that, use assertions or run-time checks (as appropriate).
-
-  * In cases where the exact size of the type matters (e.g. a 32-bit pixel
-    value, a bitmask, or a counter that has to be a particular width), use one
-    of the sized types from `<stdint.h>`, e.g. `uint32_t`.
-
+    `CheckedNumeric` is an ergonomic way to perform safe arithmetic and casting
+    in many cases.
   * When passing values across network or process boundaries, use
     explicitly-sized types for safety, since the sending and receiving ends may
     not have been compiled with the same sizes for things like `int` and
     `size_t`. However, to the greatest degree possible, avoid letting these
     sized types bleed through the APIs of the layers in question.
-
   * Don't use `std::wstring`. Use `base::string16` or `base::FilePath` instead.
     (Windows-specific code interfacing with system APIs using `wstring` and
     `wchar_t` can still use `string16` and `char16`; it is safe to assume that
@@ -228,22 +163,18 @@
 When functions need to take raw or smart pointers as parameters, use the
 following conventions. Here we refer to the parameter type as `T` and name as
 `t`.
-
-  * If the function does not modify `t`'s ownership, declare the param as `T*`. The
-    caller is expected to ensure `t` stays alive as long as necessary, generally
-    through the duration of the call. Exception: In rare cases (e.g. using
-    lambdas with STL algorithms over containers of `unique_ptr<>`s), you may be
-    forced to declare the param as `const std::unique_ptr<T>&`. Do this only when
-    required.
-
+  * If the function does not modify `t`'s ownership, declare the param as `T*`.
+    The caller is expected to ensure `t` stays alive as long as necessary,
+    generally through the duration of the call. Exception: In rare cases (e.g.
+    using lambdas with STL algorithms over containers of `unique_ptr<>`s), you
+    may be forced to declare the param as `const std::unique_ptr<T>&`. Do this
+    only when required.
   * If the function takes ownership of a non-refcounted object, declare the
     param as `std::unique_ptr<T>`.
-
   * If the function (at least sometimes) takes a ref on a refcounted object,
     declare the param as `scoped_refptr<T>`. The caller can decide
     whether it wishes to transfer ownership (by calling `std::move(t)` when
     passing `t`) or retain its ref (by simply passing t directly).
-
   * In short, functions should never take ownership of parameters passed as raw
     pointers, and there should rarely be a need to pass smart pointers by const
     ref.
@@ -277,7 +208,8 @@
 
 ## File headers
 
-All files in Chromium start with a common license header. That header should look like this:
+All files in Chromium start with a common license header. That header should
+look like this:
 
 ```c++
 // Copyright $YEAR The Chromium Authors. All rights reserved.
@@ -286,18 +218,15 @@
 ```
 
 Some important notes about this header:
-
   * There is no `(c)` after `Copyright`.
-
-  * `$YEAR` should be set to the current year at the time a file is created, and not changed thereafter.
-
-  * For files specific to Chromium OS, replace the word Chromium with the phrase Chromium OS.
-
+  * `$YEAR` should be set to the current year at the time a file is created, and
+    not changed thereafter.
+  * For files specific to Chromium OS, replace the word Chromium with the phrase
+    Chromium OS.
   * If the style changes, don't bother to update existing files to comply with
     the new style. For the same reason, don't just blindly copy an existing
     file's header when creating a new file, since the existing file may use an
     outdated style.
-
   * The Chromium project hosts mirrors of some upstream open-source projects.
     When contributing to these portions of the repository, retain the existing
     file headers.
@@ -314,40 +243,37 @@
 (debug builds and some bot configurations, but not end-user builds).
 `NOTREACHED()` is equivalent to `DCHECK(false)`. Here are some rules for using
 these:
-
   * Use `DCHECK()` or `NOTREACHED()` as assertions, e.g. to document pre- and
     post-conditions. A `DCHECK()` means "this condition must always be true",
     not "this condition is normally true, but perhaps not in exceptional
     cases." Things like disk corruption or strange network errors are examples
     of exceptional circumstances that nevertheless should not result in
     `DCHECK()` failure.
-
   * A consequence of this is that you should not handle DCHECK() failures, even
-    if failure would result in a crash. Attempting to handle a `DCHECK()` failure
-    is a statement that the `DCHECK()` can fail, which contradicts the point of
-    writing the `DCHECK()`. In particular, do not write code like the following:
+    if failure would result in a crash. Attempting to handle a `DCHECK()`
+    failure is a statement that the `DCHECK()` can fail, which contradicts the
+    point of writing the `DCHECK()`. In particular, do not write code like the
+    following:
     ```c++
       DCHECK(foo);
-      if (!foo) ...  // Can't succeed!
+      if (!foo)  // Eliminate this code.
+        ...
 
-      if (!bar) {
+      if (!bar) {  // Replace this whole conditional with "DCHECK(bar);".
         NOTREACHED();
-        return;  // Replace this whole conditional with "DCHECK(bar);" and keep going instead.
+        return;
       }
     ```
-
   * Use `CHECK()` if the consequence of a failed assertion would be a security
     vulnerability, where crashing the browser is preferable. Because this takes
     down the whole browser, sometimes there are better options than `CHECK()`.
     For example, if a renderer sends the browser process a malformed IPC, an
     attacker may control the renderer, but we can simply kill the offending
     renderer instead of crashing the whole browser.
-
   * You can temporarily use `CHECK()` instead of `DCHECK()` when trying to
     force crashes in release builds to sniff out which of your assertions is
     failing. Don't leave these in the codebase forever; remove them or change
     them back once you've solved the problem.
-
   * Don't use these macros in tests, as they crash the test binary and leave
     bots in a bad state. Use the `ASSERT_xx()` and `EXPECT_xx()` family of
     macros, which report failures gracefully and can continue running other
@@ -356,9 +282,7 @@
 ## Miscellany
 
   * Use UTF-8 file encodings and LF line endings.
-
   * Unit tests and performance tests should be placed in the same directory as
     the functionality they're testing.
-
   * The [C++ Dos and Don'ts](c++-dos-and-donts.md) page has more helpful
     information.
diff --git a/styleguide/c++/const.md b/styleguide/c++/const.md
new file mode 100644
index 0000000..7b53f82
--- /dev/null
+++ b/styleguide/c++/const.md
@@ -0,0 +1,155 @@
+# Using Const Correctly
+
+The **TLDR**, as stated in [C++ Dos and Don'ts](c++-dos-and-donts.md):
+*** promo
+For safety and simplicity, **don't return pointers or references to non-const
+objects from const methods**. Within that constraint, **mark methods as const
+where possible**.  **Avoid `const_cast` to remove const**, except when
+implementing non-const getters in terms of const getters.
+***
+
+## A brief primer on const
+
+To the compiler, the `const` qualifier on a method refers to _physical
+constness_: calling this method does not change the bits in this object.  What
+we want is _logical constness_, which is only partly overlapping: calling this
+method does not affect the object in ways callers will notice, nor does it give
+you a handle with the ability to do so.
+
+Mismatches between these concepts can occur in both directions.  When something
+is logically but not physically const, C++ provides the `mutable` keyword to
+silence compiler complaints.  This is valuable for e.g. cached calculations,
+where the cache is an implementation detail callers do not care about.  When
+something is physically but not logically const, however, the compiler will
+happily accept it, and there are no tools that will automatically save you.
+This discrepancy usually involves pointers.  For example,
+
+```cpp
+void T::Cleanup() const { delete pointer_member_; }
+```
+
+Deleting a member is a change callers are likely to care about, so this is
+probably not logically const.  But because `delete` does not affect the pointer
+itself, but only the memory it points to, this code is physically const, so it
+will compile.
+
+Or, more subtly, consider this pseudocode from a node in a tree:
+
+```cpp
+class Node {
+ public:
+  void RemoveSelf() { parent_->RemoveChild(this); }
+  void RemoveChild(Node* node) {
+    if (node == left_child_)
+      left_child_ = nullptr;
+    else if (node == right_child_)
+      right_child_ = nullptr;
+  }
+  Node* left_child() const { return left_child_; }
+  Node* right_child() const { return right_child_; }
+
+ private:
+  Node* parent_;
+  Node* left_child_;
+  Node* right_child_;
+};
+```
+
+The `left_child()` and `right_child()` methods don't change anything about
+`|this|`, so making them `const` seems fine.  But they allow code like this:
+
+```cpp
+void SignatureAppearsHarmlessToCallers(const Node& node) {
+  node.left_child()->RemoveSelf();
+  // Now |node| has no |left_child_|, despite having been passed in by const ref.
+}
+```
+
+The original class definition compiles, and looks locally fine, but it's a
+timebomb: a const method returning a handle that can be used to change the
+system in ways that affect the original object.  Eventually, someone will
+actually modify the object, potentially far away from where the handle is
+obtained.
+
+These modifications can be difficult to spot in practice.  As we see in the
+previous example, splitting related concepts or state (like "a tree") across
+several objects means a change to one object affects the behavior of others.
+And if this tree is in turn referred to by yet more objects (e.g. the DOM of a
+web page, which influences all sorts of other data structures relating to the
+page), then small changes can have visible ripples across the entire system.  In
+a codebase as complex as Chromium, it can be almost impossible to reason about
+what sorts of local changes could ultimately impact the behavior of distant
+objects, and vice versa.
+
+"Logically const correct" code assures readers that const methods will not
+change the system, directly or indirectly, nor allow callers to easily do so.
+They make it easier to reason about large-scale behavior.  But since the
+compiler verifies physical constness, it will not guarantee that code is
+actually logically const.  Hence the recommendations here.
+
+## Classes of const (in)correctness
+
+In a
+[larger discussion of this issue](https://groups.google.com/a/chromium.org/d/topic/platform-architecture-dev/C2Szi07dyQo/discussion),
+Matt Giuca
+[postulated three classes of const(in)correctness](https://groups.google.com/a/chromium.org/d/msg/platform-architecture-dev/C2Szi07dyQo/lbHMUQHMAgAJ):
+
+* **Const correct:** All code marked "const" is logically const; all code that
+  is logically const is marked "const".
+* **Const okay:** All code marked "const" is logically const, but not all code
+  that is logically const is marked "const".  (Basically, if you see "const" you
+  can trust it, but sometimes it's missing.)
+* **Const broken:** Some code marked "const" is not logically const.
+
+The Chromium codebase currently varies. A significant amount of Blink code is
+"const broken". A great deal of Chromium code is "const okay". A minority of
+code is "const correct".
+
+While "const correct" is ideal, it can take a great deal of work to achieve.
+Const (in)correctness is viral, so fixing one API often requires a yak shave.
+(On the plus side, this same property helps prevent regressions when people
+actually use const objects to access the const APIs.)
+
+At the least, strive to convert code that is "const broken" to be "const okay".
+A simple rule of thumb that will prevent most cases of "const brokenness" is for
+const methods to never return pointers to non-const objects.  This is overly
+conservative, but less than you might think, due to how objects can transitively
+affect distant, seemingly-unrelated parts of the system.  The discussion thread
+linked above has more detail, but in short, it's hard for readers and reviewers
+to prove that returning pointers-to-non-const is truly safe, and will stay safe
+through later refactorings and modifications.  Following this rule is easier
+than debating about whether individual cases are exceptions.
+
+One way to ensure code is "const okay" would be to never mark anything const.
+This is suboptimal for the same reason we don't choose to "never write comments,
+so they can never be wrong".  Marking a method "const" provides the reader
+useful information about the system behavior.  Also, despite physical constness
+being different than logical constness, using "const" correctly still does catch
+certain classes of errors at compile time. Accordingly, the
+[Google style guide requests the use of const where possible](http://google.github.io/styleguide/cppguide.html#Use_of_const),
+so mark methods const when they are logically const.
+
+Making code more const correct leads to cases where duplicate const and non-const getters are required:
+
+```cpp
+const T* Foo::GetT() const { return t_; }
+T* Foo::GetT() { return t_; }
+```
+
+If the implementation of GetT() is complex, there's a
+[trick to implement the non-const getter in terms of the const one](https://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func/123995#123995),
+courtesy of _Effective C++_:
+
+```cpp
+T* Foo::GetT() { return const_cast<T*>(static_cast<const Foo*>(this)->GetT()); }
+```
+
+While this is a mouthful, it does guarantee the implementations won't get out of
+sync and no const-incorrectness will occur. And once you've seen it a few times,
+it's a recognizable pattern.
+
+This is probably the only case where you should see `const_cast` used to remove
+constness.  Its use anywhere else is generally indicative of either "const
+broken" code, or a boundary between "const correct" and "const okay" code that
+could change to "const broken" at any future time without warning from the
+compiler.  Both cases should be fixed.
diff --git a/testing/buildbot/filters/skia_renderer.content_browsertests.filter b/testing/buildbot/filters/skia_renderer.content_browsertests.filter
index aeb98e870..dc64d63 100644
--- a/testing/buildbot/filters/skia_renderer.content_browsertests.filter
+++ b/testing/buildbot/filters/skia_renderer.content_browsertests.filter
@@ -1,5 +1,4 @@
 AuraWindowVideoCaptureDeviceBrowserTestP.*
 CaptureScreenshotTest.*
-OOPBrowserTest.Basic
 SnapshotBrowserTest.*
 WebContentsVideoCaptureDeviceBrowserTestP.*
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index c0e7c6f..b4acb74 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -530,6 +530,7 @@
     "//skia",
     "//third_party/blink/public/common",
     "//ui/accessibility:ax_enums_mojo",
+    "//ui/base/ime:text_input_types",
     "//ui/base/ime/mojo",
     "//url",
   ]
diff --git a/third_party/blink/public/platform/DEPS b/third_party/blink/public/platform/DEPS
index 93ffb17..51196b3 100644
--- a/third_party/blink/public/platform/DEPS
+++ b/third_party/blink/public/platform/DEPS
@@ -45,6 +45,7 @@
     "-third_party/blink/public/web",
     "-third_party/blink/renderer/bindings",
     "+third_party/skia",
+    "+ui/base/ime/text_input_action.h",
     "+ui/base/page_transition_types.h",
     "+ui/gfx",
     "+url",
diff --git a/third_party/blink/public/platform/TaskTypes.md b/third_party/blink/public/platform/TaskTypes.md
index 0df80cc..ce7ef0e 100644
--- a/third_party/blink/public/platform/TaskTypes.md
+++ b/third_party/blink/public/platform/TaskTypes.md
@@ -65,4 +65,4 @@
 | Worker V8            | No       | No          | No           |  No      |
 | Worker Compositor    | No       | No          | No           |  No      |
 
-Internal Translation queue supports concept of it running only in the foreground. It is paused if page that owns it goes in background.
+Internal Translation queue supports concept of it running only in the foreground. It is disabled if the page that owns it goes in background.
diff --git a/third_party/blink/public/platform/web_text_input_info.h b/third_party/blink/public/platform/web_text_input_info.h
index 9b2c1d9..7ccc7aa 100644
--- a/third_party/blink/public/platform/web_text_input_info.h
+++ b/third_party/blink/public/platform/web_text_input_info.h
@@ -30,6 +30,7 @@
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_text_input_mode.h"
 #include "third_party/blink/public/platform/web_text_input_type.h"
+#include "ui/base/ime/text_input_action.h"
 
 namespace blink {
 
@@ -57,6 +58,9 @@
   // The inputmode attribute value of the currently focused input field.
   WebTextInputMode input_mode;
 
+  // The enterkeyhint attribute value of the currently focused input field.
+  ui::TextInputAction action;
+
   BLINK_PLATFORM_EXPORT bool Equals(const WebTextInputInfo&) const;
 
   WebTextInputInfo()
@@ -66,7 +70,8 @@
         selection_end(0),
         composition_start(-1),
         composition_end(-1),
-        input_mode(kWebTextInputModeDefault) {}
+        input_mode(kWebTextInputModeDefault),
+        action(ui::TextInputAction::kDefault) {}
 };
 
 inline bool operator==(const WebTextInputInfo& a, const WebTextInputInfo& b) {
diff --git a/third_party/blink/renderer/bindings/core/v8/activity_logger_test.cc b/third_party/blink/renderer/bindings/core/v8/activity_logger_test.cc
index 5acbbaa..de19faa 100644
--- a/third_party/blink/renderer/bindings/core/v8/activity_logger_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/activity_logger_test.cc
@@ -60,8 +60,7 @@
     EXPECT_EQ(expected.size(), logged_activities_.size());
     for (wtf_size_t i = 0;
          i < std::min(expected.size(), logged_activities_.size()); ++i) {
-      EXPECT_STREQ(expected[i].Utf8().data(),
-                   logged_activities_[i].Utf8().data());
+      EXPECT_EQ(expected[i], logged_activities_[i]);
     }
     return logged_activities_ == expected;
   }
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 482d5b8..41307fe 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1338,6 +1338,11 @@
   output_dir = blink_core_output_dir
 }
 
+make_names("make_core_generated_enter_key_hint_names") {
+  in_files = [ "editing/enter_key_hint_names.json5" ]
+  output_dir = blink_core_output_dir
+}
+
 # make_qualified_names ---------------------------------------------------------
 
 make_qualified_names("make_core_generated_math_ml_names") {
@@ -1609,6 +1614,7 @@
   ":make_core_generated_css_shorthand_property_classes",
   ":make_core_generated_css_value_id_mappings",
   ":make_core_generated_cssom_types",
+  ":make_core_generated_enter_key_hint_names",
   ":make_core_generated_event_factory",
   ":make_core_generated_event_names",
   ":make_core_generated_event_target_names",
diff --git a/third_party/blink/renderer/core/animation/animation_effect.cc b/third_party/blink/renderer/core/animation/animation_effect.cc
index d2b748a..41ea240 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect.cc
@@ -33,7 +33,6 @@
 #include "third_party/blink/renderer/core/animation/animation.h"
 #include "third_party/blink/renderer/core/animation/animation_input_helpers.h"
 #include "third_party/blink/renderer/core/animation/computed_effect_timing.h"
-#include "third_party/blink/renderer/core/animation/effect_timing.h"
 #include "third_party/blink/renderer/core/animation/optional_effect_timing.h"
 #include "third_party/blink/renderer/core/animation/timing_calculations.h"
 #include "third_party/blink/renderer/core/animation/timing_input.h"
@@ -92,26 +91,7 @@
 }
 
 EffectTiming* AnimationEffect::getTiming() const {
-  EffectTiming* effect_timing = EffectTiming::Create();
-
-  effect_timing->setDelay(SpecifiedTiming().start_delay * 1000);
-  effect_timing->setEndDelay(SpecifiedTiming().end_delay * 1000);
-  effect_timing->setFill(Timing::FillModeString(SpecifiedTiming().fill_mode));
-  effect_timing->setIterationStart(SpecifiedTiming().iteration_start);
-  effect_timing->setIterations(SpecifiedTiming().iteration_count);
-  UnrestrictedDoubleOrString duration;
-  if (SpecifiedTiming().iteration_duration) {
-    duration.SetUnrestrictedDouble(
-        SpecifiedTiming().iteration_duration->InMillisecondsF());
-  } else {
-    duration.SetString("auto");
-  }
-  effect_timing->setDuration(duration);
-  effect_timing->setDirection(
-      Timing::PlaybackDirectionString(SpecifiedTiming().direction));
-  effect_timing->setEasing(SpecifiedTiming().timing_function->ToString());
-
-  return effect_timing;
+  return SpecifiedTiming().ConvertToEffectTiming();
 }
 
 ComputedEffectTiming* AnimationEffect::getComputedTiming() const {
diff --git a/third_party/blink/renderer/core/animation/timing.cc b/third_party/blink/renderer/core/animation/timing.cc
index d37f7fff..606f94e 100644
--- a/third_party/blink/renderer/core/animation/timing.cc
+++ b/third_party/blink/renderer/core/animation/timing.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/core/animation/timing.h"
+#include "third_party/blink/renderer/core/animation/effect_timing.h"
 
 namespace blink {
 
@@ -51,4 +52,25 @@
   return "normal";
 }
 
+EffectTiming* Timing::ConvertToEffectTiming() const {
+  EffectTiming* effect_timing = EffectTiming::Create();
+
+  effect_timing->setDelay(start_delay * 1000);
+  effect_timing->setEndDelay(end_delay * 1000);
+  effect_timing->setFill(FillModeString(fill_mode));
+  effect_timing->setIterationStart(iteration_start);
+  effect_timing->setIterations(iteration_count);
+  UnrestrictedDoubleOrString duration;
+  if (iteration_duration) {
+    duration.SetUnrestrictedDouble(iteration_duration->InMillisecondsF());
+  } else {
+    duration.SetString("auto");
+  }
+  effect_timing->setDuration(duration);
+  effect_timing->setDirection(PlaybackDirectionString(direction));
+  effect_timing->setEasing(timing_function->ToString());
+
+  return effect_timing;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/timing.h b/third_party/blink/renderer/core/animation/timing.h
index b4ddb4b8..9b0fd2da 100644
--- a/third_party/blink/renderer/core/animation/timing.h
+++ b/third_party/blink/renderer/core/animation/timing.h
@@ -42,6 +42,8 @@
 
 namespace blink {
 
+class EffectTiming;
+
 struct CORE_EXPORT Timing {
   USING_FAST_MALLOC(Timing);
 
@@ -79,6 +81,8 @@
     DCHECK(timing_function);
   }
 
+  EffectTiming* ConvertToEffectTiming() const;
+
   bool operator==(const Timing& other) const {
     return start_delay == other.start_delay && end_delay == other.end_delay &&
            fill_mode == other.fill_mode &&
diff --git a/third_party/blink/renderer/core/core_initializer.cc b/third_party/blink/renderer/core/core_initializer.cc
index 45a820c..433278d 100644
--- a/third_party/blink/renderer/core/core_initializer.cc
+++ b/third_party/blink/renderer/core/core_initializer.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/enter_key_hint_names.h"
 #include "third_party/blink/renderer/core/event_interface_names.h"
 #include "third_party/blink/renderer/core/event_target_names.h"
 #include "third_party/blink/renderer/core/event_type_names.h"
@@ -97,12 +98,12 @@
 
   const unsigned kCoreStaticStringsCount =
       kQualifiedNamesCount + event_interface_names::kNamesCount +
-      event_target_names::kNamesCount + event_type_names::kNamesCount +
-      fetch_initiator_type_names::kNamesCount + font_family_names::kNamesCount +
-      html_tokenizer_names::kNamesCount + http_names::kNamesCount +
-      input_mode_names::kNamesCount + input_type_names::kNamesCount +
-      media_feature_names::kNamesCount + media_type_names::kNamesCount +
-      performance_entry_names::kNamesCount;
+      enter_key_hint_names::kNamesCount + event_target_names::kNamesCount +
+      event_type_names::kNamesCount + fetch_initiator_type_names::kNamesCount +
+      font_family_names::kNamesCount + html_tokenizer_names::kNamesCount +
+      http_names::kNamesCount + input_mode_names::kNamesCount +
+      input_type_names::kNamesCount + media_feature_names::kNamesCount +
+      media_type_names::kNamesCount + performance_entry_names::kNamesCount;
 
   StringImpl::ReserveStaticStringsCapacityForSize(
       kCoreStaticStringsCount + StringImpl::AllStaticStrings().size());
@@ -118,6 +119,7 @@
   xmlns_names::Init();
 
   event_interface_names::Init();
+  enter_key_hint_names::Init();
   event_target_names::Init();
   event_type_names::Init();
   fetch_initiator_type_names::Init();
diff --git a/third_party/blink/renderer/core/editing/OWNERS b/third_party/blink/renderer/core/editing/OWNERS
index fcd162d..9dae515 100644
--- a/third_party/blink/renderer/core/editing/OWNERS
+++ b/third_party/blink/renderer/core/editing/OWNERS
@@ -4,5 +4,5 @@
 # IME-related changes
 changwan@chromium.org
 
-# TEAM: editing-dev@chromium.org
+# TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Editing
diff --git a/third_party/blink/renderer/core/editing/enter_key_hint_names.json5 b/third_party/blink/renderer/core/editing/enter_key_hint_names.json5
new file mode 100644
index 0000000..ccc62fe
--- /dev/null
+++ b/third_party/blink/renderer/core/editing/enter_key_hint_names.json5
@@ -0,0 +1,16 @@
+{
+  metadata: {
+    namespace: "enter_key_hint_names",
+    export: "CORE_EXPORT",
+  },
+
+  data: [
+    "enter",
+    "done",
+    "go",
+    "next",
+    "previous",
+    "search",
+    "send",
+  ],
+}
\ No newline at end of file
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
index 2c3a4c2f..57005359 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
@@ -46,6 +46,7 @@
 #include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h"
 #include "third_party/blink/renderer/core/editing/state_machines/backward_code_point_state_machine.h"
 #include "third_party/blink/renderer/core/editing/state_machines/forward_code_point_state_machine.h"
+#include "third_party/blink/renderer/core/enter_key_hint_names.h"
 #include "third_party/blink/renderer/core/events/composition_event.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
@@ -213,6 +214,27 @@
   return element->FastGetAttribute(html_names::kInputmodeAttr).LowerASCII();
 }
 
+AtomicString GetEnterKeyHintAttribute(Element* element) {
+  if (!element)
+    return AtomicString();
+
+  bool query_attribute = false;
+  if (auto* input = ToHTMLInputElementOrNull(*element)) {
+    query_attribute = input->SupportsInputModeAttribute();
+  } else if (IsHTMLTextAreaElement(*element)) {
+    query_attribute = true;
+  } else {
+    element->GetDocument().UpdateStyleAndLayoutTree();
+    if (HasEditableStyle(*element))
+      query_attribute = true;
+  }
+
+  if (!query_attribute)
+    return AtomicString();
+
+  return element->FastGetAttribute(html_names::kEnterkeyhintAttr).LowerASCII();
+}
+
 constexpr int kInvalidDeletionLength = -1;
 constexpr bool IsInvalidDeletionLength(const int length) {
   return length == kInvalidDeletionLength;
@@ -1290,6 +1312,7 @@
   if (!element)
     return info;
 
+  info.action = InputActionOfFocusedElement();
   info.input_mode = InputModeOfFocusedElement();
   info.type = TextInputType();
   info.flags = TextInputFlags();
@@ -1398,6 +1421,32 @@
   return flags;
 }
 
+ui::TextInputAction InputMethodController::InputActionOfFocusedElement() const {
+  if (!RuntimeEnabledFeatures::EnterKeyHintAttributeEnabled())
+    return ui::TextInputAction::kDefault;
+
+  AtomicString action =
+      GetEnterKeyHintAttribute(GetDocument().FocusedElement());
+
+  if (action.IsEmpty())
+    return ui::TextInputAction::kDefault;
+  if (action == enter_key_hint_names::kEnter)
+    return ui::TextInputAction::kEnter;
+  if (action == enter_key_hint_names::kDone)
+    return ui::TextInputAction::kDone;
+  if (action == enter_key_hint_names::kGo)
+    return ui::TextInputAction::kGo;
+  if (action == enter_key_hint_names::kNext)
+    return ui::TextInputAction::kNext;
+  if (action == enter_key_hint_names::kPrevious)
+    return ui::TextInputAction::kPrevious;
+  if (action == enter_key_hint_names::kSearch)
+    return ui::TextInputAction::kSearch;
+  if (action == enter_key_hint_names::kSend)
+    return ui::TextInputAction::kSend;
+  return ui::TextInputAction::kDefault;
+}
+
 WebTextInputMode InputMethodController::InputModeOfFocusedElement() const {
   AtomicString mode = GetInputModeAttribute(GetDocument().FocusedElement());
 
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.h b/third_party/blink/renderer/core/editing/ime/input_method_controller.h
index b72c8f3a..6bb7ad03 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.h
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.h
@@ -167,6 +167,7 @@
       int selection_end,
       size_t text_length) const;
   int TextInputFlags() const;
+  ui::TextInputAction InputActionOfFocusedElement() const;
   WebTextInputMode InputModeOfFocusedElement() const;
 
   // Implements |DocumentShutdownObserver|.
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
index a94e3666..b344229 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
@@ -370,12 +370,10 @@
   Controller().SetCompositionFromExistingText(ime_text_spans, 3, 12);
 
   Controller().SetComposition(String("123hello789"), ime_text_spans, 11, 11);
-  EXPECT_STREQ("abc1<b>2</b>3hello7<b>8</b>9",
-               div->InnerHTMLAsString().Utf8().data());
+  EXPECT_EQ("abc1<b>2</b>3hello7<b>8</b>9", div->InnerHTMLAsString());
 
   Controller().FinishComposingText(InputMethodController::kKeepSelection);
-  EXPECT_STREQ("abc1<b>2</b>3hello7<b>8</b>9",
-               div->InnerHTMLAsString().Utf8().data());
+  EXPECT_EQ("abc1<b>2</b>3hello7<b>8</b>9", div->InnerHTMLAsString());
 }
 
 TEST_F(InputMethodControllerTest, CommitTextKeepingStyle) {
@@ -391,8 +389,7 @@
   Controller().SetCompositionFromExistingText(ime_text_spans, 3, 12);
 
   Controller().CommitText(String("123789"), ime_text_spans, 0);
-  EXPECT_STREQ("abc1<b>2</b>37<b>8</b>9",
-               div->InnerHTMLAsString().Utf8().data());
+  EXPECT_EQ("abc1<b>2</b>37<b>8</b>9", div->InnerHTMLAsString());
 }
 
 TEST_F(InputMethodControllerTest, InsertTextWithNewLine) {
@@ -404,7 +401,7 @@
                                        ImeTextSpanThickness::kThin, 0));
 
   Controller().CommitText(String("hello\nworld"), ime_text_spans, 0);
-  EXPECT_STREQ("hello<div>world</div>", div->InnerHTMLAsString().Utf8().data());
+  EXPECT_EQ("hello<div>world</div>", div->InnerHTMLAsString());
 }
 
 TEST_F(InputMethodControllerTest, InsertTextWithNewLineIncrementally) {
@@ -417,8 +414,7 @@
   EXPECT_EQ("abcd", div->InnerHTMLAsString());
 
   Controller().CommitText(String("bcd\nefgh\nijkl"), ime_text_spans, 0);
-  EXPECT_STREQ("abcd<div>efgh</div><div>ijkl</div>",
-               div->InnerHTMLAsString().Utf8().data());
+  EXPECT_EQ("abcd<div>efgh</div><div>ijkl</div>", div->InnerHTMLAsString());
 }
 
 TEST_F(InputMethodControllerTest, SelectionOnConfirmExistingText) {
@@ -1276,8 +1272,8 @@
   // Delete the existing composition.
   GetDocument().setTitle(g_empty_string);
   Controller().SetComposition("", ime_text_spans, 0, 0);
-  EXPECT_STREQ("beforeinput.data:;input.data:null;compositionend.data:;",
-               GetDocument().title().Utf8().data());
+  EXPECT_EQ("beforeinput.data:;input.data:null;compositionend.data:;",
+            GetDocument().title());
 }
 
 TEST_F(InputMethodControllerTest, CompositionInputEventForInsert) {
@@ -1303,9 +1299,9 @@
   GetDocument().setTitle(g_empty_string);
   GetDocument().UpdateStyleAndLayout();
   Controller().CommitText("hello", ime_text_spans, 1);
-  EXPECT_STREQ(
+  EXPECT_EQ(
       "beforeinput.data:hello;input.data:hello;compositionend.data:hello;",
-      GetDocument().title().Utf8().data());
+      GetDocument().title());
 }
 
 TEST_F(InputMethodControllerTest, CompositionInputEventForInsertEmptyText) {
@@ -1621,10 +1617,8 @@
   EXPECT_EQ(
       1u,
       GetDocument().Markers().MarkersFor(To<Text>(*div->firstChild())).size());
-  EXPECT_STREQ("text",
-               GetMarkedText(GetDocument().Markers(), div->firstChild(), 0)
-                   .Utf8()
-                   .data());
+  EXPECT_EQ("text",
+            GetMarkedText(GetDocument().Markers(), div->firstChild(), 0));
 }
 
 TEST_F(InputMethodControllerTest,
@@ -1874,7 +1868,7 @@
   Controller().SetCompositionFromExistingText(empty_ime_text_spans, 13, 25);
   Controller().CommitText(String("content"), empty_ime_text_spans, 0);
 
-  EXPECT_STREQ("This is some content", div->InnerHTMLAsString().Utf8().data());
+  EXPECT_EQ("This is some content", div->InnerHTMLAsString());
 
   // Verify marker is under "some "
   EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 2d6f6fd..66717cff 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -272,6 +272,8 @@
                          const std::string& html_file);
   void TestInputMode(WebTextInputMode expected_input_mode,
                      const std::string& html_file);
+  void TestInputAction(ui::TextInputAction expected_input_action,
+                       const std::string& html_file);
   bool TapElement(WebInputEvent::Type, Element*);
   bool TapElementById(WebInputEvent::Type, const WebString& id);
   IntSize PrintICBSizeFromPageSize(const FloatSize& page_size);
@@ -963,6 +965,33 @@
                 "input_mode_type_search.html");
 }
 
+void WebViewTest::TestInputAction(ui::TextInputAction expected_input_action,
+                                  const std::string& html_file) {
+  RegisterMockedHttpURLLoad(html_file);
+  WebViewImpl* web_view_impl =
+      web_view_helper_.InitializeAndLoad(base_url_ + html_file);
+  web_view_impl->SetInitialFocus(false);
+  EXPECT_EQ(expected_input_action, web_view_impl->MainFrameImpl()
+                                       ->GetInputMethodController()
+                                       ->TextInputInfo()
+                                       .action);
+}
+
+TEST_F(WebViewTest, TextInputAction) {
+  TestInputAction(ui::TextInputAction::kDefault, "enter_key_hint_default.html");
+  TestInputAction(ui::TextInputAction::kDefault,
+                  "enter_key_hint_default_unknown.html");
+  TestInputAction(ui::TextInputAction::kEnter, "enter_key_hint_enter.html");
+  TestInputAction(ui::TextInputAction::kGo, "enter_key_hint_go.html");
+  TestInputAction(ui::TextInputAction::kDone, "enter_key_hint_done.html");
+  TestInputAction(ui::TextInputAction::kNext, "enter_key_hint_next.html");
+  TestInputAction(ui::TextInputAction::kPrevious,
+                  "enter_key_hint_previous.html");
+  TestInputAction(ui::TextInputAction::kSearch, "enter_key_hint_search.html");
+  TestInputAction(ui::TextInputAction::kSend, "enter_key_hint_send.html");
+  TestInputAction(ui::TextInputAction::kNext, "enter_key_hint_mixed_case.html");
+}
+
 TEST_F(WebViewTest, TextInputInfoWithReplacedElements) {
   std::string url = RegisterMockedHttpURLLoad("div_with_image.html");
   url_test_helpers::RegisterMockedURLLoad(
diff --git a/third_party/blink/renderer/core/frame/dom_window.cc b/third_party/blink/renderer/core/frame/dom_window.cc
index a72467e..a1ec2c9 100644
--- a/third_party/blink/renderer/core/frame/dom_window.cc
+++ b/third_party/blink/renderer/core/frame/dom_window.cc
@@ -160,33 +160,6 @@
   return GetFrame() && GetFrame()->GetPage();
 }
 
-bool DOMWindow::IsInsecureScriptAccess(LocalDOMWindow& accessing_window,
-                                       const KURL& url) {
-  if (!url.ProtocolIsJavaScript())
-    return false;
-
-  // If this DOMWindow isn't currently active in the Frame, then there's no
-  // way we should allow the access.
-  if (IsCurrentlyDisplayedInFrame()) {
-    // FIXME: Is there some way to eliminate the need for a separate
-    // "accessing_window == this" check?
-    if (&accessing_window == this)
-      return false;
-
-    // FIXME: The name canAccess seems to be a roundabout way to ask "can
-    // execute script".  Can we name the SecurityOrigin function better to make
-    // this more clear?
-    if (accessing_window.document()->GetSecurityOrigin()->CanAccess(
-            GetFrame()->GetSecurityContext()->GetSecurityOrigin())) {
-      return false;
-    }
-  }
-
-  accessing_window.PrintErrorMessage(
-      CrossDomainAccessErrorMessage(&accessing_window));
-  return true;
-}
-
 // FIXME: Once we're throwing exceptions for cross-origin access violations, we
 // will always sanitize the target frame details, so we can safely combine
 // 'crossDomainAccessErrorMessage' with this method after considering exactly
diff --git a/third_party/blink/renderer/core/frame/dom_window.h b/third_party/blink/renderer/core/frame/dom_window.h
index c466de2..667b1ca 100644
--- a/third_party/blink/renderer/core/frame/dom_window.h
+++ b/third_party/blink/renderer/core/frame/dom_window.h
@@ -17,7 +17,6 @@
 
 class Document;
 class InputDeviceCapabilitiesConstants;
-class KURL;
 class LocalDOMWindow;
 class Location;
 class MessageEvent;
@@ -112,7 +111,6 @@
       const LocalDOMWindow* accessing_window) const;
   String CrossDomainAccessErrorMessage(
       const LocalDOMWindow* accessing_window) const;
-  bool IsInsecureScriptAccess(LocalDOMWindow& accessing_window, const KURL&);
 
   // FIXME: When this DOMWindow is no longer the active DOMWindow (i.e.,
   // when its document is no longer the document that is displayed in its
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 755578a..114a85a 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -1522,11 +1522,8 @@
   if (!result.frame)
     return nullptr;
 
-  if ((!completed_url.IsEmpty() || result.new_window) &&
-      !result.frame->DomWindow()->IsInsecureScriptAccess(*incumbent_window,
-                                                         completed_url)) {
+  if (!completed_url.IsEmpty() || result.new_window)
     result.frame->Navigate(frame_request, WebFrameLoadType::kStandard);
-  }
 
   // TODO(japhet): window-open-noopener.html?_top and several tests in
   // html/browsers/windows/browsing-context-names/ appear to require that
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 063a9ec..b5e3f64 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1019,6 +1019,16 @@
                       WebFeature::kOpenerNavigationWithoutGesture);
   }
 
+  if (destination_url.ProtocolIsJavaScript() &&
+      !GetSecurityContext()->GetSecurityOrigin()->CanAccess(
+          target_frame.GetSecurityContext()->GetSecurityOrigin())) {
+    PrintNavigationErrorMessage(
+        target_frame,
+        "The frame attempting navigation must be same-origin with the target "
+        "if navigating to a javascript: url");
+    return false;
+  }
+
   if (GetSecurityContext()->IsSandboxed(WebSandboxFlags::kNavigation)) {
     if (!target_frame.Tree().IsDescendantOf(this) &&
         !target_frame.IsMainFrame()) {
diff --git a/third_party/blink/renderer/core/frame/location.cc b/third_party/blink/renderer/core/frame/location.cc
index 30da8a3..26e4543 100644
--- a/third_party/blink/renderer/core/frame/location.cc
+++ b/third_party/blink/renderer/core/frame/location.cc
@@ -286,14 +286,11 @@
     return;
   }
 
-  if (dom_window_->IsInsecureScriptAccess(*current_window, completed_url))
-    return;
-
   // Check the source browsing context's CSP to fulfill the CSP check
   // requirement of https://html.spec.whatwg.org/C/#navigate for javascript
   // URLs. Although the spec states we should perform this check on task
-  // execution, we do this prior to dispatch since the parent frame's CSP may be
-  // inaccessible if the target frame is out of process.
+  // execution, there are concerns about the correctness of that statement,
+  // see http://github.com/whatwg/html/issues/2591.
   Document* current_document = current_window->document();
   if (current_document && completed_url.ProtocolIsJavaScript() &&
       !ContentSecurityPolicy::ShouldBypassMainWorld(current_document)) {
diff --git a/third_party/blink/renderer/core/html/html_attribute_names.json5 b/third_party/blink/renderer/core/html/html_attribute_names.json5
index 70c006940..a7e3088 100644
--- a/third_party/blink/renderer/core/html/html_attribute_names.json5
+++ b/third_party/blink/renderer/core/html/html_attribute_names.json5
@@ -77,6 +77,7 @@
     "elementtiming",
     "enctype",
     "end",
+    "enterkeyhint",
     "event",
     "exportparts",
     "face",
diff --git a/third_party/blink/renderer/core/html/html_element.idl b/third_party/blink/renderer/core/html/html_element.idl
index 181d3f3..2b85828 100644
--- a/third_party/blink/renderer/core/html/html_element.idl
+++ b/third_party/blink/renderer/core/html/html_element.idl
@@ -43,6 +43,7 @@
     // HTMLElement includes ElementContentEditable
     // https://html.spec.whatwg.org/C/#contenteditable
     [CEReactions, CustomElementCallbacks, RaisesException=Setter] attribute DOMString contentEditable;
+    [RuntimeEnabled=EnterKeyHintAttribute, CEReactions, Reflect, ReflectOnly=("enter","done","go","next","previous","search","send")] attribute DOMString enterKeyHint;
     [ImplementedAs=isContentEditableForBinding] readonly attribute boolean isContentEditable;
     [CEReactions, Reflect, ReflectOnly=("none","text","tel","url","email","numeric","decimal","search")] attribute DOMString inputMode;
 
diff --git a/third_party/blink/renderer/core/inspector/devtools_session.cc b/third_party/blink/renderer/core/inspector/devtools_session.cc
index f4302a3d..e839efc 100644
--- a/third_party/blink/renderer/core/inspector/devtools_session.cc
+++ b/third_party/blink/renderer/core/inspector/devtools_session.cc
@@ -110,9 +110,9 @@
     // Crash renderer.
     if (method == "Page.crash")
       CHECK(false);
-    inspector_task_runner_->AppendTask(
-        CrossThreadBind(&::blink::DevToolsSession::DispatchProtocolCommandImpl,
-                        session_, call_id, method, UnwrapMessage(message)));
+    inspector_task_runner_->AppendTask(CrossThreadBindOnce(
+        &::blink::DevToolsSession::DispatchProtocolCommandImpl, session_,
+        call_id, method, UnwrapMessage(message)));
   }
 
  private:
diff --git a/third_party/blink/renderer/core/inspector/inspector_task_runner.h b/third_party/blink/renderer/core/inspector/inspector_task_runner.h
index 2b76d36..7dd6706 100644
--- a/third_party/blink/renderer/core/inspector/inspector_task_runner.h
+++ b/third_party/blink/renderer/core/inspector/inspector_task_runner.h
@@ -43,7 +43,7 @@
   // Can be called from any thread other than isolate's thread.
   // This method appends a task, and both posts to the isolate's task runner
   // and requests interrupt. Whatever comes first - executes the task.
-  using Task = WTF::CrossThreadClosure;
+  using Task = CrossThreadOnceClosure;
   void AppendTask(Task) LOCKS_EXCLUDED(mutex_);
 
   // Can be called on any thread.
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
index 3149df65..01ea3fc 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.cc
@@ -19,16 +19,6 @@
 
 namespace {
 
-void AssertValidPositionForCaretPositionComputation(
-    const PositionWithAffinity& position) {
-#if DCHECK_IS_ON()
-  DCHECK(NGOffsetMapping::AcceptsPosition(position.GetPosition()));
-  const LayoutObject* layout_object = position.AnchorNode()->GetLayoutObject();
-  DCHECK(layout_object);
-  DCHECK(layout_object->IsText() || layout_object->IsAtomicInlineLevel());
-#endif
-}
-
 // The calculation takes the following input:
 // - An inline formatting context as a |LayoutBlockFlow|
 // - An offset in the |text_content_| string of the above context
@@ -306,7 +296,6 @@
 }
 
 NGCaretPosition ComputeNGCaretPosition(const PositionWithAffinity& position) {
-  AssertValidPositionForCaretPositionComputation(position);
   LayoutBlockFlow* context =
       NGInlineFormattingContextOf(position.GetPosition());
   if (!context)
diff --git a/third_party/blink/renderer/core/page/context_menu_controller.cc b/third_party/blink/renderer/core/page/context_menu_controller.cc
index 0d69fdd..10c5c16 100644
--- a/third_party/blink/renderer/core/page/context_menu_controller.cc
+++ b/third_party/blink/renderer/core/page/context_menu_controller.cc
@@ -398,11 +398,13 @@
   data.selection_start_offset = 0;
   // HitTestResult::isSelected() ensures clean layout by performing a hit test.
   // If source_type is |kMenuSourceAdjustSelection| or
-  // |kMenuSourceAdjustSelectionReset| we know the original HitTestResult in
-  // SelectionController passed the inside check already, so let it pass.
+  // |kMenuSourceAdjustSelectionReset| or |kMenuSourceTouchHandle|we know the
+  // original HitTestResult in SelectionController passed the inside check
+  // already, so let it pass.
   if (result.IsSelected(location) ||
       source_type == kMenuSourceAdjustSelection ||
-      source_type == kMenuSourceAdjustSelectionReset) {
+      source_type == kMenuSourceAdjustSelectionReset ||
+      source_type == kMenuSourceTouchHandle) {
     data.selected_text = selected_frame->SelectedText();
     WebRange range =
         selected_frame->GetInputMethodController().GetSelectionOffsets();
diff --git a/third_party/blink/renderer/core/page/context_menu_controller_test.cc b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
index 49a2c8d..b159338 100644
--- a/third_party/blink/renderer/core/page/context_menu_controller_test.cc
+++ b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
@@ -14,6 +14,7 @@
 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
 #include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
+#include "third_party/blink/renderer/core/input/event_handler.h"
 #include "third_party/blink/renderer/core/page/context_menu_controller.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
@@ -79,6 +80,12 @@
         .ShowContextMenu(GetDocument()->GetFrame(), location, source);
   }
 
+  WebView* GetWebView() { return web_view_helper_.GetWebView(); }
+
+  WebLocalFrameImpl* LocalMainFrame() {
+    return web_view_helper_.LocalMainFrame();
+  }
+
   Document* GetDocument() {
     return static_cast<Document*>(
         web_view_helper_.LocalMainFrame()->GetDocument());
@@ -102,6 +109,75 @@
   frame_test_helpers::WebViewHelper web_view_helper_;
 };
 
+// Regression test for https://crbug.com/962413
+TEST_F(ContextMenuControllerTest, ExtendSelection) {
+  ContextMenuAllowedScope context_menu_allowed_scope;
+
+  GetDocument()->body()->SetInnerHTMLFromString(
+      "<!DOCTYPE html>"
+      "<style>"
+      "  #header {"
+      "    background-color:rgba(255,0,0,0.5);"
+      "    position:fixed;"
+      "    top:0px;"
+      "    width:100%;"
+      "    z-index:1;"
+      "    height:100px;"
+      "    user-select:none;"
+      "  }"
+      "  #content {"
+      "    background-color:rgba(0,255,0,1);"
+      "    line-height:80px;"
+      "    font-size:80px;"
+      "  }"
+      "</style>"
+      "<div id='header'>"
+      "</div>"
+      "<div id='content'>"
+      "  <div>abcd</div>"
+      "  <div id='child'>efgh</div>"
+      "</div>");
+
+  Element* child = GetDocument()->getElementById("child");
+  DOMRect* child_rect = child->getBoundingClientRect();
+  LayoutPoint child_location((child_rect->left() + child_rect->right()) / 2,
+                             (child_rect->top() + child_rect->bottom()) / 2);
+  // Long press child element to select it's text,
+  // ContextMenuController#ShowContextMenu() will be auto called after selection
+  // changed.
+  WebGestureEvent gesture_event(WebInputEvent::kGestureLongPress,
+                                WebInputEvent::kNoModifiers,
+                                CurrentTimeTicks());
+  gesture_event.SetPositionInWidget(WebFloatPoint(
+      child_location.X().ToFloat(), child_location.Y().ToFloat()));
+  GetWebView()->MainFrameWidget()->HandleInputEvent(
+      WebCoalescedInputEvent(gesture_event));
+
+  // Context menu info are sent to the WebLocalFrameClient.
+  WebContextMenuData child_context_menu_data =
+      GetWebFrameClient().GetContextMenuData();
+  EXPECT_EQ("efgh", child_context_menu_data.selected_text);
+
+  // Extend selection to select <div>abcd</div>, which is under |header| div.
+  Element* content = GetDocument()->getElementById("content");
+  DOMRect* rect = content->getBoundingClientRect();
+  LocalMainFrame()->MoveRangeSelectionExtent(
+      WebPoint(rect->left(), rect->top()));
+
+  WebRect anchor, focus;
+  GetWebView()->MainFrameWidget()->SelectionBounds(anchor, focus);
+  // Show context menu at specific location, referred to:
+  // |WebContents.showContextMenuAtTouchHandle(mSelectionRect.left,
+  // mSelectionRect.bottom)|.
+  LayoutPoint location(focus.x, focus.y + focus.height / 2.0);
+  EXPECT_TRUE(ShowContextMenu(location, kMenuSourceTouchHandle));
+
+  // Context menu info are sent to the WebLocalFrameClient.
+  WebContextMenuData context_menu_data =
+      GetWebFrameClient().GetContextMenuData();
+  EXPECT_EQ("abcd\n", context_menu_data.selected_text);
+}
+
 TEST_F(ContextMenuControllerTest, VideoNotLoaded) {
   ContextMenuAllowedScope context_menu_allowed_scope;
   HitTestResult hit_test_result;
diff --git a/third_party/blink/renderer/core/page/frame_tree.cc b/third_party/blink/renderer/core/page/frame_tree.cc
index 4dedf52..e1c9158 100644
--- a/third_party/blink/renderer/core/page/frame_tree.cc
+++ b/third_party/blink/renderer/core/page/frame_tree.cc
@@ -203,8 +203,8 @@
   if (request.GetNavigationPolicy() != kNavigationPolicyCurrentTab)
     return FindResult(current_frame, false);
 
-  Frame* frame =
-      FindFrameForNavigationInternal(name, request.GetResourceRequest().Url());
+  const KURL& url = request.GetResourceRequest().Url();
+  Frame* frame = FindFrameForNavigationInternal(name, url);
   bool new_window = false;
   if (!frame) {
     frame = CreateNewWindow(*current_frame, request, name);
@@ -212,7 +212,7 @@
     // CreateNewWindow() might have modified NavigationPolicy.
     // Set it back now that the new window is known to be the right one.
     request.SetNavigationPolicy(kNavigationPolicyCurrentTab);
-  } else if (!current_frame->CanNavigate(*frame)) {
+  } else if (!current_frame->CanNavigate(*frame, url)) {
     frame = nullptr;
   }
 
@@ -269,7 +269,11 @@
 
   for (Frame* frame = page->MainFrame(); frame;
        frame = frame->Tree().TraverseNext()) {
+    // Skip descendants of this frame that were searched above to avoid
+    // showing duplicate console messages if a frame is found by name
+    // but access is blocked.
     if (frame->Tree().GetName() == name &&
+        !frame->Tree().IsDescendantOf(this_frame_.Get()) &&
         To<LocalFrame>(this_frame_.Get())->CanNavigate(*frame, url)) {
       return frame;
     }
diff --git a/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
index a942228c2..aeda73d 100644
--- a/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
@@ -4,12 +4,12 @@
 
 #include "third_party/blink/renderer/core/paint/text_paint_timing_detector.h"
 
+#include "base/test/test_mock_time_task_runner.h"
 #include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
 #include "third_party/blink/renderer/core/svg/svg_text_content_element.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
-#include "third_party/blink/renderer/platform/testing/wtf/scoped_mock_clock.h"
 
 namespace blink {
 
@@ -23,6 +23,9 @@
   void SetUp() override {
     RenderingTest::SetUp();
     RenderingTest::EnableCompositing();
+    test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
+    // Advance clock so it isn't 0 as rendering code asserts in that case.
+    AdvanceClock(base::TimeDelta::FromMicroseconds(1));
   }
 
  protected:
@@ -67,7 +70,7 @@
     TextPaintTimingDetector* detector =
         GetPaintTimingDetector().GetTextPaintTimingDetector();
     detector->ReportSwapTime(WebWidgetClient::SwapResult::kDidSwap,
-                             CurrentTimeTicks());
+                             test_task_runner_->NowTicks());
   }
 
   TimeTicks LargestPaintStoredResult() {
@@ -82,7 +85,7 @@
     if (detector &&
         !detector->records_manager_.texts_queued_for_paint_time_.empty()) {
       detector->ReportSwapTime(WebWidgetClient::SwapResult::kDidSwap,
-                               CurrentTimeTicks());
+                               test_task_runner_->NowTicks());
     }
   }
 
@@ -97,7 +100,7 @@
         .GetPaintTimingDetector()
         .GetTextPaintTimingDetector()
         ->ReportSwapTime(WebWidgetClient::SwapResult::kDidSwap,
-                         CurrentTimeTicks());
+                         test_task_runner_->NowTicks());
   }
 
   void UpdateCandidate() {
@@ -151,6 +154,15 @@
   void RemoveElement(Element* element) {
     element->GetLayoutObject()->Parent()->GetNode()->removeChild(element);
   }
+
+  base::TimeTicks NowTicks() const { return test_task_runner_->NowTicks(); }
+
+  void AdvanceClock(base::TimeDelta delta) {
+    test_task_runner_->FastForwardBy(delta);
+  }
+
+ private:
+  scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
 };
 
 TEST_F(TextPaintTimingDetectorTest, LargestTextPaint_NoText) {
@@ -228,13 +240,13 @@
 }
 
 TEST_F(TextPaintTimingDetectorTest, UpdateResultWhenCandidateChanged) {
-  TimeTicks time1 = CurrentTimeTicks();
+  TimeTicks time1 = NowTicks();
   SetBodyInnerHTML(R"HTML(
     <div>small text</div>
   )HTML");
   UpdateAllLifecyclePhasesAndSimulateSwapTime();
   UpdateCandidate();
-  TimeTicks time2 = CurrentTimeTicks();
+  TimeTicks time2 = NowTicks();
   TimeTicks first_largest = LargestPaintStoredResult();
   EXPECT_GE(first_largest, time1);
   EXPECT_GE(time2, first_largest);
@@ -242,7 +254,7 @@
   AppendDivElementToBody("a long-long-long text");
   UpdateAllLifecyclePhasesAndSimulateSwapTime();
   UpdateCandidate();
-  TimeTicks time3 = CurrentTimeTicks();
+  TimeTicks time3 = NowTicks();
   TimeTicks second_largest = LargestPaintStoredResult();
   EXPECT_GE(second_largest, time2);
   EXPECT_GE(time3, second_largest);
@@ -279,20 +291,20 @@
 }
 
 TEST_F(TextPaintTimingDetectorTest, LargestTextPaint_ReportFirstPaintTime) {
-  WTF::ScopedMockClock clock;
-  clock.Advance(TimeDelta::FromSecondsD(1));
+  base::TimeTicks start_time = NowTicks();
+  AdvanceClock(TimeDelta::FromSecondsD(1));
   SetBodyInnerHTML(R"HTML(
   )HTML");
   Element* text = AppendDivElementToBody("text");
   UpdateAllLifecyclePhasesAndSimulateSwapTime();
-  clock.Advance(TimeDelta::FromSecondsD(1));
+  AdvanceClock(TimeDelta::FromSecondsD(1));
   text->setAttribute(html_names::kStyleAttr,
                      AtomicString("position:fixed;left:30px"));
   UpdateAllLifecyclePhasesAndSimulateSwapTime();
-  clock.Advance(TimeDelta::FromSecondsD(1));
+  AdvanceClock(TimeDelta::FromSecondsD(1));
   TextRecord* record = TextRecordOfLargestTextPaint();
   EXPECT_TRUE(record);
-  EXPECT_EQ(record->paint_time, base::TimeTicks() + TimeDelta::FromSecondsD(1));
+  EXPECT_EQ(record->paint_time, start_time + TimeDelta::FromSecondsD(1));
 }
 
 TEST_F(TextPaintTimingDetectorTest,
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_default.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_default.html
new file mode 100644
index 0000000..4cf679ef
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_default.html
@@ -0,0 +1 @@
+<input />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_default_unknown.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_default_unknown.html
new file mode 100644
index 0000000..ed9ddd8
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_default_unknown.html
@@ -0,0 +1 @@
+<input enterkeyhint="unknown" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_done.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_done.html
new file mode 100644
index 0000000..ca99e912
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_done.html
@@ -0,0 +1 @@
+<input enterkeyhint="done" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_enter.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_enter.html
new file mode 100644
index 0000000..cc3fe62
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_enter.html
@@ -0,0 +1 @@
+<input enterkeyhint="enter" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_go.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_go.html
new file mode 100644
index 0000000..adab679
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_go.html
@@ -0,0 +1 @@
+<input enterkeyhint="go" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_mixed_case.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_mixed_case.html
new file mode 100644
index 0000000..75466ab
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_mixed_case.html
@@ -0,0 +1 @@
+<input enterkeyhint="nExT" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_next.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_next.html
new file mode 100644
index 0000000..2baf333
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_next.html
@@ -0,0 +1 @@
+<input enterkeyhint="next" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_previous.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_previous.html
new file mode 100644
index 0000000..bc16192
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_previous.html
@@ -0,0 +1 @@
+<input enterkeyhint="previous" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_search.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_search.html
new file mode 100644
index 0000000..8570842
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_search.html
@@ -0,0 +1 @@
+<input enterkeyhint="search" />
diff --git a/third_party/blink/renderer/core/testing/data/enter_key_hint_send.html b/third_party/blink/renderer/core/testing/data/enter_key_hint_send.html
new file mode 100644
index 0000000..fde70de
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/enter_key_hint_send.html
@@ -0,0 +1 @@
+<input enterkeyhint="send" />
diff --git a/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc b/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc
index a4d0932..8a618f5 100644
--- a/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc
+++ b/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc
@@ -14,31 +14,7 @@
 }
 
 EffectTiming* WorkletAnimationEffect::getTiming() const {
-  EffectTiming* effect_timing = EffectTiming::Create();
-
-  // This logic mirrors the blink side logic contained in
-  // third_party\blink\renderer\core\animation\animation_effect.cc
-
-  // TODO(jortaylo): Extract this logic to Timing.h aso that it can be
-  // shared between blink and animation worklet (https://crbug.com/915344).
-  effect_timing->setDelay(specified_timing_.start_delay * 1000);
-  effect_timing->setEndDelay(specified_timing_.end_delay * 1000);
-  effect_timing->setFill(Timing::FillModeString(specified_timing_.fill_mode));
-  effect_timing->setIterationStart(specified_timing_.iteration_start);
-  effect_timing->setIterations(specified_timing_.iteration_count);
-  UnrestrictedDoubleOrString duration;
-  if (specified_timing_.iteration_duration) {
-    duration.SetUnrestrictedDouble(
-        specified_timing_.iteration_duration->InMillisecondsF());
-  } else {
-    duration.SetString("auto");
-  }
-  effect_timing->setDuration(duration);
-  effect_timing->setDirection(
-      Timing::PlaybackDirectionString(specified_timing_.direction));
-  effect_timing->setEasing(specified_timing_.timing_function->ToString());
-
-  return effect_timing;
+  return specified_timing_.ConvertToEffectTiming();
 }
 
 void WorkletAnimationEffect::setLocalTime(double time_ms, bool is_null) {
diff --git a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
index 775f4282..6d8091e 100644
--- a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
+++ b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
@@ -347,8 +347,8 @@
               kResolutionAdapterWrappingFrameForCroppingFailed);
       return;
     }
-    video_frame->AddDestructionObserver(ConvertToBaseCallback(
-        CrossThreadBind(&TrackReleaseOriginalFrame, frame)));
+    video_frame->AddDestructionObserver(ConvertToBaseOnceCallback(
+        CrossThreadBindOnce(&TrackReleaseOriginalFrame, frame)));
 
     DVLOG(3) << "desired size  " << desired_size.ToString()
              << " output natural size "
diff --git a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
index ce21cf6..d158bac 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
@@ -256,9 +256,10 @@
 void AudioScheduledSourceHandler::Finish() {
   FinishWithoutOnEnded();
 
-  PostCrossThreadTask(*task_runner_, FROM_HERE,
-                      CrossThreadBind(&AudioScheduledSourceHandler::NotifyEnded,
-                                      WrapRefCounted(this)));
+  PostCrossThreadTask(
+      *task_runner_, FROM_HERE,
+      CrossThreadBindOnce(&AudioScheduledSourceHandler::NotifyEnded,
+                          WrapRefCounted(this)));
 }
 
 void AudioScheduledSourceHandler::NotifyEnded() {
diff --git a/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc b/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc
index b1509de..e3b6c594 100644
--- a/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc
+++ b/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc
@@ -65,8 +65,8 @@
     if (elapsed_ms_ < duration_ms_) {
       PostDelayedCrossThreadTask(
           *client_thread_->GetTaskRunner(), FROM_HERE,
-          CrossThreadBind(&FIFOClient::RunTaskOnOwnThread,
-                          CrossThreadUnretained(this)),
+          CrossThreadBindOnce(&FIFOClient::RunTaskOnOwnThread,
+                              CrossThreadUnretained(this)),
           TimeDelta::FromMillisecondsD(interval_with_jitter));
     } else {
       Stop(counter_);
diff --git a/third_party/blink/renderer/platform/exported/file_path_conversion_test.cc b/third_party/blink/renderer/platform/exported/file_path_conversion_test.cc
index 151c73a..81ac9b31 100644
--- a/third_party/blink/renderer/platform/exported/file_path_conversion_test.cc
+++ b/third_party/blink/renderer/platform/exported/file_path_conversion_test.cc
@@ -41,10 +41,8 @@
   EXPECT_EQ(path_latin1.value(), WebStringToFilePath(test16bit_latin1).value());
   EXPECT_EQ(path_utf16.value(), WebStringToFilePath(test16bit_utf16).value());
 
-  EXPECT_STREQ("path",
-               FilePathToWebString(base::FilePath(FILE_PATH_LITERAL("path")))
-                   .Utf8()
-                   .data());
+  EXPECT_EQ("path",
+            FilePathToWebString(base::FilePath(FILE_PATH_LITERAL("path"))));
   EXPECT_STREQ(test8bit_latin1.Utf8().data(),
                FilePathToWebString(path_latin1).Utf8().data());
   EXPECT_STREQ(test16bit_utf16.Utf8().data(),
diff --git a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc
index fcb34a4b..d9f1f1e 100644
--- a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc
+++ b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.cc
@@ -123,7 +123,7 @@
     return;
 
   base::WaitableEvent event;
-  WTF::CrossThreadClosure on_done = CrossThreadBind(
+  CrossThreadOnceClosure on_done = CrossThreadBindOnce(
       &base::WaitableEvent::Signal, WTF::CrossThreadUnretained(&event));
   RequestMutations(std::move(on_done));
   event.Wait();
@@ -188,7 +188,7 @@
                            "AnimationWorkletMutatorDispatcherImpl::MutateAsync",
                            next_async_mutation_id);
 
-  WTF::CrossThreadClosure on_done = CrossThreadBind(
+  CrossThreadOnceClosure on_done = CrossThreadBindOnce(
       [](scoped_refptr<base::SingleThreadTaskRunner> host_queue,
          base::WeakPtr<AnimationWorkletMutatorDispatcherImpl> dispatcher,
          int next_async_mutation_id) {
@@ -278,7 +278,7 @@
 }
 
 void AnimationWorkletMutatorDispatcherImpl::RequestMutations(
-    WTF::CrossThreadClosure done_callback) {
+    CrossThreadOnceClosure done_callback) {
   DCHECK(client_);
   DCHECK(outputs_->get().IsEmpty());
 
@@ -291,7 +291,7 @@
   int next_request_index = 0;
   outputs_->get().Grow(num_requests);
   base::RepeatingClosure on_mutator_done = base::BarrierClosure(
-      num_requests, ConvertToBaseCallback(std::move(done_callback)));
+      num_requests, ConvertToBaseOnceCallback(std::move(done_callback)));
 
   for (const auto& pair : mutator_map_) {
     AnimationWorkletMutator* mutator = pair.key;
diff --git a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.h b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.h
index 25fba54..ab7eb71 100644
--- a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.h
+++ b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl.h
@@ -96,7 +96,7 @@
   // Dispatches mutation update requests. The callback is triggered once all
   // mutation updates have been computed and it runs on the animation worklet
   // thread associated with the last mutation to complete.
-  void RequestMutations(WTF::CrossThreadClosure done_callback);
+  void RequestMutations(CrossThreadOnceClosure done_callback);
 
   void MutateAsynchronouslyInternal(AsyncMutationCompleteCallback);
 
diff --git a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc
index 4bade23..dd88330 100644
--- a/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc
+++ b/third_party/blink/renderer/platform/graphics/animation_worklet_mutator_dispatcher_impl_test.cc
@@ -360,26 +360,28 @@
  public:
   AnimationWorkletMutatorDispatcher::AsyncMutationCompleteCallback
   CreateIntermediateResultCallback(MutateStatus expected_result) {
-    return ConvertToBaseCallback(
-        CrossThreadBind(&AnimationWorkletMutatorDispatcherImplAsyncTest ::
-                            VerifyExpectedMutationResult,
-                        CrossThreadUnretained(this), expected_result));
+    return ConvertToBaseOnceCallback(
+        CrossThreadBindOnce(&AnimationWorkletMutatorDispatcherImplAsyncTest ::
+                                VerifyExpectedMutationResult,
+                            CrossThreadUnretained(this), expected_result));
   }
 
   AnimationWorkletMutatorDispatcher::AsyncMutationCompleteCallback
   CreateNotReachedCallback() {
-    return ConvertToBaseCallback(CrossThreadBind([](MutateStatus unused) {
-      NOTREACHED() << "Mutate complete callback should not have been triggered";
-    }));
+    return ConvertToBaseOnceCallback(
+        CrossThreadBindOnce([](MutateStatus unused) {
+          NOTREACHED()
+              << "Mutate complete callback should not have been triggered";
+        }));
   }
 
   AnimationWorkletMutatorDispatcher::AsyncMutationCompleteCallback
   CreateTestCompleteCallback(
       MutateStatus expected_result = MutateStatus::kCompletedWithUpdate) {
-    return ConvertToBaseCallback(
-        CrossThreadBind(&AnimationWorkletMutatorDispatcherImplAsyncTest ::
-                            VerifyCompletedMutationResultAndFinish,
-                        CrossThreadUnretained(this), expected_result));
+    return ConvertToBaseOnceCallback(
+        CrossThreadBindOnce(&AnimationWorkletMutatorDispatcherImplAsyncTest ::
+                                VerifyCompletedMutationResultAndFinish,
+                            CrossThreadUnretained(this), expected_result));
   }
 
   // Executes run loop until quit closure is called.
diff --git a/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc b/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc
index 3c14d62..30b91ad 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc
@@ -5,6 +5,9 @@
 #include "third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h"
 
 #include "build/build_config.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/client/shared_image_interface.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
 #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h"
@@ -22,17 +25,23 @@
 XRWebGLDrawingBuffer::ColorBuffer::ColorBuffer(
     XRWebGLDrawingBuffer* drawing_buffer,
     const IntSize& size,
+    const gpu::Mailbox& mailbox,
     GLuint texture_id)
-    : drawing_buffer(drawing_buffer), size(size), texture_id(texture_id) {
-  gpu::gles2::GLES2Interface* gl = drawing_buffer->ContextGL();
-  gl->ProduceTextureDirectCHROMIUM(texture_id, mailbox.name);
-}
+    : drawing_buffer(drawing_buffer),
+      size(size),
+      texture_id(texture_id),
+      mailbox(mailbox) {}
 
 XRWebGLDrawingBuffer::ColorBuffer::~ColorBuffer() {
   gpu::gles2::GLES2Interface* gl = drawing_buffer->ContextGL();
   if (receive_sync_token.HasData())
     gl->WaitSyncTokenCHROMIUM(receive_sync_token.GetConstData());
   gl->DeleteTextures(1, &texture_id);
+  gpu::SyncToken sync_token;
+  gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
+  auto* sii = drawing_buffer->drawing_buffer_->ContextProvider()
+                  ->SharedImageInterface();
+  sii->DestroySharedImage(sync_token, mailbox);
 }
 
 scoped_refptr<XRWebGLDrawingBuffer> XRWebGLDrawingBuffer::Create(
@@ -171,7 +180,13 @@
 
 void XRWebGLDrawingBuffer::BeginDestruction() {
   mirror_client_ = nullptr;
-  back_color_buffer_ = nullptr;
+
+  if (back_color_buffer_) {
+    gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL();
+    gl->EndSharedImageAccessDirectCHROMIUM(back_color_buffer_->texture_id);
+    back_color_buffer_ = nullptr;
+  }
+
   front_color_buffer_ = nullptr;
   recycled_color_buffer_queue_.clear();
 }
@@ -205,11 +220,6 @@
   DVLOG(2) << __FUNCTION__
            << ": anti_aliasing_mode_=" << static_cast<int>(anti_aliasing_mode_);
 
-  storage_texture_supported_ =
-      (drawing_buffer_->webgl_version() > DrawingBuffer::kWebGL1 ||
-       extensions_util->SupportsExtension("GL_EXT_texture_storage")) &&
-      anti_aliasing_mode_ == kScreenSpaceAntialiasing;
-
 #if defined(OS_ANDROID)
   // On Android devices use a smaller numer of samples to provide more breathing
   // room for fill-rate-bound applications.
@@ -453,9 +463,17 @@
     gl->BindFramebuffer(GL_FRAMEBUFFER, resolved_framebuffer_);
   }
 
+  if (back_color_buffer_) {
+    gl->EndSharedImageAccessDirectCHROMIUM(back_color_buffer_->texture_id);
+  }
+
   back_color_buffer_ = CreateColorBuffer();
   front_color_buffer_ = nullptr;
 
+  gl->BeginSharedImageAccessDirectCHROMIUM(
+      back_color_buffer_->texture_id,
+      GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+
   if (anti_aliasing_mode_ == kMSAAImplicitResolve) {
     gl->FramebufferTexture2DMultisampleEXT(
         GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
@@ -478,30 +496,27 @@
 
 scoped_refptr<XRWebGLDrawingBuffer::ColorBuffer>
 XRWebGLDrawingBuffer::CreateColorBuffer() {
+  auto* sii = drawing_buffer_->ContextProvider()->SharedImageInterface();
+  uint32_t usage = gpu::SHARED_IMAGE_USAGE_DISPLAY |
+                   gpu::SHARED_IMAGE_USAGE_GLES2 |
+                   gpu::SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT;
+  gpu::Mailbox mailbox =
+      sii->CreateSharedImage(alpha_ ? viz::RGBA_8888 : viz::RGBX_8888,
+                             gfx::Size(size_), gfx::ColorSpace(), usage);
+
   gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL();
+  gl->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
 
-  GLuint texture_id = 0;
-  gl->GenTextures(1, &texture_id);
-  gl->BindTexture(GL_TEXTURE_2D, texture_id);
-  gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-  gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-  gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-  gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-  if (storage_texture_supported_) {
-    GLenum internal_storage_format = alpha_ ? GL_RGBA8 : GL_RGB8;
-    gl->TexStorage2DEXT(GL_TEXTURE_2D, 1, internal_storage_format,
-                        size_.Width(), size_.Height());
-  } else {
-    GLenum gl_format = alpha_ ? GL_RGBA : GL_RGB;
-    gl->TexImage2D(GL_TEXTURE_2D, 0, gl_format, size_.Width(), size_.Height(),
-                   0, gl_format, GL_UNSIGNED_BYTE, nullptr);
-  }
+  // The shared image is imported into a texture on the GL context. We take a
+  // read/write access scope whenever the color buffer is used as the back
+  // buffer.
+  GLuint texture_id =
+      gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.name);
 
   DrawingBuffer::Client* client = drawing_buffer_->client();
   client->DrawingBufferClientRestoreTexture2DBinding();
 
-  return base::AdoptRef(new ColorBuffer(this, size_, texture_id));
+  return base::AdoptRef(new ColorBuffer(this, size_, mailbox, texture_id));
 }
 
 scoped_refptr<XRWebGLDrawingBuffer::ColorBuffer>
@@ -571,10 +586,18 @@
 
   BindAndResolveDestinationFramebuffer();
 
+  if (back_color_buffer_) {
+    gl->EndSharedImageAccessDirectCHROMIUM(back_color_buffer_->texture_id);
+  }
+
   // Swap buffers
   front_color_buffer_ = back_color_buffer_;
   back_color_buffer_ = CreateOrRecycleColorBuffer();
 
+  gl->BeginSharedImageAccessDirectCHROMIUM(
+      back_color_buffer_->texture_id,
+      GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+
   if (anti_aliasing_mode_ == kMSAAImplicitResolve) {
     gl->FramebufferTexture2DMultisampleEXT(
         GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
@@ -643,12 +666,15 @@
   std::unique_ptr<viz::SingleReleaseCallback> release_callback =
       viz::SingleReleaseCallback::Create(std::move(func));
 
-  // Make our own textureId that is a reference on the same texture backing
-  // being used as the front buffer. We do not need to wait on the sync
-  // token since the mailbox was produced on the same GL context that we are
-  // using here. Similarly, the release callback will run on the same context so
-  // we don't need to send a sync token for this consume action back to it.
-  GLuint texture_id = gl->CreateAndConsumeTextureCHROMIUM(buffer->mailbox.name);
+  // Make our own textureId that is a reference on the same shared image being
+  // used as the front buffer.  We do not need a
+  // Begin/EndSharedImageAccessDirectCHROMIUM as the texture id is just for
+  // lifetime, not actual access.  We do not need to wait on the sync token
+  // since the GL context has already waited on it.  Similarly, the release
+  // callback will run on the same context so we don't need to send a sync token
+  // for this consume action back to it.
+  GLuint texture_id =
+      gl->CreateAndTexStorage2DSharedImageCHROMIUM(buffer->mailbox.name);
 
   if (out_release_callback) {
     *out_release_callback = std::move(release_callback);
@@ -706,7 +732,7 @@
       viz::SingleReleaseCallback::Create(std::move(func));
 
   GLuint texture_id =
-      gl->CreateAndConsumeTextureCHROMIUM(color_buffer->mailbox.name);
+      gl->CreateAndTexStorage2DSharedImageCHROMIUM(color_buffer->mailbox.name);
 
   scoped_refptr<StaticBitmapImage> image =
       AcceleratedStaticBitmapImage::CreateFromWebGLContextImage(
diff --git a/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h b/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h
index c723ce9..6727432 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h
@@ -76,7 +76,10 @@
 
  private:
   struct PLATFORM_EXPORT ColorBuffer : public RefCounted<ColorBuffer> {
-    ColorBuffer(XRWebGLDrawingBuffer*, const IntSize&, GLuint texture_id);
+    ColorBuffer(XRWebGLDrawingBuffer*,
+                const IntSize&,
+                const gpu::Mailbox& mailbox,
+                GLuint texture_id);
     ~ColorBuffer();
 
     // The owning XRWebGLDrawingBuffer. Note that DrawingBuffer is explicitly
@@ -84,10 +87,13 @@
     // of its ColorBuffers.
     scoped_refptr<XRWebGLDrawingBuffer> drawing_buffer;
     const IntSize size;
+
+    // The id of the texture that imports the shared image into the
+    // DrawingBuffer's context.
     const GLuint texture_id = 0;
 
-    // The mailbox used to send this buffer to the compositor.
-    gpu::Mailbox mailbox;
+    // The mailbox pointing to the shared image backing this color buffer.
+    const gpu::Mailbox mailbox;
 
     // The sync token for when this buffer was sent to the compositor.
     gpu::SyncToken produce_sync_token;
@@ -166,7 +172,6 @@
 
   AntialiasingMode anti_aliasing_mode_ = kNone;
 
-  bool storage_texture_supported_ = false;
   int max_texture_size_ = 0;
   int sample_count_ = 0;
 
diff --git a/third_party/blink/renderer/platform/heap/persistent_test.cc b/third_party/blink/renderer/platform/heap/persistent_test.cc
index 11d0dd51..8ecc2744 100644
--- a/third_party/blink/renderer/platform/heap/persistent_test.cc
+++ b/third_party/blink/renderer/platform/heap/persistent_test.cc
@@ -40,7 +40,7 @@
 TEST(PersistentTest, CrossThreadBindCancellation) {
   Receiver* receiver = MakeGarbageCollected<Receiver>();
   int counter = 0;
-  CrossThreadClosure function = blink::CrossThreadBind(
+  CrossThreadOnceClosure function = CrossThreadBindOnce(
       &Receiver::Increment, WrapCrossThreadWeakPersistent(receiver),
       WTF::CrossThreadUnretained(&counter));
 
diff --git a/third_party/blink/renderer/platform/loader/link_header_test.cc b/third_party/blink/renderer/platform/loader/link_header_test.cc
index c81e18d8..d7583f9 100644
--- a/third_party/blink/renderer/platform/loader/link_header_test.cc
+++ b/third_party/blink/renderer/platform/loader/link_header_test.cc
@@ -209,11 +209,11 @@
   ASSERT_EQ(2u, header_set.size());
   LinkHeader& header1 = header_set[0];
   LinkHeader& header2 = header_set[1];
-  EXPECT_STREQ(test_case.url, header1.Url().Ascii().data());
-  EXPECT_STREQ(test_case.rel, header1.Rel().Ascii().data());
+  EXPECT_EQ(test_case.url, header1.Url());
+  EXPECT_EQ(test_case.rel, header1.Rel());
   EXPECT_EQ(test_case.valid, header1.Valid());
-  EXPECT_STREQ(test_case.url2, header2.Url().Ascii().data());
-  EXPECT_STREQ(test_case.rel2, header2.Rel().Ascii().data());
+  EXPECT_EQ(test_case.url2, header2.Url());
+  EXPECT_EQ(test_case.rel2, header2.Rel());
   EXPECT_EQ(test_case.valid2, header2.Valid());
 }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index af6f1e6..3505bd4 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -482,6 +482,10 @@
       status: "test",
     },
     {
+      name: "EnterKeyHintAttribute",
+      status: "experimental",
+    },
+    {
       name: "EventTiming",
       origin_trial_feature_name: "EventTiming",
       status: "experimental",
@@ -1075,11 +1079,11 @@
     },
     {
       name: "PaymentHandlerChangePaymentMethod",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "PaymentMethodChangeEvent",
-      status: "experimental",
+      status: "stable",
     },
     // PaymentRequest is enabled by default on Android
     {
diff --git a/third_party/blink/renderer/platform/text/locale_mac_test.mm b/third_party/blink/renderer/platform/text/locale_mac_test.mm
index 4d118e8..1d40a9b 100644
--- a/third_party/blink/renderer/platform/text/locale_mac_test.mm
+++ b/third_party/blink/renderer/platform/text/locale_mac_test.mm
@@ -194,32 +194,27 @@
 
 TEST_F(LocaleMacTest, formatWeek) {
   ScopedTestingPlatformSupport<LocalePlatformSupport> support;
-  EXPECT_STREQ("Week 04, 2005", FormatWeek("en_US", "2005-W04").Utf8().data());
-  EXPECT_STREQ("Week 52, 2005", FormatWeek("en_US", "2005-W52").Utf8().data());
+  EXPECT_EQ("Week 04, 2005", FormatWeek("en_US", "2005-W04"));
+  EXPECT_EQ("Week 52, 2005", FormatWeek("en_US", "2005-W52"));
 }
 
 TEST_F(LocaleMacTest, formatMonth) {
-  EXPECT_STREQ("April 2005",
-               FormatMonth("en_US", "2005-04", false).Utf8().data());
-  EXPECT_STREQ("avril 2005",
-               FormatMonth("fr_FR", "2005-04", false).Utf8().data());
+  EXPECT_EQ("April 2005", FormatMonth("en_US", "2005-04", false));
+  EXPECT_EQ("avril 2005", FormatMonth("fr_FR", "2005-04", false));
   EXPECT_STREQ("2005\xE5\xB9\xB4"
                "04\xE6\x9C\x88",
                FormatMonth("ja_JP", "2005-04", false).Utf8().data());
 
-  EXPECT_STREQ("Apr 2005", FormatMonth("en_US", "2005-04", true).Utf8().data());
-  EXPECT_STREQ("avr. 2005",
-               FormatMonth("fr_FR", "2005-04", true).Utf8().data());
+  EXPECT_EQ("Apr 2005", FormatMonth("en_US", "2005-04", true));
+  EXPECT_EQ("avr. 2005", FormatMonth("fr_FR", "2005-04", true));
   EXPECT_STREQ("2005\xE5\xB9\xB4"
                "04\xE6\x9C\x88",
                FormatMonth("ja_JP", "2005-04", true).Utf8().data());
 }
 
 TEST_F(LocaleMacTest, formatDate) {
-  EXPECT_STREQ("04/27/2005",
-               FormatDate("en_US", 2005, kApril, 27).Utf8().data());
-  EXPECT_STREQ("27/04/2005",
-               FormatDate("fr_FR", 2005, kApril, 27).Utf8().data());
+  EXPECT_EQ("04/27/2005", FormatDate("en_US", 2005, kApril, 27));
+  EXPECT_EQ("27/04/2005", FormatDate("fr_FR", 2005, kApril, 27));
   // Do not test ja_JP locale. OS X 10.8 and 10.7 have different formats.
 }
 
@@ -230,12 +225,9 @@
   // way to configure NSDateFormatter to behave that way on < 10.13.
   const bool expect_ar_nbsp = base::mac::IsAtLeastOS10_13();
 
-  EXPECT_STREQ("1:23 PM",
-               FormatTime("en_US", 13, 23, 00, 000, true).Utf8().data());
-  EXPECT_STREQ("13:23",
-               FormatTime("fr_FR", 13, 23, 00, 000, true).Utf8().data());
-  EXPECT_STREQ("13:23",
-               FormatTime("ja_JP", 13, 23, 00, 000, true).Utf8().data());
+  EXPECT_EQ("1:23 PM", FormatTime("en_US", 13, 23, 00, 000, true));
+  EXPECT_EQ("13:23", FormatTime("fr_FR", 13, 23, 00, 000, true));
+  EXPECT_EQ("13:23", FormatTime("ja_JP", 13, 23, 00, 000, true));
   if (expect_ar_nbsp) {
     EXPECT_STREQ("\xD9\xA1:\xD9\xA2\xD9\xA3\xC2\xA0\xD9\x85",
                  FormatTime("ar", 13, 23, 00, 000, true).Utf8().data());
@@ -246,12 +238,9 @@
   EXPECT_STREQ("\xDB\xB1\xDB\xB3:\xDB\xB2\xDB\xB3",
                FormatTime("fa", 13, 23, 00, 000, true).Utf8().data());
 
-  EXPECT_STREQ("12:00 AM",
-               FormatTime("en_US", 00, 00, 00, 000, true).Utf8().data());
-  EXPECT_STREQ("00:00",
-               FormatTime("fr_FR", 00, 00, 00, 000, true).Utf8().data());
-  EXPECT_STREQ("0:00",
-               FormatTime("ja_JP", 00, 00, 00, 000, true).Utf8().data());
+  EXPECT_EQ("12:00 AM", FormatTime("en_US", 00, 00, 00, 000, true));
+  EXPECT_EQ("00:00", FormatTime("fr_FR", 00, 00, 00, 000, true));
+  EXPECT_EQ("0:00", FormatTime("ja_JP", 00, 00, 00, 000, true));
   if (expect_ar_nbsp) {
     EXPECT_STREQ("\xD9\xA1\xD9\xA2:\xD9\xA0\xD9\xA0\xC2\xA0\xD8\xB5",
                  FormatTime("ar", 00, 00, 00, 000, true).Utf8().data());
@@ -262,12 +251,9 @@
   EXPECT_STREQ("\xDB\xB0:\xDB\xB0\xDB\xB0",
                FormatTime("fa", 00, 00, 00, 000, true).Utf8().data());
 
-  EXPECT_STREQ("7:07:07.007 AM",
-               FormatTime("en_US", 07, 07, 07, 007, false).Utf8().data());
-  EXPECT_STREQ("07:07:07,007",
-               FormatTime("fr_FR", 07, 07, 07, 007, false).Utf8().data());
-  EXPECT_STREQ("7:07:07.007",
-               FormatTime("ja_JP", 07, 07, 07, 007, false).Utf8().data());
+  EXPECT_EQ("7:07:07.007 AM", FormatTime("en_US", 07, 07, 07, 007, false));
+  EXPECT_EQ("07:07:07,007", FormatTime("fr_FR", 07, 07, 07, 007, false));
+  EXPECT_EQ("7:07:07.007", FormatTime("ja_JP", 07, 07, 07, 007, false));
   if (expect_ar_nbsp) {
     EXPECT_STREQ(
         "\xD9\xA7:\xD9\xA0\xD9\xA7:"
@@ -290,12 +276,12 @@
 }
 
 TEST_F(LocaleMacTest, monthLabels) {
-  EXPECT_STREQ("January", MonthLabel("en_US", kJanuary).Utf8().data());
-  EXPECT_STREQ("June", MonthLabel("en_US", kJune).Utf8().data());
-  EXPECT_STREQ("December", MonthLabel("en_US", kDecember).Utf8().data());
+  EXPECT_EQ("January", MonthLabel("en_US", kJanuary));
+  EXPECT_EQ("June", MonthLabel("en_US", kJune));
+  EXPECT_EQ("December", MonthLabel("en_US", kDecember));
 
-  EXPECT_STREQ("janvier", MonthLabel("fr_FR", kJanuary).Utf8().data());
-  EXPECT_STREQ("juin", MonthLabel("fr_FR", kJune).Utf8().data());
+  EXPECT_EQ("janvier", MonthLabel("fr_FR", kJanuary));
+  EXPECT_EQ("juin", MonthLabel("fr_FR", kJune));
   EXPECT_STREQ("d\xC3\xA9"
                "cembre",
                MonthLabel("fr_FR", kDecember).Utf8().data());
@@ -306,13 +292,13 @@
 }
 
 TEST_F(LocaleMacTest, weekDayShortLabels) {
-  EXPECT_STREQ("Sun", WeekDayShortLabel("en_US", kSunday).Utf8().data());
-  EXPECT_STREQ("Wed", WeekDayShortLabel("en_US", kWednesday).Utf8().data());
-  EXPECT_STREQ("Sat", WeekDayShortLabel("en_US", kSaturday).Utf8().data());
+  EXPECT_EQ("Sun", WeekDayShortLabel("en_US", kSunday));
+  EXPECT_EQ("Wed", WeekDayShortLabel("en_US", kWednesday));
+  EXPECT_EQ("Sat", WeekDayShortLabel("en_US", kSaturday));
 
-  EXPECT_STREQ("dim.", WeekDayShortLabel("fr_FR", kSunday).Utf8().data());
-  EXPECT_STREQ("mer.", WeekDayShortLabel("fr_FR", kWednesday).Utf8().data());
-  EXPECT_STREQ("sam.", WeekDayShortLabel("fr_FR", kSaturday).Utf8().data());
+  EXPECT_EQ("dim.", WeekDayShortLabel("fr_FR", kSunday));
+  EXPECT_EQ("mer.", WeekDayShortLabel("fr_FR", kWednesday));
+  EXPECT_EQ("sam.", WeekDayShortLabel("fr_FR", kSaturday));
 
   EXPECT_STREQ("\xE6\x97\xA5",
                WeekDayShortLabel("ja_JP", kSunday).Utf8().data());
@@ -330,7 +316,7 @@
 }
 
 TEST_F(LocaleMacTest, monthFormat) {
-  EXPECT_STREQ("MMMM yyyy", MonthFormat("en_US").Utf8().data());
+  EXPECT_EQ("MMMM yyyy", MonthFormat("en_US"));
   EXPECT_STREQ("yyyy\xE5\xB9\xB4M\xE6\x9C\x88",
                MonthFormat("ja_JP").Utf8().data());
 
@@ -340,27 +326,24 @@
 }
 
 TEST_F(LocaleMacTest, timeFormat) {
-  EXPECT_STREQ("h:mm:ss a", TimeFormat("en_US").Utf8().data());
-  EXPECT_STREQ("HH:mm:ss", TimeFormat("fr_FR").Utf8().data());
-  EXPECT_STREQ("H:mm:ss", TimeFormat("ja_JP").Utf8().data());
+  EXPECT_EQ("h:mm:ss a", TimeFormat("en_US"));
+  EXPECT_EQ("HH:mm:ss", TimeFormat("fr_FR"));
+  EXPECT_EQ("H:mm:ss", TimeFormat("ja_JP"));
 }
 
 TEST_F(LocaleMacTest, shortTimeFormat) {
-  EXPECT_STREQ("h:mm a", ShortTimeFormat("en_US").Utf8().data());
-  EXPECT_STREQ("HH:mm", ShortTimeFormat("fr_FR").Utf8().data());
-  EXPECT_STREQ("H:mm", ShortTimeFormat("ja_JP").Utf8().data());
+  EXPECT_EQ("h:mm a", ShortTimeFormat("en_US"));
+  EXPECT_EQ("HH:mm", ShortTimeFormat("fr_FR"));
+  EXPECT_EQ("H:mm", ShortTimeFormat("ja_JP"));
 }
 
 TEST_F(LocaleMacTest, standAloneMonthLabels) {
-  EXPECT_STREQ("January",
-               StandAloneMonthLabel("en_US", kJanuary).Utf8().data());
-  EXPECT_STREQ("June", StandAloneMonthLabel("en_US", kJune).Utf8().data());
-  EXPECT_STREQ("December",
-               StandAloneMonthLabel("en_US", kDecember).Utf8().data());
+  EXPECT_EQ("January", StandAloneMonthLabel("en_US", kJanuary));
+  EXPECT_EQ("June", StandAloneMonthLabel("en_US", kJune));
+  EXPECT_EQ("December", StandAloneMonthLabel("en_US", kDecember));
 
-  EXPECT_STREQ("janvier",
-               StandAloneMonthLabel("fr_FR", kJanuary).Utf8().data());
-  EXPECT_STREQ("juin", StandAloneMonthLabel("fr_FR", kJune).Utf8().data());
+  EXPECT_EQ("janvier", StandAloneMonthLabel("fr_FR", kJanuary));
+  EXPECT_EQ("juin", StandAloneMonthLabel("fr_FR", kJune));
   EXPECT_STREQ("d\xC3\xA9"
                "cembre",
                StandAloneMonthLabel("fr_FR", kDecember).Utf8().data());
@@ -374,13 +357,13 @@
 }
 
 TEST_F(LocaleMacTest, shortMonthLabels) {
-  EXPECT_STREQ("Jan", ShortMonthLabel("en_US", 0).Utf8().data());
-  EXPECT_STREQ("Jan", ShortStandAloneMonthLabel("en_US", 0).Utf8().data());
-  EXPECT_STREQ("Dec", ShortMonthLabel("en_US", 11).Utf8().data());
-  EXPECT_STREQ("Dec", ShortStandAloneMonthLabel("en_US", 11).Utf8().data());
+  EXPECT_EQ("Jan", ShortMonthLabel("en_US", 0));
+  EXPECT_EQ("Jan", ShortStandAloneMonthLabel("en_US", 0));
+  EXPECT_EQ("Dec", ShortMonthLabel("en_US", 11));
+  EXPECT_EQ("Dec", ShortStandAloneMonthLabel("en_US", 11));
 
-  EXPECT_STREQ("janv.", ShortMonthLabel("fr_FR", 0).Utf8().data());
-  EXPECT_STREQ("janv.", ShortStandAloneMonthLabel("fr_FR", 0).Utf8().data());
+  EXPECT_EQ("janv.", ShortMonthLabel("fr_FR", 0));
+  EXPECT_EQ("janv.", ShortStandAloneMonthLabel("fr_FR", 0));
   EXPECT_STREQ("d\xC3\xA9"
                "c.",
                ShortMonthLabel("fr_FR", 11).Utf8().data());
@@ -405,11 +388,11 @@
 }
 
 TEST_F(LocaleMacTest, timeAMPMLabels) {
-  EXPECT_STREQ("AM", TimeAMPMLabel("en_US", 0).Utf8().data());
-  EXPECT_STREQ("PM", TimeAMPMLabel("en_US", 1).Utf8().data());
+  EXPECT_EQ("AM", TimeAMPMLabel("en_US", 0));
+  EXPECT_EQ("PM", TimeAMPMLabel("en_US", 1));
 
-  EXPECT_STREQ("AM", TimeAMPMLabel("fr_FR", 0).Utf8().data());
-  EXPECT_STREQ("PM", TimeAMPMLabel("fr_FR", 1).Utf8().data());
+  EXPECT_EQ("AM", TimeAMPMLabel("fr_FR", 0));
+  EXPECT_EQ("PM", TimeAMPMLabel("fr_FR", 1));
 
   EXPECT_STREQ("\xE5\x8D\x88\xE5\x89\x8D",
                TimeAMPMLabel("ja_JP", 0).Utf8().data());
@@ -418,15 +401,13 @@
 }
 
 TEST_F(LocaleMacTest, decimalSeparator) {
-  EXPECT_STREQ(".", DecimalSeparator("en_US").Utf8().data());
-  EXPECT_STREQ(",", DecimalSeparator("fr_FR").Utf8().data());
+  EXPECT_EQ(".", DecimalSeparator("en_US"));
+  EXPECT_EQ(",", DecimalSeparator("fr_FR"));
 }
 
 TEST_F(LocaleMacTest, invalidLocale) {
-  EXPECT_STREQ(MonthLabel("en_US", kJanuary).Utf8().data(),
-               MonthLabel("foo", kJanuary).Utf8().data());
-  EXPECT_STREQ(DecimalSeparator("en_US").Utf8().data(),
-               DecimalSeparator("foo").Utf8().data());
+  EXPECT_EQ(MonthLabel("en_US", kJanuary), MonthLabel("foo", kJanuary));
+  EXPECT_EQ(DecimalSeparator("en_US"), DecimalSeparator("foo"));
 }
 
 static void TestNumberIsReversible(const AtomicString& locale_string,
@@ -437,7 +418,7 @@
   if (should_have)
     EXPECT_TRUE(localized.Contains(should_have));
   String converted = locale->ConvertFromLocalizedNumber(localized);
-  EXPECT_STREQ(original, converted.Utf8().data());
+  EXPECT_EQ(original, converted);
 }
 
 void TestNumbers(const AtomicString& locale_string,
diff --git a/third_party/blink/renderer/platform/text/locale_win_test.cc b/third_party/blink/renderer/platform/text/locale_win_test.cc
index a3e6383..1bd59aa 100644
--- a/third_party/blink/renderer/platform/text/locale_win_test.cc
+++ b/third_party/blink/renderer/platform/text/locale_win_test.cc
@@ -160,12 +160,9 @@
 };
 
 TEST_F(LocaleWinTest, formatDate) {
-  EXPECT_STREQ("04/27/2005",
-               FormatDate(kEnglishUS, 2005, kApril, 27).Utf8().data());
-  EXPECT_STREQ("27/04/2005",
-               FormatDate(kFrenchFR, 2005, kApril, 27).Utf8().data());
-  EXPECT_STREQ("2005/04/27",
-               FormatDate(kJapaneseJP, 2005, kApril, 27).Utf8().data());
+  EXPECT_EQ("04/27/2005", FormatDate(kEnglishUS, 2005, kApril, 27));
+  EXPECT_EQ("27/04/2005", FormatDate(kFrenchFR, 2005, kApril, 27));
+  EXPECT_EQ("2005/04/27", FormatDate(kJapaneseJP, 2005, kApril, 27));
 }
 
 TEST_F(LocaleWinTest, firstDayOfWeek) {
@@ -175,12 +172,12 @@
 }
 
 TEST_F(LocaleWinTest, monthLabels) {
-  EXPECT_STREQ("January", MonthLabel(kEnglishUS, kJanuary).Utf8().data());
-  EXPECT_STREQ("June", MonthLabel(kEnglishUS, kJune).Utf8().data());
-  EXPECT_STREQ("December", MonthLabel(kEnglishUS, kDecember).Utf8().data());
+  EXPECT_EQ("January", MonthLabel(kEnglishUS, kJanuary));
+  EXPECT_EQ("June", MonthLabel(kEnglishUS, kJune));
+  EXPECT_EQ("December", MonthLabel(kEnglishUS, kDecember));
 
-  EXPECT_STREQ("janvier", MonthLabel(kFrenchFR, kJanuary).Utf8().data());
-  EXPECT_STREQ("juin", MonthLabel(kFrenchFR, kJune).Utf8().data());
+  EXPECT_EQ("janvier", MonthLabel(kFrenchFR, kJanuary));
+  EXPECT_EQ("juin", MonthLabel(kFrenchFR, kJune));
   EXPECT_STREQ(
       "d\xC3\xA9"
       "cembre",
@@ -194,13 +191,13 @@
 }
 
 TEST_F(LocaleWinTest, weekDayShortLabels) {
-  EXPECT_STREQ("Sun", WeekDayShortLabel(kEnglishUS, kSunday).Utf8().data());
-  EXPECT_STREQ("Wed", WeekDayShortLabel(kEnglishUS, kWednesday).Utf8().data());
-  EXPECT_STREQ("Sat", WeekDayShortLabel(kEnglishUS, kSaturday).Utf8().data());
+  EXPECT_EQ("Sun", WeekDayShortLabel(kEnglishUS, kSunday));
+  EXPECT_EQ("Wed", WeekDayShortLabel(kEnglishUS, kWednesday));
+  EXPECT_EQ("Sat", WeekDayShortLabel(kEnglishUS, kSaturday));
 
-  EXPECT_STREQ("dim.", WeekDayShortLabel(kFrenchFR, kSunday).Utf8().data());
-  EXPECT_STREQ("mer.", WeekDayShortLabel(kFrenchFR, kWednesday).Utf8().data());
-  EXPECT_STREQ("sam.", WeekDayShortLabel(kFrenchFR, kSaturday).Utf8().data());
+  EXPECT_EQ("dim.", WeekDayShortLabel(kFrenchFR, kSunday));
+  EXPECT_EQ("mer.", WeekDayShortLabel(kFrenchFR, kWednesday));
+  EXPECT_EQ("sam.", WeekDayShortLabel(kFrenchFR, kSaturday));
 
   EXPECT_STREQ("\xE6\x97\xA5",
                WeekDayShortLabel(kJapaneseJP, kSunday).Utf8().data());
@@ -216,53 +213,50 @@
 }
 
 TEST_F(LocaleWinTest, dateFormat) {
-  EXPECT_STREQ("y-M-d", LocaleWin::DateFormat("y-M-d").Utf8().data());
-  EXPECT_STREQ("''yy'-'''MM'''-'dd",
-               LocaleWin::DateFormat("''yy-''MM''-dd").Utf8().data());
-  EXPECT_STREQ("yyyy'-''''-'MMM'''''-'dd",
-               LocaleWin::DateFormat("yyyy-''''-MMM''''-dd").Utf8().data());
-  EXPECT_STREQ("yyyy'-'''''MMMM-dd",
-               LocaleWin::DateFormat("yyyy-''''MMMM-dd").Utf8().data());
+  EXPECT_EQ("y-M-d", LocaleWin::DateFormat("y-M-d"));
+  EXPECT_EQ("''yy'-'''MM'''-'dd", LocaleWin::DateFormat("''yy-''MM''-dd"));
+  EXPECT_EQ("yyyy'-''''-'MMM'''''-'dd",
+            LocaleWin::DateFormat("yyyy-''''-MMM''''-dd"));
+  EXPECT_EQ("yyyy'-'''''MMMM-dd", LocaleWin::DateFormat("yyyy-''''MMMM-dd"));
 }
 
 TEST_F(LocaleWinTest, monthFormat) {
   // Month format for EnglishUS:
   //  "MMMM, yyyy" on Windows 7 or older.
   //  "MMMM yyyy" on Window 8 or later.
-  EXPECT_STREQ("MMMM yyyy",
-               MonthFormat(kEnglishUS).Replace(',', "").Utf8().data());
-  EXPECT_STREQ("MMMM yyyy", MonthFormat(kFrenchFR).Utf8().data());
+  EXPECT_EQ("MMMM yyyy", MonthFormat(kEnglishUS).Replace(',', ""));
+  EXPECT_EQ("MMMM yyyy", MonthFormat(kFrenchFR));
   EXPECT_STREQ("yyyy\xE5\xB9\xB4M\xE6\x9C\x88",
                MonthFormat(kJapaneseJP).Utf8().data());
 }
 
 TEST_F(LocaleWinTest, timeFormat) {
-  EXPECT_STREQ("h:mm:ss a", TimeFormat(kEnglishUS).Utf8().data());
-  EXPECT_STREQ("HH:mm:ss", TimeFormat(kFrenchFR).Utf8().data());
-  EXPECT_STREQ("H:mm:ss", TimeFormat(kJapaneseJP).Utf8().data());
+  EXPECT_EQ("h:mm:ss a", TimeFormat(kEnglishUS));
+  EXPECT_EQ("HH:mm:ss", TimeFormat(kFrenchFR));
+  EXPECT_EQ("H:mm:ss", TimeFormat(kJapaneseJP));
 }
 
 TEST_F(LocaleWinTest, shortTimeFormat) {
-  EXPECT_STREQ("h:mm a", ShortTimeFormat(kEnglishUS).Utf8().data());
-  EXPECT_STREQ("HH:mm", ShortTimeFormat(kFrenchFR).Utf8().data());
-  EXPECT_STREQ("H:mm", ShortTimeFormat(kJapaneseJP).Utf8().data());
+  EXPECT_EQ("h:mm a", ShortTimeFormat(kEnglishUS));
+  EXPECT_EQ("HH:mm", ShortTimeFormat(kFrenchFR));
+  EXPECT_EQ("H:mm", ShortTimeFormat(kJapaneseJP));
 }
 
 TEST_F(LocaleWinTest, shortMonthLabels) {
-  EXPECT_STREQ("Jan", ShortMonthLabel(kEnglishUS, 0).Utf8().data());
-  EXPECT_STREQ("Dec", ShortMonthLabel(kEnglishUS, 11).Utf8().data());
-  EXPECT_STREQ("janv.", ShortMonthLabel(kFrenchFR, 0).Utf8().data());
+  EXPECT_EQ("Jan", ShortMonthLabel(kEnglishUS, 0));
+  EXPECT_EQ("Dec", ShortMonthLabel(kEnglishUS, 11));
+  EXPECT_EQ("janv.", ShortMonthLabel(kFrenchFR, 0));
   EXPECT_STREQ(
       "d\xC3\xA9"
       "c.",
       ShortMonthLabel(kFrenchFR, 11).Utf8().data());
-  EXPECT_STREQ("1", ShortMonthLabel(kJapaneseJP, 0).Utf8().data());
-  EXPECT_STREQ("12", ShortMonthLabel(kJapaneseJP, 11).Utf8().data());
+  EXPECT_EQ("1", ShortMonthLabel(kJapaneseJP, 0));
+  EXPECT_EQ("12", ShortMonthLabel(kJapaneseJP, 11));
 }
 
 TEST_F(LocaleWinTest, timeAMPMLabels) {
-  EXPECT_STREQ("AM", TimeAMPMLabel(kEnglishUS, 0).Utf8().data());
-  EXPECT_STREQ("PM", TimeAMPMLabel(kEnglishUS, 1).Utf8().data());
+  EXPECT_EQ("AM", TimeAMPMLabel(kEnglishUS, 0));
+  EXPECT_EQ("PM", TimeAMPMLabel(kEnglishUS, 1));
 
   EXPECT_STREQ("", TimeAMPMLabel(kFrenchFR, 0).Utf8().data());
   EXPECT_STREQ("", TimeAMPMLabel(kFrenchFR, 1).Utf8().data());
@@ -274,8 +268,8 @@
 }
 
 TEST_F(LocaleWinTest, decimalSeparator) {
-  EXPECT_STREQ(".", DecimalSeparator(kEnglishUS).Utf8().data());
-  EXPECT_STREQ(",", DecimalSeparator(kFrenchFR).Utf8().data());
+  EXPECT_EQ(".", DecimalSeparator(kEnglishUS));
+  EXPECT_EQ(",", DecimalSeparator(kFrenchFR));
 }
 
 static void TestNumberIsReversible(LCID lcid,
@@ -287,7 +281,7 @@
   if (should_have)
     EXPECT_TRUE(localized.Contains(should_have));
   String converted = locale->ConvertFromLocalizedNumber(localized);
-  EXPECT_STREQ(original, converted.Utf8().data());
+  EXPECT_EQ(original, converted);
 }
 
 void TestNumbers(LCID lcid) {
diff --git a/third_party/blink/renderer/platform/web_text_input_info.cc b/third_party/blink/renderer/platform/web_text_input_info.cc
index b1d1149..7a54fbe0 100644
--- a/third_party/blink/renderer/platform/web_text_input_info.cc
+++ b/third_party/blink/renderer/platform/web_text_input_info.cc
@@ -38,7 +38,7 @@
          selection_end == other.selection_end &&
          composition_start == other.composition_start &&
          composition_end == other.composition_end &&
-         input_mode == other.input_mode;
+         input_mode == other.input_mode && action == other.action;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/wtf/text/wtf_string_test.cc b/third_party/blink/renderer/platform/wtf/text/wtf_string_test.cc
index ef91f3f..7edfded2 100644
--- a/third_party/blink/renderer/platform/wtf/text/wtf_string_test.cc
+++ b/third_party/blink/renderer/platform/wtf/text/wtf_string_test.cc
@@ -114,34 +114,34 @@
   String test_string = "1224";
   EXPECT_TRUE(test_string.Is8Bit());
   test_string.Replace('2', "");
-  EXPECT_STREQ("14", test_string.Utf8().data());
+  EXPECT_EQ("14", test_string);
 
   test_string = "1224";
   EXPECT_TRUE(test_string.Is8Bit());
   test_string.Replace('2', "3");
-  EXPECT_STREQ("1334", test_string.Utf8().data());
+  EXPECT_EQ("1334", test_string);
 
   test_string = "1224";
   EXPECT_TRUE(test_string.Is8Bit());
   test_string.Replace('2', "555");
-  EXPECT_STREQ("15555554", test_string.Utf8().data());
+  EXPECT_EQ("15555554", test_string);
 
   test_string = "1224";
   EXPECT_TRUE(test_string.Is8Bit());
   test_string.Replace('3', "NotFound");
-  EXPECT_STREQ("1224", test_string.Utf8().data());
+  EXPECT_EQ("1224", test_string);
 
   // Cases for 16Bit source.
   // U+00E9 (=0xC3 0xA9 in UTF-8) is e with accent.
   test_string = String::FromUTF8("r\xC3\xA9sum\xC3\xA9");
   EXPECT_FALSE(test_string.Is8Bit());
   test_string.Replace(UChar(0x00E9), "e");
-  EXPECT_STREQ("resume", test_string.Utf8().data());
+  EXPECT_EQ("resume", test_string);
 
   test_string = String::FromUTF8("r\xC3\xA9sum\xC3\xA9");
   EXPECT_FALSE(test_string.Is8Bit());
   test_string.Replace(UChar(0x00E9), "");
-  EXPECT_STREQ("rsum", test_string.Utf8().data());
+  EXPECT_EQ("rsum", test_string);
 
   test_string = String::FromUTF8("r\xC3\xA9sum\xC3\xA9");
   EXPECT_FALSE(test_string.Is8Bit());
@@ -422,8 +422,8 @@
 }
 
 TEST(StringTest, DeprecatedLower) {
-  EXPECT_STREQ("link", String("LINK").DeprecatedLower().Ascii().data());
-  EXPECT_STREQ("link", String("lInk").DeprecatedLower().Ascii().data());
+  EXPECT_EQ("link", String("LINK").DeprecatedLower());
+  EXPECT_EQ("link", String("lInk").DeprecatedLower());
   EXPECT_STREQ("lin\xE1k",
                String("lIn\xC1k").DeprecatedLower().Latin1().data());
   // U+212A -> k
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 9480c6c..e25f1a1 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -444,6 +444,12 @@
         ],
     },
     {
+        'paths': ['third_party/blink/renderer/core/editing/ime'],
+        'allowed': [
+            'ui::TextInputAction',
+        ],
+    },
+    {
         'paths': ['third_party/blink/renderer/core/fetch/data_consumer_handle_test_util.cc'],
         'allowed': [
             # The existing code already contains gin::IsolateHolder.
diff --git a/third_party/blink/tools/blinkpy/style/checkers/cpp.py b/third_party/blink/tools/blinkpy/style/checkers/cpp.py
index e7ab3a7..48d67a14 100644
--- a/third_party/blink/tools/blinkpy/style/checkers/cpp.py
+++ b/third_party/blink/tools/blinkpy/style/checkers/cpp.py
@@ -1362,95 +1362,6 @@
           % (ctype_function))
 
 
-def check_exit_statement_simplifications(clean_lines, line_number, error):
-    """Looks for else or else-if statements that should be written as an
-    if statement when the prior if concludes with a return, break, continue or
-    goto statement.
-
-    Args:
-      clean_lines: A CleansedLines instance containing the file.
-      line_number: The number of the line to check.
-      error: The function to call with any errors found.
-    """
-
-    line = clean_lines.elided[line_number]  # Get rid of comments and strings.
-
-    else_match = match(r'(?P<else_indentation>\s*)(\}\s*)?else(\s+if\s*\(|(?P<else>\s*(\{\s*)?\Z))', line)
-    if not else_match:
-        return
-
-    else_indentation = else_match.group('else_indentation')
-    inner_indentation = else_indentation + ' ' * 2
-
-    previous_lines = clean_lines.elided[:line_number]
-    previous_lines.reverse()
-    line_offset = 0
-    encountered_exit_statement = False
-
-    for current_line in previous_lines:
-        line_offset -= 1
-
-        # Skip not only empty lines but also those with preprocessor directives
-        # and goto labels.
-        if current_line.strip() == '' or current_line.startswith('#') or match(r'\w+\s*:\s*$', current_line):
-            continue
-
-        # Skip lines with closing braces on the original indentation level.
-        # Even though the styleguide says they should be on the same line as
-        # the "else if" statement, we also want to check for instances where
-        # the current code does not comply with the coding style. Thus, ignore
-        # these lines and proceed to the line before that.
-        if current_line == else_indentation + '}':
-            continue
-
-        current_indentation_match = match(r'(?P<indentation>\s*)(?P<remaining_line>.*)$', current_line)
-        current_indentation = current_indentation_match.group('indentation')
-        remaining_line = current_indentation_match.group('remaining_line')
-
-        # As we're going up the lines, the first real statement to encounter
-        # has to be an exit statement (return, break, continue or goto) -
-        # otherwise, this check doesn't apply.
-        if not encountered_exit_statement:
-            # We only want to find exit statements if they are on exactly
-            # the same level of indentation as expected from the code inside
-            # the block. If the indentation doesn't strictly match then we
-            # might have a nested if or something, which must be ignored.
-            if current_indentation != inner_indentation:
-                break
-            if match(r'(return(\W+.*)|(break|continue)\s*;|goto\s*\w+;)$', remaining_line):
-                encountered_exit_statement = True
-                continue
-            break
-
-        # When code execution reaches this point, we've found an exit statement
-        # as last statement of the previous block. Now we only need to make
-        # sure that the block belongs to an "if", then we can throw an error.
-
-        # Skip lines with opening braces on the original indentation level,
-        # similar to the closing braces check above. ("if (condition)\n{")
-        if current_line == else_indentation + '{':
-            continue
-
-        # Skip everything that's further indented than our "else" or "else if".
-        if current_indentation.startswith(else_indentation) and current_indentation != else_indentation:
-            continue
-
-        # So we've got a line with same (or less) indentation. Is it an "if"?
-        # If yes: throw an error. If no: don't throw an error.
-        # Whatever the outcome, this is the end of our loop.
-        if match(r'if\s*\(', remaining_line):
-            if else_match.start('else') != -1:
-                error(line_number + line_offset, 'readability/control_flow', 4,
-                      'An else statement can be removed when the prior "if" '
-                      'concludes with a return, break, continue or goto statement.')
-            else:
-                error(line_number + line_offset, 'readability/control_flow', 4,
-                      'An else if statement should be written as an if statement '
-                      'when the prior "if" concludes with a return, break, '
-                      'continue or goto statement.')
-        break
-
-
 def replaceable_check(operator, macro, line):
     """Determine whether a basic CHECK can be replaced with a more specific one.
 
@@ -1786,7 +1697,6 @@
 
     # Some more style checks
     check_ctype_functions(clean_lines, line_number, file_state, error)
-    check_exit_statement_simplifications(clean_lines, line_number, error)
     check_check(clean_lines, line_number, error)
     check_for_comparisons_to_boolean(clean_lines, line_number, error)
 
diff --git a/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py b/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
index 2ac8cc0..c75ba99 100644
--- a/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
+++ b/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
@@ -2252,122 +2252,6 @@
             '}\n',
             '')
 
-        # 3. An else if statement should be written as an if statement
-        #    when the prior if concludes with a return statement.
-        self.assert_multi_line_lint(
-            'if (motivated) {\n'
-            '  if (liquid)\n'
-            '    return money;\n'
-            '} else if (tired) {\n'
-            '  break;\n'
-            '}',
-            '')
-        self.assert_multi_line_lint(
-            'if (condition)\n'
-            '  doSomething();\n'
-            'else if (otherCondition)\n'
-            '  doSomethingElse();\n',
-            '')
-        self.assert_multi_line_lint(
-            'if (condition)\n'
-            '  doSomething();\n'
-            'else\n'
-            '  doSomethingElse();\n',
-            '')
-        self.assert_multi_line_lint(
-            'if (condition)\n'
-            '  returnValue = foo;\n'
-            'else if (otherCondition)\n'
-            '  returnValue = bar;\n',
-            '')
-        self.assert_multi_line_lint(
-            'if (condition)\n'
-            '  returnValue = foo;\n'
-            'else\n'
-            '  returnValue = bar;\n',
-            '')
-        self.assert_multi_line_lint(
-            'if (condition)\n'
-            '  doSomething();\n'
-            'else if (liquid)\n'
-            '  return money;\n'
-            'else if (broke)\n'
-            '  return favor;\n'
-            'else\n'
-            '  sleep(28800);\n',
-            '')
-        self.assert_multi_line_lint(
-            'if (liquid) {\n'
-            '  prepare();\n'
-            '  return money;\n'
-            '} else if (greedy) {\n'
-            '  keep();\n'
-            '  return nothing;\n'
-            '}\n',
-            'An else if statement should be written as an if statement when the '
-            'prior "if" concludes with a return, break, continue or goto statement.'
-            '  [readability/control_flow] [4]')
-        self.assert_multi_line_lint(
-            '  if (stupid) {\n'
-            'infiniteLoop:\n'
-            '    goto infiniteLoop;\n'
-            '  } else if (evil)\n'
-            '    goto hell;\n',
-            ['If one part of an if-else statement uses curly braces, the other part must too.  [whitespace/braces] [4]',
-             'An else if statement should be written as an if statement when the '
-             'prior "if" concludes with a return, break, continue or goto statement.'
-             '  [readability/control_flow] [4]'])
-        self.assert_multi_line_lint(
-            'if (liquid)\n'
-            '{\n'
-            '  prepare();\n'
-            '  return money;\n'
-            '}\n'
-            'else if (greedy)\n'
-            '  keep();\n',
-            ['If one part of an if-else statement uses curly braces, the other part must too.  [whitespace/braces] [4]',
-             'An else if statement should be written as an if statement when the '
-             'prior "if" concludes with a return, break, continue or goto statement.'
-             '  [readability/control_flow] [4]'])
-        self.assert_multi_line_lint(
-            'if (gone)\n'
-            '  return;\n'
-            'else if (here)\n'
-            '  go();\n',
-            'An else if statement should be written as an if statement when the '
-            'prior "if" concludes with a return, break, continue or goto statement.'
-            '  [readability/control_flow] [4]')
-        self.assert_multi_line_lint(
-            'if (gone)\n'
-            '  return;\n'
-            'else\n'
-            '  go();\n',
-            'An else statement can be removed when the prior "if" concludes '
-            'with a return, break, continue or goto statement.'
-            '  [readability/control_flow] [4]')
-        self.assert_multi_line_lint(
-            'if (motivated) {\n'
-            '  prepare();\n'
-            '  continue;\n'
-            '} else {\n'
-            '  cleanUp();\n'
-            '  break;\n'
-            '}\n',
-            'An else statement can be removed when the prior "if" concludes '
-            'with a return, break, continue or goto statement.'
-            '  [readability/control_flow] [4]')
-        self.assert_multi_line_lint(
-            'if (tired)\n'
-            '  break;\n'
-            'else {\n'
-            '  prepare();\n'
-            '  continue;\n'
-            '}\n',
-            ['If one part of an if-else statement uses curly braces, the other part must too.  [whitespace/braces] [4]',
-             'An else statement can be removed when the prior "if" concludes '
-             'with a return, break, continue or goto statement.'
-             '  [readability/control_flow] [4]'])
-
     def test_braces(self):
         # 3. Curly braces are not required for single-line conditionals and
         #    loop bodies, but are required for single-statement bodies that
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 57be1d9..44c74c3 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4760,6 +4760,10 @@
 
 crbug.com/843135 virtual/gpu/fast/canvas/canvas-arc-circumference-fill.html [ Pass Failure ]
 
+crbug.com/941429 virtual/gpu/fast/canvas/canvas-arc-circumference.html [ Failure ]
+crbug.com/941429 virtual/gpu/fast/canvas/canvas-ellipse-circumference-fill.html [ Failure ]
+crbug.com/941429 virtual/gpu/fast/canvas/canvas-ellipse-circumference.html [ Failure ]
+
 # Sheriff 2018-05-31
 crbug.com/848398 http/tests/devtools/oopif/oopif-performance-cpu-profiles.js [ Pass Timeout Failure ]
 
@@ -5399,10 +5403,7 @@
 crbug.com/932078 http/tests/security/powerfulFeatureRestrictions/device-orientation-on-insecure-origin.html [ Skip ]
 crbug.com/932078 virtual/insecure-device-sensor-events/http/tests/security/powerfulFeatureRestrictions/device-orientation-handler-not-fired-on-insecure-origin.html [ Skip ]
 
-# These tests depend on targeting javascript: url navigations at the specified window.
-crbug.com/935064 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html [ Failure ]
 crbug.com/935064 external/wpt/content-security-policy/inheritance/iframe-all-local-schemes.sub.html [ Failure ]
-crbug.com/935064 external/wpt/content-security-policy/unsafe-hashes/javascript_src_allowed-href_blank.html [ Timeout ]
 
 # Sheriff 2019-02-28
 crbug.com/936827 external/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual.html [ Failure Pass ]
@@ -5645,7 +5646,7 @@
 crbug.com/964158 [ Linux ] external/wpt/payment-handler/change-payment-method.https.html [ Pass Crash ]
 crbug.com/899710 [ Linux ] virtual/threaded/http/tests/devtools/tracing/timeline-paint/timeline-paint-with-layout-invalidations.js [ Pass Timeout ]
 crbug.com/964239 external/wpt/css/css-scroll-snap/scroll-margin.html [ Pass Failure ]
-crbug.com/965137 [ Linux Win ] external/wpt/css/css-overflow/webkit-line-clamp-026.html [ Failure ]
+crbug.com/965137 [ Debug ] external/wpt/css/css-overflow/webkit-line-clamp-026.html [ Failure ]
 crbug.com/965134 [ Linux ] fast/events/pointerevents/multi-touch-events.html [ Failure ]
 crbug.com/965134 [ Linux ] virtual/mouseevent_fractional/fast/events/pointerevents/pointer-event-in-slop-region.html [ Failure ]
 crbug.com/965134 [ Linux ] fast/events/pointerevents/pointer-event-in-slop-region.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/editing/OWNERS b/third_party/blink/web_tests/editing/OWNERS
index aa698c2..261aa8c 100644
--- a/third_party/blink/web_tests/editing/OWNERS
+++ b/third_party/blink/web_tests/editing/OWNERS
@@ -1 +1 @@
-# TEAM: chrome-editing@chromium.org
+# TEAM: layout-dev@chromium.org
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/imagebitmap/common.sub.js b/third_party/blink/web_tests/external/wpt/2dcontext/imagebitmap/common.sub.js
index 7f99396..633c182 100644
--- a/third_party/blink/web_tests/external/wpt/2dcontext/imagebitmap/common.sub.js
+++ b/third_party/blink/web_tests/external/wpt/2dcontext/imagebitmap/common.sub.js
@@ -38,6 +38,11 @@
         resolve(video);
     };
     video.onerror = reject;
+
+    // preload=auto is required to ensure a frame is available once
+    // canplaythrough is fired. The default of preload=metadata does not
+    // gaurantee this.
+    video.preload = "auto";
     video.src = getVideoURI("/images/pattern");
 
     // Prevent WebKit from garbage collecting event handlers.
@@ -60,6 +65,11 @@
 
             var encoded = btoa(String.fromCodePoint(...new Uint8Array(data)));
             var dataUrl = `data:${type};base64,${encoded}`;
+
+            // preload=auto is required to ensure a frame is available once
+            // canplaythrough is fired. The default of preload=metadata does not
+            // gaurantee this.
+            video.preload = "auto";
             video.src = dataUrl;
 
             // Prevent WebKit from garbage collecting event handlers.
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html b/third_party/blink/web_tests/external/wpt/2dcontext/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html
index 1e2e05c..a5b2ffa 100644
--- a/third_party/blink/web_tests/external/wpt/2dcontext/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html
+++ b/third_party/blink/web_tests/external/wpt/2dcontext/wide-gamut-canvas/canvas-createImageBitmap-e_srgb.html
@@ -341,6 +341,7 @@
         video.oncanplaythrough = function() {
             resolve(video);
         }
+        video.preload = "auto";
         video.src = 'resources/pattern-srgb-fullcolor.ogv'
     }).then(testImageBitmapVideoSource);
 }, 'createImageBitmap in e-sRGB from a sRGB HTMLVideoElement with resize.');
diff --git a/third_party/blink/web_tests/external/wpt/editing/OWNERS b/third_party/blink/web_tests/external/wpt/editing/OWNERS
index 5b95fdd..42fc5a2 100644
--- a/third_party/blink/web_tests/external/wpt/editing/OWNERS
+++ b/third_party/blink/web_tests/external/wpt/editing/OWNERS
@@ -1,2 +1,2 @@
-# TEAM: editing-dev@chromium.org
+# TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Editing
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/elements-misc.js b/third_party/blink/web_tests/external/wpt/html/dom/elements-misc.js
index df415ae..b747ac6 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/elements-misc.js
+++ b/third_party/blink/web_tests/external/wpt/html/dom/elements-misc.js
@@ -53,6 +53,7 @@
 
   // Global attributes should exist even on unknown elements
   undefinedelement: {
+    enterKeyHint: {type: "enum", keywords: ["enter", "done", "go", "next", "previous", "search", "send"]},
     inputMode: {type: "enum", keywords: ["none", "text", "tel", "url", "email", "numeric", "decimal", "search"]},
   },
 };
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt
index 0de7345..9eb12ff 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 3613 tests; 3570 PASS, 43 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 3613 tests; 3572 PASS, 41 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS Partial interface Document: original interface defined
 PASS Partial interface mixin NavigatorID: original interface mixin defined
@@ -167,7 +167,7 @@
 PASS HTMLElement interface: attribute oncut
 PASS HTMLElement interface: attribute onpaste
 PASS HTMLElement interface: attribute contentEditable
-FAIL HTMLElement interface: attribute enterKeyHint assert_true: The prototype object must have a property "enterKeyHint" expected true got false
+PASS HTMLElement interface: attribute enterKeyHint
 PASS HTMLElement interface: attribute isContentEditable
 PASS HTMLElement interface: attribute inputMode
 PASS HTMLElement interface: attribute dataset
@@ -256,7 +256,7 @@
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "oncut" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "onpaste" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "contentEditable" with the proper type
-FAIL HTMLElement interface: document.createElement("noscript") must inherit property "enterKeyHint" with the proper type assert_inherits: property "enterKeyHint" not found in prototype chain
+PASS HTMLElement interface: document.createElement("noscript") must inherit property "enterKeyHint" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "isContentEditable" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "inputMode" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "dataset" with the proper type
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/reflection-misc-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/reflection-misc-expected.txt
index 0688a78..cbe47bb 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/reflection-misc-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/dom/reflection-misc-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 3727 tests; 3713 PASS, 14 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 3829 tests; 3815 PASS, 14 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS html.title: 32 tests
 PASS html.lang: 32 tests
 PASS html.dir: 62 tests
@@ -118,6 +118,7 @@
 PASS undefinedelement.hidden: 33 tests
 PASS undefinedelement.accessKey: 32 tests
 PASS undefinedelement.tabIndex: 24 tests
+PASS undefinedelement.enterKeyHint: 102 tests
 PASS undefinedelement.inputMode: 112 tests
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/activation/OWNERS b/third_party/blink/web_tests/external/wpt/html/editing/activation/OWNERS
index f65b04ed..261aa8c 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/activation/OWNERS
+++ b/third_party/blink/web_tests/external/wpt/html/editing/activation/OWNERS
@@ -1 +1 @@
-# TEAM: editing-dev@chromium.org
+# TEAM: layout-dev@chromium.org
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/editing-0/contenteditable/OWNERS b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/contenteditable/OWNERS
index f65b04ed..261aa8c 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/editing-0/contenteditable/OWNERS
+++ b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/contenteditable/OWNERS
@@ -1 +1 @@
-# TEAM: editing-dev@chromium.org
+# TEAM: layout-dev@chromium.org
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/editing-0/making-entire-documents-editable-the-designmode-idl-attribute/OWNERS b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/making-entire-documents-editable-the-designmode-idl-attribute/OWNERS
index f65b04ed..261aa8c 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/editing-0/making-entire-documents-editable-the-designmode-idl-attribute/OWNERS
+++ b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/making-entire-documents-editable-the-designmode-idl-attribute/OWNERS
@@ -1 +1 @@
-# TEAM: editing-dev@chromium.org
+# TEAM: layout-dev@chromium.org
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/editing-0/spelling-and-grammar-checking/OWNERS b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/spelling-and-grammar-checking/OWNERS
index f65b04ed..261aa8c 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/editing-0/spelling-and-grammar-checking/OWNERS
+++ b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/spelling-and-grammar-checking/OWNERS
@@ -1 +1 @@
-# TEAM: editing-dev@chromium.org
+# TEAM: layout-dev@chromium.org
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/attributes-common-to-form-controls/contains.json b/third_party/blink/web_tests/external/wpt/html/semantics/forms/attributes-common-to-form-controls/contains.json
index 357a1e6..62326d3 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/attributes-common-to-form-controls/contains.json
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/attributes-common-to-form-controls/contains.json
@@ -24,6 +24,10 @@
         "original_id": "autofocusing-a-form-control:-the-autofocus-attribute"
     },
     {
+        "id": "input-modalities-the-enterkeyhint-attribute",
+        "original_id": "input-modalities:-the-enterkeyhint-attribute"
+    },
+    {
         "id": "input-modalities-the-inputmode-attribute",
         "original_id": "input-modalities:-the-inputmode-attribute"
     },
@@ -31,4 +35,4 @@
         "id": "autofilling-form-controls-the-autocomplete-attribute",
         "original_id": "autofilling-form-controls:-the-autocomplete-attribute"
     }
-]
\ No newline at end of file
+]
diff --git a/third_party/blink/web_tests/external/wpt/selection/OWNERS b/third_party/blink/web_tests/external/wpt/selection/OWNERS
index c2f4a42..259ded51 100644
--- a/third_party/blink/web_tests/external/wpt/selection/OWNERS
+++ b/third_party/blink/web_tests/external/wpt/selection/OWNERS
@@ -1,2 +1,2 @@
-# TEAM: editing-dev@chromium.org
+# TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Editing>Selection
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-helper.js b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-helper.js
index 5426e581..986204d 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-helper.js
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-helper.js
@@ -245,6 +245,22 @@
   await remotePc.setLocalDescription(answer);
 }
 
+// Returns a promise that resolves when the |transport| gets a
+// 'statechange' event with the value |state|.
+// This should work for RTCSctpTransport, RTCDtlsTransport and RTCIceTransport.
+function waitForState(transport, state) {
+  return new Promise((resolve, reject) => {
+    const eventHandler = () => {
+      if (transport.state == state) {
+        transport.removeEventListener('statechange', eventHandler, false);
+        resolve();
+      }
+    };
+    transport.addEventListener('statechange', eventHandler, false);
+  });
+}
+
+
 // Returns a promise that resolves when |pc.iceConnectionState| is 'connected'
 // or 'completed'.
 function listenToIceConnected(pc) {
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCSctpTransport-events.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCSctpTransport-events.html
index 777ac25..dc4ea7b 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCSctpTransport-events.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCSctpTransport-events.html
@@ -7,18 +7,6 @@
 <script>
 'use strict';
 
-function waitForState(transport, state) {
-  return new Promise((resolve, reject) => {
-    const eventHandler = () => {
-      if (transport.state == state) {
-        transport.removeEventListener('statechange', eventHandler, false);
-        resolve();
-      }
-    };
-    transport.addEventListener('statechange', eventHandler, false);
-  });
-}
-
 promise_test(async t => {
   const pc1 = new RTCPeerConnection();
   t.add_cleanup(() => pc1.close());
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCSctpTransport-maxChannels.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCSctpTransport-maxChannels.html
new file mode 100644
index 0000000..e62404a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCSctpTransport-maxChannels.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCSctpTransport.prototype.maxChannels</title>
+<link rel="help" href="https://w3c.github.io/webrtc-pc/#rtcsctptransport-interface">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+promise_test(async (t) => {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+
+  assert_equals(pc.sctp, null, 'RTCSctpTransport must be null');
+  pc.createDataChannel('test');
+  const offer = await pc.createOffer();
+  await pc.setRemoteDescription(offer);
+  const answer = await pc.createAnswer();
+  await pc.setLocalDescription(answer);
+
+  assert_not_equals(pc.sctp, null, 'RTCSctpTransport must be available');
+  assert_equals(pc.sctp.maxChannels, null, 'maxChannels must not be set');
+}, 'An unconnected peerconnection must not have maxChannels set');
+
+promise_test(async (t) => {
+    const pc1 = new RTCPeerConnection();
+  t.add_cleanup(() => pc1.close());
+  const pc2 = new RTCPeerConnection();
+  t.add_cleanup(() => pc2.close());
+  coupleIceCandidates(pc1, pc2);
+  pc1.createDataChannel('');
+  const offer = await pc1.createOffer();
+  await pc1.setLocalDescription(offer);
+  const pc1ConnectedWaiter = waitForState(pc1.sctp, 'connected');
+  await pc2.setRemoteDescription(offer);
+  const pc2ConnectedWaiter = waitForState(pc2.sctp, 'connected');
+  const answer = await pc2.createAnswer();
+  await pc2.setLocalDescription(answer);
+  await pc1.setRemoteDescription(answer);
+  assert_equals(null, pc1.sctp.maxChannels);
+  assert_equals(null, pc2.sctp.maxChannels);
+  await pc1ConnectedWaiter;
+  await pc2ConnectedWaiter;
+  assert_not_equals(null, pc1.sctp.maxChannels);
+  assert_not_equals(null, pc2.sctp.maxChannels);
+  assert_equals(pc1.sctp.maxChannels, pc2.sctp.maxChannels);
+}, 'maxChannels gets instantiated after connecting');
+</script>
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-drawImage.html b/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-drawImage.html
index ba6fe4d..64d2b69 100644
--- a/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-drawImage.html
+++ b/third_party/blink/web_tests/fast/canvas/canvas-createImageBitmap-drawImage.html
@@ -73,12 +73,13 @@
                        t_video.step_func(function() {
                            return runTests(t_video, video);
                         }), false);
+video.preload = "auto";
 video.src = "resources/pattern.ogv";
 
 
 function runTests(t, element) {
     imageBitmaps = {};
-    var p1 = createImageBitmap(element).then(function (image) { 
+    var p1 = createImageBitmap(element).then(function (image) {
         imageBitmaps.noCrop = image });
     var p2 = createImageBitmap(element, 0, 0, 10, 10).then(
         function (image) { imageBitmaps.crop = image });
@@ -176,7 +177,7 @@
     }
 }
 
-function checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+function checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
          transparentPixels, tolerance) {
     checkPixelsRef(redPixel, ctx, redPixels, tolerance);
     checkPixelsRef(greenPixel, ctx, greenPixels, tolerance);
@@ -215,7 +216,7 @@
     bluePixels = [[9, 11]];;
     blackPixels = [[11, 11], [19, 19]];
     transparentPixels = [[1, 21], [21, 1], [21, 21]];
-    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
                 transparentPixels, tolerance);
 
     // shrunk to (0, 0), (10, 10)
@@ -226,7 +227,7 @@
     bluePixels = [[4, 6]];
     blackPixels = [[6, 6], [9, 9]];
     transparentPixels = [[1, 11], [11, 1], [11, 11]];
-    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
                 transparentPixels, tolerance);
 
     // shrunk to (10, 10), (20, 20)
@@ -237,7 +238,7 @@
     bluePixels = [[14, 16]];
     blackPixels = [[16, 16], [19, 19]];
     transparentPixels = [[11, 21], [21, 11], [21, 21]];
-    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
                 transparentPixels, tolerance);
 
     // black should be drawn to (10, 10), (20, 20)
@@ -281,7 +282,7 @@
     bluePixels = [[4, 6]];
     blackPixels = [[6, 6], [9, 9]];
     transparentPixels = [[11, 11], [1, 11], [11, 1]];
-    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
                 transparentPixels, tolerance);
 
     // should be drawn to (0, 0), (20, 20) with all four colors
@@ -292,7 +293,7 @@
     bluePixels = [[8, 11]];
     blackPixels = [[11, 11], [18, 18]];
     transparentPixels = [[22, 22], [1, 21], [21, 1]];
-    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
                 transparentPixels, tolerance);
 }
 
@@ -322,7 +323,7 @@
     bluePixels = [[9, 11]];
     blackPixels = [[11, 11], [19, 19]];
     transparentPixels = [[1, 21], [21, 1], [21, 21]];
-    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
                 transparentPixels, tolerance);
 
     // shrunk to (0, 0), (10, 10)
@@ -333,7 +334,7 @@
     bluePixels = [[4, 6]];
     blackPixels = [[6, 6], [9, 9]];
     transparentPixels = [[1, 11], [11, 1], [11, 11]];
-    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels, 
+    checkPixels(ctx, redPixels, greenPixels, bluePixels, blackPixels,
                 transparentPixels, tolerance);
 }
 
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-drawImage-live-video.html b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-live-video.html
index 3dd5f50..08a501f 100644
--- a/third_party/blink/web_tests/fast/canvas/canvas-drawImage-live-video.html
+++ b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-live-video.html
@@ -21,15 +21,13 @@
   var ctx = canvas.getContext("2d");
 
     var video = document.getElementById("video");
-    video.addEventListener("playing", drawFirstFrame, true);
-    video.play();
+    video.addEventListener("canplaythrough", drawFirstFrame, true);
 
     function drawFirstFrame() {
-      video.removeEventListener("playing", drawFirstFrame, true);
+      video.removeEventListener("canplaythrough", drawFirstFrame, true);
       ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
-      requestAnimationFrame(function() {
-        video.addEventListener("timeupdate", updateVideo, true);
-      });
+      video.addEventListener("timeupdate", updateVideo, true);
+      video.play();
     }
 
     var referenceImageData;
@@ -37,25 +35,28 @@
     var imagesAreTheSame;
 
     function updateVideo() {
-      t.step(function(){
+      t.step(function() {
           if (!processedFirstFrame) {
-          ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
-          referenceImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-          processedFirstFrame = true;
-        } else {
-          video.removeEventListener("timeupdate", updateVideo, true);
-          ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
-          var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-          imagesAreTheSame = true;
-          for(var i = 0; i < imageData.data.length; ++i) {
-            if (imageData.data[i] != referenceImageData.data[i]) {
-              imagesAreTheSame = false;
-              break;
+            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+            referenceImageData = ctx.getImageData(
+                0, 0, canvas.width, canvas.height);
+            processedFirstFrame = true;
+          } else {
+            if (video.currentTime == 0)
+              return;
+            video.removeEventListener("timeupdate", updateVideo, true);
+            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+            var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+            imagesAreTheSame = true;
+            for(var i = 0; i < imageData.data.length; ++i) {
+              if (imageData.data[i] != referenceImageData.data[i]) {
+                imagesAreTheSame = false;
+                break;
+              }
             }
+            assert_false(imagesAreTheSame);
+            t.done();
           }
-          assert_false(imagesAreTheSame);
-          t.done();
-        }
       });
     }
 }, 'Verify that consecutive drawImage from a live video correctly propagates frame updates.');
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-drawImage-video-imageSmoothingEnabled.html b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-video-imageSmoothingEnabled.html
index e46cec7..1fc588c 100644
--- a/third_party/blink/web_tests/fast/canvas/canvas-drawImage-video-imageSmoothingEnabled.html
+++ b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-video-imageSmoothingEnabled.html
@@ -41,6 +41,7 @@
         ctx2.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, width, height);
         compareTwoCanvases(ctx1, ctx2, width, height);
     });
+    video.preload = "auto";
     video.src = "../../compositing/resources/video.ogv";
 }, 'drawImage from a video should look differently with imageSmoothing enabled');
 </script>
diff --git a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-e_srgb.html b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-e_srgb.html
index dcfd071..c55fd15 100644
--- a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-e_srgb.html
+++ b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-e_srgb.html
@@ -347,6 +347,7 @@
         video.oncanplaythrough = function() {
             resolve(video);
         }
+        video.preload = "auto";
         video.src = 'resources/pattern-srgb-fullcolor.ogv'
     }).then(testImageBitmapVideoSource);
 }, 'createImageBitmap in e-sRGB from a sRGB HTMLVideoElement with resize.');
diff --git a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-linear-rgb.html b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-linear-rgb.html
index cfd8572..7f03dc8 100644
--- a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-linear-rgb.html
+++ b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-linear-rgb.html
@@ -347,6 +347,7 @@
         video.oncanplaythrough = function() {
             resolve(video);
         }
+        video.preload = "auto";
         video.src = 'resources/pattern-srgb-fullcolor.ogv'
     }).then(testImageBitmapVideoSource);
 }, 'createImageBitmap in linear RGB from a sRGB HTMLVideoElement with resize.');
diff --git a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-p3.html b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-p3.html
index 210a5c0..a8f37875 100644
--- a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-p3.html
+++ b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-p3.html
@@ -334,6 +334,7 @@
         video.oncanplaythrough = function() {
             resolve(video);
         }
+        video.preload = "auto";
         video.src = 'resources/pattern-srgb-fullcolor.ogv'
     }).then(testImageBitmapFromVideo);
 }, 'createImageBitmap in P3 from a sRGB HTMLVideoElement with resize.');
diff --git a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-rec2020.html b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-rec2020.html
index 01598755..e251cec 100644
--- a/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-rec2020.html
+++ b/third_party/blink/web_tests/fast/canvas/color-space/canvas-createImageBitmap-rec2020.html
@@ -344,6 +344,7 @@
         video.oncanplaythrough = function() {
             resolve(video);
         }
+        video.preload = "auto";
         video.src = 'resources/pattern-srgb-fullcolor.ogv'
     }).then(testImageBitmapFromVideo);
 }, 'createImageBitmap in Rec2020 from a sRGB HTMLVideoElement with resize.');
diff --git a/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video-resize.html b/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video-resize.html
index 9963b752..c7a11bd 100644
--- a/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video-resize.html
+++ b/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video-resize.html
@@ -22,5 +22,6 @@
     prepareResizedImageBitmapsAndRuntTests(testOptions);
 };
 
+video.preload = "auto";
 video.src = "resources/red-green-blue-white-2x2.ogv";
 </script>
diff --git a/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video.html b/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video.html
index b833c35..9d817a1 100644
--- a/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video.html
+++ b/third_party/blink/web_tests/fast/webgl/texImage-imageBitmap-from-video.html
@@ -73,6 +73,7 @@
     video.oncanplaythrough = function() {
         generateTest();
     }
+    video.preload = "auto";
     video.src = "resources/red-green.ogv";
 }
 </script>
diff --git a/third_party/blink/web_tests/gamepad/gamepad-event-listeners.html b/third_party/blink/web_tests/gamepad/gamepad-event-listeners.html
index 43f5df3a..ab01a56 100644
--- a/third_party/blink/web_tests/gamepad/gamepad-event-listeners.html
+++ b/third_party/blink/web_tests/gamepad/gamepad-event-listeners.html
@@ -350,5 +350,33 @@
     testGamepadStateAllDisconnected();
 }, "Remove all connection event listeners while handling a gamepad ID change.");
 
+promise_test(async (t) => {
+    disconnectGamepads();
+    testGamepadStateAllDisconnected();
+
+    // Connect two gamepads.
+    let connectPromise1 = onGamepadEventWithIndex('gamepadconnected', 0);
+    let connectPromise2 = onGamepadEventWithIndex('gamepadconnected', 1);
+    connectGamepads(2);
+    await Promise.all([connectPromise1, connectPromise2]);
+
+    // Ensure that the gamepad state is already updated inside the listener.
+    let disconnectListener = () => {
+        let gamepads = navigator.getGamepads();
+        assert_equals(gamepads[0], null);
+        assert_equals(gamepads[1], null);
+    };
+    window.addEventListener('gamepaddisconnected', disconnectListener);
+
+    // Disconnect both gamepads.
+    let disconnectPromise1 = onGamepadEventWithIndex('gamepaddisconnected', 0);
+    let disconnectPromise2 = onGamepadEventWithIndex('gamepaddisconnected', 1);
+    disconnectGamepads();
+    await Promise.all([disconnectPromise1, disconnectPromise2]);
+
+    window.removeEventListener('gamepaddisconnected', disconnectListener);
+    testGamepadStateAllDisconnected();
+}, "Query state inside multiple callbacks does not cause re-entrancy");
+
 </script>
 </body>
diff --git a/third_party/blink/web_tests/hdr/video-canvas-alpha.html b/third_party/blink/web_tests/hdr/video-canvas-alpha.html
index a24bc9f..b576b2d1 100644
--- a/third_party/blink/web_tests/hdr/video-canvas-alpha.html
+++ b/third_party/blink/web_tests/hdr/video-canvas-alpha.html
@@ -26,6 +26,6 @@
   </script>
  </head>
  <body onload="onLoad();">
-   <video></video><canvas width="320px" height="240px" style="position:relative;left:-100px;"> </canvas>
+   <video preload="auto"></video><canvas width="320px" height="240px" style="position:relative;left:-100px;"> </canvas>
  </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/media/preload-conditions.html b/third_party/blink/web_tests/http/tests/media/preload-conditions.html
index 46e449d..27301db 100644
--- a/third_party/blink/web_tests/http/tests/media/preload-conditions.html
+++ b/third_party/blink/web_tests/http/tests/media/preload-conditions.html
@@ -132,7 +132,7 @@
         break;
       case 'metadata':
         media.onloadedmetadata = t.step_func(_ => {
-          assert_equals(media.readyState, HTMLMediaElement.HAVE_METADATA);
+          assert_greater_than_equal(media.readyState, HTMLMediaElement.HAVE_METADATA);
           --expectedLoading;
           if (expectedLoading == 0)
             runNextTest();
diff --git a/third_party/blink/web_tests/http/tests/media/video-preload-metadata.html b/third_party/blink/web_tests/http/tests/media/video-preload-metadata.html
index d7558405..5c40398 100644
--- a/third_party/blink/web_tests/http/tests/media/video-preload-metadata.html
+++ b/third_party/blink/web_tests/http/tests/media/video-preload-metadata.html
@@ -24,8 +24,8 @@
       return;
     }
 
-    assert_equals(video.buffered.length, 1);
-    assert_equals(video.seekable.length, 1);
+    assert_greater_than_equal(video.buffered.length, 1);
+    assert_greater_than_equal(video.seekable.length, 1);
 
     // If the entire clip wasn't buffered, the buffered range should be smaller
     // than the seekable range; which should be the duration since the server
diff --git a/third_party/blink/web_tests/http/tests/navigation/javascript-url-cross-origin-expected.txt b/third_party/blink/web_tests/http/tests/navigation/javascript-url-cross-origin-expected.txt
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/navigation/javascript-url-cross-origin-expected.txt
@@ -0,0 +1 @@
+PASS
diff --git a/third_party/blink/web_tests/http/tests/navigation/javascript-url-cross-origin.html b/third_party/blink/web_tests/http/tests/navigation/javascript-url-cross-origin.html
new file mode 100644
index 0000000..23c6f58
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/navigation/javascript-url-cross-origin.html
@@ -0,0 +1,19 @@
+<body>
+<script>
+if (window.testRunner) {
+  testRunner.dumpAsText();
+  testRunner.setDumpConsoleMessages(false);
+  testRunner.waitUntilDone();
+  testRunner.setCanOpenWindows();
+}
+
+window.onmessage = e => {
+  document.body.appendChild(document.createTextNode(e.data));
+  if (window.testRunner)
+    testRunner.notifyDone();
+}
+
+name = "target";
+window.open("http://localhost:8000/navigation/resources/javascript-url-cross-origin-window.html");
+</script>
+</body>
diff --git a/third_party/blink/web_tests/http/tests/navigation/resources/javascript-url-cross-origin-window.html b/third_party/blink/web_tests/http/tests/navigation/resources/javascript-url-cross-origin-window.html
new file mode 100644
index 0000000..2a66636
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/navigation/resources/javascript-url-cross-origin-window.html
@@ -0,0 +1,8 @@
+
+<a id="a" href="javascript:postMessage('FAIL', '*');" target="target"></a>
+<script>
+a.click();
+setTimeout(function() {
+  opener.postMessage("PASS", "*");
+}, 0);
+</script>
diff --git a/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin-expected.txt b/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin-expected.txt
index c694a96..a2e3f22 100644
--- a/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin-expected.txt
@@ -1,2 +1 @@
-CONSOLE ERROR: line 1: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8080". Protocols, domains, and ports must match.
 This test passes if there's no alert dialog.
diff --git a/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin.html b/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin.html
index ac84a69..8ca5b8f 100644
--- a/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin.html
+++ b/third_party/blink/web_tests/http/tests/security/synchronous-frame-load-in-javascript-url-inherits-correct-origin.html
@@ -5,6 +5,7 @@
 	testRunner.dumpAsText();
 	testRunner.waitUntilDone();
 	testRunner.setCanOpenWindows();
+	testRunner.setDumpConsoleMessages(false);
 	testRunner.setCloseRemainingWindowsWhenComplete(true);
 }
 
@@ -23,7 +24,7 @@
 				a.click();
 
 				return "<script>(" + function() {
-					opener.location = "javascript:alert(location)";
+                    try { opener.location = "javascript:alert(location)"; } catch (e) {}
 
 					if (window.testRunner)
 						testRunner.notifyDone();
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript-expected.txt b/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript-expected.txt
index fb569d11..26670d3 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript-expected.txt
@@ -1,4 +1,3 @@
-CONSOLE ERROR: line 9: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
 
 
 --------
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript.html b/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript.html
index 28d239e5..53ec901 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript.html
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-assign-location-href-javascript.html
@@ -3,6 +3,7 @@
 if (window.testRunner) {
   testRunner.dumpAsText();
   testRunner.dumpChildFrames();
+  testRunner.setDumpConsoleMessages(false);
 }
 
 function runTest() {
@@ -10,7 +11,7 @@
 }
 
 </script>
-<body onload="runTest()">
-<iframe src="http://localhost:8000/security/resources/localPage.html"></iframe>
+<body>
+<iframe onload="runTest()" src="http://localhost:8000/security/resources/localPage.html"></iframe>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations-expected.txt b/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations-expected.txt
index da81816..25a3dd4 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations-expected.txt
@@ -1,20 +1,20 @@
-CONSOLE ERROR: line 13: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 19: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 14: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 20: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 16: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 21: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 19: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 19: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 20: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 20: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 22: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 21: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 24: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 19: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 25: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 20: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
-CONSOLE ERROR: line 27: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
+CONSOLE ERROR: line 21: Unsafe JavaScript attempt to initiate navigation for frame with URL 'data:text/html,<p>Inner text should not be replaced.<p>' from frame with URL 'http://127.0.0.1:8000/security/xss-DENIED-javascript-variations.html'. The frame attempting navigation must be same-origin with the target if navigating to a javascript: url
 
 
 
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations.html b/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations.html
index 2b9ddba..bf643598 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations.html
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-javascript-variations.html
@@ -6,25 +6,20 @@
   testRunner.dumpChildFrames();
 }
 
+var cases = [
+  " javascript:document.write('FAIL')",
+  "javascript\t:document.write('FAIL')",
+  "javascript:document.write('FAIL')"
+];
+
 function runTest() {
   var a = window.frames[0];
-// java\0script is invalid url.
 
-  a.location.href = " javascript:document.write('FAIL')";
-  a.location.href = "javascript\t:document.write('FAIL')";
-
-  a.location.href = "javascript:document.write('FAIL')";
-
-
-  a.location.replace(" javascript:document.write('FAIL')");
-  a.location.replace("javascript\t:document.write('FAIL')");
-
-  a.location.replace("javascript:document.write('FAIL')");
-
-  a.location = " javascript:document.write('FAIL')";
-  a.location = "javascript\t:document.write('FAIL')";
-
-  a.location = "javascript:document.write('FAIL')";
+  for (var i = 0; i < cases.length; i++) {
+    try { a.location.href = cases[i]; } catch(e) {}
+    try { a.location.replace(cases[i]); } catch(e) {}
+    try { a.location = cases[i]; } catch(e) {}
+  }
 }
 </script>
 
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-expected.txt b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-expected.txt
index 14edb39..9567ca6 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-expected.txt
@@ -1,3 +1,2 @@
-CONSOLE ERROR: line 13: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8080". Protocols, domains, and ports must match.
 This test passes if there is no alert dialog.
 
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces-expected.txt b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces-expected.txt
index 14edb39..9567ca6 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces-expected.txt
@@ -1,3 +1,2 @@
-CONSOLE ERROR: line 13: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8080". Protocols, domains, and ports must match.
 This test passes if there is no alert dialog.
 
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces.html b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces.html
index d0e28e3..9be76c6 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces.html
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url-with-spaces.html
@@ -4,6 +4,7 @@
 <script>
 if (window.testRunner) {
     testRunner.dumpAsText();
+    testRunner.setDumpConsoleMessages(false);
     testRunner.waitUntilDone();
 }
 function runTest() {
diff --git a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url.html b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url.html
index c237e6e7..e929e71d 100644
--- a/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url.html
+++ b/third_party/blink/web_tests/http/tests/security/xss-DENIED-window-open-javascript-url.html
@@ -4,6 +4,7 @@
 <script>
 if (window.testRunner) {
     testRunner.dumpAsText();
+    testRunner.setDumpConsoleMessages(false);
     testRunner.waitUntilDone();
 }
 function runTest() {
diff --git a/third_party/blink/web_tests/media/alpha-video-playback.html b/third_party/blink/web_tests/media/alpha-video-playback.html
index 71aa824..839c7063 100644
--- a/third_party/blink/web_tests/media/alpha-video-playback.html
+++ b/third_party/blink/web_tests/media/alpha-video-playback.html
@@ -28,6 +28,6 @@
   }
 </script>
 <body onload="startTest();">
-  <video width="320" height="240"></video>
+  <video preload="auto" width="320" height="240"></video>
   <canvas width="320" height="240"></canvas>
 </body>
diff --git a/third_party/blink/web_tests/media/video-buffered-unknown-duration.html b/third_party/blink/web_tests/media/video-buffered-unknown-duration.html
index 3cbce76..eed10cf 100644
--- a/third_party/blink/web_tests/media/video-buffered-unknown-duration.html
+++ b/third_party/blink/web_tests/media/video-buffered-unknown-duration.html
@@ -23,9 +23,13 @@
 
     video.onloadeddata = t.step_func(function() {
         video.onloadeddata = null;
-        assert_equals(video.buffered.length, 1);
-        assert_greater_than_equal(video.buffered.start(0), 0);
-        assert_not_equals(video.buffered.end(0), Infinity);
+
+        // We may not have video.buffered.length at this time, but if we do it
+        // should be between 0 and some value less than infinity.
+        if (video.buffered.length > 0) {
+            assert_greater_than_equal(video.buffered.start(0), 0);
+            assert_not_equals(video.buffered.end(0), Infinity);
+        }
         assert_equals(video.currentTime, 0);
         assert_equals(video.duration, Infinity);
         video.play();
diff --git a/third_party/blink/web_tests/media/video-canvas-alpha.html b/third_party/blink/web_tests/media/video-canvas-alpha.html
index 82c438b..39fbf7d4 100644
--- a/third_party/blink/web_tests/media/video-canvas-alpha.html
+++ b/third_party/blink/web_tests/media/video-canvas-alpha.html
@@ -26,6 +26,6 @@
   </script>
  </head>
  <body onload="onLoad();">
-   <video></video><canvas width="320px" height="240px" style="position:relative;left:-100px;"> </canvas>
+   <video preload="auto"></video><canvas width="320px" height="240px" style="position:relative;left:-100px;"> </canvas>
  </body>
 </html>
diff --git a/third_party/blink/web_tests/media/video-persistence.html b/third_party/blink/web_tests/media/video-persistence.html
index 4f31080..298a26a 100644
--- a/third_party/blink/web_tests/media/video-persistence.html
+++ b/third_party/blink/web_tests/media/video-persistence.html
@@ -22,7 +22,7 @@
 </style>
 <div id='player'>
   <div id='container'>
-    <video></video>
+    <video preload="auto"></video>
   </div>
   <div id='controls'>
     <button>foo</button><button>bar</button>
@@ -40,7 +40,7 @@
   if (window.testRunner && window.eventSender && window.internals) {
     testRunner.waitUntilDone();
 
-    video.addEventListener('loadedmetadata', e => {
+    video.addEventListener('canplaythrough', e => {
       var bounding = document.querySelector('#fs').getBoundingClientRect();
       eventSender.mouseMoveTo(bounding.left + bounding.width / 2,
                               bounding.top + bounding.height / 2);
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 835f0b5..8f5d74c 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -883,6 +883,7 @@
     getter paymentRequestOrigin
     getter topOrigin
     getter total
+    method changePaymentMethod
     method constructor
     method openWindow
     method respondWith
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index e69f600..cc366a2b 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -4561,9 +4561,15 @@
     getter userHint
     method constructor
     setter userHint
+interface PaymentMethodChangeEvent : PaymentRequestUpdateEvent
+    attribute @@toStringTag
+    getter methodDetails
+    getter methodName
+    method constructor
 interface PaymentRequest : EventTarget
     attribute @@toStringTag
     getter id
+    getter onpaymentmethodchange
     getter onshippingaddresschange
     getter onshippingoptionchange
     getter shippingAddress
@@ -4574,6 +4580,7 @@
     method constructor
     method hasEnrolledInstrument
     method show
+    setter onpaymentmethodchange
     setter onshippingaddresschange
     setter onshippingoptionchange
 interface PaymentRequestUpdateEvent : Event
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCSctpTransport-maxChannels-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCSctpTransport-maxChannels-expected.txt
new file mode 100644
index 0000000..41a583a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCSctpTransport-maxChannels-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = Cannot read property 'addEventListener' of null
+FAIL An unconnected peerconnection must not have maxChannels set assert_not_equals: RTCSctpTransport must be available got disallowed value null
+FAIL maxChannels gets instantiated after connecting promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'maxChannels' of null"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
index ce383ee..d199ffb5 100644
--- a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
@@ -106,6 +106,7 @@
     property dir
     property dispatchEvent
     property draggable
+    property enterKeyHint
     property firstChild
     property firstElementChild
     property focus
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index b995799..c776896 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -2718,6 +2718,7 @@
     getter dataset
     getter dir
     getter draggable
+    getter enterKeyHint
     getter hidden
     getter inert
     getter innerText
@@ -2830,6 +2831,7 @@
     setter contentEditable
     setter dir
     setter draggable
+    setter enterKeyHint
     setter hidden
     setter inert
     setter innerText
diff --git a/third_party/sqlite/BUILD.gn b/third_party/sqlite/BUILD.gn
index c18755f..a766730 100644
--- a/third_party/sqlite/BUILD.gn
+++ b/third_party/sqlite/BUILD.gn
@@ -384,28 +384,21 @@
 if (is_win || is_mac || is_linux) {
   executable("sqlite_shell") {
     include_dirs = [
-      # shell.c contains an '#include "sqlite3.h", which we want to be
-      # resolved to //third_party/sqlite/shell.h.
+      # SQLite's shell.c contains an '#include "sqlite3.h", which we want to be
+      # resolved to //third_party/sqlite/sqlite3.h.
       ".",
     ]
 
     sources = [
-      "amalgamation/shell/shell.c",
-
-      # Include a dummy c++ file to force linking of libstdc++.
-      "build_as_cpp.cc",
+      "sqlite_shell_icu_helper.cc",
+      "sqlite_shell_icu_helper.h",
+      "sqlite_shell_shim.c",
     ]
 
-    if (is_linux) {
-      sources += [ "patched/src/shell_icu_linux.c" ]
-    }
-
-    if (is_win) {
-      sources += [ "patched/src/shell_icu_win.c" ]
-    }
-
     deps = [
       ":sqlite",
+      "//base",
+      "//base:i18n",
       "//third_party/icu",
     ]
 
diff --git a/third_party/sqlite/DEPS b/third_party/sqlite/DEPS
new file mode 100644
index 0000000..f261b62
--- /dev/null
+++ b/third_party/sqlite/DEPS
@@ -0,0 +1,6 @@
+specific_include_rules = {
+  'sqlite_shell_icu_helper\.cc': [
+    '+base',
+  ],
+}
+
diff --git a/third_party/sqlite/amalgamation/shell/shell.c b/third_party/sqlite/amalgamation/shell/shell.c
index 6c4a4fa6..2f89439 100644
--- a/third_party/sqlite/amalgamation/shell/shell.c
+++ b/third_party/sqlite/amalgamation/shell/shell.c
@@ -16789,16 +16789,6 @@
   }
 #endif
 
-  /* Begin evanm patch. */
-#if !defined(__APPLE__)
-  extern int sqlite_shell_init_icu();
-  if( !sqlite_shell_init_icu() ){
-    fprintf(stderr, "%s: warning: couldn't find icudt38.dll; "
-                    "queries against ICU FTS tables will fail.\n", argv[0]);
-  }
-#endif /* !defined(__APPLE__) */
-  /* End evanm patch. */
-
   /* Do an initial pass through the command-line argument to locate
   ** the name of the database file, the name of the initialization file,
   ** the size of the alternative malloc heap,
diff --git a/third_party/sqlite/build_as_cpp.cc b/third_party/sqlite/build_as_cpp.cc
deleted file mode 100644
index 988c0cc..0000000
--- a/third_party/sqlite/build_as_cpp.cc
+++ /dev/null
@@ -1,3 +0,0 @@
-// Copyright 2013 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.
diff --git a/third_party/sqlite/patched/Makefile.linux-gcc b/third_party/sqlite/patched/Makefile.linux-gcc
index 3047d17..b838b84 100644
--- a/third_party/sqlite/patched/Makefile.linux-gcc
+++ b/third_party/sqlite/patched/Makefile.linux-gcc
@@ -60,13 +60,6 @@
 OPTS = -DNDEBUG=1
 OPTS += -DHAVE_FDATASYNC=1
 
-# Support for loading Chromium ICU data in sqlite3.
-ifeq ($(shell uname -s),Darwin)
-SHELL_ICU =
-else
-SHELL_ICU = $(TOP)/src/shell_icu_linux.c -licuuc
-endif
-
 #### The suffix to add to executable files.  ".exe" for windows.
 #    Nothing for unix.
 #
diff --git a/third_party/sqlite/patched/main.mk b/third_party/sqlite/patched/main.mk
index 4d2b9ba..f418eec 100644
--- a/third_party/sqlite/patched/main.mk
+++ b/third_party/sqlite/patched/main.mk
@@ -552,7 +552,7 @@
 
 sqlite3$(EXE):	shell.c libsqlite3.a sqlite3.h
 	$(TCCX) $(READLINE_FLAGS) -o sqlite3$(EXE) $(SHELL_OPT) \
-		shell.c $(SHELL_ICU) libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB)
+		shell.c libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB)
 
 sqldiff$(EXE):	$(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
 	$(TCCX) -o sqldiff$(EXE) -DSQLITE_THREADSAFE=0 \
diff --git a/third_party/sqlite/patched/src/shell.c.in b/third_party/sqlite/patched/src/shell.c.in
index abf1850..87188c6 100644
--- a/third_party/sqlite/patched/src/shell.c.in
+++ b/third_party/sqlite/patched/src/shell.c.in
@@ -9106,16 +9106,6 @@
   }
 #endif
 
-  /* Begin evanm patch. */
-#if !defined(__APPLE__)
-  extern int sqlite_shell_init_icu();
-  if( !sqlite_shell_init_icu() ){
-    fprintf(stderr, "%s: warning: couldn't find icudt38.dll; "
-                    "queries against ICU FTS tables will fail.\n", argv[0]);
-  }
-#endif /* !defined(__APPLE__) */
-  /* End evanm patch. */
-
   /* Do an initial pass through the command-line argument to locate
   ** the name of the database file, the name of the initialization file,
   ** the size of the alternative malloc heap,
diff --git a/third_party/sqlite/patched/src/shell_icu_linux.c b/third_party/sqlite/patched/src/shell_icu_linux.c
deleted file mode 100644
index 4ad0e42..0000000
--- a/third_party/sqlite/patched/src/shell_icu_linux.c
+++ /dev/null
@@ -1,27 +0,0 @@
-/* Copyright 2007 Google Inc. All Rights Reserved.
-**/
-
-#include <limits.h>
-#include <unistd.h>
-#include "unicode/putil.h"
-#include "unicode/udata.h"
-
-/*
-** This function attempts to load the ICU data tables from a data file.
-** Returns 0 on failure, nonzero on success.
-** This a hack job of icu_utils.cc:Initialize().  It's Chrome-specific code.
-*/
-int sqlite_shell_init_icu() {
-  char bin_dir[PATH_MAX + 1];
-  int bin_dir_size = readlink("/proc/self/exe", bin_dir, PATH_MAX);
-  if (bin_dir_size < 0 || bin_dir_size > PATH_MAX)
-    return 0;
-  bin_dir[bin_dir_size] = 0;;
-
-  u_setDataDirectory(bin_dir);
-  // Only look for the packaged data file;
-  // the default behavior is to look for individual files.
-  UErrorCode err = U_ZERO_ERROR;
-  udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
-  return err == U_ZERO_ERROR;
-}
diff --git a/third_party/sqlite/patched/src/shell_icu_win.c b/third_party/sqlite/patched/src/shell_icu_win.c
deleted file mode 100644
index 67ebbf4..0000000
--- a/third_party/sqlite/patched/src/shell_icu_win.c
+++ /dev/null
@@ -1,32 +0,0 @@
-/* Copyright 2011 Google Inc. All Rights Reserved.
-**/
-
-#include <windows.h>
-#include "unicode/udata.h"
-
-/*
-** This function attempts to load the ICU data tables from a DLL.
-** Returns 0 on failure, nonzero on success.
-** This a hack job of icu_utils.cc:Initialize().  It's Chrome-specific code.
-*/
-
-#define ICU_DATA_SYMBOL "icudt" U_ICU_VERSION_SHORT "_dat"
-int sqlite_shell_init_icu() {
-  HMODULE module;
-  FARPROC addr;
-  UErrorCode err;
-
-  // Chrome dropped U_ICU_VERSION_SHORT from the icu data dll name.
-  module = LoadLibrary(L"icudt.dll");
-  if (!module)
-    return 0;
-
-  addr = GetProcAddress(module, ICU_DATA_SYMBOL);
-  if (!addr)
-    return 0;
-
-  err = U_ZERO_ERROR;
-  udata_setCommonData(addr, &err);
-
-  return 1;
-}
diff --git a/third_party/sqlite/patches/0001-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch b/third_party/sqlite/patches/0001-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
deleted file mode 100644
index 910bd2b..0000000
--- a/third_party/sqlite/patches/0001-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
+++ /dev/null
@@ -1,145 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: "tc@google.com" <tc@google.com>
-Date: Tue, 6 Jan 2009 22:39:41 +0000
-Subject: [PATCH 1/9] Custom shell.c helpers to load Chromium's ICU data.
-
-History uses fts3 with an icu-based segmenter.  These changes allow building a
-sqlite3 binary for Linux or Windows which can read those files.
-
-Original review URL: https://codereview.chromium.org/42250
----
- third_party/sqlite/patched/Makefile.linux-gcc |  7 ++++
- third_party/sqlite/patched/main.mk            |  2 +-
- third_party/sqlite/patched/src/shell.c.in     | 10 ++++++
- .../sqlite/patched/src/shell_icu_linux.c      | 27 ++++++++++++++++
- .../sqlite/patched/src/shell_icu_win.c        | 32 +++++++++++++++++++
- 5 files changed, 77 insertions(+), 1 deletion(-)
- create mode 100644 third_party/sqlite/patched/src/shell_icu_linux.c
- create mode 100644 third_party/sqlite/patched/src/shell_icu_win.c
-
-diff --git a/third_party/sqlite/patched/Makefile.linux-gcc b/third_party/sqlite/patched/Makefile.linux-gcc
-index b838b844a312..3047d172389b 100644
---- a/third_party/sqlite/patched/Makefile.linux-gcc
-+++ b/third_party/sqlite/patched/Makefile.linux-gcc
-@@ -60,6 +60,13 @@ TLIBS =
- OPTS = -DNDEBUG=1
- OPTS += -DHAVE_FDATASYNC=1
- 
-+# Support for loading Chromium ICU data in sqlite3.
-+ifeq ($(shell uname -s),Darwin)
-+SHELL_ICU =
-+else
-+SHELL_ICU = $(TOP)/src/shell_icu_linux.c -licuuc
-+endif
-+
- #### The suffix to add to executable files.  ".exe" for windows.
- #    Nothing for unix.
- #
-diff --git a/third_party/sqlite/patched/main.mk b/third_party/sqlite/patched/main.mk
-index f418eec68aed..4d2b9bae5bbf 100644
---- a/third_party/sqlite/patched/main.mk
-+++ b/third_party/sqlite/patched/main.mk
-@@ -552,7 +552,7 @@ libsqlite3.a:	$(LIBOBJ)
- 
- sqlite3$(EXE):	shell.c libsqlite3.a sqlite3.h
- 	$(TCCX) $(READLINE_FLAGS) -o sqlite3$(EXE) $(SHELL_OPT) \
--		shell.c libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB)
-+		shell.c $(SHELL_ICU) libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB)
- 
- sqldiff$(EXE):	$(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
- 	$(TCCX) -o sqldiff$(EXE) -DSQLITE_THREADSAFE=0 \
-diff --git a/third_party/sqlite/patched/src/shell.c.in b/third_party/sqlite/patched/src/shell.c.in
-index 87188c6aff7d..abf1850307c9 100644
---- a/third_party/sqlite/patched/src/shell.c.in
-+++ b/third_party/sqlite/patched/src/shell.c.in
-@@ -9106,6 +9106,16 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
-   }
- #endif
- 
-+  /* Begin evanm patch. */
-+#if !defined(__APPLE__)
-+  extern int sqlite_shell_init_icu();
-+  if( !sqlite_shell_init_icu() ){
-+    fprintf(stderr, "%s: warning: couldn't find icudt38.dll; "
-+                    "queries against ICU FTS tables will fail.\n", argv[0]);
-+  }
-+#endif /* !defined(__APPLE__) */
-+  /* End evanm patch. */
-+
-   /* Do an initial pass through the command-line argument to locate
-   ** the name of the database file, the name of the initialization file,
-   ** the size of the alternative malloc heap,
-diff --git a/third_party/sqlite/patched/src/shell_icu_linux.c b/third_party/sqlite/patched/src/shell_icu_linux.c
-new file mode 100644
-index 000000000000..4ad0e42d2293
---- /dev/null
-+++ b/third_party/sqlite/patched/src/shell_icu_linux.c
-@@ -0,0 +1,27 @@
-+/* Copyright 2007 Google Inc. All Rights Reserved.
-+**/
-+
-+#include <limits.h>
-+#include <unistd.h>
-+#include "unicode/putil.h"
-+#include "unicode/udata.h"
-+
-+/*
-+** This function attempts to load the ICU data tables from a data file.
-+** Returns 0 on failure, nonzero on success.
-+** This a hack job of icu_utils.cc:Initialize().  It's Chrome-specific code.
-+*/
-+int sqlite_shell_init_icu() {
-+  char bin_dir[PATH_MAX + 1];
-+  int bin_dir_size = readlink("/proc/self/exe", bin_dir, PATH_MAX);
-+  if (bin_dir_size < 0 || bin_dir_size > PATH_MAX)
-+    return 0;
-+  bin_dir[bin_dir_size] = 0;;
-+
-+  u_setDataDirectory(bin_dir);
-+  // Only look for the packaged data file;
-+  // the default behavior is to look for individual files.
-+  UErrorCode err = U_ZERO_ERROR;
-+  udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
-+  return err == U_ZERO_ERROR;
-+}
-diff --git a/third_party/sqlite/patched/src/shell_icu_win.c b/third_party/sqlite/patched/src/shell_icu_win.c
-new file mode 100644
-index 000000000000..67ebbf4fbdb4
---- /dev/null
-+++ b/third_party/sqlite/patched/src/shell_icu_win.c
-@@ -0,0 +1,32 @@
-+/* Copyright 2011 Google Inc. All Rights Reserved.
-+**/
-+
-+#include <windows.h>
-+#include "unicode/udata.h"
-+
-+/*
-+** This function attempts to load the ICU data tables from a DLL.
-+** Returns 0 on failure, nonzero on success.
-+** This a hack job of icu_utils.cc:Initialize().  It's Chrome-specific code.
-+*/
-+
-+#define ICU_DATA_SYMBOL "icudt" U_ICU_VERSION_SHORT "_dat"
-+int sqlite_shell_init_icu() {
-+  HMODULE module;
-+  FARPROC addr;
-+  UErrorCode err;
-+
-+  // Chrome dropped U_ICU_VERSION_SHORT from the icu data dll name.
-+  module = LoadLibrary(L"icudt.dll");
-+  if (!module)
-+    return 0;
-+
-+  addr = GetProcAddress(module, ICU_DATA_SYMBOL);
-+  if (!addr)
-+    return 0;
-+
-+  err = U_ZERO_ERROR;
-+  udata_setCommonData(addr, &err);
-+
-+  return 1;
-+}
--- 
-2.21.0.1020.gf2820cf01a-goog
-
diff --git a/third_party/sqlite/patches/0002-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch b/third_party/sqlite/patches/0001-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
similarity index 90%
rename from third_party/sqlite/patches/0002-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
rename to third_party/sqlite/patches/0001-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
index 0403573..dd9284a 100644
--- a/third_party/sqlite/patches/0002-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
+++ b/third_party/sqlite/patches/0001-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 1 May 2019 14:44:32 -0700
-Subject: [PATCH 2/9] Don't generate VDBE code for VACUUM after a syntax error
+Subject: [PATCH 1/8] Don't generate VDBE code for VACUUM after a syntax error
 
 This backports https://www.sqlite.org/src/info/930842470da27d72
 
@@ -23,5 +23,5 @@
  #ifndef SQLITE_BUG_COMPATIBLE_20160819
      /* Default behavior:  Report an error if the argument to VACUUM is
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/patches/0003-Fix-use-after-free-xDestroy-error.patch b/third_party/sqlite/patches/0002-Fix-use-after-free-xDestroy-error.patch
similarity index 96%
rename from third_party/sqlite/patches/0003-Fix-use-after-free-xDestroy-error.patch
rename to third_party/sqlite/patches/0002-Fix-use-after-free-xDestroy-error.patch
index 9d0aec7..1838d06 100644
--- a/third_party/sqlite/patches/0003-Fix-use-after-free-xDestroy-error.patch
+++ b/third_party/sqlite/patches/0002-Fix-use-after-free-xDestroy-error.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 1 May 2019 14:47:06 -0700
-Subject: [PATCH 3/9] Fix use-after-free xDestroy error
+Subject: [PATCH 2/8] Fix use-after-free xDestroy error
 
 This backports https://www.sqlite.org/src/info/1dbbb0101e8213b9
 
@@ -82,5 +82,5 @@
 +
 +finish_test
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/patches/0004-Fix-memory-leak-segfault.patch b/third_party/sqlite/patches/0003-Fix-memory-leak-segfault.patch
similarity index 97%
rename from third_party/sqlite/patches/0004-Fix-memory-leak-segfault.patch
rename to third_party/sqlite/patches/0003-Fix-memory-leak-segfault.patch
index 1a386bdc..34404b2 100644
--- a/third_party/sqlite/patches/0004-Fix-memory-leak-segfault.patch
+++ b/third_party/sqlite/patches/0003-Fix-memory-leak-segfault.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Fri, 3 May 2019 14:46:06 -0700
-Subject: [PATCH 4/9] Fix memory-leak/segfault
+Subject: [PATCH 3/8] Fix memory-leak/segfault
 
 Backports https://www.sqlite.org/src/info/a9b90aa12eecdd9f
 
@@ -82,5 +82,5 @@
 +
  finish_test
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/patches/0005-Ensure-UTF16-strings-are-zero-terminated.patch b/third_party/sqlite/patches/0004-Ensure-UTF16-strings-are-zero-terminated.patch
similarity index 95%
rename from third_party/sqlite/patches/0005-Ensure-UTF16-strings-are-zero-terminated.patch
rename to third_party/sqlite/patches/0004-Ensure-UTF16-strings-are-zero-terminated.patch
index 43ad5bd..1a90dc7 100644
--- a/third_party/sqlite/patches/0005-Ensure-UTF16-strings-are-zero-terminated.patch
+++ b/third_party/sqlite/patches/0004-Ensure-UTF16-strings-are-zero-terminated.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Fri, 3 May 2019 14:49:17 -0700
-Subject: [PATCH 5/9] Ensure UTF16 strings are zero-terminated
+Subject: [PATCH 4/8] Ensure UTF16 strings are zero-terminated
 
 Backports https://www.sqlite.org/src/info/3a16ddf91f0c9c51
 
@@ -49,5 +49,5 @@
  ** Existing representations MEM_Int and MEM_Real are invalidated if
  ** bForce is true but are retained if bForce is false.
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/patches/0006-Detect-errors-in-byte-offset.patch b/third_party/sqlite/patches/0005-Detect-errors-in-byte-offset.patch
similarity index 94%
rename from third_party/sqlite/patches/0006-Detect-errors-in-byte-offset.patch
rename to third_party/sqlite/patches/0005-Detect-errors-in-byte-offset.patch
index cec9e7d1..5a655a54 100644
--- a/third_party/sqlite/patches/0006-Detect-errors-in-byte-offset.patch
+++ b/third_party/sqlite/patches/0005-Detect-errors-in-byte-offset.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 15 May 2019 18:32:47 -0700
-Subject: [PATCH 6/9] Detect errors in byte offset
+Subject: [PATCH 5/8] Detect errors in byte offset
 
 Backports https://www.sqlite.org/src/vdiff?from=3c75605b4652ae88&to=ad8fc5d8b440c49d
 
@@ -33,5 +33,5 @@
    }
    pPage->nFree = (u16)(nFree - iCellFirst);
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/patches/0007-Ensure-correct-zero-termination-of-UTF16-strings.patch b/third_party/sqlite/patches/0006-Ensure-correct-zero-termination-of-UTF16-strings.patch
similarity index 97%
rename from third_party/sqlite/patches/0007-Ensure-correct-zero-termination-of-UTF16-strings.patch
rename to third_party/sqlite/patches/0006-Ensure-correct-zero-termination-of-UTF16-strings.patch
index 3bbd93e..77e469b1 100644
--- a/third_party/sqlite/patches/0007-Ensure-correct-zero-termination-of-UTF16-strings.patch
+++ b/third_party/sqlite/patches/0006-Ensure-correct-zero-termination-of-UTF16-strings.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 15 May 2019 18:39:12 -0700
-Subject: [PATCH 7/9] Ensure correct zero-termination of UTF16 strings
+Subject: [PATCH 6/8] Ensure correct zero-termination of UTF16 strings
 
 Backports https://www.sqlite.org/src/info/d612fb7873cf59df
 
@@ -91,5 +91,5 @@
    }
    if( pVal->flags&MEM_Null ){
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/patches/0008-Fix-OP_Delete-assert.patch b/third_party/sqlite/patches/0007-Fix-OP_Delete-assert.patch
similarity index 98%
rename from third_party/sqlite/patches/0008-Fix-OP_Delete-assert.patch
rename to third_party/sqlite/patches/0007-Fix-OP_Delete-assert.patch
index 8a77058a..183c5e4f 100644
--- a/third_party/sqlite/patches/0008-Fix-OP_Delete-assert.patch
+++ b/third_party/sqlite/patches/0007-Fix-OP_Delete-assert.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Fri, 17 May 2019 16:00:46 -0700
-Subject: [PATCH 8/9] Fix OP_Delete assert
+Subject: [PATCH 7/8] Fix OP_Delete assert
 
 Backports https://www.sqlite.org/src/info/915388ab39ba3ca8
 
@@ -87,5 +87,5 @@
 +
  finish_test
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/patches/0009-Initialize-18-byte-overrun-area-for-btree.patch b/third_party/sqlite/patches/0008-Initialize-18-byte-overrun-area-for-btree.patch
similarity index 94%
rename from third_party/sqlite/patches/0009-Initialize-18-byte-overrun-area-for-btree.patch
rename to third_party/sqlite/patches/0008-Initialize-18-byte-overrun-area-for-btree.patch
index 9147d2a..af21025 100644
--- a/third_party/sqlite/patches/0009-Initialize-18-byte-overrun-area-for-btree.patch
+++ b/third_party/sqlite/patches/0008-Initialize-18-byte-overrun-area-for-btree.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Fri, 17 May 2019 16:03:01 -0700
-Subject: [PATCH 9/9] Initialize 18-byte overrun area for btree
+Subject: [PATCH 8/8] Initialize 18-byte overrun area for btree
 
 Backports https://sqlite.org/src/info/4b05caeb1b9767ba
 
@@ -39,5 +39,5 @@
            if( rc ){
              sqlite3_free(pCellKey);
 -- 
-2.21.0.1020.gf2820cf01a-goog
+2.21.0
 
diff --git a/third_party/sqlite/sqlite_shell_icu_helper.cc b/third_party/sqlite/sqlite_shell_icu_helper.cc
new file mode 100644
index 0000000..b96c0d2
--- /dev/null
+++ b/third_party/sqlite/sqlite_shell_icu_helper.cc
@@ -0,0 +1,12 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/sqlite/sqlite_shell_icu_helper.h"
+
+#include "base/i18n/icu_util.h"
+#include "base/logging.h"
+
+void InitializeICUForSqliteShell() {
+  CHECK(base::i18n::InitializeICU());
+}
\ No newline at end of file
diff --git a/third_party/sqlite/sqlite_shell_icu_helper.h b/third_party/sqlite/sqlite_shell_icu_helper.h
new file mode 100644
index 0000000..70eb76f
--- /dev/null
+++ b/third_party/sqlite/sqlite_shell_icu_helper.h
@@ -0,0 +1,23 @@
+// 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_SQLITE_SQLITE_SHELL_ICU_HELPER_H_
+#define THIRD_PARTY_SQLITE_SQLITE_SHELL_ICU_HELPER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Exposes base::i18n::InitializeICU() to the SQLite shell.
+//
+// Chrome's startup sequence calls base::i18n::InitializeICU(). This function
+// exposes the same logic to SQLite's shell, so Chrome developers can debug
+// SQLite with the ICU tables used in production.
+void InitializeICUForSqliteShell();
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // THIRD_PARTY_SQLITE_SQLITE_SHELL_ICU_HELPER_H_
diff --git a/third_party/sqlite/sqlite_shell_shim.c b/third_party/sqlite/sqlite_shell_shim.c
new file mode 100644
index 0000000..9fd5355
--- /dev/null
+++ b/third_party/sqlite/sqlite_shell_shim.c
@@ -0,0 +1,23 @@
+// 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.
+
+// This is a shim that injects Chrome's ICU initialization sequence into
+// SQLite's shell. BUILD.gn uses this instead of building the sqlite_shell.c
+// file in the amalgamation directly.
+
+#include "third_party/sqlite/sqlite_shell_icu_helper.h"
+
+// On Windows, SQLite's shell.c defines wmain() instead of main() by default.
+// This preprocessor macro causes it to use main().
+#define SQLITE_SHELL_IS_UTF8 1
+
+// While processing shell.c, rename main() to sqlite_shell_main().
+#define main sqlite_shell_main
+#include "third_party/sqlite/amalgamation/shell/shell.c"
+#undef main
+
+int main(int argc, char** argv) {
+  InitializeICUForSqliteShell();
+  return sqlite_shell_main(argc, argv);
+}
\ No newline at end of file
diff --git a/third_party/webxr_test_pages/webxr-samples/input-selection.html b/third_party/webxr_test_pages/webxr-samples/input-selection.html
index 9229d04..a457b23 100644
--- a/third_party/webxr_test_pages/webxr-samples/input-selection.html
+++ b/third_party/webxr_test_pages/webxr-samples/input-selection.html
@@ -75,8 +75,10 @@
 
       // XR globals.
       let xrButton = null;
-      let xrImmersiveRefSpace = null;
-      let xrNonImmersiveRefSpace = null;
+      let xrImmersiveRefSpaceBase = null;
+      let xrImmersiveRefSpaceOffset = null;
+      let xrNonImmersiveRefSpaceBase = null;
+      let xrNonImmersiveRefSpaceOffset = null;
 
       // WebGL scene globals.
       let gl = null;
@@ -327,12 +329,10 @@
             throw e;
           }
         }).then((refSpace) => {
-          if (session.mode.startsWith('immersive')) {
-            xrImmersiveRefSpace = refSpace;
-          } else {
-            xrNonImmersiveRefSpace = refSpace;
-          }
-
+          // Save the session-specific reference space, and apply the
+          // current player orientation/position as originOffset.
+          setRefSpace(session, refSpace, false);
+          updateOriginOffset(session);
           session.requestAnimationFrame(onXRFrame);
         });
       }
@@ -345,7 +345,7 @@
 
       function onSelect(ev) {
         let session = ev.frame.session;
-        let refSpace = getRefSpace(session);
+        let refSpace = getRefSpace(session, true);
 
         let headPose = ev.frame.getPose(session.viewerSpace, refSpace);
         if (!headPose) return;
@@ -388,13 +388,13 @@
               }
               console.log('rotate by', rotationDelta);
             }
-            if (hitResult.node == floorNode) {
-               // New position uses x/z values of the hit test result, keeping y at 0 (floor level)
-               playerInWorldSpaceNew[0] = hitResult.intersection[0];
-               playerInWorldSpaceNew[1] = 0;
-               playerInWorldSpaceNew[2] = hitResult.intersection[2];
-               console.log('teleport to', playerInWorldSpaceNew);
-            }
+          }
+          if (hitResult.node == floorNode) {
+             // New position uses x/z values of the hit test result, keeping y at 0 (floor level)
+             playerInWorldSpaceNew[0] = hitResult.intersection[0];
+             playerInWorldSpaceNew[1] = 0;
+             playerInWorldSpaceNew[2] = hitResult.intersection[2];
+             console.log('teleport to', playerInWorldSpaceNew);
           }
         }
 
@@ -417,7 +417,11 @@
         // Update tracking space origin so that origin + playerOffset == player location in world space
         vec3.sub(trackingSpaceOriginInWorldSpace, playerInWorldSpaceNew, playerOffsetInWorldSpaceNew);
 
-        // Compute the origin offset
+        updateOriginOffset(session);
+      }
+
+      function updateOriginOffset(session) {
+        // Compute the origin offset based on player position/orientation.
         quat.identity(invOrientation);
         quat.rotateY(invOrientation, invOrientation, -trackingSpaceHeadingDegrees * Math.PI / 180);
         vec3.negate(invPosition, trackingSpaceOriginInWorldSpace);
@@ -426,8 +430,10 @@
           {x: invPosition[0], y: invPosition[1], z: invPosition[2]},
           {x: invOrientation[0], y: invOrientation[1], z: invOrientation[2], w: invOrientation[3]});
 
-        // Update refSpace to use a new originOffset with the teleported player position and orientation
-        refSpace = refSpace.getOffsetReferenceSpace(xform);
+        // Update offset reference to use a new originOffset with the teleported player position and orientation.
+        // This new offset needs to be applied to the base ref space.
+        let refSpace = getRefSpace(session, false).getOffsetReferenceSpace(xform);
+        setRefSpace(session, refSpace, true);
 
         console.log('teleport to', trackingSpaceOriginInWorldSpace);
       }
@@ -449,15 +455,31 @@
       let invPosition = vec3.create();
       let invOrientation = quat.create();
 
-      function getRefSpace(session) {
+      function getRefSpace(session, isOffset) {
         return session.mode.startsWith('immersive') ?
-               xrImmersiveRefSpace :
-               xrNonImmersiveRefSpace;
+              (isOffset ? xrImmersiveRefSpaceOffset : xrImmersiveRefSpaceBase) :
+              (isOffset ? xrNonImmersiveRefSpaceOffset : xrNonImmersiveRefSpaceBase);
+      }
+
+      function setRefSpace(session, refSpace, isOffset) {
+        if (session.mode.startsWith('immersive')) {
+          if (isOffset) {
+            xrImmersiveRefSpaceOffset = refSpace;
+          } else {
+            xrImmersiveRefSpaceBase = refSpace;
+          }
+        } else {
+          if (isOffset) {
+            xrNonImmersiveRefSpaceOffset = refSpace;
+          } else {
+            xrNonImmersiveRefSpaceBase = refSpace;
+          }
+        }
       }
 
       function onXRFrame(time, frame) {
         let session = frame.session;
-        let refSpace = getRefSpace(session);
+        let refSpace = getRefSpace(session, true);
 
         let pose = frame.getViewerPose(refSpace);
         scene.startFrame();
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a02fb6b..d805e38 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -3831,11 +3831,12 @@
 <enum name="AutofillLocalCardMigrationDecisionMetric">
   <int value="0" label="Migration offered"/>
   <int value="1" label="User used new card"/>
-  <int value="2" label="Failed enablement prerequisites"/>
+  <int value="2" label="Failed migration prerequisites"/>
   <int value="3" label="Reached max strikes"/>
   <int value="4" label="No migratable cards"/>
   <int value="5" label="Get upload details RPC failed"/>
   <int value="6" label="All cards are unsupported"/>
+  <int value="7" label="Single local card"/>
 </enum>
 
 <enum name="AutofillLocalCardMigrationDialogOffer">
@@ -32775,6 +32776,7 @@
   <int value="-2048927838" label="AutoplayWhitelistSettings:enabled"/>
   <int value="-2048732429" label="enable-alternative-services"/>
   <int value="-2048679945" label="NTPOfflinePageDownloadSuggestions:disabled"/>
+  <int value="-2048395454" label="ChromeDuetLabeled:disabled"/>
   <int value="-2047832738" label="enable-system-timezone-automatic-detection"/>
   <int value="-2047822258" label="enable-avfoundation"/>
   <int value="-2047190657" label="SyncUserConsentSeparateType:enabled"/>
@@ -33556,6 +33558,7 @@
   <int value="-966290456" label="WebAuthenticationCtap2:enabled"/>
   <int value="-965842218" label="MultiDeviceApi:disabled"/>
   <int value="-964676765" label="enable-accelerated-mjpeg-decode"/>
+  <int value="-962030536" label="ChromeDuetLabeled:enabled"/>
   <int value="-957200826" label="enable-spdy-proxy-auth"/>
   <int value="-956696029" label="scheduler-configuration"/>
   <int value="-951394314" label="top-chrome-md"/>
@@ -42710,8 +42713,7 @@
   <int value="3" label="the user's home page"/>
   <int value="4" label="other (typically an arbitrary URL)"/>
   <int value="5" label="obsolete: instant new tab page"/>
-  <int value="6"
-      label="obsolete: search results page with search term replacement"/>
+  <int value="6" label="search results page with search term replacement"/>
   <int value="7" label="new tab page with omnibox as starting focus"/>
   <int value="8" label="new tab page with fakebox as starting focus"/>
   <int value="9" label="search results page without search term replacement"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 556d795..3d4289b 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -5855,6 +5855,17 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.InteractiveWindowResize.TimeToPresent.MaxLatency"
+    units="ms">
+  <owner>oshima@google.com</owner>
+  <owner>omrilio@google.com</owner>
+  <summary>
+    Maximum time between when the size of a window changes during an interactive
+    window resize and the results are drawn (presented) on screen. This is
+    recorded for each movement of the mouse/pointer that results in a resize.
+  </summary>
+</histogram>
+
 <histogram name="Ash.Login.Lock.AuthMethod.Switched"
     enum="AuthMethodSwitchType">
   <owner>jdufault@google.com</owner>
@@ -62887,6 +62898,25 @@
   </summary>
 </histogram>
 
+<histogram name="Navigation.MainFrameHasRTLDomain" enum="Boolean"
+    expires_after="M82">
+  <owner>cthomp@chromium.org</owner>
+  <summary>
+    Whether the main-frame navigation was to a URL with an RTL domain name.
+  </summary>
+</histogram>
+
+<histogram name="Navigation.MainFrameHasRTLDomainDifferentPage" enum="Boolean"
+    expires_after="M82">
+  <owner>cthomp@chromium.org</owner>
+  <summary>
+    Whether the main-frame navigation was to a URL with an RTL domain name,
+    recorded for each main-frame avigation that replaces a document object. This
+    is not reported for reference fragment navigations, pushState/replaceState
+    or same page history navigation.
+  </summary>
+</histogram>
+
 <histogram name="Navigation.MainFrameScheme" enum="NavigationScheme">
   <owner>elawrence@chromium.org</owner>
   <owner>estark@chromium.org</owner>
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 8299a75..74163a4 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -116,6 +116,7 @@
 
 java_cpp_enum("java_enums_srcjar") {
   sources = [
+    "../base/ime/text_input_action.h",
     "../base/ime/text_input_type.h",
     "../base/page_transition_types.h",
     "../base/pointer/pointer_device.h",
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index 8826b212..ce594dc 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -7,6 +7,7 @@
 
 source_set("text_input_types") {
   sources = [
+    "text_input_action.h",
     "text_input_flags.h",
     "text_input_mode.h",
     "text_input_type.h",
diff --git a/ui/base/ime/text_input_action.h b/ui/base/ime/text_input_action.h
new file mode 100644
index 0000000..d39ca17
--- /dev/null
+++ b/ui/base/ime/text_input_action.h
@@ -0,0 +1,29 @@
+// 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 UI_BASE_IME_TEXT_INPUT_ACTION_H_
+#define UI_BASE_IME_TEXT_INPUT_ACTION_H_
+
+namespace ui {
+
+// This mode corresponds to enterkeyhint
+// https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-enterkeyhint-attribute
+//
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.ui.base.ime
+enum class TextInputAction {
+  kDefault,
+  kEnter,
+  kDone,
+  kGo,
+  kNext,
+  kPrevious,
+  kSearch,
+  kSend,
+  kMax = kSend,
+};
+
+}  // namespace ui
+
+#endif  // UI_BASE_IME_TEXT_INPUT_ACTION_H_
diff --git a/ui/base/models/tree_node_iterator.h b/ui/base/models/tree_node_iterator.h
index 7d53b2d..755a8a3 100644
--- a/ui/base/models/tree_node_iterator.h
+++ b/ui/base/models/tree_node_iterator.h
@@ -45,7 +45,7 @@
   }
 
   explicit TreeNodeIterator(NodeType* node) {
-    if (!node->empty())
+    if (!node->children().empty())
       positions_.push(Position<NodeType>(node, 0));
   }
 
diff --git a/ui/base/models/tree_node_model.h b/ui/base/models/tree_node_model.h
index 70b8ef1..7563111 100644
--- a/ui/base/models/tree_node_model.h
+++ b/ui/base/models/tree_node_model.h
@@ -17,6 +17,10 @@
 #include "base/strings/string16.h"
 #include "ui/base/models/tree_model.h"
 
+namespace bookmarks {
+class BookmarkModel;
+}
+
 namespace ui {
 
 // TreeNodeModel and TreeNodes provide an implementation of TreeModel around
@@ -72,6 +76,8 @@
 template <class NodeType>
 class TreeNode : public TreeModelNode {
  public:
+  using TreeNodes = std::vector<std::unique_ptr<NodeType>>;
+
   TreeNode() : parent_(nullptr) {}
 
   explicit TreeNode(const base::string16& title)
@@ -84,7 +90,7 @@
   NodeType* Add(std::unique_ptr<NodeType> node, int index) {
     DCHECK(node);
     DCHECK_GE(index, 0);
-    DCHECK_LE(index, child_count());
+    DCHECK_LE(size_t{index}, children_.size());
     DCHECK(!node->parent_);
     node->parent_ = static_cast<NodeType*>(this);
     NodeType* node_ptr = node.get();
@@ -94,7 +100,8 @@
 
   // Removes the node at the given index. Returns the removed node.
   std::unique_ptr<NodeType> Remove(int index) {
-    DCHECK(index >= 0 && index < child_count());
+    DCHECK_GE(index, 0);
+    DCHECK_LT(size_t{index}, children_.size());
     children_[index]->parent_ = nullptr;
     std::unique_ptr<NodeType> ptr = std::move(children_[index]);
     children_.erase(children_.begin() + index);
@@ -104,12 +111,9 @@
   // Removes the given node. Prefer to remove by index if you know it to avoid
   // the search for the node to remove.
   std::unique_ptr<NodeType> Remove(NodeType* node) {
-    auto i = std::find_if(children_.begin(), children_.end(),
-                          [node](const std::unique_ptr<NodeType>& ptr) {
-                            return ptr.get() == node;
-                          });
-    DCHECK(i != children_.end());
-    return Remove(i - children_.begin());
+    int i = GetIndexOf(node);
+    DCHECK_NE(-1, i);
+    return Remove(i);
   }
 
   // Removes all the children from this node.
@@ -122,11 +126,11 @@
   // Returns true if this is the root node.
   bool is_root() const { return parent_ == nullptr; }
 
-  // Returns the number of children.
-  int child_count() const { return static_cast<int>(children_.size()); }
+  const TreeNodes& children() const { return children_; }
 
-  // Returns true if this node has no children.
-  bool empty() const { return children_.empty(); }
+  // Returns the number of children.
+  // TODO(https://crbug.com/956419): Remove; use children().size().
+  int child_count() const { return static_cast<int>(children_.size()); }
 
   // Returns the number of all nodes in the subtree rooted at this node,
   // including this node.
@@ -138,9 +142,10 @@
   }
 
   // Returns the node at |index|.
+  // TODO(https://crbug.com/956419): Remove; use children()[index].
   const NodeType* GetChild(int index) const {
     DCHECK_GE(index, 0);
-    DCHECK_LT(index, child_count());
+    DCHECK_LT(size_t{index}, children_.size());
     return children_[index].get();
   }
   NodeType* GetChild(int index) {
@@ -174,10 +179,10 @@
     return parent_ ? parent_->HasAncestor(ancestor) : false;
   }
 
- protected:
-  std::vector<std::unique_ptr<NodeType>>& children() { return children_; }
-
  private:
+  // TODO(https://crbug.com/956314): Remove this.
+  friend class bookmarks::BookmarkModel;
+
   // Title displayed in the tree.
   base::string16 title_;
 
@@ -185,7 +190,7 @@
   NodeType* parent_;
 
   // This node's children.
-  typename std::vector<std::unique_ptr<NodeType>> children_;
+  TreeNodes children_;
 
   DISALLOW_COPY_AND_ASSIGN(TreeNode);
 };
@@ -280,7 +285,7 @@
 
   int GetChildCount(TreeModelNode* parent) override {
     DCHECK(parent);
-    return AsNode(parent)->child_count();
+    return int{AsNode(parent)->children().size()};
   }
 
   NodeType* GetChild(TreeModelNode* parent, int index) override {
diff --git a/ui/base/models/tree_node_model_unittest.cc b/ui/base/models/tree_node_model_unittest.cc
index b39d82bc..2cf9fee7 100644
--- a/ui/base/models/tree_node_model_unittest.cc
+++ b/ui/base/models/tree_node_model_unittest.cc
@@ -140,7 +140,7 @@
   root.DeleteAll();
 
   EXPECT_EQ(0, root.child_count());
-  EXPECT_TRUE(root.empty());
+  EXPECT_TRUE(root.children().empty());
 }
 
 // Verifies if GetIndexOf() returns the correct index for the specified node.
diff --git a/ui/gfx/platform_font_win.cc b/ui/gfx/platform_font_win.cc
index 59fe1faa..eb18954 100644
--- a/ui/gfx/platform_font_win.cc
+++ b/ui/gfx/platform_font_win.cc
@@ -76,10 +76,8 @@
 
   Microsoft::WRL::ComPtr<IDWriteFontCollection> font_collection;
   hr = factory->GetSystemFontCollection(font_collection.GetAddressOf());
-  if (FAILED(hr)) {
-    CHECK(false);
+  if (FAILED(hr))
     return hr;
-  }
 
   // We try to find a matching font by triggering DirectWrite to substitute the
   // font passed in with a matching font (FontSubstitutes registry key)
@@ -129,7 +127,7 @@
   Microsoft::WRL::ComPtr<IDWriteFontCollection> font_collection;
   hr = factory->GetSystemFontCollection(font_collection.GetAddressOf());
   if (FAILED(hr)) {
-    CHECK(false);
+    // On some old windows, the call to GetSystemFontCollection may fail.
     return hr;
   }
 
@@ -474,8 +472,9 @@
   HRESULT hr = GetMatchingDirectWriteFont(
       &font_info, italic, win::GetDirectWriteFactory(), &dwrite_font);
   if (FAILED(hr)) {
-    CHECK(false);
-    return nullptr;
+    // If we are not able to find a font using Direct Write, fallback to
+    // the old GDI font.
+    return CreateHFontRefFromGDI(gdi_font, font_metrics);
   }
 
   DWRITE_FONT_METRICS dwrite_font_metrics = {0};
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 70246bc..a54c5b0 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -281,6 +281,14 @@
   }
   if (is_win) {
     sources += [
+      "child_window_win.cc",
+      "child_window_win.h",
+      "dc_layer_tree.cc",
+      "dc_layer_tree.h",
+      "direct_composition_child_surface_win.cc",
+      "direct_composition_child_surface_win.h",
+      "direct_composition_surface_win.cc",
+      "direct_composition_surface_win.h",
       "gl_angle_util_win.cc",
       "gl_angle_util_win.h",
       "gl_bindings_autogen_wgl.cc",
@@ -295,14 +303,22 @@
       "gl_surface_wgl.h",
       "gl_wgl_api_implementation.cc",
       "gl_wgl_api_implementation.h",
+      "swap_chain_presenter.cc",
+      "swap_chain_presenter.h",
       "vsync_provider_win.cc",
       "vsync_provider_win.h",
       "vsync_thread_win.cc",
       "vsync_thread_win.h",
     ]
 
-    libs = [ "dwmapi.lib" ]
-    ldflags = [ "/DELAYLOAD:dwmapi.dll" ]
+    libs = [
+      "dxgi.lib",
+      "dwmapi.lib",
+    ]
+    ldflags = [
+      "/DELAYLOAD:dwmapi.dll",
+      "/DELAYLOAD:dxgi.dll",
+    ]
 
     assert(use_egl)
     data_deps += [
@@ -518,6 +534,7 @@
 
   if (is_win) {
     sources += [
+      "direct_composition_surface_win_unittest.cc",
       "gl_image_dxgi_unittest.cc",
       "wgl_api_unittest.cc",
     ]
@@ -541,6 +558,7 @@
     "//base",
     "//testing/gmock",
     "//testing/gtest",
+    "//ui/base:base",
     "//ui/gfx",
     "//ui/gfx:test_support",
     "//ui/gfx/geometry",
diff --git a/ui/gl/DEPS b/ui/gl/DEPS
index f6939ed..e2673ea 100644
--- a/ui/gl/DEPS
+++ b/ui/gl/DEPS
@@ -13,6 +13,10 @@
   "angle_platform_impl.cc": [
     "+third_party/angle/include/platform/Platform.h",
   ],
+  "direct_composition_surface_win_unittest.cc": [
+    "+ui/base/win/hidden_window.h",
+    "+ui/platform_window",
+  ],
   "gl_angle_util_win.cc": [
     "+third_party/angle/include/EGL/egl.h",
     "+third_party/angle/include/EGL/eglext.h",
diff --git a/gpu/ipc/service/child_window_win.cc b/ui/gl/child_window_win.cc
similarity index 97%
rename from gpu/ipc/service/child_window_win.cc
rename to ui/gl/child_window_win.cc
index 0d69495..9ae650f 100644
--- a/gpu/ipc/service/child_window_win.cc
+++ b/ui/gl/child_window_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/child_window_win.h"
+#include "ui/gl/child_window_win.h"
 
 #include <memory>
 
@@ -13,7 +13,7 @@
 #include "ui/gfx/win/hwnd_util.h"
 #include "ui/gfx/win/window_impl.h"
 
-namespace gpu {
+namespace gl {
 
 namespace {
 
@@ -157,4 +157,4 @@
   return thread_->task_runner();
 }
 
-}  // namespace gpu
+}  // namespace gl
diff --git a/gpu/ipc/service/child_window_win.h b/ui/gl/child_window_win.h
similarity index 86%
rename from gpu/ipc/service/child_window_win.h
rename to ui/gl/child_window_win.h
index c36e0d5..c977e76b 100644
--- a/gpu/ipc/service/child_window_win.h
+++ b/ui/gl/child_window_win.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef GPU_IPC_SERVICE_CHILD_WINDOW_WIN_H_
-#define GPU_IPC_SERVICE_CHILD_WINDOW_WIN_H_
+#ifndef UI_GL_CHILD_WINDOW_WIN_H_
+#define UI_GL_CHILD_WINDOW_WIN_H_
 
 #include "base/memory/weak_ptr.h"
 #include "base/task_runner.h"
@@ -11,7 +11,7 @@
 
 #include <windows.h>
 
-namespace gpu {
+namespace gl {
 
 // The window DirectComposition renders into needs to be owned by the process
 // that's currently doing the rendering. The class creates and owns a window
@@ -39,6 +39,6 @@
   DISALLOW_COPY_AND_ASSIGN(ChildWindowWin);
 };
 
-}  // namespace gpu
+}  // namespace gl
 
-#endif  // GPU_IPC_SERVICE_CHILD_WINDOW_WIN_H_
+#endif  // UI_GL_CHILD_WINDOW_WIN_H_
diff --git a/gpu/ipc/service/dc_layer_tree.cc b/ui/gl/dc_layer_tree.cc
similarity index 97%
rename from gpu/ipc/service/dc_layer_tree.cc
rename to ui/gl/dc_layer_tree.cc
index 7cb99eca..a0a59cb 100644
--- a/gpu/ipc/service/dc_layer_tree.cc
+++ b/ui/gl/dc_layer_tree.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/dc_layer_tree.h"
+#include "ui/gl/dc_layer_tree.h"
 
 #include "base/trace_event/trace_event.h"
-#include "gpu/ipc/service/direct_composition_child_surface_win.h"
-#include "gpu/ipc/service/swap_chain_presenter.h"
+#include "ui/gl/direct_composition_child_surface_win.h"
+#include "ui/gl/swap_chain_presenter.h"
 
-namespace gpu {
+namespace gl {
 namespace {
 bool SizeContains(const gfx::Size& a, const gfx::Size& b) {
   return gfx::Rect(a).Contains(gfx::Rect(b));
@@ -227,4 +227,4 @@
   return true;
 }
 
-}  // namespace gpu
+}  // namespace gl
diff --git a/gpu/ipc/service/dc_layer_tree.h b/ui/gl/dc_layer_tree.h
similarity index 95%
rename from gpu/ipc/service/dc_layer_tree.h
rename to ui/gl/dc_layer_tree.h
index ed719c67..67a23791 100644
--- a/gpu/ipc/service/dc_layer_tree.h
+++ b/ui/gl/dc_layer_tree.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef GPU_IPC_SERVICE_DC_LAYER_TREE_H_
-#define GPU_IPC_SERVICE_DC_LAYER_TREE_H_
+#ifndef UI_GL_DC_LAYER_TREE_H_
+#define UI_GL_DC_LAYER_TREE_H_
 
 #include <windows.h>
 #include <d3d11.h>
@@ -15,7 +15,7 @@
 #include "ui/gfx/geometry/size.h"
 #include "ui/gl/dc_renderer_layer_params.h"
 
-namespace gpu {
+namespace gl {
 
 class DirectCompositionChildSurfaceWin;
 class SwapChainPresenter;
@@ -125,6 +125,6 @@
   DISALLOW_COPY_AND_ASSIGN(DCLayerTree);
 };
 
-}  // namespace gpu
+}  // namespace gl
 
-#endif  // GPU_IPC_SERVICE_DC_LAYER_TREE_H_
+#endif  // UI_GL_DC_LAYER_TREE_H_
diff --git a/gpu/ipc/service/direct_composition_child_surface_win.cc b/ui/gl/direct_composition_child_surface_win.cc
similarity index 96%
rename from gpu/ipc/service/direct_composition_child_surface_win.cc
rename to ui/gl/direct_composition_child_surface_win.cc
index 54c063f..bcfb9c43 100644
--- a/gpu/ipc/service/direct_composition_child_surface_win.cc
+++ b/ui/gl/direct_composition_child_surface_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/direct_composition_child_surface_win.h"
+#include "ui/gl/direct_composition_child_surface_win.h"
 
 #include <d3d11_1.h>
 #include <dcomptypes.h>
@@ -13,7 +13,6 @@
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
 #include "base/win/windows_version.h"
-#include "ui/display/display_switches.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/gl/color_space_utils.h"
 #include "ui/gl/egl_util.h"
@@ -33,7 +32,7 @@
 #define EGL_D3D_TEXTURE_ANGLE 0x33A3
 #endif /* EGL_ANGLE_d3d_texture_client_buffer */
 
-namespace gpu {
+namespace gl {
 
 namespace {
 // Only one DirectComposition surface can be rendered into at a time. Track
@@ -55,7 +54,7 @@
       return false;
 
     Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-        gl::QueryD3D11DeviceObjectFromANGLE();
+        QueryD3D11DeviceObjectFromANGLE();
     if (!d3d11_device) {
       DLOG(ERROR) << "Not using swap chain tearing because failed to retrieve "
                      "D3D11 device from ANGLE";
@@ -114,9 +113,9 @@
   Destroy();
 }
 
-bool DirectCompositionChildSurfaceWin::Initialize(gl::GLSurfaceFormat format) {
-  d3d11_device_ = gl::QueryD3D11DeviceObjectFromANGLE();
-  dcomp_device_ = gl::QueryDirectCompositionDevice(d3d11_device_);
+bool DirectCompositionChildSurfaceWin::Initialize(GLSurfaceFormat format) {
+  d3d11_device_ = QueryD3D11DeviceObjectFromANGLE();
+  dcomp_device_ = QueryDirectCompositionDevice(d3d11_device_);
   if (!dcomp_device_)
     return false;
 
@@ -272,7 +271,7 @@
   return true;
 }
 
-bool DirectCompositionChildSurfaceWin::OnMakeCurrent(gl::GLContext* context) {
+bool DirectCompositionChildSurfaceWin::OnMakeCurrent(GLContext* context) {
   if (g_current_surface != dcomp_surface_.Get()) {
     if (g_current_surface) {
       HRESULT hr = g_current_surface->SuspendDraw();
@@ -321,7 +320,7 @@
   // |real_surface_|.
   ui::ScopedReleaseCurrent release_current;
 
-  DXGI_FORMAT dxgi_format = gl::ColorSpaceUtils::GetDXGIFormat(color_space_);
+  DXGI_FORMAT dxgi_format = ColorSpaceUtils::GetDXGIFormat(color_space_);
 
   bool force_swap_chain = UseSwapChainFrameStatistics();
   bool use_swap_chain = force_swap_chain || !enable_dc_layers_;
@@ -380,7 +379,7 @@
     Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain;
     if (SUCCEEDED(swap_chain_.As(&swap_chain))) {
       swap_chain->SetColorSpace1(
-          gl::ColorSpaceUtils::GetDXGIColorSpace(color_space_));
+          ColorSpaceUtils::GetDXGIColorSpace(color_space_));
     }
   }
 
@@ -458,7 +457,7 @@
 
   // ResizeBuffers can't change alpha blending mode.
   if (swap_chain_ && resize_only) {
-    DXGI_FORMAT format = gl::ColorSpaceUtils::GetDXGIFormat(color_space_);
+    DXGI_FORMAT format = ColorSpaceUtils::GetDXGIFormat(color_space_);
     UINT flags =
         IsSwapChainTearingSupported() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
     HRESULT hr = swap_chain_->ResizeBuffers(2 /* BufferCount */, size.width(),
@@ -590,4 +589,4 @@
   pending_frames_.clear();
 }
 
-}  // namespace gpu
+}  // namespace gl
diff --git a/gpu/ipc/service/direct_composition_child_surface_win.h b/ui/gl/direct_composition_child_surface_win.h
similarity index 88%
rename from gpu/ipc/service/direct_composition_child_surface_win.h
rename to ui/gl/direct_composition_child_surface_win.h
index f956ce0..34a847f 100644
--- a/gpu/ipc/service/direct_composition_child_surface_win.h
+++ b/ui/gl/direct_composition_child_surface_win.h
@@ -2,28 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef GPU_IPC_SERVICE_DIRECT_COMPOSITION_CHILD_SURFACE_WIN_H_
-#define GPU_IPC_SERVICE_DIRECT_COMPOSITION_CHILD_SURFACE_WIN_H_
+#ifndef UI_GL_DIRECT_COMPOSITION_CHILD_SURFACE_WIN_H_
+#define UI_GL_DIRECT_COMPOSITION_CHILD_SURFACE_WIN_H_
 
 #include <windows.h>
 #include <d3d11.h>
 #include <dcomp.h>
 #include <wrl/client.h>
 
-#include "gpu/ipc/service/gpu_ipc_service_export.h"
 #include "ui/gl/gl_surface_egl.h"
 
-namespace gpu {
+namespace gl {
 
-class GPU_IPC_SERVICE_EXPORT DirectCompositionChildSurfaceWin
-    : public gl::GLSurfaceEGL {
+class DirectCompositionChildSurfaceWin : public GLSurfaceEGL {
  public:
   DirectCompositionChildSurfaceWin();
 
   static bool UseSwapChainFrameStatistics();
 
   // GLSurfaceEGL implementation.
-  bool Initialize(gl::GLSurfaceFormat format) override;
+  bool Initialize(GLSurfaceFormat format) override;
   void Destroy() override;
   gfx::Size GetSize() override;
   bool IsOffscreen() override;
@@ -31,7 +29,7 @@
   gfx::SwapResult SwapBuffers(PresentationCallback callback) override;
   bool FlipsVertically() const override;
   bool SupportsPostSubBuffer() override;
-  bool OnMakeCurrent(gl::GLContext* context) override;
+  bool OnMakeCurrent(GLContext* context) override;
   bool SupportsDCLayers() const override;
   bool SetDrawRectangle(const gfx::Rect& rect) override;
   gfx::Vector2d GetDrawOffset() const override;
@@ -123,6 +121,6 @@
   DISALLOW_COPY_AND_ASSIGN(DirectCompositionChildSurfaceWin);
 };
 
-}  // namespace gpu
+}  // namespace gl
 
-#endif  // GPU_IPC_SERVICE_DIRECT_COMPOSITION_CHILD_SURFACE_WIN_H_
+#endif  // UI_GL_DIRECT_COMPOSITION_CHILD_SURFACE_WIN_H_
diff --git a/gpu/ipc/service/direct_composition_surface_win.cc b/ui/gl/direct_composition_surface_win.cc
similarity index 94%
rename from gpu/ipc/service/direct_composition_surface_win.cc
rename to ui/gl/direct_composition_surface_win.cc
index 4171c83b..ceb798c 100644
--- a/gpu/ipc/service/direct_composition_surface_win.cc
+++ b/ui/gl/direct_composition_surface_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/direct_composition_surface_win.h"
+#include "ui/gl/direct_composition_surface_win.h"
 
 #include <dxgi1_6.h>
 
@@ -17,8 +17,8 @@
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
 #include "base/win/windows_version.h"
-#include "gpu/ipc/service/dc_layer_tree.h"
-#include "gpu/ipc/service/direct_composition_child_surface_win.h"
+#include "ui/gl/dc_layer_tree.h"
+#include "ui/gl/direct_composition_child_surface_win.h"
 #include "ui/gl/gl_angle_util_win.h"
 #include "ui/gl/gl_surface_presentation_helper.h"
 #include "ui/gl/gl_switches.h"
@@ -29,7 +29,7 @@
 #define EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE 0x33A6
 #endif /* EGL_ANGLE_flexible_surface_compatibility */
 
-namespace gpu {
+namespace gl {
 namespace {
 struct OverlaySupportInfo {
   DXGI_FORMAT format;
@@ -70,7 +70,7 @@
     return;
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
   if (!d3d11_device) {
     DLOG(ERROR) << "Failed to retrieve D3D11 device";
     return;
@@ -187,7 +187,7 @@
     VSyncCallback vsync_callback,
     HWND parent_window,
     const Settings& settings)
-    : gl::GLSurfaceEGL(),
+    : GLSurfaceEGL(),
       child_window_(parent_window),
       task_runner_(base::ThreadTaskRunnerHandle::Get()),
       root_surface_(new DirectCompositionChildSurfaceWin()),
@@ -196,8 +196,8 @@
           settings.disable_larger_than_screen_overlays)),
       vsync_provider_(std::move(vsync_provider)),
       vsync_callback_(std::move(vsync_callback)),
-      presentation_helper_(std::make_unique<gl::GLSurfacePresentationHelper>(
-          vsync_provider_.get())),
+      presentation_helper_(
+          std::make_unique<GLSurfacePresentationHelper>(vsync_provider_.get())),
       weak_ptr_factory_(this) {}
 
 DirectCompositionSurfaceWin::~DirectCompositionSurfaceWin() {
@@ -222,13 +222,13 @@
 
     // Flexible surface compatibility is required to be able to MakeCurrent with
     // the default pbuffer surface.
-    if (!gl::GLSurfaceEGL::IsEGLFlexibleSurfaceCompatibilitySupported()) {
+    if (!GLSurfaceEGL::IsEGLFlexibleSurfaceCompatibilitySupported()) {
       DLOG(ERROR) << "EGL_ANGLE_flexible_surface_compatibility not supported";
       return false;
     }
 
     Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-        gl::QueryD3D11DeviceObjectFromANGLE();
+        QueryD3D11DeviceObjectFromANGLE();
     if (!d3d11_device) {
       DLOG(ERROR) << "Failed to retrieve D3D11 device";
       return false;
@@ -243,7 +243,7 @@
 
     // This will fail if DirectComposition DLL can't be loaded.
     Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device =
-        gl::QueryDirectCompositionDevice(d3d11_device);
+        QueryDirectCompositionDevice(d3d11_device);
     if (!dcomp_device) {
       DLOG(ERROR) << "Failed to retrieve direct composition device";
       return false;
@@ -391,14 +391,14 @@
   return hdr_monitor_found;
 }
 
-bool DirectCompositionSurfaceWin::Initialize(gl::GLSurfaceFormat format) {
-  d3d11_device_ = gl::QueryD3D11DeviceObjectFromANGLE();
+bool DirectCompositionSurfaceWin::Initialize(GLSurfaceFormat format) {
+  d3d11_device_ = QueryD3D11DeviceObjectFromANGLE();
   if (!d3d11_device_) {
     DLOG(ERROR) << "Failed to retrieve D3D11 device from ANGLE";
     return false;
   }
 
-  dcomp_device_ = gl::QueryDirectCompositionDevice(d3d11_device_);
+  dcomp_device_ = QueryDirectCompositionDevice(d3d11_device_);
   if (!dcomp_device_) {
     DLOG(ERROR)
         << "Failed to retrieve direct compostion device from D3D11 device";
@@ -414,7 +414,7 @@
   if (!layer_tree_->Initialize(window_, d3d11_device_, dcomp_device_))
     return false;
 
-  if (!root_surface_->Initialize(gl::GLSurfaceFormat()))
+  if (!root_surface_->Initialize(GLSurfaceFormat()))
     return false;
 
   if (root_surface_->UseSwapChainFrameStatistics()) {
@@ -425,7 +425,7 @@
   }
 
   if ((SupportsGpuVSync() && vsync_callback_) || main_thread_vsync_callback_) {
-    vsync_thread_ = std::make_unique<gl::VSyncThreadWin>(
+    vsync_thread_ = std::make_unique<VSyncThreadWin>(
         window_, d3d11_device_,
         base::BindRepeating(
             &DirectCompositionSurfaceWin::HandleVSyncOnVSyncThread,
@@ -472,7 +472,7 @@
     PresentationCallback callback) {
   TRACE_EVENT0("gpu", "DirectCompositionSurfaceWin::SwapBuffers");
 
-  base::Optional<gl::GLSurfacePresentationHelper::ScopedSwapBuffers>
+  base::Optional<GLSurfacePresentationHelper::ScopedSwapBuffers>
       scoped_swap_buffers;
   if (!root_surface_->UseSwapChainFrameStatistics()) {
     scoped_swap_buffers.emplace(presentation_helper_.get(),
@@ -534,7 +534,7 @@
   return true;
 }
 
-bool DirectCompositionSurfaceWin::OnMakeCurrent(gl::GLContext* context) {
+bool DirectCompositionSurfaceWin::OnMakeCurrent(GLContext* context) {
   if (presentation_helper_)
     presentation_helper_->OnMakeCurrent(context, this);
   return root_surface_->OnMakeCurrent(context);
@@ -613,4 +613,4 @@
   return root_surface_->swap_chain();
 }
 
-}  // namespace gpu
+}  // namespace gl
diff --git a/gpu/ipc/service/direct_composition_surface_win.h b/ui/gl/direct_composition_surface_win.h
similarity index 89%
rename from gpu/ipc/service/direct_composition_surface_win.h
rename to ui/gl/direct_composition_surface_win.h
index 50874327..18277e7d 100644
--- a/gpu/ipc/service/direct_composition_surface_win.h
+++ b/ui/gl/direct_composition_surface_win.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef GPU_IPC_SERVICE_DIRECT_COMPOSITION_SURFACE_WIN_H_
-#define GPU_IPC_SERVICE_DIRECT_COMPOSITION_SURFACE_WIN_H_
+#ifndef UI_GL_DIRECT_COMPOSITION_SURFACE_WIN_H_
+#define UI_GL_DIRECT_COMPOSITION_SURFACE_WIN_H_
 
 #include <windows.h>
 #include <d3d11.h>
@@ -13,21 +13,17 @@
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
-#include "gpu/ipc/service/child_window_win.h"
-#include "gpu/ipc/service/gpu_ipc_service_export.h"
+#include "ui/gl/child_window_win.h"
+#include "ui/gl/gl_export.h"
 #include "ui/gl/gl_surface_egl.h"
 
 namespace gl {
-class GLSurfacePresentationHelper;
-class VSyncThreadWin;
-}
-
-namespace gpu {
 class DCLayerTree;
 class DirectCompositionChildSurfaceWin;
+class GLSurfacePresentationHelper;
+class VSyncThreadWin;
 
-class GPU_IPC_SERVICE_EXPORT DirectCompositionSurfaceWin
-    : public gl::GLSurfaceEGL {
+class GL_EXPORT DirectCompositionSurfaceWin : public GLSurfaceEGL {
  public:
   using VSyncCallback =
       base::RepeatingCallback<void(base::TimeTicks, base::TimeDelta)>;
@@ -83,7 +79,7 @@
   bool InitializeNativeWindow();
 
   // GLSurfaceEGL implementation.
-  bool Initialize(gl::GLSurfaceFormat format) override;
+  bool Initialize(GLSurfaceFormat format) override;
   void Destroy() override;
   gfx::Size GetSize() override;
   bool IsOffscreen() override;
@@ -103,7 +99,7 @@
   bool SetEnableDCLayers(bool enable) override;
   bool FlipsVertically() const override;
   bool SupportsPostSubBuffer() override;
-  bool OnMakeCurrent(gl::GLContext* context) override;
+  bool OnMakeCurrent(GLContext* context) override;
   bool SupportsDCLayers() const override;
   bool UseOverlaysForVideo() const override;
   bool SupportsProtectedVideo() const override;
@@ -146,13 +142,13 @@
   scoped_refptr<DirectCompositionChildSurfaceWin> root_surface_;
   std::unique_ptr<DCLayerTree> layer_tree_;
 
-  std::unique_ptr<gl::VSyncThreadWin> vsync_thread_;
+  std::unique_ptr<VSyncThreadWin> vsync_thread_;
   std::unique_ptr<gfx::VSyncProvider> vsync_provider_;
 
   const VSyncCallback vsync_callback_;
   bool vsync_callback_enabled_ = false;
 
-  std::unique_ptr<gl::GLSurfacePresentationHelper> presentation_helper_;
+  std::unique_ptr<GLSurfacePresentationHelper> presentation_helper_;
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
   Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device_;
@@ -163,6 +159,6 @@
   DISALLOW_COPY_AND_ASSIGN(DirectCompositionSurfaceWin);
 };
 
-}  // namespace gpu
+}  // namespace gl
 
-#endif  // GPU_IPC_SERVICE_DIRECT_COMPOSITION_SURFACE_WIN_H_
+#endif  // UI_GL_DIRECT_COMPOSITION_SURFACE_WIN_H_
diff --git a/gpu/ipc/service/direct_composition_surface_win_unittest.cc b/ui/gl/direct_composition_surface_win_unittest.cc
similarity index 87%
rename from gpu/ipc/service/direct_composition_surface_win_unittest.cc
rename to ui/gl/direct_composition_surface_win_unittest.cc
index 76db7f4..6315b63 100644
--- a/gpu/ipc/service/direct_composition_surface_win_unittest.cc
+++ b/ui/gl/direct_composition_surface_win_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/direct_composition_surface_win.h"
+#include "ui/gl/direct_composition_surface_win.h"
 
 #include "base/bind_helpers.h"
 #include "base/memory/ref_counted_memory.h"
@@ -27,19 +27,8 @@
 #include "ui/platform_window/platform_window_delegate.h"
 #include "ui/platform_window/win/win_window.h"
 
-namespace gpu {
+namespace gl {
 namespace {
-
-bool CheckIfDCSupported() {
-  if (!gl::QueryDirectCompositionDevice(
-          gl::QueryD3D11DeviceObjectFromANGLE())) {
-    LOG(WARNING)
-        << "GL implementation not using DirectComposition, skipping test.";
-    return false;
-  }
-  return true;
-}
-
 class TestPlatformDelegate : public ui::PlatformWindowDelegate {
  public:
   // ui::PlatformWindowDelegate implementation.
@@ -114,13 +103,27 @@
  public:
   DirectCompositionSurfaceTest() : parent_window_(ui::GetHiddenWindow()) {}
 
-  ~DirectCompositionSurfaceTest() override {
+ protected:
+  void SetUp() override {
+    // Without this, the following check always fails.
+    gl::init::InitializeGLNoExtensionsOneOff();
+    if (!QueryDirectCompositionDevice(QueryD3D11DeviceObjectFromANGLE())) {
+      LOG(WARNING)
+          << "GL implementation not using DirectComposition, skipping test.";
+      return;
+    }
+    surface_ = CreateDirectCompositionSurfaceWin();
+    context_ = CreateGLContext(surface_);
+    surface_->SetEnableDCLayers(true);
+  }
+
+  void TearDown() override {
     context_ = nullptr;
     if (surface_)
       DestroySurface(std::move(surface_));
+    gl::init::ShutdownGL(false);
   }
 
- protected:
   scoped_refptr<DirectCompositionSurfaceWin>
   CreateDirectCompositionSurfaceWin() {
     DirectCompositionSurfaceWin::Settings settings;
@@ -129,7 +132,7 @@
             /*vsync_provider=*/nullptr,
             DirectCompositionSurfaceWin::VSyncCallback(), parent_window_,
             settings);
-    EXPECT_TRUE(surface->Initialize(gl::GLSurfaceFormat()));
+    EXPECT_TRUE(surface->Initialize(GLSurfaceFormat()));
 
     // ImageTransportSurfaceDelegate::DidCreateAcceleratedSurfaceChildWindow()
     // is called in production code here. However, to remove dependency from
@@ -141,31 +144,24 @@
     return surface;
   }
 
-  scoped_refptr<gl::GLContext> CreateGLContext(
+  scoped_refptr<GLContext> CreateGLContext(
       scoped_refptr<DirectCompositionSurfaceWin> surface) {
-    scoped_refptr<gl::GLContext> context = gl::init::CreateGLContext(
-        nullptr, surface.get(), gl::GLContextAttribs());
+    scoped_refptr<GLContext> context =
+        gl::init::CreateGLContext(nullptr, surface.get(), GLContextAttribs());
     EXPECT_TRUE(context->MakeCurrent(surface.get()));
     return context;
   }
 
-  virtual void InitializeSurface() {
-    surface_ = CreateDirectCompositionSurfaceWin();
-    context_ = CreateGLContext(surface_);
-  }
-
   HWND parent_window_;
   scoped_refptr<DirectCompositionSurfaceWin> surface_;
-  scoped_refptr<gl::GLContext> context_;
+  scoped_refptr<GLContext> context_;
 };
 
 TEST_F(DirectCompositionSurfaceTest, TestMakeCurrent) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
   EXPECT_TRUE(surface_->Resize(gfx::Size(100, 100), 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
 
   // First SetDrawRectangle must be full size of surface.
   EXPECT_FALSE(surface_->SetDrawRectangle(gfx::Rect(0, 0, 50, 50)));
@@ -185,18 +181,18 @@
   EXPECT_TRUE(context_->IsCurrent(surface_.get()));
 
   EXPECT_TRUE(surface_->Resize(gfx::Size(50, 50), 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_TRUE(context_->IsCurrent(surface_.get()));
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(0, 0, 50, 50)));
   EXPECT_TRUE(context_->IsCurrent(surface_.get()));
 
   scoped_refptr<DirectCompositionSurfaceWin> surface2 =
       CreateDirectCompositionSurfaceWin();
-  scoped_refptr<gl::GLContext> context2 = CreateGLContext(surface2.get());
+  scoped_refptr<GLContext> context2 = CreateGLContext(surface2.get());
 
   surface2->SetEnableDCLayers(true);
   EXPECT_TRUE(surface2->Resize(gfx::Size(100, 100), 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   // The previous IDCompositionSurface should be suspended when another
   // surface is being drawn to.
   EXPECT_TRUE(surface2->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
@@ -211,11 +207,11 @@
 
 // Tests that switching using EnableDCLayers works.
 TEST_F(DirectCompositionSurfaceTest, DXGIDCLayerSwitch) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
+  surface_->SetEnableDCLayers(false);
   EXPECT_TRUE(surface_->Resize(gfx::Size(100, 100), 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_FALSE(surface_->GetBackbufferSwapChainForTesting());
 
   // First SetDrawRectangle must be full size of surface for DXGI swapchain.
@@ -256,11 +252,11 @@
 
 // Ensure that the swapchain's alpha is correct.
 TEST_F(DirectCompositionSurfaceTest, SwitchAlpha) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
+  surface_->SetEnableDCLayers(false);
   EXPECT_TRUE(surface_->Resize(gfx::Size(100, 100), 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_FALSE(surface_->GetBackbufferSwapChainForTesting());
 
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
@@ -273,11 +269,11 @@
 
   // Resize to the same parameters should have no effect.
   EXPECT_TRUE(surface_->Resize(gfx::Size(100, 100), 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_TRUE(surface_->GetBackbufferSwapChainForTesting());
 
   EXPECT_TRUE(surface_->Resize(gfx::Size(100, 100), 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, false));
+                               GLSurface::ColorSpace::UNSPECIFIED, false));
   EXPECT_FALSE(surface_->GetBackbufferSwapChainForTesting());
 
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
@@ -290,20 +286,17 @@
 
 // Ensure that the GLImage isn't presented again unless it changes.
 TEST_F(DirectCompositionSurfaceTest, NoPresentTwice) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(50, 50);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
       CreateNV12Texture(d3d11_device, texture_size, false);
 
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
   image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
 
@@ -344,8 +337,8 @@
   EXPECT_EQ(2u, last_present_count);
 
   // The image changed, we should get a new present
-  scoped_refptr<gl::GLImageDXGI> image_dxgi2(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi2(
+      new GLImageDXGI(texture_size, nullptr));
   image_dxgi2->SetTexture(texture, 0);
   image_dxgi2->SetColorSpace(gfx::ColorSpace::CreateREC709());
 
@@ -367,20 +360,17 @@
 // is support - swapchain should be the minimum of the decoded
 // video buffer size and the onscreen video size
 TEST_F(DirectCompositionSurfaceTest, SwapchainSizeWithScaledOverlays) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(64, 64);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
       CreateNV12Texture(d3d11_device, texture_size, false);
 
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
   image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
 
@@ -431,20 +421,17 @@
 // Ensure the swapchain size is set to the correct size if HW overlay scaling
 // is not support - swapchain should be the onscreen video size
 TEST_F(DirectCompositionSurfaceTest, SwapchainSizeWithoutScaledOverlays) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(80, 80);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
       CreateNV12Texture(d3d11_device, texture_size, false);
 
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
   image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
 
@@ -488,20 +475,17 @@
 
 // Test protected video flags
 TEST_F(DirectCompositionSurfaceTest, ProtectedVideos) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(1280, 720);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
       CreateNV12Texture(d3d11_device, texture_size, false);
 
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
   image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
   gfx::Size window_size(640, 360);
@@ -604,21 +588,27 @@
   }
 
  protected:
-  void InitializeSurface() override {
+  void SetUp() override {
     static_cast<ui::PlatformWindow*>(&window_)->Show();
-    DirectCompositionSurfaceTest::InitializeSurface();
+    DirectCompositionSurfaceTest::SetUp();
+  }
+
+  void TearDown() override {
+    // Test harness times out without DestroyWindow() here.
+    if (IsWindow(parent_window_))
+      DestroyWindow(parent_window_);
+    DirectCompositionSurfaceTest::TearDown();
   }
 
   void PixelTestSwapChain(bool layers_enabled) {
-    if (!CheckIfDCSupported())
+    if (!surface_)
       return;
+    if (!layers_enabled)
+      surface_->SetEnableDCLayers(false);
 
-    InitializeSurface();
-
-    surface_->SetEnableDCLayers(layers_enabled);
     gfx::Size window_size(100, 100);
     EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                                 gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                                 GLSurface::ColorSpace::UNSPECIFIED, true));
     EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
 
     glClearColor(1.0, 0.0, 0.0, 1.0);
@@ -667,24 +657,22 @@
   void TestVideo(const gfx::ColorSpace& color_space,
                  SkColor expected_color,
                  bool check_color) {
-    if (!CheckIfDCSupported())
+    if (!surface_)
       return;
-    InitializeSurface();
-    surface_->SetEnableDCLayers(true);
 
     gfx::Size window_size(100, 100);
     EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                                 gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                                 GLSurface::ColorSpace::UNSPECIFIED, true));
 
     Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-        gl::QueryD3D11DeviceObjectFromANGLE();
+        QueryD3D11DeviceObjectFromANGLE();
 
     gfx::Size texture_size(50, 50);
     Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
         CreateNV12Texture(d3d11_device, texture_size, false);
 
-    scoped_refptr<gl::GLImageDXGI> image_dxgi(
-        new gl::GLImageDXGI(texture_size, nullptr));
+    scoped_refptr<GLImageDXGI> image_dxgi(
+        new GLImageDXGI(texture_size, nullptr));
     image_dxgi->SetTexture(texture, 0);
     image_dxgi->SetColorSpace(color_space);
 
@@ -743,17 +731,15 @@
 }
 
 TEST_F(DirectCompositionPixelTest, SoftwareVideoSwapchain) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   gfx::Size window_size(100, 100);
   EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size y_size(50, 50);
   gfx::Size uv_size(25, 25);
@@ -763,11 +749,11 @@
       gfx::RowSizeForBufferFormat(uv_size.width(), gfx::BufferFormat::RG_88, 0);
   std::vector<uint8_t> y_data(y_stride * y_size.height(), 0xff);
   std::vector<uint8_t> uv_data(uv_stride * uv_size.height(), 0xff);
-  auto y_image = base::MakeRefCounted<gl::GLImageRefCountedMemory>(y_size);
+  auto y_image = base::MakeRefCounted<GLImageRefCountedMemory>(y_size);
 
   y_image->Initialize(new base::RefCountedBytes(y_data),
                       gfx::BufferFormat::R_8);
-  auto uv_image = base::MakeRefCounted<gl::GLImageRefCountedMemory>(uv_size);
+  auto uv_image = base::MakeRefCounted<GLImageRefCountedMemory>(uv_size);
   uv_image->Initialize(new base::RefCountedBytes(uv_data),
                        gfx::BufferFormat::RG_88);
   y_image->SetColorSpace(gfx::ColorSpace::CreateREC709());
@@ -792,17 +778,15 @@
 }
 
 TEST_F(DirectCompositionPixelTest, VideoHandleSwapchain) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   gfx::Size window_size(100, 100);
   EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(50, 50);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
@@ -813,8 +797,7 @@
   resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
                                &handle);
   // The format doesn't matter, since we aren't binding.
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   ASSERT_TRUE(image_dxgi->InitializeHandle(base::win::ScopedHandle(handle), 0,
                                            gfx::BufferFormat::RGBA_8888));
 
@@ -839,21 +822,19 @@
 }
 
 TEST_F(DirectCompositionPixelTest, SkipVideoLayerEmptyBoundsRect) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   gfx::Size window_size(100, 100);
   EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
 
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(50, 50);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
@@ -864,8 +845,7 @@
   resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
                                &handle);
   // The format doesn't matter, since we aren't binding.
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   ASSERT_TRUE(image_dxgi->InitializeHandle(base::win::ScopedHandle(handle), 0,
                                            gfx::BufferFormat::RGBA_8888));
 
@@ -892,24 +872,22 @@
 }
 
 TEST_F(DirectCompositionPixelTest, SkipVideoLayerEmptyContentsRect) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
   // Swap chain size is overridden to content rect size only if scaled overlays
   // are supported.
   DirectCompositionSurfaceWin::SetScaledOverlaysSupportedForTesting(true);
-  surface_->SetEnableDCLayers(true);
 
   gfx::Size window_size(100, 100);
   EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
 
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(50, 50);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
@@ -920,8 +898,7 @@
   resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
                                &handle);
   // The format doesn't matter, since we aren't binding.
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   ASSERT_TRUE(image_dxgi->InitializeHandle(base::win::ScopedHandle(handle), 0,
                                            gfx::BufferFormat::RGBA_8888));
 
@@ -948,23 +925,20 @@
 }
 
 TEST_F(DirectCompositionPixelTest, NV12SwapChain) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
   DirectCompositionSurfaceWin::SetPreferNV12OverlaysForTesting();
-  InitializeSurface();
-
-  surface_->SetEnableDCLayers(true);
 
   gfx::Size window_size(100, 100);
   EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
 
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(50, 50);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
@@ -975,8 +949,7 @@
   resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
                                &handle);
   // The format doesn't matter, since we aren't binding.
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   ASSERT_TRUE(image_dxgi->InitializeHandle(base::win::ScopedHandle(handle), 0,
                                            gfx::BufferFormat::RGBA_8888));
 
@@ -1013,24 +986,22 @@
 }
 
 TEST_F(DirectCompositionPixelTest, NonZeroBoundsOffset) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
   // Swap chain size is overridden to content rect size only if scaled overlays
   // are supported.
   DirectCompositionSurfaceWin::SetScaledOverlaysSupportedForTesting(true);
-  surface_->SetEnableDCLayers(true);
 
   gfx::Size window_size(100, 100);
   EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
 
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(50, 50);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
@@ -1041,8 +1012,7 @@
   resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
                                &handle);
   // The format doesn't matter, since we aren't binding.
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   ASSERT_TRUE(image_dxgi->InitializeHandle(base::win::ScopedHandle(handle), 0,
                                            gfx::BufferFormat::RGBA_8888));
 
@@ -1084,21 +1054,19 @@
 }
 
 TEST_F(DirectCompositionPixelTest, ResizeVideoLayer) {
-  if (!CheckIfDCSupported())
+  if (!surface_)
     return;
-  InitializeSurface();
-  surface_->SetEnableDCLayers(true);
 
   gfx::Size window_size(100, 100);
   EXPECT_TRUE(surface_->Resize(window_size, 1.0,
-                               gl::GLSurface::ColorSpace::UNSPECIFIED, true));
+                               GLSurface::ColorSpace::UNSPECIFIED, true));
   EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
 
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
-      gl::QueryD3D11DeviceObjectFromANGLE();
+      QueryD3D11DeviceObjectFromANGLE();
 
   gfx::Size texture_size(50, 50);
   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
@@ -1109,8 +1077,7 @@
   resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
                                &handle);
   // The format doesn't matter, since we aren't binding.
-  scoped_refptr<gl::GLImageDXGI> image_dxgi(
-      new gl::GLImageDXGI(texture_size, nullptr));
+  scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   ASSERT_TRUE(image_dxgi->InitializeHandle(base::win::ScopedHandle(handle), 0,
                                            gfx::BufferFormat::RGBA_8888));
 
@@ -1156,4 +1123,4 @@
 }
 
 }  // namespace
-}  // namespace gpu
+}  // namespace gl
diff --git a/ui/gl/egl_api_unittest.cc b/ui/gl/egl_api_unittest.cc
index 25ba3db8..bbf2ce1e 100644
--- a/ui/gl/egl_api_unittest.cc
+++ b/ui/gl/egl_api_unittest.cc
@@ -18,8 +18,6 @@
     fake_client_extension_string_ = "";
     fake_extension_string_ = "";
 
-    // TODO(dyen): Add a way to bind mock drivers for testing.
-    init::ShutdownGL(false);
     g_driver_egl.fn.eglInitializeFn = &FakeInitialize;
     g_driver_egl.fn.eglTerminateFn = &FakeTerminate;
     g_driver_egl.fn.eglQueryStringFn = &FakeQueryString;
diff --git a/ui/gl/gl_surface_egl_unittest.cc b/ui/gl/gl_surface_egl_unittest.cc
index b397b43..1349516 100644
--- a/ui/gl/gl_surface_egl_unittest.cc
+++ b/ui/gl/gl_surface_egl_unittest.cc
@@ -21,14 +21,19 @@
 
 namespace {
 
-class GLSurfaceEGLTest : public testing::Test {};
+class GLSurfaceEGLTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    GLSurfaceTestSupport::InitializeOneOffImplementation(
+        GLImplementation::kGLImplementationEGLGLES2, true);
+  }
+
+  void TearDown() override { gl::init::ShutdownGL(false); }
+};
 
 #if !defined(MEMORY_SANITIZER)
 // Fails under MSAN: crbug.com/886995
-TEST(GLSurfaceEGLTest, SurfaceFormatTest) {
-  GLSurfaceTestSupport::InitializeOneOffImplementation(
-      GLImplementation::kGLImplementationEGLGLES2, true);
-
+TEST_F(GLSurfaceEGLTest, SurfaceFormatTest) {
   GLSurfaceFormat surface_format = GLSurfaceFormat();
   surface_format.SetDepthBits(24);
   surface_format.SetStencilBits(8);
@@ -67,9 +72,7 @@
   void OnActivationChanged(bool active) override {}
 };
 
-TEST(GLSurfaceEGLTest, FixedSizeExtension) {
-  GLSurfaceTestSupport::InitializeOneOffImplementation(
-      GLImplementation::kGLImplementationEGLGLES2, true);
+TEST_F(GLSurfaceEGLTest, FixedSizeExtension) {
   TestPlatformDelegate platform_delegate;
   gfx::Size window_size(400, 500);
   ui::WinWindow window(&platform_delegate, gfx::Rect(window_size));
diff --git a/gpu/ipc/service/swap_chain_presenter.cc b/ui/gl/swap_chain_presenter.cc
similarity index 97%
rename from gpu/ipc/service/swap_chain_presenter.cc
rename to ui/gl/swap_chain_presenter.cc
index 5f2cf20..60bb0e65 100644
--- a/gpu/ipc/service/swap_chain_presenter.cc
+++ b/ui/gl/swap_chain_presenter.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/swap_chain_presenter.h"
+#include "ui/gl/swap_chain_presenter.h"
 
 #include <d3d11_1.h>
 
@@ -10,16 +10,15 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/trace_event.h"
-#include "components/crash/core/common/crash_key.h"
-#include "gpu/ipc/service/dc_layer_tree.h"
-#include "gpu/ipc/service/direct_composition_surface_win.h"
 #include "ui/gfx/color_space_win.h"
 #include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gl/dc_layer_tree.h"
+#include "ui/gl/direct_composition_surface_win.h"
 #include "ui/gl/gl_image_dxgi.h"
 #include "ui/gl/gl_image_memory.h"
 #include "ui/gl/gl_switches.h"
 
-namespace gpu {
+namespace gl {
 namespace {
 // Some drivers fail to correctly handle BT.709 video in overlays. This flag
 // converts them to BT.601 in the video processor.
@@ -243,8 +242,8 @@
 }
 
 Microsoft::WRL::ComPtr<ID3D11Texture2D> SwapChainPresenter::UploadVideoImages(
-    gl::GLImageMemory* y_image_memory,
-    gl::GLImageMemory* uv_image_memory) {
+    GLImageMemory* y_image_memory,
+    GLImageMemory* uv_image_memory) {
   gfx::Size texture_size = y_image_memory->GetSize();
   gfx::Size uv_image_size = uv_image_memory->GetSize();
   if (uv_image_size.height() != texture_size.height() / 2 ||
@@ -258,15 +257,6 @@
   TRACE_EVENT1("gpu", "SwapChainPresenter::UploadVideoImages", "size",
                texture_size.ToString());
 
-  static crash_reporter::CrashKeyString<32> texture_size_key(
-      "dynamic-texture-size");
-  texture_size_key.Set(texture_size.ToString());
-
-  static crash_reporter::CrashKeyString<2> first_use_key(
-      "dynamic-texture-first-use");
-  bool first_use = !staging_texture_ || (staging_texture_size_ != texture_size);
-  first_use_key.Set(first_use ? "1" : "0");
-
   bool use_dynamic_texture = !layer_tree_->disable_nv12_dynamic_textures();
 
   D3D11_TEXTURE2D_DESC desc = {};
@@ -498,7 +488,7 @@
 }
 
 bool SwapChainPresenter::TryPresentToDecodeSwapChain(
-    gl::GLImageDXGI* image_dxgi,
+    GLImageDXGI* image_dxgi,
     const gfx::Rect& content_rect,
     const gfx::Size& swap_chain_size) {
   if (!base::FeatureList::IsEnabled(
@@ -578,7 +568,7 @@
 }
 
 bool SwapChainPresenter::PresentToDecodeSwapChain(
-    gl::GLImageDXGI* image_dxgi,
+    GLImageDXGI* image_dxgi,
     const gfx::Rect& content_rect,
     const gfx::Size& swap_chain_size) {
   DCHECK(!swap_chain_size.IsEmpty());
@@ -707,12 +697,11 @@
 
 bool SwapChainPresenter::PresentToSwapChain(
     const ui::DCRendererLayerParams& params) {
-  gl::GLImageDXGI* image_dxgi =
-      gl::GLImageDXGI::FromGLImage(params.y_image.get());
-  gl::GLImageMemory* y_image_memory =
-      gl::GLImageMemory::FromGLImage(params.y_image.get());
-  gl::GLImageMemory* uv_image_memory =
-      gl::GLImageMemory::FromGLImage(params.uv_image.get());
+  GLImageDXGI* image_dxgi = GLImageDXGI::FromGLImage(params.y_image.get());
+  GLImageMemory* y_image_memory =
+      GLImageMemory::FromGLImage(params.y_image.get());
+  GLImageMemory* uv_image_memory =
+      GLImageMemory::FromGLImage(params.uv_image.get());
 
   if (!image_dxgi && (!y_image_memory || !uv_image_memory)) {
     DLOG(ERROR) << "Video GLImages are missing";
@@ -1204,4 +1193,4 @@
   return true;
 }
 
-}  // namespace gpu
+}  // namespace gl
diff --git a/gpu/ipc/service/swap_chain_presenter.h b/ui/gl/swap_chain_presenter.h
similarity index 94%
rename from gpu/ipc/service/swap_chain_presenter.h
rename to ui/gl/swap_chain_presenter.h
index 99ca2a0..dc8c7dc 100644
--- a/gpu/ipc/service/swap_chain_presenter.h
+++ b/ui/gl/swap_chain_presenter.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef GPU_IPC_SERVICE_SWAP_CHAIN_PRESENTER_H_
-#define GPU_IPC_SERVICE_SWAP_CHAIN_PRESENTER_H_
+#ifndef UI_GL_SWAP_CHAIN_PRESENTER_H_
+#define UI_GL_SWAP_CHAIN_PRESENTER_H_
 
 #include <windows.h>
 #include <d3d11.h>
@@ -16,12 +16,9 @@
 #include "ui/gl/dc_renderer_layer_params.h"
 
 namespace gl {
+class DCLayerTree;
 class GLImageDXGI;
 class GLImageMemory;
-}  // namespace gl
-
-namespace gpu {
-class DCLayerTree;
 
 // SwapChainPresenter holds a swap chain, direct composition visuals, and other
 // associated resources for a single overlay layer.  It is updated by calling
@@ -92,8 +89,8 @@
   // Upload given YUV buffers to an NV12 texture that can be used to create
   // video processor input view.  Returns nullptr on failure.
   Microsoft::WRL::ComPtr<ID3D11Texture2D> UploadVideoImages(
-      gl::GLImageMemory* y_image_memory,
-      gl::GLImageMemory* uv_image_memory);
+      GLImageMemory* y_image_memory,
+      GLImageMemory* uv_image_memory);
 
   // Releases resources that might hold indirect references to the swap chain.
   void ReleaseSwapChainResources();
@@ -132,14 +129,14 @@
   // Try presenting to a decode swap chain based on various conditions such as
   // global state (e.g. finch, NV12 support), texture flags, and transform.
   // Returns true on success.  See PresentToDecodeSwapChain() for more info.
-  bool TryPresentToDecodeSwapChain(gl::GLImageDXGI* image_dxgi,
+  bool TryPresentToDecodeSwapChain(GLImageDXGI* image_dxgi,
                                    const gfx::Rect& content_rect,
                                    const gfx::Size& swap_chain_size);
 
   // Present to a decode swap chain created from compatible video decoder
   // buffers using given |image_dxgi| with destination size |swap_chain_size|.
   // Returns true on success.
-  bool PresentToDecodeSwapChain(gl::GLImageDXGI* image_dxgi,
+  bool PresentToDecodeSwapChain(GLImageDXGI* image_dxgi,
                                 const gfx::Rect& content_rect,
                                 const gfx::Size& swap_chain_size);
 
@@ -198,8 +195,8 @@
   Microsoft::WRL::ComPtr<IDCompositionVisual2> clip_visual_;
 
   // GLImages that were presented in the last frame.
-  scoped_refptr<gl::GLImage> last_y_image_;
-  scoped_refptr<gl::GLImage> last_uv_image_;
+  scoped_refptr<GLImage> last_y_image_;
+  scoped_refptr<GLImage> last_uv_image_;
 
   // NV12 staging texture used for software decoded YUV buffers.  Mapped to CPU
   // for copying from YUV buffers.  Texture usage is DYNAMIC or STAGING.
@@ -227,6 +224,6 @@
   DISALLOW_COPY_AND_ASSIGN(SwapChainPresenter);
 };
 
-}  // namespace gpu
+}  // namespace gl
 
-#endif  // GPU_IPC_SERVICE_SWAP_CHAIN_PRESENTER_H_
+#endif  // UI_GL_SWAP_CHAIN_PRESENTER_H_
diff --git a/ui/views/test/scoped_views_test_helper.cc b/ui/views/test/scoped_views_test_helper.cc
index 2f28510..bb1521d 100644
--- a/ui/views/test/scoped_views_test_helper.cc
+++ b/ui/views/test/scoped_views_test_helper.cc
@@ -37,8 +37,7 @@
   test_helper_->SetUp();
 
   ui::InitializeInputMethodForTesting();
-  if (!ui::Clipboard::GetForCurrentThread())
-    ui::TestClipboard::CreateForCurrentThread();
+  ui::TestClipboard::CreateForCurrentThread();
 }
 
 ScopedViewsTestHelper::~ScopedViewsTestHelper() {
diff --git a/ui/wm/core/cursor_manager.cc b/ui/wm/core/cursor_manager.cc
index 25d4854..a39e3f0 100644
--- a/ui/wm/core/cursor_manager.cc
+++ b/ui/wm/core/cursor_manager.cc
@@ -92,10 +92,16 @@
 }
 
 void CursorManager::SetCursor(gfx::NativeCursor cursor) {
+  bool previously_visible = GetCursor().native_type() != ui::CursorType::kNone;
   state_on_unlock_->set_cursor(cursor);
   if (cursor_lock_count_ == 0 &&
       GetCursor() != state_on_unlock_->cursor()) {
     delegate_->SetCursor(state_on_unlock_->cursor(), this);
+    bool is_visible = cursor.native_type() != ui::CursorType::kNone;
+    if (is_visible != previously_visible) {
+      for (auto& observer : observers_)
+        observer.OnCursorVisibilityChanged(is_visible);
+    }
   }
 }
 
@@ -109,8 +115,11 @@
   if (cursor_lock_count_ == 0 &&
       IsCursorVisible() != state_on_unlock_->visible()) {
     delegate_->SetVisibility(state_on_unlock_->visible(), this);
-    for (auto& observer : observers_)
-      observer.OnCursorVisibilityChanged(true);
+    if (GetCursor().native_type() != ui::CursorType::kNone) {
+      // If the cursor is a visible type, notify the observers.
+      for (auto& observer : observers_)
+        observer.OnCursorVisibilityChanged(true);
+    }
   }
 }
 
@@ -225,8 +234,10 @@
 void CursorManager::CommitVisibility(bool visible) {
   // TODO(tdanderson): Find a better place for this so we don't
   // notify the observers more than is necessary.
-  for (auto& observer : observers_)
-    observer.OnCursorVisibilityChanged(visible);
+  for (auto& observer : observers_) {
+    observer.OnCursorVisibilityChanged(
+        GetCursor().native_type() == ui::CursorType::kNone ? false : visible);
+  }
   current_state_->SetVisible(visible);
 }
 
diff --git a/ui/wm/core/cursor_manager_unittest.cc b/ui/wm/core/cursor_manager_unittest.cc
index 17e0603..43b7872 100644
--- a/ui/wm/core/cursor_manager_unittest.cc
+++ b/ui/wm/core/cursor_manager_unittest.cc
@@ -259,6 +259,7 @@
 }
 
 TEST_F(CursorManagerTest, TestCursorClientObserver) {
+  cursor_manager_.SetCursor(ui::CursorType::kPointer);
   // Add two observers. Both should have OnCursorVisibilityChanged()
   // invoked when the visibility of the cursor changes.
   wm::TestingCursorClientObserver observer_a;
@@ -283,7 +284,7 @@
   EXPECT_FALSE(observer_a.is_cursor_visible());
   EXPECT_FALSE(observer_b.is_cursor_visible());
 
-  // Set the cursor set.
+  // Set the cursor size.
   cursor_manager_.SetCursorSize(ui::CursorSize::kLarge);
   EXPECT_TRUE(observer_a.did_cursor_size_change());
   EXPECT_EQ(ui::CursorSize::kLarge, observer_a.cursor_size());
@@ -324,6 +325,35 @@
   EXPECT_TRUE(observer_a.did_visibility_change());
   EXPECT_FALSE(observer_b.did_visibility_change());
   EXPECT_TRUE(observer_a.is_cursor_visible());
+
+  // Hide the cursor by changing the cursor type.
+  cursor_manager_.SetCursor(ui::CursorType::kPointer);
+  observer_a.reset();
+  cursor_manager_.SetCursor(ui::CursorType::kNone);
+  EXPECT_TRUE(observer_a.did_visibility_change());
+  EXPECT_FALSE(observer_a.is_cursor_visible());
+
+  // Show the cursor by changing the cursor type.
+  observer_a.reset();
+  cursor_manager_.SetCursor(ui::CursorType::kPointer);
+  EXPECT_TRUE(observer_a.did_visibility_change());
+  EXPECT_TRUE(observer_a.is_cursor_visible());
+
+  // Changing the type to another visible type doesn't cause unnecessary
+  // callbacks.
+  observer_a.reset();
+  cursor_manager_.SetCursor(ui::CursorType::kHand);
+  EXPECT_FALSE(observer_a.did_visibility_change());
+  EXPECT_FALSE(observer_a.is_cursor_visible());
+
+  // If the type is kNone, showing the cursor shouldn't cause observers to
+  // think that the cursor is now visible.
+  cursor_manager_.HideCursor();
+  cursor_manager_.SetCursor(ui::CursorType::kNone);
+  observer_a.reset();
+  cursor_manager_.ShowCursor();
+  EXPECT_TRUE(observer_a.did_visibility_change());
+  EXPECT_FALSE(observer_a.is_cursor_visible());
 }
 
 // This test validates that the cursor visiblity state is restored when a