diff --git a/DEPS b/DEPS
index cc154a5..12a973a 100644
--- a/DEPS
+++ b/DEPS
@@ -96,7 +96,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': '6171fd4dd88d738437b803fbf5374be6bf9c5342',
+  'catapult_revision': 'e7bf345be18c1f99e8ddd3d161c1f46013d095ed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/android_webview/browser/aw_metrics_service_client.cc b/android_webview/browser/aw_metrics_service_client.cc
index 5079786..3e82802 100644
--- a/android_webview/browser/aw_metrics_service_client.cc
+++ b/android_webview/browser/aw_metrics_service_client.cc
@@ -8,6 +8,7 @@
 #include "android_webview/common/aw_version_info_values.h"
 #include "android_webview/jni/AwMetricsServiceClient_jni.h"
 #include "base/android/build_info.h"
+#include "base/android/jni_string.h"
 #include "base/bind.h"
 #include "base/files/file_util.h"
 #include "base/guid.h"
@@ -23,7 +24,10 @@
 #include "components/metrics/profiler/profiler_metrics_provider.h"
 #include "components/metrics/ui/screen_info_metrics_provider.h"
 #include "components/metrics/url_constants.h"
+#include "components/metrics/version_utils.h"
 #include "components/prefs/pref_service.h"
+#include "components/version_info/channel_android.h"
+#include "components/version_info/version_info.h"
 #include "content/public/browser/browser_thread.h"
 
 namespace android_webview {
@@ -68,6 +72,15 @@
   return;
 }
 
+version_info::Channel GetChannelFromPackageName() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  std::string package_name = base::android::ConvertJavaStringToUTF8(
+      env, Java_AwMetricsServiceClient_getWebViewPackageName(env));
+  // We can't determine the channel for stand-alone WebView, since it has the
+  // same package name across channels. It will always be "unknown".
+  return version_info::ChannelFromPackageName(package_name.c_str());
+}
+
 }  // namespace
 
 // static
@@ -86,6 +99,7 @@
   DCHECK(request_context_ == nullptr);
   pref_service_ = pref_service;
   request_context_ = request_context;
+  channel_ = GetChannelFromPackageName();
 
   std::string* guid = new std::string;
   // Initialization happens on the UI thread, but getting the GUID should happen
@@ -186,9 +200,7 @@
 }
 
 metrics::SystemProfileProto::Channel AwMetricsServiceClient::GetChannel() {
-  // "Channel" means stable, beta, etc. WebView doesn't have channel info yet.
-  // TODO(paulmiller) Update this once we have channel info.
-  return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
+  return metrics::AsProtobufChannel(channel_);
 }
 
 std::string AwMetricsServiceClient::GetVersionString() {
@@ -224,7 +236,10 @@
 }
 
 AwMetricsServiceClient::AwMetricsServiceClient()
-    : is_enabled_(false), pref_service_(nullptr), request_context_(nullptr) {}
+    : is_enabled_(false),
+      pref_service_(nullptr),
+      request_context_(nullptr),
+      channel_(version_info::Channel::UNKNOWN) {}
 
 AwMetricsServiceClient::~AwMetricsServiceClient() {}
 
diff --git a/android_webview/browser/aw_metrics_service_client.h b/android_webview/browser/aw_metrics_service_client.h
index b68d215..733bd6b 100644
--- a/android_webview/browser/aw_metrics_service_client.h
+++ b/android_webview/browser/aw_metrics_service_client.h
@@ -14,6 +14,7 @@
 #include "components/metrics/enabled_state_provider.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "components/metrics/metrics_service_client.h"
+#include "components/version_info/channel.h"
 
 class PrefService;
 
@@ -85,6 +86,7 @@
   net::URLRequestContextGetter* request_context_;
   std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_;
   std::unique_ptr<metrics::MetricsService> metrics_service_;
+  version_info::Channel channel_;
 
   DISALLOW_COPY_AND_ASSIGN(AwMetricsServiceClient);
 };
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
index abd1f8c..9e2aa4c 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
@@ -419,6 +419,7 @@
         // The WebView package name is used to locate the separate Service to which we copy crash
         // minidumps. This package name must be set before a render process has a chance to crash -
         // otherwise we might try to copy a minidump without knowing what process to copy it to.
+        // It's also used to determine channel for UMA, so it must be set before initializing UMA.
         AwBrowserProcess.setWebViewPackageName(webViewPackageName);
         AwBrowserProcess.configureChildProcessLauncher(webViewPackageName, isExternalService);
         AwBrowserProcess.start();
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
index 81ec9a5..f25d41d 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -158,6 +158,11 @@
         sWebViewPackageName = webViewPackageName;
     }
 
+    public static String getWebViewPackageName() {
+        if (sWebViewPackageName == null) return ""; // May be null in testing.
+        return sWebViewPackageName;
+    }
+
     /**
      * Trigger minidump copying, which in turn triggers minidump uploading.
      */
diff --git a/android_webview/java/src/org/chromium/android_webview/AwMetricsServiceClient.java b/android_webview/java/src/org/chromium/android_webview/AwMetricsServiceClient.java
index 209c8a6..949b51a 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwMetricsServiceClient.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwMetricsServiceClient.java
@@ -79,5 +79,10 @@
         }
     }
 
+    @CalledByNative
+    public static String getWebViewPackageName() {
+        return AwBrowserProcess.getWebViewPackageName();
+    }
+
     public static native void nativeSetMetricsEnabled(boolean enabled);
 }
diff --git a/ash/system/audio/tray_audio.cc b/ash/system/audio/tray_audio.cc
index b6787ec..a526925a 100644
--- a/ash/system/audio/tray_audio.cc
+++ b/ash/system/audio/tray_audio.cc
@@ -75,11 +75,11 @@
   }
 }
 
-void TrayAudio::DestroyDefaultView() {
-  volume_view_ = NULL;
+void TrayAudio::OnDefaultViewDestroyed() {
+  volume_view_ = nullptr;
 }
 
-void TrayAudio::DestroyDetailedView() {
+void TrayAudio::OnDetailedViewDestroyed() {
   if (audio_detail_view_) {
     audio_detail_view_ = nullptr;
   } else if (volume_view_) {
diff --git a/ash/system/audio/tray_audio.h b/ash/system/audio/tray_audio.h
index 99f137b..dbee288 100644
--- a/ash/system/audio/tray_audio.h
+++ b/ash/system/audio/tray_audio.h
@@ -50,8 +50,8 @@
   // Overridden from SystemTrayItem.
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
   bool ShouldShowShelf() const override;
 
   // Overridden from CrasAudioHandler::AudioObserver.
diff --git a/ash/system/audio/tray_audio_unittest.cc b/ash/system/audio/tray_audio_unittest.cc
index 592f7550..1e26c6d 100644
--- a/ash/system/audio/tray_audio_unittest.cc
+++ b/ash/system/audio/tray_audio_unittest.cc
@@ -4,8 +4,10 @@
 
 #include "ash/system/audio/tray_audio.h"
 
+#include "ash/system/status_area_widget.h"
 #include "ash/system/tray/system_tray.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/test/status_area_widget_test_helper.h"
 
 namespace ash {
 
@@ -20,12 +22,20 @@
   EXPECT_FALSE(tray_audio->volume_view_for_testing());
   EXPECT_FALSE(tray_audio->pop_up_volume_view_for_testing());
 
+  // When set to autohide, the shelf shouldn't be shown.
+  StatusAreaWidget* status = StatusAreaWidgetTestHelper::GetStatusAreaWidget();
+  EXPECT_FALSE(status->ShouldShowShelf());
+
   // Simulate ARC asking to show the volume view.
   TrayAudio::ShowPopUpVolumeView();
 
   // Volume view is now visible.
   EXPECT_TRUE(tray_audio->volume_view_for_testing());
   EXPECT_TRUE(tray_audio->pop_up_volume_view_for_testing());
+
+  // This does not force the shelf to automatically show. Regression tests for
+  // crbug.com/729188
+  EXPECT_FALSE(status->ShouldShowShelf());
 }
 
 }  // namespace ash
diff --git a/ash/system/bluetooth/tray_bluetooth.cc b/ash/system/bluetooth/tray_bluetooth.cc
index 222f4cd..5c8674c 100644
--- a/ash/system/bluetooth/tray_bluetooth.cc
+++ b/ash/system/bluetooth/tray_bluetooth.cc
@@ -533,12 +533,8 @@
   Shell::Get()->system_tray_notifier()->RemoveBluetoothObserver(this);
 }
 
-views::View* TrayBluetooth::CreateTrayView(LoginStatus status) {
-  return NULL;
-}
-
 views::View* TrayBluetooth::CreateDefaultView(LoginStatus status) {
-  CHECK(default_ == NULL);
+  CHECK(default_ == nullptr);
   default_ = new tray::BluetoothDefaultView(this);
   default_->SetEnabled(status != LoginStatus::LOCKED);
   default_->Update();
@@ -547,23 +543,21 @@
 
 views::View* TrayBluetooth::CreateDetailedView(LoginStatus status) {
   if (!Shell::Get()->tray_bluetooth_helper()->GetBluetoothAvailable())
-    return NULL;
+    return nullptr;
   ShellPort::Get()->RecordUserMetricsAction(
       UMA_STATUS_AREA_DETAILED_BLUETOOTH_VIEW);
-  CHECK(detailed_ == NULL);
+  CHECK(detailed_ == nullptr);
   detailed_ = new tray::BluetoothDetailedView(this, status);
   detailed_->Update();
   return detailed_;
 }
 
-void TrayBluetooth::DestroyTrayView() {}
-
-void TrayBluetooth::DestroyDefaultView() {
-  default_ = NULL;
+void TrayBluetooth::OnDefaultViewDestroyed() {
+  default_ = nullptr;
 }
 
-void TrayBluetooth::DestroyDetailedView() {
-  detailed_ = NULL;
+void TrayBluetooth::OnDetailedViewDestroyed() {
+  detailed_ = nullptr;
 }
 
 void TrayBluetooth::UpdateAfterLoginStatusChange(LoginStatus status) {}
diff --git a/ash/system/bluetooth/tray_bluetooth.h b/ash/system/bluetooth/tray_bluetooth.h
index 2e3dc514..1d16b442 100644
--- a/ash/system/bluetooth/tray_bluetooth.h
+++ b/ash/system/bluetooth/tray_bluetooth.h
@@ -27,12 +27,10 @@
 
  private:
   // Overridden from SystemTrayItem.
-  views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
   void UpdateAfterLoginStatusChange(LoginStatus status) override;
 
   // Overridden from BluetoothObserver.
diff --git a/ash/system/brightness/tray_brightness.cc b/ash/system/brightness/tray_brightness.cc
index c79eab5..b4ad50f 100644
--- a/ash/system/brightness/tray_brightness.cc
+++ b/ash/system/brightness/tray_brightness.cc
@@ -174,7 +174,7 @@
 
 TrayBrightness::TrayBrightness(SystemTray* system_tray)
     : SystemTrayItem(system_tray, UMA_DISPLAY_BRIGHTNESS),
-      brightness_view_(NULL),
+      brightness_view_(nullptr),
       current_percent_(100.0),
       got_current_percent_(false),
       weak_ptr_factory_(this) {
@@ -208,34 +208,28 @@
     HandleBrightnessChanged(percent, false);
 }
 
-views::View* TrayBrightness::CreateTrayView(LoginStatus status) {
-  return NULL;
-}
-
 views::View* TrayBrightness::CreateDefaultView(LoginStatus status) {
-  CHECK(brightness_view_ == NULL);
+  CHECK(brightness_view_ == nullptr);
   brightness_view_ = new tray::BrightnessView(true, current_percent_);
   return brightness_view_;
 }
 
 views::View* TrayBrightness::CreateDetailedView(LoginStatus status) {
-  CHECK(brightness_view_ == NULL);
+  CHECK(brightness_view_ == nullptr);
   ShellPort::Get()->RecordUserMetricsAction(
       UMA_STATUS_AREA_DETAILED_BRIGHTNESS_VIEW);
   brightness_view_ = new tray::BrightnessView(false, current_percent_);
   return brightness_view_;
 }
 
-void TrayBrightness::DestroyTrayView() {}
-
-void TrayBrightness::DestroyDefaultView() {
+void TrayBrightness::OnDefaultViewDestroyed() {
   if (brightness_view_ && brightness_view_->is_default_view())
-    brightness_view_ = NULL;
+    brightness_view_ = nullptr;
 }
 
-void TrayBrightness::DestroyDetailedView() {
+void TrayBrightness::OnDetailedViewDestroyed() {
   if (brightness_view_ && !brightness_view_->is_default_view())
-    brightness_view_ = NULL;
+    brightness_view_ = nullptr;
 }
 
 void TrayBrightness::UpdateAfterLoginStatusChange(LoginStatus status) {}
diff --git a/ash/system/brightness/tray_brightness.h b/ash/system/brightness/tray_brightness.h
index c443f6f..ad3c1b1 100644
--- a/ash/system/brightness/tray_brightness.h
+++ b/ash/system/brightness/tray_brightness.h
@@ -35,12 +35,10 @@
   void HandleInitialBrightness(double percent);
 
   // Overridden from SystemTrayItem.
-  views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
   void UpdateAfterLoginStatusChange(LoginStatus status) override;
   bool ShouldShowShelf() const override;
 
diff --git a/ash/system/cast/tray_cast.cc b/ash/system/cast/tray_cast.cc
index 70678868..f0ea1cc 100644
--- a/ash/system/cast/tray_cast.cc
+++ b/ash/system/cast/tray_cast.cc
@@ -464,15 +464,15 @@
   return detailed_;
 }
 
-void TrayCast::DestroyTrayView() {
+void TrayCast::OnTrayViewDestroyed() {
   tray_ = nullptr;
 }
 
-void TrayCast::DestroyDefaultView() {
+void TrayCast::OnDefaultViewDestroyed() {
   default_ = nullptr;
 }
 
-void TrayCast::DestroyDetailedView() {
+void TrayCast::OnDetailedViewDestroyed() {
   detailed_ = nullptr;
 }
 
diff --git a/ash/system/cast/tray_cast.h b/ash/system/cast/tray_cast.h
index 3e6b920..8101f0c 100644
--- a/ash/system/cast/tray_cast.h
+++ b/ash/system/cast/tray_cast.h
@@ -42,9 +42,9 @@
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnTrayViewDestroyed() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
 
   // Overridden from ShellObserver.
   void OnCastingSessionStartedOrStopped(bool started) override;
diff --git a/ash/system/date/tray_system_info.cc b/ash/system/date/tray_system_info.cc
index 7075708..a5f814f 100644
--- a/ash/system/date/tray_system_info.cc
+++ b/ash/system/date/tray_system_info.cc
@@ -62,11 +62,11 @@
   return default_view_;
 }
 
-void TraySystemInfo::DestroyTrayView() {
+void TraySystemInfo::OnTrayViewDestroyed() {
   tray_view_ = nullptr;
 }
 
-void TraySystemInfo::DestroyDefaultView() {
+void TraySystemInfo::OnDefaultViewDestroyed() {
   default_view_ = nullptr;
 }
 
diff --git a/ash/system/date/tray_system_info.h b/ash/system/date/tray_system_info.h
index 45a3194..f39a5da 100644
--- a/ash/system/date/tray_system_info.h
+++ b/ash/system/date/tray_system_info.h
@@ -40,8 +40,8 @@
   // SystemTrayItem:
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
+  void OnTrayViewDestroyed() override;
+  void OnDefaultViewDestroyed() override;
   void UpdateAfterShelfAlignmentChange() override;
 
   // ClockObserver:
diff --git a/ash/system/display_scale/tray_scale.cc b/ash/system/display_scale/tray_scale.cc
index 3e67189..6ec7ce38 100644
--- a/ash/system/display_scale/tray_scale.cc
+++ b/ash/system/display_scale/tray_scale.cc
@@ -44,11 +44,11 @@
   return scale_detail_view_;
 }
 
-void TrayScale::DestroyDefaultView() {
+void TrayScale::OnDefaultViewDestroyed() {
   scale_view_ = nullptr;
 }
 
-void TrayScale::DestroyDetailedView() {
+void TrayScale::OnDetailedViewDestroyed() {
   scale_detail_view_ = nullptr;
 }
 
diff --git a/ash/system/display_scale/tray_scale.h b/ash/system/display_scale/tray_scale.h
index 64d3277..885534b 100644
--- a/ash/system/display_scale/tray_scale.h
+++ b/ash/system/display_scale/tray_scale.h
@@ -27,8 +27,8 @@
   // Overridden from SystemTrayItem.
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
   bool ShouldShowShelf() const override;
 
   tray::ScaleView* scale_view_;
diff --git a/ash/system/enterprise/tray_enterprise.cc b/ash/system/enterprise/tray_enterprise.cc
index cd340e1..25976e1 100644
--- a/ash/system/enterprise/tray_enterprise.cc
+++ b/ash/system/enterprise/tray_enterprise.cc
@@ -61,7 +61,7 @@
   return tray_view_;
 }
 
-void TrayEnterprise::DestroyDefaultView() {
+void TrayEnterprise::OnDefaultViewDestroyed() {
   tray_view_ = nullptr;
 }
 
diff --git a/ash/system/enterprise/tray_enterprise.h b/ash/system/enterprise/tray_enterprise.h
index b00ef68..8f3eb68 100644
--- a/ash/system/enterprise/tray_enterprise.h
+++ b/ash/system/enterprise/tray_enterprise.h
@@ -29,7 +29,7 @@
 
   // Overridden from SystemTrayItem.
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyDefaultView() override;
+  void OnDefaultViewDestroyed() override;
 
   // Overridden from EnterpriseDomainObserver.
   void OnEnterpriseDomainChanged() override;
diff --git a/ash/system/ime/tray_ime_chromeos.cc b/ash/system/ime/tray_ime_chromeos.cc
index 0b3d22c..9ecec4f7 100644
--- a/ash/system/ime/tray_ime_chromeos.cc
+++ b/ash/system/ime/tray_ime_chromeos.cc
@@ -146,9 +146,9 @@
 
 TrayIME::TrayIME(SystemTray* system_tray)
     : SystemTrayItem(system_tray, UMA_IME),
-      tray_label_(NULL),
-      default_(NULL),
-      detailed_(NULL),
+      tray_label_(nullptr),
+      default_(nullptr),
+      detailed_(nullptr),
       keyboard_suppressed_(false),
       is_visible_(true) {
   SystemTrayNotifier* tray_notifier = Shell::Get()->system_tray_notifier();
@@ -224,7 +224,7 @@
 }
 
 views::View* TrayIME::CreateTrayView(LoginStatus status) {
-  CHECK(tray_label_ == NULL);
+  CHECK(tray_label_ == nullptr);
   tray_label_ = new TrayItemView(this);
   tray_label_->CreateLabel();
   SetupLabelForTray(tray_label_->label());
@@ -235,7 +235,7 @@
 }
 
 views::View* TrayIME::CreateDefaultView(LoginStatus status) {
-  CHECK(default_ == NULL);
+  CHECK(default_ == nullptr);
   default_ = new tray::IMEDefaultView(
       this, GetDefaultViewLabel(ShouldShowImeTrayItem(ime_list_.size())));
   default_->SetVisible(ShouldDefaultViewBeVisible());
@@ -243,23 +243,23 @@
 }
 
 views::View* TrayIME::CreateDetailedView(LoginStatus status) {
-  CHECK(detailed_ == NULL);
+  CHECK(detailed_ == nullptr);
   detailed_ = new tray::IMEDetailedView(this);
   detailed_->SetImeManagedMessage(ime_managed_message_);
   detailed_->Init(ShouldShowKeyboardToggle(), GetSingleImeBehavior());
   return detailed_;
 }
 
-void TrayIME::DestroyTrayView() {
-  tray_label_ = NULL;
+void TrayIME::OnTrayViewDestroyed() {
+  tray_label_ = nullptr;
 }
 
-void TrayIME::DestroyDefaultView() {
-  default_ = NULL;
+void TrayIME::OnDefaultViewDestroyed() {
+  default_ = nullptr;
 }
 
-void TrayIME::DestroyDetailedView() {
-  detailed_ = NULL;
+void TrayIME::OnDetailedViewDestroyed() {
+  detailed_ = nullptr;
 }
 
 void TrayIME::OnIMERefresh() {
diff --git a/ash/system/ime/tray_ime_chromeos.h b/ash/system/ime/tray_ime_chromeos.h
index 46674e0c..ac60097 100644
--- a/ash/system/ime/tray_ime_chromeos.h
+++ b/ash/system/ime/tray_ime_chromeos.h
@@ -59,9 +59,9 @@
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnTrayViewDestroyed() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
 
   // Overridden from IMEObserver.
   void OnIMERefresh() override;
diff --git a/ash/system/network/tray_network.cc b/ash/system/network/tray_network.cc
index 24665c61..6455d7357 100644
--- a/ash/system/network/tray_network.cc
+++ b/ash/system/network/tray_network.cc
@@ -207,9 +207,9 @@
 
 TrayNetwork::TrayNetwork(SystemTray* system_tray)
     : SystemTrayItem(system_tray, UMA_NETWORK),
-      tray_(NULL),
-      default_(NULL),
-      detailed_(NULL) {
+      tray_(nullptr),
+      default_(nullptr),
+      detailed_(nullptr) {
   network_state_observer_.reset(new TrayNetworkStateObserver(this));
   SystemTrayNotifier* notifier = Shell::Get()->system_tray_notifier();
   notifier->AddNetworkObserver(this);
@@ -223,44 +223,44 @@
 }
 
 views::View* TrayNetwork::CreateTrayView(LoginStatus status) {
-  CHECK(tray_ == NULL);
+  CHECK(tray_ == nullptr);
   if (!chromeos::NetworkHandler::IsInitialized())
-    return NULL;
+    return nullptr;
   tray_ = new tray::NetworkTrayView(this);
   return tray_;
 }
 
 views::View* TrayNetwork::CreateDefaultView(LoginStatus status) {
-  CHECK(default_ == NULL);
+  CHECK(default_ == nullptr);
   if (!chromeos::NetworkHandler::IsInitialized())
-    return NULL;
-  CHECK(tray_ != NULL);
+    return nullptr;
+  CHECK(tray_ != nullptr);
   default_ = new tray::NetworkDefaultView(this);
   default_->SetEnabled(status != LoginStatus::LOCKED);
   return default_;
 }
 
 views::View* TrayNetwork::CreateDetailedView(LoginStatus status) {
-  CHECK(detailed_ == NULL);
+  CHECK(detailed_ == nullptr);
   ShellPort::Get()->RecordUserMetricsAction(
       UMA_STATUS_AREA_DETAILED_NETWORK_VIEW);
   if (!chromeos::NetworkHandler::IsInitialized())
-    return NULL;
+    return nullptr;
   detailed_ = new tray::NetworkListView(this, status);
   detailed_->Init();
   return detailed_;
 }
 
-void TrayNetwork::DestroyTrayView() {
-  tray_ = NULL;
+void TrayNetwork::OnTrayViewDestroyed() {
+  tray_ = nullptr;
 }
 
-void TrayNetwork::DestroyDefaultView() {
-  default_ = NULL;
+void TrayNetwork::OnDefaultViewDestroyed() {
+  default_ = nullptr;
 }
 
-void TrayNetwork::DestroyDetailedView() {
-  detailed_ = NULL;
+void TrayNetwork::OnDetailedViewDestroyed() {
+  detailed_ = nullptr;
 }
 
 void TrayNetwork::RequestToggleWifi() {
diff --git a/ash/system/network/tray_network.h b/ash/system/network/tray_network.h
index 03b85af..93c68d69 100644
--- a/ash/system/network/tray_network.h
+++ b/ash/system/network/tray_network.h
@@ -36,9 +36,9 @@
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnTrayViewDestroyed() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
 
   // NetworkObserver
   void RequestToggleWifi() override;
diff --git a/ash/system/network/tray_vpn.cc b/ash/system/network/tray_vpn.cc
index a03e2c7..8eadaf2a 100644
--- a/ash/system/network/tray_vpn.cc
+++ b/ash/system/network/tray_vpn.cc
@@ -178,11 +178,11 @@
   return detailed_;
 }
 
-void TrayVPN::DestroyDefaultView() {
+void TrayVPN::OnDefaultViewDestroyed() {
   default_ = nullptr;
 }
 
-void TrayVPN::DestroyDetailedView() {
+void TrayVPN::OnDetailedViewDestroyed() {
   detailed_ = nullptr;
 }
 
diff --git a/ash/system/network/tray_vpn.h b/ash/system/network/tray_vpn.h
index fb81ba4..cda9c84 100644
--- a/ash/system/network/tray_vpn.h
+++ b/ash/system/network/tray_vpn.h
@@ -28,8 +28,8 @@
   // SystemTrayItem
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
 
   // TrayNetworkStateObserver::Delegate
   void NetworkStateChanged() override;
diff --git a/ash/system/power/tray_power.cc b/ash/system/power/tray_power.cc
index 71353b5..8482046 100644
--- a/ash/system/power/tray_power.cc
+++ b/ash/system/power/tray_power.cc
@@ -169,7 +169,7 @@
 TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center)
     : SystemTrayItem(system_tray, UMA_POWER),
       message_center_(message_center),
-      power_tray_(NULL),
+      power_tray_(nullptr),
       notification_state_(NOTIFICATION_NONE),
       usb_charger_was_connected_(false),
       line_power_was_connected_(false),
@@ -186,7 +186,7 @@
   // There may not be enough information when this is created about whether
   // there is a battery or not. So always create this, and adjust visibility as
   // necessary.
-  CHECK(power_tray_ == NULL);
+  CHECK(power_tray_ == nullptr);
   power_tray_ = new tray::PowerTrayView(this);
   power_tray_->UpdateStatus(false);
   return power_tray_;
@@ -195,11 +195,11 @@
 views::View* TrayPower::CreateDefaultView(LoginStatus status) {
   // Make sure icon status is up to date. (Also triggers stub activation).
   PowerStatus::Get()->RequestStatusUpdate();
-  return NULL;
+  return nullptr;
 }
 
-void TrayPower::DestroyTrayView() {
-  power_tray_ = NULL;
+void TrayPower::OnTrayViewDestroyed() {
+  power_tray_ = nullptr;
 }
 
 void TrayPower::OnPowerStatusChanged() {
diff --git a/ash/system/power/tray_power.h b/ash/system/power/tray_power.h
index ff35549..c34d49f 100644
--- a/ash/system/power/tray_power.h
+++ b/ash/system/power/tray_power.h
@@ -72,7 +72,7 @@
   // Overridden from SystemTrayItem.
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyTrayView() override;
+  void OnTrayViewDestroyed() override;
 
   // Overridden from PowerStatus::Observer.
   void OnPowerStatusChanged() override;
diff --git a/ash/system/rotation/tray_rotation_lock.cc b/ash/system/rotation/tray_rotation_lock.cc
index 1c6a3e6..9fb2ee1 100644
--- a/ash/system/rotation/tray_rotation_lock.cc
+++ b/ash/system/rotation/tray_rotation_lock.cc
@@ -202,10 +202,10 @@
   StopObservingRotation();
 }
 
-void TrayRotationLock::DestroyTrayView() {
+void TrayRotationLock::OnTrayViewDestroyed() {
   StopObservingRotation();
   Shell::Get()->RemoveShellObserver(this);
-  TrayImageItem::DestroyTrayView();
+  TrayImageItem::OnTrayViewDestroyed();
 }
 
 bool TrayRotationLock::GetInitialVisibility() {
diff --git a/ash/system/rotation/tray_rotation_lock.h b/ash/system/rotation/tray_rotation_lock.h
index 1dbe7a7..03df368 100644
--- a/ash/system/rotation/tray_rotation_lock.h
+++ b/ash/system/rotation/tray_rotation_lock.h
@@ -36,7 +36,7 @@
   void OnMaximizeModeEnded() override;
 
   // TrayImageItem:
-  void DestroyTrayView() override;
+  void OnTrayViewDestroyed() override;
 
  protected:
   // TrayImageItem:
diff --git a/ash/system/rotation/tray_rotation_lock_unittest.cc b/ash/system/rotation/tray_rotation_lock_unittest.cc
index 4101e250..a48b59e5 100644
--- a/ash/system/rotation/tray_rotation_lock_unittest.cc
+++ b/ash/system/rotation/tray_rotation_lock_unittest.cc
@@ -42,7 +42,7 @@
 
   // Destroys only the |tray_view_|. Tests may call this to simulate destruction
   // order during the deletion of the StatusAreaWidget.
-  void DestroyTrayView();
+  void OnTrayViewDestroyed();
 
   // Sets up a TrayRotationLock, its tray view, and its default view, for the
   // given SystemTray and its display. On a primary display all will be
@@ -73,9 +73,9 @@
       StatusAreaWidgetTestHelper::GetUserLoginStatus());
 }
 
-void TrayRotationLockTest::DestroyTrayView() {
+void TrayRotationLockTest::OnTrayViewDestroyed() {
   tray_view_.reset();
-  tray_->DestroyTrayView();
+  tray_->OnTrayViewDestroyed();
 }
 
 void TrayRotationLockTest::SetUpForStatusAreaWidget(
@@ -244,7 +244,7 @@
 TEST_F(TrayRotationLockTest, LockUpdatedDuringDesctruction) {
   Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager(
       true);
-  DestroyTrayView();
+  OnTrayViewDestroyed();
   Shell::Get()->screen_orientation_controller()->ToggleUserRotationLock();
   Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager(
       false);
diff --git a/ash/system/screen_security/screen_tray_item.cc b/ash/system/screen_security/screen_tray_item.cc
index f988b96..46cb6c7 100644
--- a/ash/system/screen_security/screen_tray_item.cc
+++ b/ash/system/screen_security/screen_tray_item.cc
@@ -164,11 +164,11 @@
 
 void ScreenTrayItem::RecordStoppedFromNotificationViewMetric() {}
 
-void ScreenTrayItem::DestroyTrayView() {
+void ScreenTrayItem::OnTrayViewDestroyed() {
   tray_view_ = nullptr;
 }
 
-void ScreenTrayItem::DestroyDefaultView() {
+void ScreenTrayItem::OnDefaultViewDestroyed() {
   default_view_ = nullptr;
 }
 
diff --git a/ash/system/screen_security/screen_tray_item.h b/ash/system/screen_security/screen_tray_item.h
index 6d39e56..8985d6a 100644
--- a/ash/system/screen_security/screen_tray_item.h
+++ b/ash/system/screen_security/screen_tray_item.h
@@ -122,8 +122,8 @@
   // Overridden from SystemTrayItem.
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override = 0;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
+  void OnTrayViewDestroyed() override;
+  void OnDefaultViewDestroyed() override;
 
  private:
   tray::ScreenTrayView* tray_view_;
diff --git a/ash/system/session/tray_session_length_limit.cc b/ash/system/session/tray_session_length_limit.cc
index 73014ec..66ed507 100644
--- a/ash/system/session/tray_session_length_limit.cc
+++ b/ash/system/session/tray_session_length_limit.cc
@@ -69,7 +69,7 @@
 }
 
 // View has been removed from tray bubble.
-void TraySessionLengthLimit::DestroyDefaultView() {
+void TraySessionLengthLimit::OnDefaultViewDestroyed() {
   tray_bubble_view_ = nullptr;
 }
 
diff --git a/ash/system/session/tray_session_length_limit.h b/ash/system/session/tray_session_length_limit.h
index fef3b4f0..9f16ade 100644
--- a/ash/system/session/tray_session_length_limit.h
+++ b/ash/system/session/tray_session_length_limit.h
@@ -32,7 +32,7 @@
 
   // SystemTrayItem:
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyDefaultView() override;
+  void OnDefaultViewDestroyed() override;
 
   // SessionLengthLimitObserver:
   void OnSessionStartTimeChanged() override;
diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc
index 11693ba4..b455456 100644
--- a/ash/system/status_area_widget.cc
+++ b/ash/system/status_area_widget.cc
@@ -148,8 +148,13 @@
 }
 
 bool StatusAreaWidget::ShouldShowShelf() const {
-  return (system_tray_ && system_tray_->ShouldShowShelf()) ||
-         views::TrayBubbleView::IsATrayBubbleOpen();
+  // The system tray bubble may or may not want to force the shelf to be
+  // visible.
+  if (system_tray_ && system_tray_->IsSystemBubbleVisible())
+    return system_tray_->ShouldShowShelf();
+
+  // All other tray bubbles will force the shelf to be visible.
+  return views::TrayBubbleView::IsATrayBubbleOpen();
 }
 
 bool StatusAreaWidget::IsMessageBubbleShown() const {
diff --git a/ash/system/tiles/tray_tiles.cc b/ash/system/tiles/tray_tiles.cc
index 60e9024f..256ab5ff 100644
--- a/ash/system/tiles/tray_tiles.cc
+++ b/ash/system/tiles/tray_tiles.cc
@@ -36,7 +36,7 @@
   return default_view_;
 }
 
-void TrayTiles::DestroyDefaultView() {
+void TrayTiles::OnDefaultViewDestroyed() {
   default_view_ = nullptr;
 }
 
diff --git a/ash/system/tiles/tray_tiles.h b/ash/system/tiles/tray_tiles.h
index 6100a2c..a9d808f 100644
--- a/ash/system/tiles/tray_tiles.h
+++ b/ash/system/tiles/tray_tiles.h
@@ -32,7 +32,7 @@
 
   // SystemTrayItem:
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyDefaultView() override;
+  void OnDefaultViewDestroyed() override;
 
   TilesDefaultView* default_view_;
 
diff --git a/ash/system/tray/system_tray.cc b/ash/system/tray/system_tray.cc
index 3b09824..121f245 100644
--- a/ash/system/tray/system_tray.cc
+++ b/ash/system/tray/system_tray.cc
@@ -213,7 +213,7 @@
   key_event_watcher_.reset();
   system_bubble_.reset();
   for (const auto& item : items_)
-    item->DestroyTrayView();
+    item->OnTrayViewDestroyed();
 }
 
 void SystemTray::InitializeTrayItems(
diff --git a/ash/system/tray/system_tray_bubble.cc b/ash/system/tray/system_tray_bubble.cc
index e740202..bf94470e 100644
--- a/ash/system/tray/system_tray_bubble.cc
+++ b/ash/system/tray/system_tray_bubble.cc
@@ -226,10 +226,10 @@
        it != items_.end(); ++it) {
     switch (bubble_type_) {
       case BUBBLE_TYPE_DEFAULT:
-        (*it)->DestroyDefaultView();
+        (*it)->OnDefaultViewDestroyed();
         break;
       case BUBBLE_TYPE_DETAILED:
-        (*it)->DestroyDetailedView();
+        (*it)->OnDetailedViewDestroyed();
         break;
     }
   }
diff --git a/ash/system/tray/system_tray_item.cc b/ash/system/tray/system_tray_item.cc
index 51fdb0a..409c5b6 100644
--- a/ash/system/tray/system_tray_item.cc
+++ b/ash/system/tray/system_tray_item.cc
@@ -29,11 +29,11 @@
   return nullptr;
 }
 
-void SystemTrayItem::DestroyTrayView() {}
+void SystemTrayItem::OnTrayViewDestroyed() {}
 
-void SystemTrayItem::DestroyDefaultView() {}
+void SystemTrayItem::OnDefaultViewDestroyed() {}
 
-void SystemTrayItem::DestroyDetailedView() {}
+void SystemTrayItem::OnDetailedViewDestroyed() {}
 
 void SystemTrayItem::TransitionDetailedView() {
   transition_delay_timer_.Start(
diff --git a/ash/system/tray/system_tray_item.h b/ash/system/tray/system_tray_item.h
index 41f12b19..fb2ef32f 100644
--- a/ash/system/tray/system_tray_item.h
+++ b/ash/system/tray/system_tray_item.h
@@ -90,9 +90,9 @@
   // These functions are called when the corresponding view item is about to be
   // removed. An item should do appropriate cleanup in these functions.
   // The default implementation does nothing.
-  virtual void DestroyTrayView();
-  virtual void DestroyDefaultView();
-  virtual void DestroyDetailedView();
+  virtual void OnTrayViewDestroyed();
+  virtual void OnDefaultViewDestroyed();
+  virtual void OnDetailedViewDestroyed();
 
   // Updates the tray view (if applicable) when the user's login status changes.
   // It is not necessary the update the default or detailed view, since the
diff --git a/ash/system/tray/tray_details_view_unittest.cc b/ash/system/tray/tray_details_view_unittest.cc
index 45a497d..374062e 100644
--- a/ash/system/tray/tray_details_view_unittest.cc
+++ b/ash/system/tray/tray_details_view_unittest.cc
@@ -75,9 +75,9 @@
     detailed_view_ = new TestDetailsView(this);
     return detailed_view_;
   }
-  void DestroyTrayView() override { tray_view_ = NULL; }
-  void DestroyDefaultView() override { default_view_ = NULL; }
-  void DestroyDetailedView() override { detailed_view_ = NULL; }
+  void OnTrayViewDestroyed() override { tray_view_ = NULL; }
+  void OnDefaultViewDestroyed() override { default_view_ = NULL; }
+  void OnDetailedViewDestroyed() override { detailed_view_ = NULL; }
 
   views::View* tray_view() const { return tray_view_; }
   views::View* default_view() const { return default_view_; }
diff --git a/ash/system/tray/tray_image_item.cc b/ash/system/tray/tray_image_item.cc
index 8d4b4e9..f4b0b55 100644
--- a/ash/system/tray/tray_image_item.cc
+++ b/ash/system/tray/tray_image_item.cc
@@ -39,7 +39,7 @@
   return tray_view_;
 }
 
-void TrayImageItem::DestroyTrayView() {
+void TrayImageItem::OnTrayViewDestroyed() {
   tray_view_ = nullptr;
 }
 
diff --git a/ash/system/tray/tray_image_item.h b/ash/system/tray/tray_image_item.h
index ff4402f..7e0d8ee7 100644
--- a/ash/system/tray/tray_image_item.h
+++ b/ash/system/tray/tray_image_item.h
@@ -36,7 +36,7 @@
 
   // Overridden from SystemTrayItem.
   views::View* CreateTrayView(LoginStatus status) override;
-  void DestroyTrayView() override;
+  void OnTrayViewDestroyed() override;
 
   // Sets the color of the icon to |color|.
   void SetIconColor(SkColor color);
diff --git a/ash/system/tray_accessibility.cc b/ash/system/tray_accessibility.cc
index 2f59baa1..6b17e76 100644
--- a/ash/system/tray_accessibility.cc
+++ b/ash/system/tray_accessibility.cc
@@ -345,8 +345,8 @@
     : TrayImageItem(system_tray,
                     kSystemTrayAccessibilityIcon,
                     UMA_ACCESSIBILITY),
-      default_(NULL),
-      detailed_menu_(NULL),
+      default_(nullptr),
+      detailed_menu_(nullptr),
       tray_icon_visible_(false),
       login_(GetCurrentLoginStatus()),
       previous_accessibility_state_(GetAccessibilityState()),
@@ -376,7 +376,7 @@
 }
 
 views::View* TrayAccessibility::CreateDefaultView(LoginStatus status) {
-  CHECK(default_ == NULL);
+  CHECK(default_ == nullptr);
 
   // Shows accessibility menu if:
   // - on login screen (not logged in);
@@ -388,16 +388,16 @@
       !delegate->ShouldShowAccessibilityMenu() &&
       // On login screen, keeps the initial visibility of the menu.
       (status != LoginStatus::LOCKED || !show_a11y_menu_on_lock_screen_))
-    return NULL;
+    return nullptr;
 
-  CHECK(default_ == NULL);
+  CHECK(default_ == nullptr);
   default_ = new tray::DefaultAccessibilityView(this);
 
   return default_;
 }
 
 views::View* TrayAccessibility::CreateDetailedView(LoginStatus status) {
-  CHECK(detailed_menu_ == NULL);
+  CHECK(detailed_menu_ == nullptr);
 
   ShellPort::Get()->RecordUserMetricsAction(
       UMA_STATUS_AREA_DETAILED_ACCESSIBILITY);
@@ -405,12 +405,12 @@
   return detailed_menu_;
 }
 
-void TrayAccessibility::DestroyDefaultView() {
-  default_ = NULL;
+void TrayAccessibility::OnDefaultViewDestroyed() {
+  default_ = nullptr;
 }
 
-void TrayAccessibility::DestroyDetailedView() {
-  detailed_menu_ = NULL;
+void TrayAccessibility::OnDetailedViewDestroyed() {
+  detailed_menu_ = nullptr;
 }
 
 void TrayAccessibility::UpdateAfterLoginStatusChange(LoginStatus status) {
diff --git a/ash/system/tray_accessibility.h b/ash/system/tray_accessibility.h
index 1025b58..e4df8ec 100644
--- a/ash/system/tray_accessibility.h
+++ b/ash/system/tray_accessibility.h
@@ -101,8 +101,8 @@
   bool GetInitialVisibility() override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
   void UpdateAfterLoginStatusChange(LoginStatus status) override;
 
   // Overridden from AccessibilityObserver.
diff --git a/ash/system/tray_caps_lock.cc b/ash/system/tray_caps_lock.cc
index 8bbd3e8b..d3cdc26 100644
--- a/ash/system/tray_caps_lock.cc
+++ b/ash/system/tray_caps_lock.cc
@@ -225,7 +225,7 @@
   return default_;
 }
 
-void TrayCapsLock::DestroyDefaultView() {
+void TrayCapsLock::OnDefaultViewDestroyed() {
   default_ = nullptr;
 }
 
diff --git a/ash/system/tray_caps_lock.h b/ash/system/tray_caps_lock.h
index b670f7df..8c86411b 100644
--- a/ash/system/tray_caps_lock.h
+++ b/ash/system/tray_caps_lock.h
@@ -30,7 +30,7 @@
   // Overridden from TrayImageItem.
   bool GetInitialVisibility() override;
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyDefaultView() override;
+  void OnDefaultViewDestroyed() override;
 
   CapsLockDefaultView* default_;
 
diff --git a/ash/system/tray_tracing.cc b/ash/system/tray_tracing.cc
index e7ab164..8eeaf78 100644
--- a/ash/system/tray_tracing.cc
+++ b/ash/system/tray_tracing.cc
@@ -91,21 +91,21 @@
 }
 
 views::View* TrayTracing::CreateDefaultView(LoginStatus status) {
-  CHECK(default_ == NULL);
+  CHECK(default_ == nullptr);
   if (tray_view() && tray_view()->visible())
     default_ = new tray::DefaultTracingView(this);
   return default_;
 }
 
 views::View* TrayTracing::CreateDetailedView(LoginStatus status) {
-  return NULL;
+  return nullptr;
 }
 
-void TrayTracing::DestroyDefaultView() {
-  default_ = NULL;
+void TrayTracing::OnDefaultViewDestroyed() {
+  default_ = nullptr;
 }
 
-void TrayTracing::DestroyDetailedView() {}
+void TrayTracing::OnDetailedViewDestroyed() {}
 
 void TrayTracing::OnTracingModeChanged(bool value) {
   SetTrayIconVisible(value);
diff --git a/ash/system/tray_tracing.h b/ash/system/tray_tracing.h
index d32077b7..ff5d7ef 100644
--- a/ash/system/tray_tracing.h
+++ b/ash/system/tray_tracing.h
@@ -38,8 +38,8 @@
   bool GetInitialVisibility() override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
 
   // Overridden from TracingObserver.
   void OnTracingModeChanged(bool value) override;
diff --git a/ash/system/update/tray_update.cc b/ash/system/update/tray_update.cc
index c90f9de..5a1058a9 100644
--- a/ash/system/update/tray_update.cc
+++ b/ash/system/update/tray_update.cc
@@ -155,7 +155,7 @@
   return nullptr;
 }
 
-void TrayUpdate::DestroyDefaultView() {
+void TrayUpdate::OnDefaultViewDestroyed() {
   update_view_ = nullptr;
 }
 
diff --git a/ash/system/update/tray_update.h b/ash/system/update/tray_update.h
index ca7f06f5..c723d6fc 100644
--- a/ash/system/update/tray_update.h
+++ b/ash/system/update/tray_update.h
@@ -52,7 +52,7 @@
   // Overridden from TrayImageItem.
   bool GetInitialVisibility() override;
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyDefaultView() override;
+  void OnDefaultViewDestroyed() override;
 
   // Expose label information for testing.
   views::Label* GetLabelForTesting();
diff --git a/ash/system/user/tray_user.cc b/ash/system/user/tray_user.cc
index 04517be..170df54 100644
--- a/ash/system/user/tray_user.cc
+++ b/ash/system/user/tray_user.cc
@@ -74,13 +74,13 @@
   return user_;
 }
 
-void TrayUser::DestroyTrayView() {
+void TrayUser::OnTrayViewDestroyed() {
   layout_view_ = nullptr;
   avatar_ = nullptr;
   label_ = nullptr;
 }
 
-void TrayUser::DestroyDefaultView() {
+void TrayUser::OnDefaultViewDestroyed() {
   user_ = nullptr;
 }
 
diff --git a/ash/system/user/tray_user.h b/ash/system/user/tray_user.h
index b83b8ef2..cf43714 100644
--- a/ash/system/user/tray_user.h
+++ b/ash/system/user/tray_user.h
@@ -62,8 +62,8 @@
   // Overridden from SystemTrayItem.
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
+  void OnTrayViewDestroyed() override;
+  void OnDefaultViewDestroyed() override;
   void UpdateAfterLoginStatusChange(LoginStatus status) override;
   void UpdateAfterShelfAlignmentChange() override;
 
diff --git a/ash/test/test_system_tray_item.cc b/ash/test/test_system_tray_item.cc
index 43bc2a37..8be316c 100644
--- a/ash/test/test_system_tray_item.cc
+++ b/ash/test/test_system_tray_item.cc
@@ -62,15 +62,15 @@
   return detailed_view_;
 }
 
-void TestSystemTrayItem::DestroyTrayView() {
+void TestSystemTrayItem::OnTrayViewDestroyed() {
   tray_view_ = nullptr;
 }
 
-void TestSystemTrayItem::DestroyDefaultView() {
+void TestSystemTrayItem::OnDefaultViewDestroyed() {
   default_view_ = nullptr;
 }
 
-void TestSystemTrayItem::DestroyDetailedView() {
+void TestSystemTrayItem::OnDetailedViewDestroyed() {
   detailed_view_ = nullptr;
 }
 
diff --git a/ash/test/test_system_tray_item.h b/ash/test/test_system_tray_item.h
index 9ddaeb4..4f95e043 100644
--- a/ash/test/test_system_tray_item.h
+++ b/ash/test/test_system_tray_item.h
@@ -31,9 +31,9 @@
   views::View* CreateTrayView(LoginStatus status) override;
   views::View* CreateDefaultView(LoginStatus status) override;
   views::View* CreateDetailedView(LoginStatus status) override;
-  void DestroyTrayView() override;
-  void DestroyDefaultView() override;
-  void DestroyDetailedView() override;
+  void OnTrayViewDestroyed() override;
+  void OnDefaultViewDestroyed() override;
+  void OnDetailedViewDestroyed() override;
   void UpdateAfterLoginStatusChange(LoginStatus status) override;
 
  private:
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index eaad972..28f58a33 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -468,11 +468,6 @@
   std::unique_ptr<BeginFrameCallbackList> ProcessLayerTreeMutations();
 
   std::unique_ptr<ScrollAndScaleSet> ProcessScrollDeltas();
-
-  void set_max_memory_needed_bytes(size_t bytes) {
-    max_memory_needed_bytes_ = bytes;
-  }
-
   FrameRateCounter* fps_counter() { return fps_counter_.get(); }
   MemoryHistory* memory_history() { return memory_history_.get(); }
   DebugRectHistory* debug_rect_history() { return debug_rect_history_.get(); }
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index ffc5b7d..ad90940e 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -546,6 +546,7 @@
       "javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java",
       "javatests/src/org/chromium/chrome/browser/vr_shell/MockVrCoreVersionCheckerImpl.java",
       "javatests/src/org/chromium/chrome/browser/vr_shell/MockVrDaydreamApi.java",
+      "javatests/src/org/chromium/chrome/browser/vr_shell/nfc_apk/SimNfcActivity.java",
       "javatests/src/org/chromium/chrome/browser/vr_shell/NfcSimUtils.java",
       "javatests/src/org/chromium/chrome/browser/vr_shell/VrFeedbackInfoBarTest.java",
       "javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTest.java",
@@ -581,17 +582,6 @@
       "//third_party/WebKit/LayoutTests/resources/testharness.js",
     ]
   }
-
-  android_library("vr_nfc_simulator_java") {
-    testonly = true
-
-    java_files = [
-      "javatests/src/org/chromium/chrome/browser/vr_shell/nfc_apk/SimNfcActivity.java",
-      "javatests/src/org/chromium/chrome/browser/vr_shell/NfcSimUtils.java",
-    ]
-
-    deps = []
-  }
 }
 
 # Overrides icon / name defined in chrome_java_resources.
@@ -964,9 +954,12 @@
     android_manifest_dep = ":vr_nfc_simulator_apk_manifest"
 
     deps = [
-      ":vr_nfc_simulator_java",
+      ":chrome_test_vr_java",
     ]
     proguard_enabled = false
+
+    # APK exceeds dex limit if not enabled
+    enable_multidex = true
   }
 }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 384e420..fa1e1d6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -381,6 +381,10 @@
             TraceEvent.begin("ChromeTabbedActivity.initializeCompositor");
             super.initializeCompositor();
 
+            // LocaleManager can only function after the native library is loaded.
+            mLocaleManager = LocaleManager.getInstance();
+            mLocaleManager.showSearchEnginePromoIfNeeded(this, null);
+
             mTabModelSelectorImpl.onNativeLibraryReady(getTabContentManager());
 
             mTabModelObserver = new TabModelSelectorTabModelObserver(mTabModelSelectorImpl) {
@@ -468,18 +472,13 @@
                 IncognitoNotificationManager.dismissIncognitoNotification();
             }
 
-            // LocaleManager can only function after the native library is loaded.
-            mLocaleManager = LocaleManager.getInstance();
-            boolean searchEnginePromoShown =
-                    mLocaleManager.showSearchEnginePromoIfNeeded(this, null);
-
             ChromePreferenceManager preferenceManager = ChromePreferenceManager.getInstance();
             // Promos can only be shown when we start with ACTION_MAIN intent and
             // after FRE is complete. Native initialization can finish before the FRE flow is
             // complete, and this will only show promos on the second opportunity. This is
             // because the FRE is shown on the first opportunity, and we don't want to show such
             // content back to back.
-            if (!searchEnginePromoShown && !mIntentWithEffect
+            if (!mLocaleManager.hasShownSearchEnginePromoThisSession() && !mIntentWithEffect
                     && FirstRunStatus.getFirstRunFlowComplete()
                     && preferenceManager.getPromosSkippedOnFirstStart()) {
                 // Data reduction promo should be temporarily suppressed if the sign in promo is
@@ -2168,6 +2167,6 @@
 
     @Override
     public boolean supportsFullscreenActivity() {
-        return true;
+        return !VrShellDelegate.isInVr();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSheetContent.java
index 720b874..5113024 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSheetContent.java
@@ -44,6 +44,8 @@
         });
         ((BottomToolbarPhone) activity.getToolbarManager().getToolbar())
                 .setOtherToolbarStyle(mToolbarView);
+
+        mToolbarView.setActionBarDelegate(activity.getBottomSheet().getActionBarDelegate());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSheetContent.java
index f6da0d95..6408a27fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSheetContent.java
@@ -71,6 +71,8 @@
             }
         };
         ApplicationStatus.registerStateListenerForActivity(mActivityStateListener, activity);
+
+        mToolbarView.setActionBarDelegate(activity.getBottomSheet().getActionBarDelegate());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistorySheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistorySheetContent.java
index e73cc31..6f2d465a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistorySheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistorySheetContent.java
@@ -43,6 +43,8 @@
         });
         ((BottomToolbarPhone) activity.getToolbarManager().getToolbar())
                 .setOtherToolbarStyle(mToolbarView);
+
+        mToolbarView.setActionBarDelegate(activity.getBottomSheet().getActionBarDelegate());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
index a03d62d..dac7902 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
@@ -10,15 +10,11 @@
 import android.support.annotation.Nullable;
 import android.widget.Button;
 
-import org.chromium.base.ActivityState;
-import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
 import org.chromium.base.VisibleForTesting;
-import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.locale.LocaleManager.SearchEnginePromoType;
-import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.chrome.browser.widget.PromoDialog;
 import org.chromium.chrome.browser.widget.RadioButtonLayout;
 
@@ -41,37 +37,14 @@
     private DefaultSearchEngineDialogHelper mHelper;
 
     /**
-     * Construct and show the dialog.  Will be asynchronous if the TemplateUrlService has not yet
-     * been loaded.
+     * Construct the default search engine promo.
      *
      * @param activity    Activity to build the dialog with.
      * @param dialogType  Type of dialog to show.
      * @param onDismissed Notified about whether the user chose an engine when it got dismissed.
      */
-    public static void show(final Activity activity, @SearchEnginePromoType final int dialogType,
-            @Nullable final Callback<Boolean> onDismissed) {
-        assert LibraryLoader.isInitialized();
-
-        // Load up the search engines.
-        final TemplateUrlService instance = TemplateUrlService.getInstance();
-        instance.registerLoadListener(new TemplateUrlService.LoadListener() {
-            @Override
-            public void onTemplateUrlServiceLoaded() {
-                instance.unregisterLoadListener(this);
-
-                if (ApplicationStatus.getStateForActivity(activity) == ActivityState.DESTROYED) {
-                    if (onDismissed != null) onDismissed.onResult(false);
-                    return;
-                }
-
-                new DefaultSearchEnginePromoDialog(activity, dialogType, onDismissed).show();
-            }
-        });
-        if (!instance.isLoaded()) instance.load();
-    }
-
-    private DefaultSearchEnginePromoDialog(
-            Activity activity, int dialogType, @Nullable Callback<Boolean> onDismissed) {
+    DefaultSearchEnginePromoDialog(Activity activity, @SearchEnginePromoType int dialogType,
+            @Nullable Callback<Boolean> onDismissed) {
         super(activity);
         mDialogType = dialogType;
         mOnDismissed = onDismissed;
@@ -80,6 +53,8 @@
         // No one should be able to bypass this dialog by clicking outside or by hitting back.
         setCancelable(false);
         setCanceledOnTouchOutside(false);
+
+        if (dialogType == LocaleManager.SEARCH_ENGINE_PROMO_SHOW_NEW) forceOpaqueBackground();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
index 19491e4f..28726fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
@@ -11,11 +11,15 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -26,12 +30,14 @@
 import org.chromium.chrome.browser.snackbar.Snackbar;
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
+import org.chromium.chrome.browser.widget.PromoDialog;
 import org.chromium.ui.base.PageTransition;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.List;
+import java.util.concurrent.Callable;
 
 /**
  * Manager for some locale specific logics.
@@ -69,7 +75,8 @@
 
     private static LocaleManager sInstance;
 
-    private boolean mSearchEnginePromoShown;
+    private boolean mSearchEnginePromoCompleted;
+    private boolean mSearchEnginePromoShownThisSession;
 
     // LocaleManager is a singleton and it should not have strong reference to UI objects.
     // SnackbarManager is owned by ChromeActivity and is not null as long as the activity is alive.
@@ -92,6 +99,7 @@
     /**
      * @return An instance of the {@link LocaleManager}. This should only be called on UI thread.
      */
+    @SuppressFBWarnings("LI_LAZY_INIT_STATIC")
     @CalledByNative
     public static LocaleManager getInstance() {
         assert ThreadUtils.runningOnUiThread();
@@ -107,7 +115,7 @@
     public LocaleManager() {
         int state = ContextUtils.getAppSharedPreferences().getInt(
                 KEY_SEARCH_ENGINE_PROMO_SHOW_STATE, SEARCH_ENGINE_PROMO_SHOULD_CHECK);
-        mSearchEnginePromoShown = state == SEARCH_ENGINE_PROMO_CHECKED_AND_SHOWN;
+        mSearchEnginePromoCompleted = state == SEARCH_ENGINE_PROMO_CHECKED_AND_SHOWN;
     }
 
     /**
@@ -214,22 +222,79 @@
      * @return Whether such dialog is needed.
      */
     public boolean showSearchEnginePromoIfNeeded(
-            Activity activity, @Nullable Callback<Boolean> onDismissed) {
-        int shouldShow = getSearchEnginePromoShowType();
+            final Activity activity, final @Nullable Callback<Boolean> onDismissed) {
+        final int shouldShow = getSearchEnginePromoShowType();
+
+        Callable<PromoDialog> dialogCreator;
         switch (shouldShow) {
             case SEARCH_ENGINE_PROMO_DONT_SHOW:
-                return false;
-            case SEARCH_ENGINE_PROMO_SHOW_SOGOU:
-                new SogouPromoDialog(activity, this, onDismissed).show();
                 return true;
+            case SEARCH_ENGINE_PROMO_SHOW_SOGOU:
+                dialogCreator = new Callable<PromoDialog>() {
+                    @Override
+                    public PromoDialog call() throws Exception {
+                        return new SogouPromoDialog(activity, LocaleManager.this, onDismissed);
+                    }
+                };
+                break;
             case SEARCH_ENGINE_PROMO_SHOW_EXISTING:
             case SEARCH_ENGINE_PROMO_SHOW_NEW:
-                DefaultSearchEnginePromoDialog.show(activity, shouldShow, onDismissed);
-                return true;
+                dialogCreator = new Callable<PromoDialog>() {
+                    @Override
+                    public PromoDialog call() throws Exception {
+                        return new DefaultSearchEnginePromoDialog(
+                                activity, shouldShow, onDismissed);
+                    }
+                };
+                break;
             default:
                 assert false;
                 return false;
         }
+
+        mSearchEnginePromoShownThisSession = true;
+        ensureTemplateUrlServiceLoadedAndShowDialog(dialogCreator, activity, onDismissed);
+        return true;
+    }
+
+    private void ensureTemplateUrlServiceLoadedAndShowDialog(
+            final Callable<PromoDialog> dialogCreator, final Activity activity,
+            final Callback<Boolean> onDismissed) {
+        assert LibraryLoader.isInitialized();
+
+        // Load up the search engines.
+        final TemplateUrlService instance = TemplateUrlService.getInstance();
+        if (!instance.isLoaded()) {
+            instance.registerLoadListener(new TemplateUrlService.LoadListener() {
+                @Override
+                public void onTemplateUrlServiceLoaded() {
+                    instance.unregisterLoadListener(this);
+
+                    // If the activity has been destroyed by the time the TemplateUrlService has
+                    // loaded, then do not attempt to show the dialog.
+                    if (ApplicationStatus.getStateForActivity(activity)
+                            == ActivityState.DESTROYED) {
+                        if (onDismissed != null) onDismissed.onResult(false);
+                        return;
+                    }
+
+                    showPromoDialog(dialogCreator);
+                }
+            });
+            instance.load();
+        } else {
+            showPromoDialog(dialogCreator);
+        }
+    }
+
+    private void showPromoDialog(Callable<PromoDialog> dialogCreator) {
+        try {
+            dialogCreator.call().show();
+        } catch (Exception e) {
+            // Exception is caught purely because Callable states it can be thrown.  This is never
+            // expected to be hit.
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -280,16 +345,6 @@
      */
     @SearchEnginePromoType
     public int getSearchEnginePromoShowType() {
-        return getSearchEnginePromoShowType(false);
-    }
-
-    /**
-     * Check the type of search engine promo that would be shown when necessary.
-     * @param readOnly Perform the checks without any preference side effects.
-     * @return Whether and which search engine promo should be shown.
-     */
-    @SearchEnginePromoType
-    public int getSearchEnginePromoShowType(boolean readOnly) {
         if (!isSpecialLocaleEnabled()) return SEARCH_ENGINE_PROMO_DONT_SHOW;
         SharedPreferences preferences = ContextUtils.getAppSharedPreferences();
         if (preferences.getBoolean(PREF_PROMO_SHOWN, false)) {
@@ -327,7 +382,7 @@
                 .edit()
                 .putInt(KEY_SEARCH_ENGINE_PROMO_SHOW_STATE, SEARCH_ENGINE_PROMO_CHECKED_AND_SHOWN)
                 .apply();
-        mSearchEnginePromoShown = true;
+        mSearchEnginePromoCompleted = true;
     }
 
     /**
@@ -369,11 +424,25 @@
      */
     public void recordLocaleBasedSearchWidgetMetrics(boolean widgetPresent) {}
 
-    /**
-     * @return Whether the search engine promo has been shown.
-     */
+    // Deprecated.  Use hasCompletedSearchEnginePromo.
+    // TODO(tedchoc): Remove once downstream uses hasCompletedSearchEnginePromo.
     public boolean hasShownSearchEnginePromo() {
-        return mSearchEnginePromoShown;
+        return hasCompletedSearchEnginePromo();
+    }
+
+    /**
+     * @return Whether the search engine promo has been shown and the user selected a valid option
+     *         and successfully completed the promo.
+     */
+    public boolean hasCompletedSearchEnginePromo() {
+        return mSearchEnginePromoCompleted;
+    }
+
+    /**
+     * @return Whether the search engine promo has been shown in this session.
+     */
+    public boolean hasShownSearchEnginePromoThisSession() {
+        return mSearchEnginePromoShownThisSession;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
index 144a7ba0..a22a6fb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
@@ -68,7 +68,7 @@
     /**
      * Creates an instance of the dialog.
      */
-    public SogouPromoDialog(Activity activity, LocaleManager localeManager,
+    SogouPromoDialog(Activity activity, LocaleManager localeManager,
             @Nullable Callback<Boolean> onDismissed) {
         super(activity);
         mLocaleManager = localeManager;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
index 5d56a630..218ffe5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProvider.java
@@ -29,7 +29,6 @@
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.firstrun.FirstRunFlowSequencer;
 import org.chromium.chrome.browser.locale.LocaleManager;
-import org.chromium.chrome.browser.locale.LocaleManager.SearchEnginePromoType;
 import org.chromium.chrome.browser.omnibox.LocationBarLayout;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService.LoadListener;
@@ -392,10 +391,7 @@
     static boolean shouldShowFullString() {
         Intent freIntent = FirstRunFlowSequencer.checkIfFirstRunIsNecessary(
                 getDelegate().getContext(), null, false);
-        @SearchEnginePromoType
-        int type = LocaleManager.getInstance().getSearchEnginePromoShowType(true);
-        return freIntent == null && type != LocaleManager.SEARCH_ENGINE_PROMO_SHOW_EXISTING
-                && type != LocaleManager.SEARCH_ENGINE_PROMO_SHOW_NEW;
+        return freIntent == null;
     }
 
     /** Sets an {@link SearchWidgetProviderDelegate} to interact with. */
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 d6d4e143b..52d7e5e 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
@@ -16,8 +16,6 @@
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnClickListener;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.widget.FrameLayout;
 import android.widget.PopupWindow.OnDismissListener;
 
 import org.chromium.base.ApiCompatibilityUtils;
@@ -188,34 +186,12 @@
             ToolbarControlContainer controlContainer, final AppMenuHandler menuHandler,
             AppMenuPropertiesDelegate appMenuPropertiesDelegate,
             Invalidator invalidator, Callback<Boolean> urlFocusChangedCallback) {
-        mActionBarDelegate = new ActionModeController.ActionBarDelegate() {
-            @Override
-            public void setControlTopMargin(int margin) {
-                MarginLayoutParams lp = (MarginLayoutParams)
-                        mControlContainer.getLayoutParams();
-                lp.topMargin = margin;
-                mControlContainer.setLayoutParams(lp);
-            }
-
-            @Override
-            public int getControlTopMargin() {
-                FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)
-                        mControlContainer.getLayoutParams();
-                return lp.topMargin;
-            }
-
-            @Override
-            public ActionBar getSupportActionBar() {
-                return activity.getSupportActionBar();
-            }
-
-            @Override
-            public void setActionBarBackgroundVisibility(boolean visible) {
-                int visibility = visible ? View.VISIBLE : View.GONE;
-                activity.findViewById(R.id.action_bar_black_background).setVisibility(visibility);
-                // TODO(tedchoc): Add support for changing the color based on the brand color.
-            }
-        };
+        if (activity.getBottomSheet() != null) {
+            mActionBarDelegate =
+                    new ViewShiftingActionBarDelegate(activity, activity.getBottomSheet());
+        } else {
+            mActionBarDelegate = new ViewShiftingActionBarDelegate(activity, controlContainer);
+        }
 
         mToolbarModel = new ToolbarModelImpl(activity.getBottomSheet());
         mControlContainer = controlContainer;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ViewShiftingActionBarDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ViewShiftingActionBarDelegate.java
new file mode 100644
index 0000000..f90345ed
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ViewShiftingActionBarDelegate.java
@@ -0,0 +1,59 @@
+// 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.
+
+package org.chromium.chrome.browser.toolbar;
+
+import android.support.v7.app.ActionBar;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+
+/**
+ * An {@link ActionModeController.ActionBarDelegate} that shifts a view as the action bar appears.
+ */
+public class ViewShiftingActionBarDelegate implements ActionModeController.ActionBarDelegate {
+    /** The view that will be shifted as the action bar appears. */
+    private final View mShiftingView;
+
+    /** A handle to a {@link ChromeActivity}. */
+    private final ChromeActivity mActivity;
+
+    /**
+     * @param activity A handle to the {@link ChromeActivity}.
+     * @param shiftingView The view that will shift when the action bar appears.
+     */
+    public ViewShiftingActionBarDelegate(ChromeActivity activity, View shiftingView) {
+        mActivity = activity;
+        mShiftingView = shiftingView;
+    }
+
+    @Override
+    public void setControlTopMargin(int margin) {
+        ViewGroup.MarginLayoutParams lp =
+                (ViewGroup.MarginLayoutParams) mShiftingView.getLayoutParams();
+        lp.topMargin = margin;
+        mShiftingView.setLayoutParams(lp);
+    }
+
+    @Override
+    public int getControlTopMargin() {
+        ViewGroup.MarginLayoutParams lp =
+                (ViewGroup.MarginLayoutParams) mShiftingView.getLayoutParams();
+        return lp.topMargin;
+    }
+
+    @Override
+    public ActionBar getSupportActionBar() {
+        return mActivity.getSupportActionBar();
+    }
+
+    @Override
+    public void setActionBarBackgroundVisibility(boolean visible) {
+        int visibility = visible ? View.VISIBLE : View.GONE;
+        mActivity.findViewById(R.id.action_bar_black_background).setVisibility(visibility);
+        // TODO(tedchoc): Add support for changing the color based on the brand color.
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java
index 30626367..502b33aee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java
@@ -6,6 +6,10 @@
 
 import android.app.Activity;
 import android.content.DialogInterface;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -68,6 +72,17 @@
     }
 
     /**
+     * Force the promo dialog to have a fully opaque background hiding any underlying content.
+     */
+    protected void forceOpaqueBackground() {
+        LayerDrawable background = new LayerDrawable(new Drawable[] {
+                new ColorDrawable(Color.WHITE),
+                new ColorDrawable(ApiCompatibilityUtils.getColor(
+                        getContext().getResources(), R.color.modal_dialog_scrim_color))});
+        mScrimView.setBackground(background);
+    }
+
+    /**
      * Adds a View to the layout within the scrollable area.
      * See {@link PromoDialogLayout#addControl}.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
index 858f570..c2c743e38 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
@@ -44,7 +44,9 @@
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.toolbar.ActionModeController.ActionBarDelegate;
 import org.chromium.chrome.browser.toolbar.BottomToolbarPhone;
+import org.chromium.chrome.browser.toolbar.ViewShiftingActionBarDelegate;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.util.MathUtils;
 import org.chromium.chrome.browser.widget.FadingBackgroundView;
@@ -231,6 +233,9 @@
     /** The activity displaying the bottom sheet. */
     private ChromeActivity mActivity;
 
+    /** A delegate for when the action bar starts showing. */
+    private ViewShiftingActionBarDelegate mActionBarDelegate;
+
     /**
      * An interface defining content that can be displayed inside of the bottom sheet for Chrome
      * Home.
@@ -467,6 +472,14 @@
         mContentSwapAnimatorSet = null;
     }
 
+    /**
+     * @return An action bar delegate that appropriately moves the sheet when the action bar is
+     *         shown.
+     */
+    public ActionBarDelegate getActionBarDelegate() {
+        return mActionBarDelegate;
+    }
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent e) {
         // If touch is disabled, act like a black hole and consume touch events without doing
@@ -594,6 +607,7 @@
         mControlContainer = controlContainer;
         mToolbarHeight = mControlContainer.getHeight();
         mActivity = activity;
+        mActionBarDelegate = new ViewShiftingActionBarDelegate(mActivity, this);
 
         mBottomSheetContentContainer = (FrameLayout) findViewById(R.id.bottom_sheet_content);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java
index b2b89227..8bb64aa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java
@@ -30,6 +30,8 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.toolbar.ActionModeController;
+import org.chromium.chrome.browser.toolbar.ToolbarActionModeCallback;
 import org.chromium.chrome.browser.widget.NumberRollView;
 import org.chromium.chrome.browser.widget.TintedDrawable;
 import org.chromium.chrome.browser.widget.TintedImageButton;
@@ -104,6 +106,7 @@
 
     private boolean mHasSearchView;
     private LinearLayout mSearchView;
+    private EditText mSearchText;
     private EditText mSearchEditText;
     private TintedImageButton mClearTextButton;
     private SearchDelegate mSearchDelegate;
@@ -220,6 +223,7 @@
         LayoutInflater.from(getContext()).inflate(R.layout.search_toolbar, this);
 
         mSearchView = (LinearLayout) findViewById(R.id.search_view);
+        mSearchText = (EditText) mSearchView.findViewById(R.id.search_text);
 
         mSearchEditText = (EditText) findViewById(R.id.search_text);
         mSearchEditText.setHint(hintStringResId);
@@ -390,6 +394,16 @@
     }
 
     /**
+     * Set a custom delegate for when the action mode starts showing for the search view.
+     * @param delegate The delegate to use.
+     */
+    public void setActionBarDelegate(ActionModeController.ActionBarDelegate delegate) {
+        ToolbarActionModeCallback callback = new ToolbarActionModeCallback();
+        callback.setActionModeController(new ActionModeController(getContext(), delegate));
+        mSearchText.setCustomSelectionActionModeCallback(callback);
+    }
+
+    /**
      * Hides the search edit text box and related views.
      */
     public void hideSearchView() {
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index cba352a..5a09d694 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1130,6 +1130,7 @@
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarPhone.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarTabController.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarTablet.java",
+  "java/src/org/chromium/chrome/browser/toolbar/ViewShiftingActionBarDelegate.java",
   "java/src/org/chromium/chrome/browser/upgrade/PackageReplacedBroadcastReceiver.java",
   "java/src/org/chromium/chrome/browser/upgrade/UpgradeActivity.java",
   "java/src/org/chromium/chrome/browser/upgrade/UpgradeIntentService.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
index 72a79cf..5429068d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java
@@ -27,7 +27,6 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.firstrun.FirstRunActivity;
-import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.searchwidget.SearchActivity.SearchActivityDelegate;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.test.util.ApplicationTestUtils;
@@ -35,8 +34,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
 
 /**
  * Tests for the SearchWidgetProvider.
@@ -103,12 +100,6 @@
         super.setUp();
         ApplicationTestUtils.setUp(getInstrumentation().getTargetContext(), true);
         SearchActivity.setDelegateForTests(new TestSearchDelegate());
-        LocaleManager.setInstanceForTest(new LocaleManager() {
-            @Override
-            public int getSearchEnginePromoShowType(boolean readOnly) {
-                return LocaleManager.SEARCH_ENGINE_PROMO_SHOW_EXISTING;
-            }
-        });
 
         mContext = new TestContext();
         mDelegate = new TestDelegate(mContext);
@@ -132,59 +123,24 @@
 
         // The microphone icon should disappear if voice queries are unavailable.
         mDelegate.mViews.clear();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedVoiceSearchAvailability(false);
-            }
-        });
+        SearchWidgetProvider.updateCachedVoiceSearchAvailability(false);
         checkWidgetStates(TEXT_GENERIC, View.GONE);
 
-        // After recording that the default search engine is "X", it should say "Search with X" as
-        // long as we can show the full string.
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
-        });
-        checkWidgetStates(TEXT_GENERIC, View.GONE);
-
+        // After recording that the default search engine is "X", it should say "Search with X".
         mDelegate.mViews.clear();
-        LocaleManager.setInstanceForTest(new LocaleManager() {
-            @Override
-            public int getSearchEnginePromoShowType(boolean readOnly) {
-                return LocaleManager.SEARCH_ENGINE_PROMO_DONT_SHOW;
-            }
-        });
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
-        });
+        SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
         checkWidgetStates(TEXT_SEARCH_ENGINE_FULL, View.GONE);
 
         // The microphone icon should appear if voice queries are available.
         mDelegate.mViews.clear();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedVoiceSearchAvailability(true);
-            }
-        });
+        SearchWidgetProvider.updateCachedVoiceSearchAvailability(true);
         checkWidgetStates(TEXT_SEARCH_ENGINE_FULL, View.VISIBLE);
     }
 
     @SmallTest
     @CommandLineFlags.Remove(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
-    public void testUpdateCachedEngineNameBeforeFirstRun() throws ExecutionException {
-        assertFalse(ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
-            @Override
-            public Boolean call() throws Exception {
-                return SearchWidgetProvider.shouldShowFullString();
-            }
-        }));
+    public void testUpdateCachedEngineNameBeforeFirstRun() {
+        assertFalse(SearchWidgetProvider.shouldShowFullString());
         SearchWidgetProvider.handleAction(
                 new Intent(SearchWidgetProvider.ACTION_UPDATE_ALL_WIDGETS));
 
@@ -196,12 +152,7 @@
         // already displaying the generic string, and should continue doing so, so they don't get
         // updated.
         mDelegate.mViews.clear();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
-        });
+        SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
         assertEquals(0, mDelegate.mViews.size());
 
         // Manually set the preference, then update the cached engine name again.  The
@@ -212,12 +163,7 @@
                 .edit()
                 .putString(SearchWidgetProvider.PREF_SEARCH_ENGINE_SHORTNAME, TEXT_SEARCH_ENGINE)
                 .apply();
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
-            }
-        });
+        SearchWidgetProvider.updateCachedEngineName(TEXT_SEARCH_ENGINE);
         checkWidgetStates(TEXT_GENERIC, View.VISIBLE);
     }
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index a0714804..613e668 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -413,8 +413,6 @@
     "favicon/favicon_utils.h",
     "favicon/large_icon_service_factory.cc",
     "favicon/large_icon_service_factory.h",
-    "feature_engagement_tracker/feature_engagement_tracker_factory.cc",
-    "feature_engagement_tracker/feature_engagement_tracker_factory.h",
     "file_select_helper.cc",
     "file_select_helper.h",
     "file_select_helper_mac.mm",
@@ -1528,7 +1526,6 @@
     "//components/favicon/content",
     "//components/favicon/core",
     "//components/favicon_base",
-    "//components/feature_engagement_tracker",
     "//components/flags_ui",
     "//components/gcm_driver",
     "//components/google/core/browser",
@@ -3137,6 +3134,8 @@
       "download/download_request_infobar_delegate_android.h",
       "engagement/site_engagement_service_android.cc",
       "engagement/site_engagement_service_android.h",
+      "feature_engagement_tracker/feature_engagement_tracker_factory.cc",
+      "feature_engagement_tracker/feature_engagement_tracker_factory.h",
       "geolocation/geolocation_infobar_delegate_android.cc",
       "geolocation/geolocation_infobar_delegate_android.h",
       "history/android/android_history_provider_service.cc",
@@ -3260,6 +3259,7 @@
       "//chrome/browser/android/webapk:proto",
       "//components/cdm/browser",
       "//components/data_usage/android",
+      "//components/feature_engagement_tracker",
       "//components/payments/content/android",
       "//components/precache/content",
       "//components/resources:components_resources",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d36739be..edfe86d 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -46,8 +46,6 @@
 #include "components/dom_distiller/core/dom_distiller_switches.h"
 #include "components/error_page/common/error_page_switches.h"
 #include "components/favicon/core/features.h"
-#include "components/feature_engagement_tracker/public/feature_constants.h"
-#include "components/feature_engagement_tracker/public/feature_list.h"
 #include "components/flags_ui/feature_entry.h"
 #include "components/flags_ui/feature_entry_macros.h"
 #include "components/flags_ui/flags_storage.h"
@@ -109,6 +107,8 @@
 
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/chrome_feature_list.h"
+#include "components/feature_engagement_tracker/public/feature_constants.h"
+#include "components/feature_engagement_tracker/public/feature_list.h"
 #else  // OS_ANDROID
 #include "ui/message_center/message_center_switches.h"
 #endif  // OS_ANDROID
@@ -1794,12 +1794,14 @@
      flag_descriptions::kChromeHomeSwipeLogicDescription, kOsAndroid,
      MULTI_VALUE_TYPE(kChromeHomeSwipeLogicChoices)},
 #endif  // OS_ANDROID
+#if defined(OS_ANDROID)
     {"iph-demo-mode-choice", flag_descriptions::kIphDemoModeChoiceName,
-     flag_descriptions::kIphDemoModeChoiceDescription, kOsAll,
+     flag_descriptions::kIphDemoModeChoiceDescription, kOsAndroid,
      FEATURE_WITH_PARAMS_VALUE_TYPE(
          feature_engagement_tracker::kIPHDemoMode,
          feature_engagement_tracker::kIPHDemoModeChoiceVariations,
          feature_engagement_tracker::kIPHDemoMode.name)},
+#endif  // OS_ANDROID
     {"num-raster-threads", flag_descriptions::kNumRasterThreadsName,
      flag_descriptions::kNumRasterThreadsDescription, kOsAll,
      MULTI_VALUE_TYPE(kNumRasterThreadsChoices)},
diff --git a/chrome/browser/android/vr_shell/BUILD.gn b/chrome/browser/android/vr_shell/BUILD.gn
index f09c501..cefac4c1 100644
--- a/chrome/browser/android/vr_shell/BUILD.gn
+++ b/chrome/browser/android/vr_shell/BUILD.gn
@@ -86,6 +86,7 @@
       "//base",
       "//cc/paint",
       "//components/security_state/core",
+      "//components/toolbar:vector_icons",
       "//components/url_formatter",
       "//components/vector_icons",
       "//content/public/browser",
diff --git a/chrome/browser/android/vr_shell/textures/url_bar_texture.cc b/chrome/browser/android/vr_shell/textures/url_bar_texture.cc
index b47429b..5ad3fc2 100644
--- a/chrome/browser/android/vr_shell/textures/url_bar_texture.cc
+++ b/chrome/browser/android/vr_shell/textures/url_bar_texture.cc
@@ -8,6 +8,7 @@
 #include "cc/paint/skia_paint_canvas.h"
 #include "chrome/browser/android/vr_shell/color_scheme.h"
 #include "chrome/browser/android/vr_shell/textures/render_text_wrapper.h"
+#include "components/toolbar/vector_icons.h"
 #include "components/url_formatter/url_formatter.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/font.h"
@@ -37,21 +38,25 @@
 
 using security_state::SecurityLevel;
 
+// See ToolbarModelImpl::GetVectorIcon().
 const struct gfx::VectorIcon& getSecurityIcon(SecurityLevel level) {
   switch (level) {
-    case SecurityLevel::NONE:
-    case SecurityLevel::HTTP_SHOW_WARNING:
-    case SecurityLevel::SECURITY_WARNING:
-      return ui::kInfoOutlineIcon;
-    case SecurityLevel::SECURE:
-    case SecurityLevel::EV_SECURE:
-      return ui::kLockIcon;
-    case SecurityLevel::DANGEROUS:
-      return ui::kWarningIcon;
-    case SecurityLevel::SECURE_WITH_POLICY_INSTALLED_CERT:  // ChromeOS only.
+    case security_state::NONE:
+    case security_state::HTTP_SHOW_WARNING:
+      return toolbar::kHttpIcon;
+    case security_state::EV_SECURE:
+    case security_state::SECURE:
+      return toolbar::kHttpsValidIcon;
+    case security_state::SECURITY_WARNING:
+      // Surface Dubious as Neutral.
+      return toolbar::kHttpIcon;
+    case security_state::SECURE_WITH_POLICY_INSTALLED_CERT:  // ChromeOS only.
+      return ui::kBusinessIcon;
+    case security_state::DANGEROUS:
+      return toolbar::kHttpsInvalidIcon;
     default:
       NOTREACHED();
-      return ui::kWarningIcon;
+      return toolbar::kHttpsInvalidIcon;
   }
 }
 
@@ -60,17 +65,19 @@
   switch (level) {
     case SecurityLevel::NONE:
     case SecurityLevel::HTTP_SHOW_WARNING:
+      return color_scheme.deemphasized;
+    case SecurityLevel::EV_SECURE:
+    case SecurityLevel::SECURE:
+      return color_scheme.secure;
     case SecurityLevel::SECURITY_WARNING:
       return color_scheme.deemphasized;
-    case SecurityLevel::SECURE:
-    case SecurityLevel::EV_SECURE:
-      return color_scheme.secure;
+    case SecurityLevel::SECURE_WITH_POLICY_INSTALLED_CERT:  // ChromeOS only.
+      return color_scheme.insecure;
     case SecurityLevel::DANGEROUS:
       return color_scheme.insecure;
-    case SecurityLevel::SECURE_WITH_POLICY_INSTALLED_CERT:  // ChromeOS only.
     default:
       NOTREACHED();
-      return color_scheme.warning;
+      return color_scheme.insecure;
   }
 }
 
diff --git a/chrome/browser/chromeos/note_taking_helper.cc b/chrome/browser/chromeos/note_taking_helper.cc
index 586029e..9528f50 100644
--- a/chrome/browser/chromeos/note_taking_helper.cc
+++ b/chrome/browser/chromeos/note_taking_helper.cc
@@ -37,6 +37,8 @@
 #include "extensions/common/api/app_runtime.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_handlers/action_handlers_handler.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permissions_data.h"
 #include "url/gurl.h"
 
 namespace app_runtime = extensions::api::app_runtime;
@@ -329,8 +331,10 @@
   if (!lock_screen_apps::StateController::IsEnabled())
     return false;
 
-  if (!IsWhitelistedChromeApp(app))
+  if (!app->permissions_data()->HasAPIPermission(
+          extensions::APIPermission::kLockScreen)) {
     return false;
+  }
 
   return extensions::ActionHandlersInfo::HasLockScreenActionHandler(
       app, app_runtime::ACTION_TYPE_NEW_NOTE);
diff --git a/chrome/browser/chromeos/note_taking_helper_unittest.cc b/chrome/browser/chromeos/note_taking_helper_unittest.cc
index c2f8783..50434e7 100644
--- a/chrome/browser/chromeos/note_taking_helper_unittest.cc
+++ b/chrome/browser/chromeos/note_taking_helper_unittest.cc
@@ -219,11 +219,12 @@
   scoped_refptr<const extensions::Extension> CreateExtension(
       const extensions::ExtensionId& id,
       const std::string& name) {
-    return CreateExtension(id, name, nullptr);
+    return CreateExtension(id, name, nullptr, nullptr);
   }
   scoped_refptr<const extensions::Extension> CreateExtension(
       const extensions::ExtensionId& id,
       const std::string& name,
+      std::unique_ptr<base::Value> permissions,
       std::unique_ptr<base::Value> action_handlers) {
     std::unique_ptr<base::DictionaryValue> manifest =
         extensions::DictionaryBuilder()
@@ -244,6 +245,9 @@
     if (action_handlers)
       manifest->Set("action_handlers", std::move(action_handlers));
 
+    if (permissions)
+      manifest->Set("permissions", std::move(permissions));
+
     return extensions::ExtensionBuilder()
         .SetManifest(std::move(manifest))
         .SetID(id)
@@ -404,9 +408,9 @@
 
   // Install Keep app that does not support lock screen note taking - it should
   // be reported not to support lock screen note taking.
-  scoped_refptr<const extensions::Extension> prod_extension =
-      CreateExtension(NoteTakingHelper::kProdKeepExtensionId, kProdKeepAppName,
-                      std::move(lock_disabled_action_handler));
+  scoped_refptr<const extensions::Extension> prod_extension = CreateExtension(
+      NoteTakingHelper::kProdKeepExtensionId, kProdKeepAppName,
+      nullptr /* permissions */, std::move(lock_disabled_action_handler));
   InstallExtension(prod_extension.get(), profile());
   EXPECT_TRUE(helper()->IsAppAvailable(profile()));
   std::vector<NoteTakingAppInfo> apps = helper()->GetAvailableApps(profile());
@@ -430,6 +434,7 @@
   // enable-lock-screen-apps flag is set).
   scoped_refptr<const extensions::Extension> dev_extension =
       CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName,
+                      extensions::ListBuilder().Append("lockScreen").Build(),
                       std::move(lock_enabled_action_handler));
   InstallExtension(dev_extension.get(), profile());
   apps = helper()->GetAvailableApps(profile());
@@ -462,6 +467,7 @@
   // Install lock screen enabled Keep note taking app.
   scoped_refptr<const extensions::Extension> dev_extension =
       CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName,
+                      extensions::ListBuilder().Append("lockScreen").Build(),
                       std::move(lock_enabled_action_handler));
   InstallExtension(dev_extension.get(), profile());
 
@@ -498,6 +504,37 @@
             GetAppString(apps[0]));
 }
 
+TEST_P(NoteTakingHelperTest, PreferredAppWithNoLockScreenPermission) {
+  Init(ENABLE_PALETTE | ENABLE_LOCK_SCREEN_APPS);
+
+  ASSERT_FALSE(helper()->IsAppAvailable(profile()));
+  ASSERT_TRUE(helper()->GetAvailableApps(profile()).empty());
+
+  std::unique_ptr<base::Value> lock_enabled_action_handler =
+      extensions::ListBuilder()
+          .Append(extensions::DictionaryBuilder()
+                      .Set("action", app_runtime::ToString(
+                                         app_runtime::ACTION_TYPE_NEW_NOTE))
+                      .SetBoolean("enabled_on_lock_screen", true)
+                      .Build())
+          .Build();
+
+  // Install lock screen enabled Keep note taking app, but wihtout lock screen
+  // permission listed.
+  scoped_refptr<const extensions::Extension> dev_extension = CreateExtension(
+      NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName,
+      nullptr /* permissions */, std::move(lock_enabled_action_handler));
+  InstallExtension(dev_extension.get(), profile());
+
+  // Verify that the app is not reported to support lock screen note taking.
+  std::vector<NoteTakingAppInfo> apps = helper()->GetAvailableApps(profile());
+  ASSERT_EQ(1u, apps.size());
+  EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName,
+                         false /* preferred */,
+                         NoteTakingLockScreenSupport::kNotSupported),
+            GetAppString(apps[0]));
+}
+
 TEST_P(NoteTakingHelperTest,
        PreferredAppWithotLockSupportClearsLockScreenPref) {
   Init(ENABLE_PALETTE | ENABLE_LOCK_SCREEN_APPS);
@@ -517,6 +554,7 @@
   // Install dev Keep app that supports lock screen note taking.
   scoped_refptr<const extensions::Extension> dev_extension =
       CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName,
+                      extensions::ListBuilder().Append("lockScreen").Build(),
                       lock_enabled_action_handler->CreateDeepCopy());
   InstallExtension(dev_extension.get(), profile());
 
@@ -524,7 +562,8 @@
   const extensions::ExtensionId kNewNoteId = crx_file::id_util::GenerateId("a");
   const std::string kName = "Some App";
   scoped_refptr<const extensions::Extension> has_new_note = CreateExtension(
-      kNewNoteId, kName, lock_enabled_action_handler->CreateDeepCopy());
+      kNewNoteId, kName, extensions::ListBuilder().Append("lockScreen").Build(),
+      lock_enabled_action_handler->CreateDeepCopy());
   InstallExtension(has_new_note.get(), profile());
 
   // Verify that only Keep app is reported to support lock screen note taking.
@@ -607,6 +646,7 @@
 
   scoped_refptr<const extensions::Extension> dev_extension =
       CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName,
+                      extensions::ListBuilder().Append("lockScreen").Build(),
                       std::move(lock_enabled_action_handler));
   InstallExtension(dev_extension.get(), profile());
 
@@ -640,6 +680,7 @@
 
   scoped_refptr<const extensions::Extension> dev_extension =
       CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName,
+                      extensions::ListBuilder().Append("lockScreen").Build(),
                       std::move(lock_enabled_action_handler));
   InstallExtension(dev_extension.get(), profile());
   std::vector<NoteTakingAppInfo> apps = helper()->GetAvailableApps(profile());
@@ -663,14 +704,15 @@
 
   // "action_handlers": ["new_note"]
   scoped_refptr<const extensions::Extension> has_new_note = CreateExtension(
-      kNewNoteId, kName,
+      kNewNoteId, kName, nullptr /* permissions */,
       extensions::ListBuilder()
           .Append(app_runtime::ToString(app_runtime::ACTION_TYPE_NEW_NOTE))
           .Build());
   InstallExtension(has_new_note.get(), profile());
   // "action_handlers": []
   scoped_refptr<const extensions::Extension> empty_array =
-      CreateExtension(kEmptyArrayId, kName, extensions::ListBuilder().Build());
+      CreateExtension(kEmptyArrayId, kName, nullptr /* permissions*/,
+                      extensions::ListBuilder().Build());
   InstallExtension(empty_array.get(), profile());
   // (no action handler entry)
   scoped_refptr<const extensions::Extension> none =
@@ -704,7 +746,8 @@
                       .Build())
           .Build();
   scoped_refptr<const extensions::Extension> has_new_note = CreateExtension(
-      kNewNoteId, kName, std::move(lock_enabled_action_handler));
+      kNewNoteId, kName, extensions::ListBuilder().Append("lockScreen").Build(),
+      std::move(lock_enabled_action_handler));
   InstallExtension(has_new_note.get(), profile());
 
   // Only the "new_note" extension is returned from GetAvailableApps.
@@ -719,7 +762,7 @@
   Init(ENABLE_PALETTE);
 
   scoped_refptr<const extensions::Extension> extension = CreateExtension(
-      NoteTakingHelper::kProdKeepExtensionId, "Keep",
+      NoteTakingHelper::kProdKeepExtensionId, "Keep", nullptr /* permissions */,
       extensions::ListBuilder()
           .Append(app_runtime::ToString(app_runtime::ACTION_TYPE_NEW_NOTE))
           .Build());
diff --git a/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc b/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc
index 4368cec..d1ad2a19 100644
--- a/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc
+++ b/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc
@@ -132,9 +132,7 @@
     return tray()->CreateDefaultView(GetLoginStatus());
   }
 
-  void DestroyMenuItem() {
-    return tray()->DestroyDefaultView();
-  }
+  void DestroyMenuItem() { return tray()->OnDefaultViewDestroyed(); }
 
   bool CanCreateMenuItem() {
     views::View* menu_item_view = CreateMenuItem();
@@ -154,9 +152,9 @@
   }
 
   void CloseDetailMenu() {
-    CHECK(tray()->detailed_menu_);
-    tray()->DestroyDetailedView();
-    tray()->detailed_menu_ = NULL;
+    ASSERT_TRUE(tray()->detailed_menu_);
+    tray()->OnDetailedViewDestroyed();
+    EXPECT_FALSE(tray()->detailed_menu_);
   }
 
   void ClickSpokenFeedbackOnDetailMenu() {
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 054f5e6b..db5fad8 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -353,11 +353,6 @@
     "Experiment to have all APIs reflect the layout viewport. This will "
     "make window.scroll properties relative to the layout viewport.";
 
-const char kIphDemoModeChoiceName[] = "In-Product Help Demo Mode";
-
-const char kIphDemoModeChoiceDescription[] =
-    "Selects the In-Product Help demo mode.";
-
 const char kColorCorrectRenderingName[] = "Color correct rendering";
 
 const char kColorCorrectRenderingDescription[] =
@@ -1990,6 +1985,17 @@
 
 #endif  // defined(OS_ANDROID)
 
+//  In-Product Help flags
+
+#if defined(OS_ANDROID)
+
+const char kIphDemoModeChoiceName[] = "In-Product Help Demo Mode";
+
+const char kIphDemoModeChoiceDescription[] =
+    "Selects the In-Product Help demo mode.";
+
+#endif  // defined(OS_ANDROID)
+
 //  Settings window flags
 
 const char kSettingsWindowName[] = "Show settings in a window";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 4bc25493..5ccc0d6 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -406,9 +406,6 @@
 extern const char kInertVisualViewportName[];
 extern const char kInertVisualViewportDescription[];
 
-extern const char kIphDemoModeChoiceName[];
-extern const char kIphDemoModeChoiceDescription[];
-
 extern const char kJavascriptHarmonyName[];
 extern const char kJavascriptHarmonyDescription[];
 
@@ -988,6 +985,9 @@
 extern const char kHerbPrototypeChoicesDescription[];
 extern const char kHerbPrototypeFlavorElderberry[];
 
+extern const char kIphDemoModeChoiceName[];
+extern const char kIphDemoModeChoiceDescription[];
+
 extern const char kLsdPermissionPromptName[];
 extern const char kLsdPermissionPromptDescription[];
 
diff --git a/chrome/browser/metrics/chrome_metrics_service_accessor.h b/chrome/browser/metrics/chrome_metrics_service_accessor.h
index db9c035..143a252 100644
--- a/chrome/browser/metrics/chrome_metrics_service_accessor.h
+++ b/chrome/browser/metrics/chrome_metrics_service_accessor.h
@@ -59,6 +59,7 @@
 }
 
 namespace safe_browsing {
+class ChromeCleanerControllerDelegate;
 class DownloadUrlSBClient;
 class IncidentReportingService;
 class ReporterRunner;
@@ -120,6 +121,7 @@
   friend class speech::ChromeSpeechRecognitionManagerDelegate;
   friend class system_logs::ChromeInternalLogSource;
   friend class UmaSessionStats;
+  friend class safe_browsing::ChromeCleanerControllerDelegate;
   friend class safe_browsing::DownloadUrlSBClient;
   friend class safe_browsing::IncidentReportingService;
   friend class safe_browsing::ReporterRunner;
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index 37a49f70..9d24cd6d 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -72,12 +72,12 @@
 #include "components/metrics/net/cellular_logic_helper.h"
 #include "components/metrics/net/net_metrics_log_uploader.h"
 #include "components/metrics/net/network_metrics_provider.h"
-#include "components/metrics/net/version_utils.h"
 #include "components/metrics/profiler/profiler_metrics_provider.h"
 #include "components/metrics/profiler/tracking_synchronizer.h"
 #include "components/metrics/stability_metrics_helper.h"
 #include "components/metrics/ui/screen_info_metrics_provider.h"
 #include "components/metrics/url_constants.h"
+#include "components/metrics/version_utils.h"
 #include "components/omnibox/browser/omnibox_metrics_provider.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
diff --git a/chrome/browser/page_load_metrics/OWNERS b/chrome/browser/page_load_metrics/OWNERS
index cdec1f2..396da01 100644
--- a/chrome/browser/page_load_metrics/OWNERS
+++ b/chrome/browser/page_load_metrics/OWNERS
@@ -1,5 +1,8 @@
 csharrison@chromium.org
 bmcquade@chromium.org
 jkarlin@chromium.org
-kinuko@chromium.org
 ryansturm@chromium.org
+
+# For straightforward addition/removal to existing observers only (for
+# APAC).
+kinuko@chromium.org
diff --git a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
index 74621a4..cc58a62 100644
--- a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
+++ b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
@@ -17,13 +17,11 @@
 (function() {
 
 /**
- * The estimated amount of complete scans needed to enroll a fingerprint. Used
- * to help us estimate the progress of an enroll session.
- * TODO(xiaoyinh@): This will be replaced by percentage of completion in the
- * future.
+ * The duration in ms of a fingerprint icon flash when a user touches the
+ * fingerprint sensor during an enroll session.
  * @const {number}
  */
-var SUCCESSFUL_SCANS_TO_COMPLETE = 15;
+var FLASH_DURATION_MS = 300;
 
 /**
  * The amount of millseconds after a successful but not completed scan before a
@@ -32,6 +30,15 @@
  */
 var SHOW_TAP_SENSOR_MESSAGE_DELAY_MS = 2000;
 
+/**
+ * The estimated amount of complete scans needed to enroll a fingerprint. Used
+ * to help us estimate the progress of an enroll session.
+ * TODO(xiaoyinh@): This will be replaced by percentage of completion in the
+ * future.
+ * @const {number}
+ */
+var SUCCESSFUL_SCANS_TO_COMPLETE = 15;
+
 Polymer({
   is: 'settings-setup-fingerprint-dialog',
 
@@ -161,6 +168,14 @@
           this.setProblem_(scan.result);
           if (scan.result == settings.FingerprintResultType.SUCCESS) {
             this.problemMessage_ = '';
+            // Flash the fingerprint icon blue so that users get some feedback
+            // when a successful scan has been registered.
+            this.$.image.animate(
+                {
+                  fill: ['var(--google-blue-700)', 'var(--google-grey-500)'],
+                  opacity: [0.7, 1.0],
+                },
+                FLASH_DURATION_MS);
             this.$.arc.animate(this.receivedScanCount_ * slice,
                 (this.receivedScanCount_ + 1) * slice);
             this.receivedScanCount_++;
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc
index 0dcb88d..1d2e6e7 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.cc
@@ -4,84 +4,379 @@
 
 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h"
 
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
 #include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/singleton.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+#include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
+#include "net/http/http_status_code.h"
 
 namespace safe_browsing {
 
 namespace {
 
-// Global instance that is set and unset by ChromeCleanerController's
-// constructor and destructor.
-ChromeCleanerController* g_chrome_cleaner_controller = nullptr;
+using ::chrome_cleaner::mojom::ChromePrompt;
+using ::chrome_cleaner::mojom::PromptAcceptance;
+using ::content::BrowserThread;
+
+// TODO(alito): Move these shared exit codes to the chrome_cleaner component.
+// https://crbug.com/727956
+constexpr int kRebootRequiredExitCode = 15;
+constexpr int kRebootNotRequiredExitCode = 0;
+
+// Attempts to change the Chrome Cleaner binary's suffix to ".exe". Will return
+// an empty FilePath on failure. Should be called on a sequence with traits
+// appropriate for IO operations.
+base::FilePath VerifyAndRenameDownloadedCleaner(base::FilePath downloaded_path,
+                                                int http_response_code) {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  if (downloaded_path.empty() || !base::PathExists(downloaded_path))
+    return base::FilePath();
+
+  if (http_response_code != net::HTTP_OK) {
+    base::DeleteFile(downloaded_path, /*recursive=*/false);
+    return base::FilePath();
+  }
+
+  base::FilePath executable_path(
+      downloaded_path.ReplaceExtension(FILE_PATH_LITERAL("exe")));
+
+  if (!base::ReplaceFile(downloaded_path, executable_path, nullptr)) {
+    base::DeleteFile(downloaded_path, /*recursive=*/false);
+    return base::FilePath();
+  }
+
+  return executable_path;
+}
+
+void OnChromeCleanerFetched(
+    ChromeCleanerControllerDelegate::FetchedCallback fetched_callback,
+    base::FilePath downloaded_path,
+    int http_response_code) {
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::BACKGROUND,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+      base::BindOnce(VerifyAndRenameDownloadedCleaner, downloaded_path,
+                     http_response_code),
+      std::move(fetched_callback));
+}
+
+ChromeCleanerController::IdleReason IdleReasonWhenConnectionClosedTooSoon(
+    ChromeCleanerController::State current_state) {
+  DCHECK(current_state == ChromeCleanerController::State::kScanning ||
+         current_state == ChromeCleanerController::State::kInfected);
+
+  return current_state == ChromeCleanerController::State::kScanning
+             ? ChromeCleanerController::IdleReason::kScanningFailed
+             : ChromeCleanerController::IdleReason::kConnectionLost;
+}
 
 }  // namespace
 
-// static
-scoped_refptr<ChromeCleanerController> ChromeCleanerController::GetInstance() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ChromeCleanerControllerDelegate::ChromeCleanerControllerDelegate() = default;
 
-  if (g_chrome_cleaner_controller)
-    return make_scoped_refptr(g_chrome_cleaner_controller);
+ChromeCleanerControllerDelegate::~ChromeCleanerControllerDelegate() = default;
 
-  return make_scoped_refptr(new ChromeCleanerController());
+void ChromeCleanerControllerDelegate::FetchAndVerifyChromeCleaner(
+    FetchedCallback fetched_callback) {
+  FetchChromeCleaner(
+      base::BindOnce(&OnChromeCleanerFetched, base::Passed(&fetched_callback)));
 }
 
-ChromeCleanerController* ChromeCleanerController::GetRawInstanceForTesting() {
-  return g_chrome_cleaner_controller;
+bool ChromeCleanerControllerDelegate::
+    SafeBrowsingExtendedReportingScoutEnabled() {
+  return safe_browsing::SafeBrowsingExtendedReportingScoutEnabled();
+}
+
+bool ChromeCleanerControllerDelegate::IsMetricsAndCrashReportingEnabled() {
+  return ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled();
+}
+
+// static
+ChromeCleanerController* ChromeCleanerController::GetInstance() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  return base::Singleton<ChromeCleanerController>::get();
+}
+
+void ChromeCleanerController::SetDelegateForTesting(
+    ChromeCleanerControllerDelegate* delegate) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  delegate_ = delegate ? delegate : real_delegate_.get();
+  DCHECK(delegate_);
+}
+
+void ChromeCleanerController::DismissRebootForTesting() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_EQ(State::kRebootRequired, state());
+  state_ = State::kIdle;
 }
 
 void ChromeCleanerController::AddObserver(Observer* observer) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  // TODO(alito): Implement this. Add to |observer_list_| and notify |observer|
-  //              of the current state.
+  observer_list_.AddObserver(observer);
+  NotifyObserver(observer);
 }
 
 void ChromeCleanerController::RemoveObserver(Observer* observer) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  // TODO(alito): Implement this.
+  observer_list_.RemoveObserver(observer);
 }
 
-ChromeCleanerController::ChromeCleanerController() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(!g_chrome_cleaner_controller);
-
-  g_chrome_cleaner_controller = this;
-}
-
-ChromeCleanerController::~ChromeCleanerController() {
+void ChromeCleanerController::Scan(
+    const SwReporterInvocation& reporter_invocation) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  // An instance of ChromeCleanerController must keep itself alive while in any
-  // state other than |kIdle| and |kRebootRequired|.
-  DCHECK(state_ == State::kIdle || state_ == State::kRebootRequired);
-  DCHECK_EQ(this, g_chrome_cleaner_controller);
+  DCHECK(reporter_invocation.BehaviourIsSupported(
+      SwReporterInvocation::BEHAVIOUR_TRIGGER_PROMPT));
 
-  g_chrome_cleaner_controller = nullptr;
+  if (state() != State::kIdle)
+    return;
+
+  DCHECK(!reporter_invocation_);
+  reporter_invocation_ =
+      base::MakeUnique<SwReporterInvocation>(reporter_invocation);
+  SetStateAndNotifyObservers(State::kScanning);
+  delegate_->FetchAndVerifyChromeCleaner(base::BindOnce(
+      &ChromeCleanerController::OnChromeCleanerFetchedAndVerified,
+      base::Unretained(this)));
 }
 
+void ChromeCleanerController::ReplyWithUserResponse(
+    UserResponse user_response) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(prompt_user_callback_);
+
+  if (state() != State::kInfected)
+    return;
+
+  PromptAcceptance acceptance = PromptAcceptance::DENIED;
+  State new_state = State::kIdle;
+  switch (user_response) {
+    case UserResponse::kAccepted:
+      // The ACCEPTED_WITHOUT_LOGS and ACCEPTED_WITH_LOGS values are going to be
+      // merged. Right now, it makes no difference which one we are sending in
+      // the response.
+      acceptance = PromptAcceptance::ACCEPTED_WITHOUT_LOGS;
+      new_state = State::kCleaning;
+      break;
+    case UserResponse::kDenied:  // Fallthrough
+    case UserResponse::kDismissed:
+      acceptance = PromptAcceptance::DENIED;
+      idle_reason_ = IdleReason::kUserDeclinedCleanup;
+      new_state = State::kIdle;
+      break;
+  }
+
+  BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)
+      ->PostTask(FROM_HERE,
+                 base::BindOnce(std::move(prompt_user_callback_), acceptance));
+
+  // The transition to a new state should happen only after the response has
+  // been posted on the UI thread so that if we transition to the kIdle state,
+  // the response callback is not cleared before it has been posted.
+  SetStateAndNotifyObservers(new_state);
+}
+
+ChromeCleanerController::ChromeCleanerController()
+    : real_delegate_(base::MakeUnique<ChromeCleanerControllerDelegate>()),
+      delegate_(real_delegate_.get()),
+      weak_factory_(this) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+ChromeCleanerController::~ChromeCleanerController() = default;
+
 void ChromeCleanerController::NotifyObserver(Observer* observer) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  // TODO(alito): Implement this. Call |observer|'s function corresponding to
-  //              the current state.
+
+  switch (state_) {
+    case State::kIdle:
+      observer->OnIdle(idle_reason_);
+      break;
+    case State::kScanning:
+      observer->OnScanning();
+      break;
+    case State::kInfected:
+      observer->OnInfected(*files_to_delete_);
+      break;
+    case State::kCleaning:
+      observer->OnCleaning(*files_to_delete_);
+      break;
+    case State::kRebootRequired:
+      observer->OnRebootRequired();
+      break;
+  }
 }
 
-void ChromeCleanerController::NotifyAllObservers() const {
+void ChromeCleanerController::SetStateAndNotifyObservers(State state) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_NE(state_, state);
+
+  state_ = state;
+
+  if (state_ == State::kIdle || state_ == State::kRebootRequired)
+    ResetCleanerDataAndInvalidateWeakPtrs();
+
   for (auto& observer : observer_list_)
     NotifyObserver(&observer);
 }
 
-void ChromeCleanerController::SetKeepAlive(bool keep_alive) {
+void ChromeCleanerController::ResetCleanerDataAndInvalidateWeakPtrs() {
+  weak_factory_.InvalidateWeakPtrs();
+  reporter_invocation_.reset();
+  files_to_delete_.reset();
+  prompt_user_callback_.Reset();
+}
+
+void ChromeCleanerController::OnChromeCleanerFetchedAndVerified(
+    base::FilePath executable_path) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_EQ(State::kScanning, state());
+  DCHECK(reporter_invocation_);
+
+  if (executable_path.empty()) {
+    idle_reason_ = IdleReason::kScanningFailed;
+    SetStateAndNotifyObservers(State::kIdle);
+    return;
+  }
+
+  DCHECK(executable_path.MatchesExtension(FILE_PATH_LITERAL(".exe")));
+
+  ChromeCleanerRunner::ChromeMetricsStatus metrics_status =
+      delegate_->IsMetricsAndCrashReportingEnabled()
+          ? ChromeCleanerRunner::ChromeMetricsStatus::kEnabled
+          : ChromeCleanerRunner::ChromeMetricsStatus::kDisabled;
+
+  ChromeCleanerRunner::CleanerLogsStatus cleaner_logs_status =
+      delegate_->SafeBrowsingExtendedReportingScoutEnabled()
+          ? ChromeCleanerRunner::CleanerLogsStatus::kUploadEnabled
+          : ChromeCleanerRunner::CleanerLogsStatus::kUploadDisabled;
+
+  ChromeCleanerRunner::RunChromeCleanerAndReplyWithExitCode(
+      executable_path, *reporter_invocation_, metrics_status,
+      cleaner_logs_status,
+      base::Bind(&ChromeCleanerController::WeakOnPromptUser,
+                 weak_factory_.GetWeakPtr()),
+      base::Bind(&ChromeCleanerController::OnConnectionClosed,
+                 weak_factory_.GetWeakPtr()),
+      base::Bind(&ChromeCleanerController::OnCleanerProcessDone,
+                 weak_factory_.GetWeakPtr()),
+      // Our callbacks should be dispatched to the UI thread only.
+      base::ThreadTaskRunnerHandle::Get());
+}
+
+// static
+void ChromeCleanerController::WeakOnPromptUser(
+    const base::WeakPtr<ChromeCleanerController>& controller,
+    std::unique_ptr<std::set<base::FilePath>> files_to_delete,
+    ChromePrompt::PromptUserCallback prompt_user_callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  // If the weak pointer has been invalidated, the controller is no longer able
+  // to receive callbacks, so respond with PromptAcceptance::Denied immediately.
+  if (!controller) {
+    BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)
+        ->PostTask(FROM_HERE, base::BindOnce(std::move(prompt_user_callback),
+                                             PromptAcceptance::DENIED));
+  }
+
+  controller->OnPromptUser(std::move(files_to_delete),
+                           std::move(prompt_user_callback));
+}
+
+void ChromeCleanerController::OnPromptUser(
+    std::unique_ptr<std::set<base::FilePath>> files_to_delete,
+    ChromePrompt::PromptUserCallback prompt_user_callback) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(files_to_delete);
+  DCHECK_EQ(State::kScanning, state());
+  DCHECK(!files_to_delete_);
+  DCHECK(!prompt_user_callback_);
+
+  if (files_to_delete->empty()) {
+    BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)
+        ->PostTask(FROM_HERE, base::BindOnce(std::move(prompt_user_callback),
+                                             PromptAcceptance::DENIED));
+    idle_reason_ = IdleReason::kScanningFoundNothing;
+    SetStateAndNotifyObservers(State::kIdle);
+    return;
+  }
+
+  files_to_delete_ = std::move(files_to_delete);
+  prompt_user_callback_ = std::move(prompt_user_callback);
+  SetStateAndNotifyObservers(State::kInfected);
+}
+
+void ChromeCleanerController::OnConnectionClosed() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_NE(State::kIdle, state());
+  DCHECK_NE(State::kRebootRequired, state());
+
+  if (state() == State::kScanning || state() == State::kInfected) {
+    idle_reason_ = IdleReasonWhenConnectionClosedTooSoon(state());
+    SetStateAndNotifyObservers(State::kIdle);
+    return;
+  }
+  // Nothing to do if OnConnectionClosed() is called in other states:
+  // - This function will not be called in the kIdle and kRebootRequired
+  //   states since we invalidate all weak pointers when we enter those states.
+  // - In the kCleaning state, we don't care about the connection to the Chrome
+  //   Cleaner process since communication via Mojo is complete and only the
+  //   exit code of the process is of any use to us (for deciding whether we
+  //   need to reboot).
+}
+
+void ChromeCleanerController::OnCleanerProcessDone(
+    ChromeCleanerRunner::ProcessStatus process_status) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (keep_alive == keep_alive_)
+  if (state() == State::kScanning || state() == State::kInfected) {
+    idle_reason_ = IdleReasonWhenConnectionClosedTooSoon(state());
+    SetStateAndNotifyObservers(State::kIdle);
     return;
+  }
 
-  keep_alive_ = keep_alive;
-  if (keep_alive_)
-    AddRef();
-  else
-    Release();
+  DCHECK_EQ(State::kCleaning, state());
+  DCHECK_NE(ChromeCleanerRunner::LaunchStatus::kLaunchFailed,
+            process_status.launch_status);
+
+  if (process_status.launch_status !=
+      ChromeCleanerRunner::LaunchStatus::kSuccess) {
+    idle_reason_ = IdleReason::kCleaningFailed;
+    SetStateAndNotifyObservers(State::kIdle);
+    return;
+  }
+
+  if (process_status.exit_code == kRebootRequiredExitCode) {
+    SetStateAndNotifyObservers(State::kRebootRequired);
+    return;
+  }
+
+  if (process_status.exit_code == kRebootNotRequiredExitCode) {
+    idle_reason_ = IdleReason::kCleaningSucceeded;
+    SetStateAndNotifyObservers(State::kIdle);
+    return;
+  }
+
+  idle_reason_ = IdleReason::kCleaningFailed;
+  SetStateAndNotifyObservers(State::kIdle);
 }
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h
index 418fcaf2..0b75a2c3 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h
@@ -5,13 +5,44 @@
 #ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_CONTROLLER_WIN_H_
 #define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_CONTROLLER_WIN_H_
 
+#include <memory>
+#include <set>
+
+#include "base/files/file_path.h"
 #include "base/macros.h"
-#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/threading/thread_checker.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
+#include "components/chrome_cleaner/public/interfaces/chrome_prompt.mojom.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
 
 namespace safe_browsing {
 
+// Delegate class that provides services to the ChromeCleanerController class
+// and can be overridden by tests via
+// SetChromeCleanerControllerDelegateForTesting().
+class ChromeCleanerControllerDelegate {
+ public:
+  using FetchedCallback = base::OnceCallback<void(base::FilePath)>;
+
+  ChromeCleanerControllerDelegate();
+  virtual ~ChromeCleanerControllerDelegate();
+
+  // Fetches and verifies the Chrome Cleaner binary and passes the name of the
+  // executable to |fetched_callback|. The file name will have the ".exe"
+  // extension. If the operation fails, the file name passed to
+  // |fecthed_callback| will be empty.
+  virtual void FetchAndVerifyChromeCleaner(FetchedCallback fetched_callback);
+  virtual bool SafeBrowsingExtendedReportingScoutEnabled();
+  virtual bool IsMetricsAndCrashReportingEnabled();
+};
+
 // Controller class that keeps track of the execution of the Chrome Cleaner and
 // the various states through which the execution will transition. Observers can
 // register themselves to be notified of state changes. Intended to be used by
@@ -19,8 +50,7 @@
 //
 // This class lives on, and all its members should be called only on, the UI
 // thread.
-class ChromeCleanerController
-    : public base::RefCounted<ChromeCleanerController> {
+class ChromeCleanerController {
  public:
   enum class State {
     // The default state where there is no Chrome Cleaner process or IPC to keep
@@ -33,7 +63,10 @@
     // up an IPC between Chrome and the Cleaner process, and the actual
     // scanning done by the Cleaner.
     kScanning,
-    // Scanning has been completed and harmful or unwanted software was found.
+    // Scanning has been completed and harmful or unwanted software was
+    // found. In this state, the controller is waiting to get a response from
+    // the user on whether or not they want the cleaner to remove the harmful
+    // software that was found.
     kInfected,
     // The Cleaner process is cleaning the machine.
     kCleaning,
@@ -43,22 +76,41 @@
     kRebootRequired,
   };
 
+  enum class IdleReason {
+    kInitial,
+    kScanningFoundNothing,
+    kScanningFailed,
+    kConnectionLost,
+    kUserDeclinedCleanup,
+    kCleaningFailed,
+    kCleaningSucceeded,
+  };
+
+  enum class UserResponse {
+    // User accepted the cleanup operation.
+    kAccepted,
+    // User explicitly denied the cleanup operation, for example by clicking the
+    // Cleaner dialog's cancel button.
+    kDenied,
+    // The cleanup operation was denied when the user dismissed the Cleaner
+    // dialog, for example by pressing the ESC key.
+    kDismissed,
+  };
+
   class Observer {
    public:
-    virtual void OnIdle() {}
+    virtual void OnIdle(IdleReason idle_reason) {}
     virtual void OnScanning() {}
-    virtual void OnInfected() {}
-    virtual void OnCleaning() {}
+    virtual void OnInfected(const std::set<base::FilePath>& files_to_delete) {}
+    virtual void OnCleaning(const std::set<base::FilePath>& files_to_delete) {}
     virtual void OnRebootRequired() {}
 
    protected:
     virtual ~Observer() = default;
   };
 
-  // Returns an existing instance of the controller if there is one, or else
-  // will create a new instance. There is at most one instance of the controller
-  // class at any time.
-  static scoped_refptr<ChromeCleanerController> GetInstance();
+  // Returns the singleton controller object.
+  static ChromeCleanerController* GetInstance();
 
   State state() const { return state_; }
 
@@ -67,32 +119,93 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
-  // TODO(alito): add other functions, such as Scan(), Clean() etc.
+  // Downloads the Chrome Cleaner binary, executes it and waits for the Cleaner
+  // to communicate with Chrome about harmful software found on the
+  // system. During this time, the controller will be in the kScanning state. If
+  // any of the steps fail or if the Cleaner does not find harmful software on
+  // the system, the controller will transition to the kIdle state, passing to
+  // observers the reason for the transition. Otherwise, the scanner will
+  // transition to the kInfected state.
+  //
+  // |reporter_invocation| is the invocation that was used to run the reporter
+  // which found possible harmful software on the system.
+  //
+  // A call to Scan() will be a no-op if the controller is not in the kIdle
+  // state. This gracefully handles cases where multiple user responses are
+  // received, for example if a user manages to click on a "Scan" button
+  // multiple times.
+  void Scan(const SwReporterInvocation& reporter_invocation);
 
-  static ChromeCleanerController* GetRawInstanceForTesting();
+  // Sends the user's response, as to whether or not they want the Chrome
+  // Cleaner to remove harmful software that was found, to the Chrome Cleaner
+  // process.
+  //
+  // A call to ReplyWithUserResponse() will be a no-op if the controller is not
+  // in the kInfected state. This gracefully handles cases where multiple user
+  // responses are received, for example if a user manages to click on a
+  // "Cleanup" button multiple times.
+  void ReplyWithUserResponse(UserResponse user_response);
+
+  // Passing in a nullptr as |delegate| resets the delegate to a default
+  // production version.
+  void SetDelegateForTesting(ChromeCleanerControllerDelegate* delegate);
+  void DismissRebootForTesting();
 
  private:
-  friend class base::RefCounted<ChromeCleanerController>;
+  friend struct base::DefaultSingletonTraits<ChromeCleanerController>;
 
   ChromeCleanerController();
   ~ChromeCleanerController();
 
   void NotifyObserver(Observer* observer) const;
-  void NotifyAllObservers() const;
-  void SetKeepAlive(bool keep_alive);
+  void SetStateAndNotifyObservers(State state);
+  // Used to invalidate weak pointers and reset accumulated data that is no
+  // longer needed when entering the kIdle or kRebootRequired states.
+  void ResetCleanerDataAndInvalidateWeakPtrs();
+
+  // Callback that is called when the Chrome Cleaner binary has been fetched and
+  // verified. An empty |executable_path| signals failure. A non-empty
+  // |executable_path| must have the '.exe' file extension.
+  void OnChromeCleanerFetchedAndVerified(base::FilePath executable_path);
+
+  // Callback that checks if the weak pointer |controller| is still valid, and
+  // if so will call OnPromptuser(). If |controller| is no longer valid, will
+  // immediately send a Mojo response denying the cleanup operation.
+  //
+  // The other Mojo callbacks below do not need corresponding "weak" callbacks,
+  // because for those cases nothing needs to be done if the weak pointer
+  // referencing the controller instance is no longer valid (Chrome's Callback
+  // objects become no-ops if the bound weak pointer is not valid).
+  static void WeakOnPromptUser(
+      const base::WeakPtr<ChromeCleanerController>& controller,
+      std::unique_ptr<std::set<base::FilePath>> files_to_delete,
+      chrome_cleaner::mojom::ChromePrompt::PromptUserCallback
+          prompt_user_callback);
+
+  void OnPromptUser(std::unique_ptr<std::set<base::FilePath>> files_to_delete,
+                    chrome_cleaner::mojom::ChromePrompt::PromptUserCallback
+                        prompt_user_callback);
+  void OnConnectionClosed();
+  void OnCleanerProcessDone(ChromeCleanerRunner::ProcessStatus process_status);
+
+  std::unique_ptr<ChromeCleanerControllerDelegate> real_delegate_;
+  // Pointer to either real_delegate_ or one set by tests.
+  ChromeCleanerControllerDelegate* delegate_;
 
   State state_ = State::kIdle;
+  IdleReason idle_reason_ = IdleReason::kInitial;
+  std::unique_ptr<SwReporterInvocation> reporter_invocation_;
+  std::unique_ptr<std::set<base::FilePath>> files_to_delete_;
+  // The Mojo callback that should be called to send a response to the Chrome
+  // Cleaner process. This must be posted to run on the IO thread.
+  chrome_cleaner::mojom::ChromePrompt::PromptUserCallback prompt_user_callback_;
 
   base::ObserverList<Observer> observer_list_;
 
-  // Used to indicate that this instance needs to be kept alive. The instance
-  // should not be destroyed while in any of the |kScanning|, |kInfected|, or
-  // |kCleaning| states, which are the states where we have an active IPC to the
-  // Chrome Cleaner process.
-  bool keep_alive_ = false;
-
   THREAD_CHECKER(thread_checker_);
 
+  base::WeakPtrFactory<ChromeCleanerController> weak_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(ChromeCleanerController);
 };
 
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win_unittest.cc
index 8485cfa..fd9a195 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win_unittest.cc
@@ -4,48 +4,456 @@
 
 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h"
 
+#include <string>
+#include <tuple>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/mock_chrome_cleaner_process_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
 
 namespace safe_browsing {
 namespace {
 
-class ChromeCleanerControllerTest : public ::testing::Test {
- private:
-  // We need this because we need a UI thread during tests. The thread bundle
-  // must be the first member of the class so that it will be destroyed last.
-  content::TestBrowserThreadBundle thread_bundle_;
+using ::testing::Combine;
+using ::testing::DoAll;
+using ::testing::InvokeWithoutArgs;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+using ::testing::Values;
+using ::testing::_;
+using CrashPoint = MockChromeCleanerProcess::CrashPoint;
+using IdleReason = ChromeCleanerController::IdleReason;
+using State = ChromeCleanerController::State;
+using UserResponse = ChromeCleanerController::UserResponse;
+
+class MockChromeCleanerControllerObserver
+    : public ChromeCleanerController::Observer {
+ public:
+  MOCK_METHOD1(OnIdle, void(ChromeCleanerController::IdleReason));
+  MOCK_METHOD0(OnScanning, void());
+  MOCK_METHOD1(OnInfected, void(const std::set<base::FilePath>&));
+  MOCK_METHOD1(OnCleaning, void(const std::set<base::FilePath>&));
+  MOCK_METHOD0(OnRebootRequired, void());
 };
 
-TEST_F(ChromeCleanerControllerTest, GetInstance) {
-  // Ensure that GetInstance() returns the same controller object between
-  // invocations as long someone is holding on to a reference to the controller.
-  //
-  // Also ensure that the controller object is destroyed if it is in the IDLE
-  // state and there are no more references to it.
+enum class MetricsStatus {
+  kEnabled,
+  kDisabled,
+};
 
-  {
-    scoped_refptr<ChromeCleanerController> controller =
-        ChromeCleanerController::GetInstance();
-    EXPECT_TRUE(controller);
-    EXPECT_EQ(controller, ChromeCleanerController::GetRawInstanceForTesting());
-    EXPECT_EQ(controller->state(), ChromeCleanerController::State::kIdle);
+enum class ScoutStatus {
+  kEnabled,
+  kDisabled,
+};
 
-    // The same controller instance is returned as long as there are references
-    // to it.
-    EXPECT_EQ(controller, ChromeCleanerController::GetInstance());
+// Simple test fixture that passes an invalid process handle back to the
+// ChromeCleanerRunner class and is intended for testing simple things like
+// command line flags that Chrome sends to the Chrome Cleaner process.
+//
+// Parameters:
+// - metrics_status (MetricsStatus): whether Chrome metrics reporting is
+//   enabled.
+// - scout_status (ScoutStatus): whether logs upload in the Cleaner process
+//   should be enabled.
+class ChromeCleanerControllerSimpleTest
+    : public testing::TestWithParam<std::tuple<MetricsStatus, ScoutStatus>>,
+      public ChromeCleanerRunnerTestDelegate,
+      public ChromeCleanerControllerDelegate {
+ public:
+  ChromeCleanerControllerSimpleTest()
+      : command_line_(base::CommandLine::NO_PROGRAM) {}
+
+  ~ChromeCleanerControllerSimpleTest() override {}
+
+  void SetUp() override {
+    MetricsStatus metrics_status;
+    ScoutStatus scout_status;
+    std::tie(metrics_status, scout_status) = GetParam();
+
+    metrics_enabled_ = metrics_status == MetricsStatus::kEnabled;
+    scout_enabled_ = scout_status == ScoutStatus::kEnabled;
+
+    SetChromeCleanerRunnerTestDelegateForTesting(this);
+    ChromeCleanerController::GetInstance()->SetDelegateForTesting(this);
+    scoped_feature_list_.InitAndEnableFeature(kInBrowserCleanerUIFeature);
   }
-  // All references have now gone out of scope and the controller object should
-  // be destroyed.
-  EXPECT_FALSE(ChromeCleanerController::GetRawInstanceForTesting());
 
-  // GetInstance() always returns a valid object.
-  scoped_refptr<ChromeCleanerController> controller =
-      ChromeCleanerController::GetInstance();
-  EXPECT_TRUE(controller);
-  EXPECT_EQ(controller, ChromeCleanerController::GetRawInstanceForTesting());
+  void TearDown() override {
+    ChromeCleanerController::GetInstance()->SetDelegateForTesting(nullptr);
+    SetChromeCleanerRunnerTestDelegateForTesting(nullptr);
+  }
+
+  // ChromeCleanerControllerDelegate overrides.
+
+  void FetchAndVerifyChromeCleaner(FetchedCallback fetched_callback) override {
+    // In this fixture, we only test the cases when fetching the cleaner
+    // executable succeeds.
+    std::move(fetched_callback)
+        .Run(base::FilePath(FILE_PATH_LITERAL("chrome_cleaner.exe")));
+  }
+
+  bool SafeBrowsingExtendedReportingScoutEnabled() override {
+    return scout_enabled_;
+  }
+
+  bool IsMetricsAndCrashReportingEnabled() override { return metrics_enabled_; }
+
+  // ChromeCleanerRunnerTestDelegate overrides.
+
+  base::Process LaunchTestProcess(
+      const base::CommandLine& command_line,
+      const base::LaunchOptions& launch_options) override {
+    command_line_ = command_line;
+    // Return an invalid process.
+    return base::Process();
+  }
+
+ protected:
+  // We need this because we need UI and IO threads during tests. The thread
+  // bundle should be the first member of the class so that it will be destroyed
+  // last.
+  content::TestBrowserThreadBundle thread_bundle_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  bool metrics_enabled_;
+  bool scout_enabled_;
+  base::CommandLine command_line_;
+
+  StrictMock<MockChromeCleanerControllerObserver> mock_observer_;
+};
+
+SwReporterInvocation GetInvocationWithPromptTrigger() {
+  SwReporterInvocation invocation = {};
+  invocation.supported_behaviours |=
+      SwReporterInvocation::BEHAVIOUR_TRIGGER_PROMPT;
+  return invocation;
 }
 
+TEST_P(ChromeCleanerControllerSimpleTest, FlagsPassedToCleanerProcess) {
+  ChromeCleanerController* controller = ChromeCleanerController::GetInstance();
+  ASSERT_TRUE(controller);
+
+  EXPECT_CALL(mock_observer_, OnIdle(_)).Times(1);
+  controller->AddObserver(&mock_observer_);
+  EXPECT_EQ(controller->state(), State::kIdle);
+
+  EXPECT_CALL(mock_observer_, OnScanning()).Times(1);
+  controller->Scan(GetInvocationWithPromptTrigger());
+  EXPECT_EQ(controller->state(), State::kScanning);
+
+  base::RunLoop run_loop;
+  // The run loop will quit when we get back to the kIdle state, which will
+  // happen when launching the Chrome Cleaner process fails (due to the
+  // definition of LaunchTestProcess() in the test fixture class).
+  EXPECT_CALL(mock_observer_, OnIdle(IdleReason::kScanningFailed))
+      .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.QuitWhenIdle(); }));
+  run_loop.Run();
+
+  EXPECT_EQ(controller->state(), State::kIdle);
+  EXPECT_EQ(metrics_enabled_,
+            command_line_.HasSwitch(chrome_cleaner::kUmaUserSwitch));
+  EXPECT_EQ(metrics_enabled_, command_line_.HasSwitch(
+                                  chrome_cleaner::kEnableCrashReportingSwitch));
+  EXPECT_EQ(scout_enabled_, command_line_.HasSwitch(
+                                chrome_cleaner::kEnableCleanerLoggingSwitch));
+
+  controller->RemoveObserver(&mock_observer_);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    All,
+    ChromeCleanerControllerSimpleTest,
+    Combine(Values(MetricsStatus::kDisabled, MetricsStatus::kEnabled),
+            Values(ScoutStatus::kDisabled, ScoutStatus::kEnabled)));
+
+enum class CleanerProcessStatus {
+  kFetchFailure,
+  kFetchSuccessInvalidProcess,
+  kFetchSuccessValidProcess,
+};
+
+enum class UwsFoundStatus {
+  kNoUwsFound,
+  kUwsFoundRebootRequired,
+  kUwsFoundNoRebootRequired,
+};
+
+// Test fixture that runs a mock Chrome Cleaner process in various
+// configurations and mocks the user's response.
+class ChromeCleanerControllerTest
+    : public testing::TestWithParam<
+          std::tuple<CleanerProcessStatus,
+                     MockChromeCleanerProcess::CrashPoint,
+                     UwsFoundStatus,
+                     ChromeCleanerController::UserResponse>>,
+      public ChromeCleanerRunnerTestDelegate,
+      public ChromeCleanerControllerDelegate {
+ public:
+  ~ChromeCleanerControllerTest() override {}
+
+  void SetUp() override {
+    std::tie(process_status_, crash_point_, uws_found_status_, user_response_) =
+        GetParam();
+
+    cleaner_process_options_.SetDoFindUws(uws_found_status_ !=
+                                          UwsFoundStatus::kNoUwsFound);
+    cleaner_process_options_.set_reboot_required(
+        uws_found_status_ == UwsFoundStatus::kUwsFoundRebootRequired);
+    cleaner_process_options_.set_crash_point(crash_point_);
+
+    controller_ = ChromeCleanerController::GetInstance();
+    ASSERT_TRUE(controller_);
+
+    scoped_feature_list_.InitAndEnableFeature(kInBrowserCleanerUIFeature);
+    SetChromeCleanerRunnerTestDelegateForTesting(this);
+    controller_->SetDelegateForTesting(this);
+  }
+
+  void TearDown() override {
+    if (controller_->state() == State::kRebootRequired)
+      controller_->DismissRebootForTesting();
+
+    controller_->SetDelegateForTesting(nullptr);
+    SetChromeCleanerRunnerTestDelegateForTesting(nullptr);
+  }
+
+  // ChromeCleanerControllerDelegate overrides.
+
+  void FetchAndVerifyChromeCleaner(FetchedCallback fetched_callback) override {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            std::move(fetched_callback),
+            process_status_ != CleanerProcessStatus::kFetchFailure
+                ? base::FilePath(FILE_PATH_LITERAL("chrome_cleaner.exe"))
+                : base::FilePath()));
+  }
+
+  bool SafeBrowsingExtendedReportingScoutEnabled() override {
+    // Returning an arbitrary value since this is not being tested in this
+    // fixture.
+    return false;
+  }
+
+  bool IsMetricsAndCrashReportingEnabled() override {
+    // Returning an arbitrary value since this is not being tested in this
+    // fixture.
+    return false;
+  }
+
+  // ChromeCleanerRunnerTestDelegate overrides.
+
+  base::Process LaunchTestProcess(
+      const base::CommandLine& command_line,
+      const base::LaunchOptions& launch_options) override {
+    if (process_status_ != CleanerProcessStatus::kFetchSuccessValidProcess)
+      return base::Process();
+
+    // Add switches and program name that the test process needs for the multi
+    // process tests.
+    base::CommandLine test_process_command_line =
+        base::GetMultiProcessTestChildBaseCommandLine();
+    test_process_command_line.AppendArguments(command_line,
+                                              /*include_program=*/false);
+
+    cleaner_process_options_.AddSwitchesToCommandLine(
+        &test_process_command_line);
+
+    base::SpawnChildResult result = base::SpawnMultiProcessTestChild(
+        "MockChromeCleanerProcessMain", test_process_command_line,
+        launch_options);
+
+    EXPECT_TRUE(result.process.IsValid());
+    return std::move(result.process);
+  }
+
+  ChromeCleanerController::State ExpectedFinalState() {
+    if (process_status_ == CleanerProcessStatus::kFetchSuccessValidProcess &&
+        crash_point_ == CrashPoint::kNone &&
+        uws_found_status_ == UwsFoundStatus::kUwsFoundRebootRequired &&
+        user_response_ == UserResponse::kAccepted) {
+      return State::kRebootRequired;
+    }
+    return State::kIdle;
+  }
+
+  bool ExpectedOnIdleCalled() { return ExpectedFinalState() == State::kIdle; }
+
+  bool ExpectedOnInfectedCalled() {
+    return process_status_ == CleanerProcessStatus::kFetchSuccessValidProcess &&
+           crash_point_ != CrashPoint::kOnStartup &&
+           crash_point_ != CrashPoint::kAfterConnection &&
+           uws_found_status_ != UwsFoundStatus::kNoUwsFound;
+  }
+
+  bool ExpectedOnCleaningCalled() {
+    return ExpectedOnInfectedCalled() &&
+           crash_point_ != CrashPoint::kAfterRequestSent &&
+           user_response_ == UserResponse::kAccepted;
+  }
+
+  bool ExpectedOnRebootRequiredCalled() {
+    return ExpectedFinalState() == State::kRebootRequired;
+  }
+
+  bool ExpectedUwsFound() { return ExpectedOnInfectedCalled(); }
+
+  ChromeCleanerController::IdleReason ExpectedIdleReason() {
+    EXPECT_EQ(ExpectedFinalState(), State::kIdle);
+
+    if (process_status_ != CleanerProcessStatus::kFetchSuccessValidProcess ||
+        crash_point_ == CrashPoint::kOnStartup ||
+        crash_point_ == CrashPoint::kAfterConnection) {
+      return IdleReason::kScanningFailed;
+    }
+
+    if (uws_found_status_ == UwsFoundStatus::kNoUwsFound)
+      return IdleReason::kScanningFoundNothing;
+
+    if (ExpectedOnInfectedCalled() &&
+        (user_response_ == UserResponse::kDenied ||
+         user_response_ == UserResponse::kDismissed)) {
+      return IdleReason::kUserDeclinedCleanup;
+    }
+
+    if (ExpectedOnInfectedCalled() &&
+        user_response_ == UserResponse::kAccepted &&
+        crash_point_ == CrashPoint::kAfterResponseReceived) {
+      return IdleReason::kCleaningFailed;
+    }
+
+    return IdleReason::kCleaningSucceeded;
+  }
+
+ protected:
+  // We need this because we need UI and IO threads during tests. The thread
+  // bundle should be the first member of the class so that it will be destroyed
+  // last.
+  content::TestBrowserThreadBundle thread_bundle_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  CleanerProcessStatus process_status_;
+  MockChromeCleanerProcess::CrashPoint crash_point_;
+  UwsFoundStatus uws_found_status_;
+  ChromeCleanerController::UserResponse user_response_;
+
+  MockChromeCleanerProcess::Options cleaner_process_options_;
+
+  StrictMock<MockChromeCleanerControllerObserver> mock_observer_;
+  ChromeCleanerController* controller_;
+};
+
+MULTIPROCESS_TEST_MAIN(MockChromeCleanerProcessMain) {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  MockChromeCleanerProcess::Options options;
+  EXPECT_TRUE(MockChromeCleanerProcess::Options::FromCommandLine(*command_line,
+                                                                 &options));
+
+  std::string chrome_mojo_pipe_token = command_line->GetSwitchValueASCII(
+      chrome_cleaner::kChromeMojoPipeTokenSwitch);
+  EXPECT_FALSE(chrome_mojo_pipe_token.empty());
+
+  // Since failures in any of the above calls to EXPECT_*() do not actually fail
+  // the test, we need to ensure that we return an exit code to indicate test
+  // failure in such cases.
+  if (::testing::Test::HasFailure())
+    return MockChromeCleanerProcess::kInternalTestFailureExitCode;
+
+  MockChromeCleanerProcess mock_cleaner_process(options,
+                                                chrome_mojo_pipe_token);
+  return mock_cleaner_process.Run();
+}
+
+TEST_P(ChromeCleanerControllerTest, WithMockCleanerProcess) {
+  EXPECT_CALL(mock_observer_, OnIdle(_)).Times(1);
+  controller_->AddObserver(&mock_observer_);
+  EXPECT_EQ(controller_->state(), State::kIdle);
+
+  EXPECT_CALL(mock_observer_, OnScanning()).Times(1);
+  controller_->Scan(GetInvocationWithPromptTrigger());
+  EXPECT_EQ(controller_->state(), State::kScanning);
+
+  base::RunLoop run_loop;
+
+  std::set<base::FilePath> files_to_delete_on_infected;
+  std::set<base::FilePath> files_to_delete_on_cleaning;
+
+  if (ExpectedOnIdleCalled()) {
+    EXPECT_CALL(mock_observer_, OnIdle(ExpectedIdleReason()))
+        .WillOnce(
+            InvokeWithoutArgs([&run_loop]() { run_loop.QuitWhenIdle(); }));
+  }
+
+  if (ExpectedOnInfectedCalled()) {
+    EXPECT_CALL(mock_observer_, OnInfected(_))
+        .WillOnce(DoAll(SaveArg<0>(&files_to_delete_on_infected),
+                        InvokeWithoutArgs([this]() {
+                          controller_->ReplyWithUserResponse(user_response_);
+                        })));
+  }
+
+  if (ExpectedOnCleaningCalled()) {
+    EXPECT_CALL(mock_observer_, OnCleaning(_))
+        .WillOnce(SaveArg<0>(&files_to_delete_on_cleaning));
+  }
+
+  if (ExpectedOnRebootRequiredCalled()) {
+    EXPECT_CALL(mock_observer_, OnRebootRequired())
+        .WillOnce(
+            InvokeWithoutArgs([&run_loop]() { run_loop.QuitWhenIdle(); }));
+  }
+
+  // Assert here that we expect at least one of OnIdle or OnRebootRequired to be
+  // called, since otherwise, the test is set up incorrectly and is expected to
+  // never stop.
+  ASSERT_TRUE(ExpectedOnIdleCalled() || ExpectedOnRebootRequiredCalled());
+  run_loop.Run();
+
+  EXPECT_EQ(controller_->state(), ExpectedFinalState());
+  EXPECT_EQ(!files_to_delete_on_infected.empty(), ExpectedUwsFound());
+  EXPECT_EQ(!files_to_delete_on_cleaning.empty(),
+            ExpectedUwsFound() && ExpectedOnCleaningCalled());
+  if (!files_to_delete_on_infected.empty() &&
+      !files_to_delete_on_cleaning.empty()) {
+    EXPECT_EQ(files_to_delete_on_infected, files_to_delete_on_cleaning);
+  }
+
+  controller_->RemoveObserver(&mock_observer_);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    All,
+    ChromeCleanerControllerTest,
+    Combine(Values(CleanerProcessStatus::kFetchFailure,
+                   CleanerProcessStatus::kFetchSuccessInvalidProcess,
+                   CleanerProcessStatus::kFetchSuccessValidProcess),
+            Values(CrashPoint::kNone,
+                   CrashPoint::kOnStartup,
+                   CrashPoint::kAfterConnection,
+                   // CrashPoint::kAfterRequestSent is not used because we
+                   // cannot ensure the order between the Mojo request being
+                   // received by Chrome and the connection being lost.
+                   CrashPoint::kAfterResponseReceived),
+            Values(UwsFoundStatus::kNoUwsFound,
+                   UwsFoundStatus::kUwsFoundRebootRequired,
+                   UwsFoundStatus::kUwsFoundNoRebootRequired),
+            Values(UserResponse::kAccepted,
+                   UserResponse::kDenied,
+                   UserResponse::kDismissed)));
+
 }  // namespace
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
index b12caaea..3cc91aaa 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
@@ -16,6 +16,8 @@
 using ChromeCleanerFetchedCallback =
     base::OnceCallback<void(base::FilePath, int /*http response code*/)>;
 
+// Fetches the Chrome Cleaner binary. This function can be called from any
+// sequence and |fetched_callback| will be called back on that same sequence.
 void FetchChromeCleaner(ChromeCleanerFetchedCallback fetched_callback);
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc
index b8bddc9..25c1575b 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc
@@ -42,6 +42,10 @@
 
 }  // namespace
 
+ChromeCleanerRunner::ProcessStatus::ProcessStatus(LaunchStatus launch_status,
+                                                  int exit_code)
+    : launch_status(launch_status), exit_code(exit_code) {}
+
 // static
 void ChromeCleanerRunner::RunChromeCleanerAndReplyWithExitCode(
     const base::FilePath& cleaner_executable_path,
@@ -137,7 +141,7 @@
         chrome_cleaner::kEnableCleanerLoggingSwitch);
 }
 
-ChromeCleanerRunner::LaunchStatus
+ChromeCleanerRunner::ProcessStatus
 ChromeCleanerRunner::LaunchAndWaitForExitOnBackgroundThread() {
   mojo::edk::OutgoingBrokerClientInvitation invitation;
   std::string mojo_pipe_token = mojo::edk::GenerateRandomToken();
@@ -159,9 +163,8 @@
                                                launch_options)
           : base::LaunchProcess(cleaner_command_line_, launch_options);
 
-  constexpr int kBadProcessExitCode = std::numeric_limits<int>::max();
   if (!cleaner_process.IsValid())
-    return {false, kBadProcessExitCode};
+    return ProcessStatus(LaunchStatus::kLaunchFailed);
 
   // ChromePromptImpl tasks will need to run on the IO thread. There is no
   // need to synchronize its creation, since the client end will wait for this
@@ -178,10 +181,13 @@
       mojo::edk::ConnectionParams(mojo::edk::TransportProtocol::kLegacy,
                                   channel.PassServerHandle()));
 
-  int exit_code = kBadProcessExitCode;
-  if (cleaner_process.WaitForExit(&exit_code))
-    return {true, exit_code};
-  return {false, kBadProcessExitCode};
+  int exit_code = -1;
+  if (!cleaner_process.WaitForExit(&exit_code)) {
+    return ProcessStatus(
+        LaunchStatus::kLaunchSucceededFailedToWaitForCompletion);
+  }
+
+  return ProcessStatus(LaunchStatus::kSuccess, exit_code);
 }
 
 ChromeCleanerRunner::~ChromeCleanerRunner() = default;
@@ -216,10 +222,10 @@
     task_runner_->PostTask(FROM_HERE, std::move(on_connection_closed_));
 }
 
-void ChromeCleanerRunner::OnProcessDone(LaunchStatus launch_status) {
+void ChromeCleanerRunner::OnProcessDone(ProcessStatus process_status) {
   if (on_process_done_) {
     task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(std::move(on_process_done_), launch_status));
+        FROM_HERE, base::BindOnce(std::move(on_process_done_), process_status));
   }
 }
 
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.h
index 6d05018..dc9cce1 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.h
@@ -59,17 +59,30 @@
     kUploadDisabled,
   };
 
-  struct LaunchStatus {
-    // If false, indicates that either the Chrome Cleaner process handle
-    // returned by base::LaunchProcess() was invalid or that something went
-    // wrong while waiting for the process to exit.
-    bool process_ok;
-    // The exit code from the Chrome Cleaner process. Should not be used if
-    // |process_ok| is false.
-    int exit_code;
+  enum class LaunchStatus {
+    // Got an invalid process when attempting to launch the Chrome Cleaner
+    // process. As a result, the Mojo pipe was never set up and the
+    // |on_connection_closed| and |on_prompt_user| callbacks passed to
+    // RunChromeCleanerAndReplyWithExitCode() will never be run.
+    kLaunchFailed,
+    // Waiting for the Chrome Cleaner process to exit failed.
+    kLaunchSucceededFailedToWaitForCompletion,
+    // Successfully waited for the Chrome Cleaner process to exit and received
+    // the process's exit code.
+    kSuccess,
   };
 
-  using ProcessDoneCallback = base::OnceCallback<void(LaunchStatus)>;
+  struct ProcessStatus {
+    LaunchStatus launch_status;
+    // The exit code from the Chrome Cleaner process. Should be used only if
+    // |launch_status| is |kSuccess|.
+    int exit_code;
+
+    ProcessStatus(LaunchStatus launch_status = LaunchStatus::kLaunchFailed,
+                  int exit_code = std::numeric_limits<int>::max());
+  };
+
+  using ProcessDoneCallback = base::OnceCallback<void(ProcessStatus)>;
 
   // Executes the Chrome Cleaner in the background, initializes the Mojo IPC
   // between Chrome and the Chrome Cleaner process, and forwards Mojo callbacks
@@ -85,7 +98,7 @@
   // will communicate with Chrome via a Mojo IPC interface and any IPC requests
   // or notifications are passed to the caller via the |on_prompt_user| and
   // |on_connection_closed| callbacks. Finally, when the Chrome Cleaner process
-  // terminates, a LaunchStatus is passed along to |on_process_done|.
+  // terminates, a ProcessStatus is passed along to |on_process_done|.
   //
   // The details of the mojo interface are documented in
   // "components/chrome_cleaner/public/interfaces/chrome_prompt.mojom.h".
@@ -113,7 +126,7 @@
                       ChromeCleanerRunner::ProcessDoneCallback on_process_done,
                       scoped_refptr<base::SequencedTaskRunner> task_runner);
 
-  LaunchStatus LaunchAndWaitForExitOnBackgroundThread();
+  ProcessStatus LaunchAndWaitForExitOnBackgroundThread();
 
   void CreateChromePromptImpl(
       chrome_cleaner::mojom::ChromePromptRequest chrome_prompt_request);
@@ -123,7 +136,7 @@
                     chrome_cleaner::mojom::ChromePrompt::PromptUserCallback
                         prompt_user_callback);
   void OnConnectionClosed();
-  void OnProcessDone(LaunchStatus launch_status);
+  void OnProcessDone(ProcessStatus launch_status);
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc
index 8f62791..b4660dc3 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc
@@ -122,9 +122,9 @@
 
   void OnConnectionClosed() {}
 
-  void OnProcessDone(ChromeCleanerRunner::LaunchStatus launch_status) {
+  void OnProcessDone(ChromeCleanerRunner::ProcessStatus process_status) {
     on_process_done_called_ = true;
-    launch_status_ = launch_status;
+    process_status_ = process_status;
     run_loop_.QuitWhenIdle();
   }
 
@@ -142,7 +142,7 @@
 
   // Variables set by OnProcessDone().
   bool on_process_done_called_ = false;
-  ChromeCleanerRunner::LaunchStatus launch_status_;
+  ChromeCleanerRunner::ProcessStatus process_status_;
 
   base::RunLoop run_loop_;
 };
@@ -300,9 +300,9 @@
     QuitTestRunLoopIfCommunicationDone();
   }
 
-  void OnProcessDone(ChromeCleanerRunner::LaunchStatus launch_status) {
+  void OnProcessDone(ChromeCleanerRunner::ProcessStatus process_status) {
     on_process_done_called_ = true;
-    launch_status_ = launch_status;
+    process_status_ = process_status;
     QuitTestRunLoopIfCommunicationDone();
   }
 
@@ -316,7 +316,8 @@
   PromptAcceptance prompt_acceptance_to_send_ = PromptAcceptance::UNSPECIFIED;
 
   // Set by OnProcessDone().
-  ChromeCleanerRunner::LaunchStatus launch_status_ = {false, -1};
+  ChromeCleanerRunner::ProcessStatus process_status_;
+
   // Set by OnPromptUser().
   std::unique_ptr<std::set<base::FilePath>> received_files_to_delete_;
 
@@ -363,9 +364,11 @@
               cleaner_process_options_.files_to_delete());
   }
 
-  EXPECT_TRUE(launch_status_.process_ok);
-  EXPECT_EQ(launch_status_.exit_code, cleaner_process_options_.ExpectedExitCode(
-                                          prompt_acceptance_to_send_));
+  EXPECT_EQ(process_status_.launch_status,
+            ChromeCleanerRunner::LaunchStatus::kSuccess);
+  EXPECT_EQ(
+      process_status_.exit_code,
+      cleaner_process_options_.ExpectedExitCode(prompt_acceptance_to_send_));
 }
 
 INSTANTIATE_TEST_CASE_P(
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc
index eb5975d..596c288 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h"
 
+#include <algorithm>
+#include <vector>
+
 #include "base/logging.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
@@ -41,4 +44,13 @@
                      });
 }
 
+bool SafeBrowsingExtendedReportingScoutEnabled() {
+  std::vector<Profile*> profiles = ProfileManager::GetLastOpenedProfiles();
+  return std::any_of(
+      profiles.begin(), profiles.end(), [](const Profile* profile) {
+        return profile && GetExtendedReportingLevel(*profile->GetPrefs()) ==
+                              SBER_LEVEL_SCOUT;
+      });
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h
index dbaac3fe..d8b0c8b 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h
@@ -16,6 +16,10 @@
 // Browsing extended reporting.
 bool SafeBrowsingExtendedReportingEnabled();
 
+// Returns true if there is a profile in which the user has opted into Safe
+// Browsing extended reporting with reporting level SBER_LEVEL_SCOUT.
+bool SafeBrowsingExtendedReportingScoutEnabled();
+
 }  // namespace safe_browsing
 
 #endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_CLIENT_INFO_WIN_H_
diff --git a/chrome/browser/ssl/ssl_browser_tests.cc b/chrome/browser/ssl/ssl_browser_tests.cc
index fe83aa9..661ed01 100644
--- a/chrome/browser/ssl/ssl_browser_tests.cc
+++ b/chrome/browser/ssl/ssl_browser_tests.cc
@@ -1151,7 +1151,6 @@
 }
 
 // Visits a page with https error and then goes back using GoToOffset.
-// Disabled because its flaky: http://crbug.com/40932, http://crbug.com/43575.
 IN_PROC_BROWSER_TEST_F(SSLUITest,
                        TestHTTPSExpiredCertAndGoBackViaMenu) {
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 24e5e840..167fa80 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -561,6 +561,7 @@
     "//components/sync_preferences",
     "//components/sync_sessions",
     "//components/toolbar",
+    "//components/toolbar:vector_icons",
     "//components/tracing:startup_tracing",
     "//components/ui_devtools",
     "//components/undo",
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
index aef2527..1bcb9da 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
@@ -134,7 +134,9 @@
     std::unique_ptr<views::View> extra_content_view,
     std::unique_ptr<views::View> trailing_button,
     bool clickable,
-    bool extra_trailing_inset) {
+    bool extra_trailing_inset,
+    views::GridLayout::Alignment vertical_alignment =
+        views::GridLayout::LEADING) {
   const int trailing_inset = extra_trailing_inset
                                  ? kPaymentRequestRowHorizontalInsets +
                                        kPaymentRequestRowExtraRightInset
@@ -150,14 +152,14 @@
   views::ColumnSet* columns = layout->AddColumnSet(0);
   // A column for the section name.
   constexpr int kNameColumnWidth = 112;
-  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
+  columns->AddColumn(views::GridLayout::LEADING, vertical_alignment, 0,
                      views::GridLayout::FIXED, kNameColumnWidth, 0);
 
   constexpr int kPaddingAfterName = 32;
   columns->AddPaddingColumn(0, kPaddingAfterName);
 
   // A column for the content.
-  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::LEADING, 1,
+  columns->AddColumn(views::GridLayout::FILL, vertical_alignment, 1,
                      views::GridLayout::USE_PREF, 0, 0);
   // A column for the extra content.
   columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
@@ -331,10 +333,10 @@
     button->set_id(id_);
     button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
     button->SetEnabled(button_enabled);
-    return CreatePaymentSheetRow(listener_, section_name_,
-                                 std::move(content_view), nullptr,
-                                 std::move(button), /*clickable=*/false,
-                                 /*extra_trailing_inset=*/false);
+    return CreatePaymentSheetRow(
+        listener_, section_name_, std::move(content_view), nullptr,
+        std::move(button), /*clickable=*/false,
+        /*extra_trailing_inset=*/false, views::GridLayout::CENTER);
   }
 
   views::ButtonListener* listener_;
@@ -544,23 +546,13 @@
   // request's details, followed by a label indicating "N more items..." if
   // there are more than 2 items in the details. The total label and amount
   // always follow.
-  constexpr int kMaxNumberOfItemsShown = 2;
-  int hidden_item_count = items.size() - kMaxNumberOfItemsShown;
-  if (hidden_item_count > 0) {
-    layout->StartRow(0, 0);
-    std::unique_ptr<views::Label> label =
-        CreateHintLabel(l10n_util::GetPluralStringFUTF16(
-            IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS, hidden_item_count));
-    layout->AddView(label.release());
-    if (is_mixed_currency) {
-      std::unique_ptr<views::Label> multiple_currency_label =
-          CreateHintLabel(l10n_util::GetStringUTF16(
-              IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MULTIPLE_CURRENCY_INDICATOR));
-      layout->AddView(multiple_currency_label.release());
-    }
-  }
-
-  for (size_t i = 0; i < items.size() && i < kMaxNumberOfItemsShown; ++i) {
+  constexpr size_t kMaxNumberOfItemsShown = 2;
+  // Don't show a line reading "1 more" because the item itself can be shown in
+  // the same space.
+  size_t displayed_items = items.size() <= kMaxNumberOfItemsShown + 1
+                               ? items.size()
+                               : kMaxNumberOfItemsShown;
+  for (size_t i = 0; i < items.size() && i < displayed_items; ++i) {
     layout->StartRow(0, 0);
     std::unique_ptr<views::Label> summary =
         base::MakeUnique<views::Label>(base::UTF8ToUTF16(items[i]->label));
@@ -577,6 +569,21 @@
             .release());
   }
 
+  size_t hidden_item_count = items.size() - displayed_items;
+  if (hidden_item_count > 0) {
+    layout->StartRow(0, 0);
+    std::unique_ptr<views::Label> label =
+        CreateHintLabel(l10n_util::GetPluralStringFUTF16(
+            IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS, hidden_item_count));
+    layout->AddView(label.release());
+    if (is_mixed_currency) {
+      std::unique_ptr<views::Label> multiple_currency_label =
+          CreateHintLabel(l10n_util::GetStringUTF16(
+              IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MULTIPLE_CURRENCY_INDICATOR));
+      layout->AddView(multiple_currency_label.release());
+    }
+  }
+
   layout->StartRow(0, 0);
   layout->AddView(
       CreateBoldLabel(base::UTF8ToUTF16(spec()->details().total->label))
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
index 483f7f71..c9c7d11 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
@@ -11,7 +11,6 @@
 #include "base/strings/string16.h"
 #include "chrome/browser/permissions/permission_request.h"
 #include "chrome/browser/platform_util.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -25,7 +24,6 @@
 #include "components/url_formatter/elide_url.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/models/combobox_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -34,12 +32,7 @@
 #include "ui/views/bubble/bubble_dialog_delegate.h"
 #include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/controls/button/checkbox.h"
-#include "ui/views/controls/button/menu_button.h"
-#include "ui/views/controls/button/menu_button_listener.h"
-#include "ui/views/controls/combobox/combobox.h"
-#include "ui/views/controls/combobox/combobox_listener.h"
 #include "ui/views/controls/label.h"
-#include "ui/views/controls/menu/menu_runner.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/grid_layout.h"
 
@@ -50,102 +43,14 @@
 
 }  // namespace
 
-// This class is a MenuButton which is given a PermissionMenuModel. It
-// shows the current checked item in the menu model, and notifies its listener
-// about any updates to the state of the selection.
-// TODO(gbillock): refactor PermissionMenuButton to work like this and re-use?
-class PermissionCombobox : public views::MenuButton,
-                           public views::MenuButtonListener {
- public:
-  // Get notifications when the selection changes.
-  class Listener {
-   public:
-    virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
-  };
-
-  PermissionCombobox(Profile* profile,
-                     Listener* listener,
-                     int index,
-                     const GURL& url,
-                     ContentSetting setting);
-  ~PermissionCombobox() override;
-
-  int index() const { return index_; }
-
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
-  // MenuButtonListener:
-  void OnMenuButtonClicked(views::MenuButton* source,
-                           const gfx::Point& point,
-                           const ui::Event* event) override;
-
-  // Callback when a permission's setting is changed.
-  void PermissionChanged(const PageInfoUI::PermissionInfo& permission);
-
- private:
-  int index_;
-  Listener* listener_;
-  std::unique_ptr<PermissionMenuModel> model_;
-  std::unique_ptr<views::MenuRunner> menu_runner_;
-};
-
-PermissionCombobox::PermissionCombobox(Profile* profile,
-                                       Listener* listener,
-                                       int index,
-                                       const GURL& url,
-                                       ContentSetting setting)
-    : MenuButton(base::string16(), this, true),
-      index_(index),
-      listener_(listener),
-      model_(new PermissionMenuModel(
-          profile,
-          url,
-          setting,
-          base::Bind(&PermissionCombobox::PermissionChanged,
-                     base::Unretained(this)))) {
-  SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting)));
-  SizeToPreferredSize();
-}
-
-PermissionCombobox::~PermissionCombobox() {}
-
-void PermissionCombobox::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  MenuButton::GetAccessibleNodeData(node_data);
-  node_data->SetValue(GetText());
-}
-
-void PermissionCombobox::OnMenuButtonClicked(views::MenuButton* source,
-                                             const gfx::Point& point,
-                                             const ui::Event* event) {
-  menu_runner_.reset(
-      new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
-
-  gfx::Point p(point);
-  p.Offset(-source->width(), 0);
-  menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), this,
-                          gfx::Rect(p, gfx::Size()), views::MENU_ANCHOR_TOPLEFT,
-                          ui::MENU_SOURCE_NONE);
-}
-
-void PermissionCombobox::PermissionChanged(
-    const PageInfoUI::PermissionInfo& permission) {
-  SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting)));
-  SizeToPreferredSize();
-
-  listener_->PermissionSelectionChanged(
-      index_, permission.setting == CONTENT_SETTING_ALLOW);
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // View implementation for the permissions bubble.
 class PermissionsBubbleDialogDelegateView
-    : public views::BubbleDialogDelegateView,
-      public PermissionCombobox::Listener {
+    : public views::BubbleDialogDelegateView {
  public:
   PermissionsBubbleDialogDelegateView(
       PermissionPromptImpl* owner,
-      const std::vector<PermissionRequest*>& requests,
-      const std::vector<bool>& accept_state);
+      const std::vector<PermissionRequest*>& requests);
   ~PermissionsBubbleDialogDelegateView() override;
 
   void CloseBubble();
@@ -164,9 +69,6 @@
   int GetDialogButtons() const override;
   base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
 
-  // PermissionCombobox::Listener:
-  void PermissionSelectionChanged(int index, bool allowed) override;
-
   // Updates the anchor's arrow and view. Also repositions the bubble so it's
   // displayed in the correct location.
   void UpdateAnchor(views::View* anchor_view,
@@ -175,10 +77,7 @@
 
  private:
   PermissionPromptImpl* owner_;
-  bool multiple_requests_;
   base::string16 display_origin_;
-  std::unique_ptr<PermissionMenuModel> menu_button_model_;
-  std::vector<PermissionCombobox*> customize_comboboxes_;
   views::Checkbox* persist_checkbox_;
 
   DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDialogDelegateView);
@@ -186,11 +85,8 @@
 
 PermissionsBubbleDialogDelegateView::PermissionsBubbleDialogDelegateView(
     PermissionPromptImpl* owner,
-    const std::vector<PermissionRequest*>& requests,
-    const std::vector<bool>& accept_state)
-    : owner_(owner),
-      multiple_requests_(requests.size() > 1),
-      persist_checkbox_(nullptr) {
+    const std::vector<PermissionRequest*>& requests)
+    : owner_(owner), persist_checkbox_(nullptr) {
   DCHECK(!requests.empty());
 
   set_close_on_deactivate(false);
@@ -206,20 +102,6 @@
 
   bool show_persistence_toggle = true;
   for (size_t index = 0; index < requests.size(); index++) {
-    DCHECK(index < accept_state.size());
-    // The row is laid out containing a leading-aligned label area and a
-    // trailing column which will be filled if there are multiple permission
-    // requests.
-    views::View* row = new views::View();
-    views::GridLayout* row_layout = new views::GridLayout(row);
-    row->SetLayoutManager(row_layout);
-    views::ColumnSet* columns = row_layout->AddColumnSet(0);
-    columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
-                       0, views::GridLayout::USE_PREF, 0, 0);
-    columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
-                       100, views::GridLayout::USE_PREF, 0, 0);
-    row_layout->StartRow(0, 0);
-
     views::View* label_container = new views::View();
     int indent =
         provider->GetDistanceMetric(DISTANCE_SUBSECTION_HORIZONTAL_INDENT);
@@ -236,22 +118,11 @@
         new views::Label(requests.at(index)->GetMessageTextFragment());
     label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     label_container->AddChildView(label);
-    row_layout->AddView(label_container);
+    AddChildView(label_container);
 
     // Only show the toggle if every request wants to show it.
     show_persistence_toggle = show_persistence_toggle &&
                               requests[index]->ShouldShowPersistenceToggle();
-    if (requests.size() > 1) {
-      PermissionCombobox* combobox = new PermissionCombobox(
-          owner->GetProfile(), this, index, requests[index]->GetOrigin(),
-          accept_state[index] ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK);
-      row_layout->AddView(combobox);
-      customize_comboboxes_.push_back(combobox);
-    } else {
-      row_layout->AddView(new views::View());
-    }
-
-    AddChildView(row);
   }
 
   if (show_persistence_toggle) {
@@ -316,10 +187,7 @@
 }
 
 int PermissionsBubbleDialogDelegateView::GetDialogButtons() const {
-  int buttons = ui::DIALOG_BUTTON_OK;
-  if (!multiple_requests_)
-    buttons |= ui::DIALOG_BUTTON_CANCEL;
-  return buttons;
+  return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
 }
 
 base::string16 PermissionsBubbleDialogDelegateView::GetDialogButtonLabel(
@@ -354,12 +222,6 @@
   return true;
 }
 
-void PermissionsBubbleDialogDelegateView::PermissionSelectionChanged(
-    int index,
-    bool allowed) {
-  owner_->ToggleAccept(index, allowed);
-}
-
 void PermissionsBubbleDialogDelegateView::UpdateAnchor(
     views::View* anchor_view,
     const gfx::Point& anchor_point,
@@ -403,8 +265,8 @@
   if (bubble_delegate_)
     bubble_delegate_->CloseBubble();
 
-  bubble_delegate_ = new PermissionsBubbleDialogDelegateView(
-      this, delegate_->Requests(), delegate_->AcceptStates());
+  bubble_delegate_ =
+      new PermissionsBubbleDialogDelegateView(this, delegate_->Requests());
 
   // Set |parent_window| because some valid anchors can become hidden.
   bubble_delegate_->set_parent_window(
@@ -483,7 +345,3 @@
   if (delegate_)
     delegate_->Deny();
 }
-
-Profile* PermissionPromptImpl::GetProfile() {
-  return browser_->profile();
-}
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
index a6c017a1..3c1140c7 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
@@ -20,7 +20,6 @@
 
 class Browser;
 class PermissionsBubbleDialogDelegateView;
-class Profile;
 
 class PermissionPromptImpl : public PermissionPrompt {
  public:
@@ -42,8 +41,6 @@
   void Accept();
   void Deny();
 
-  Profile* GetProfile();
-
  private:
   // These three functions have separate implementations for Views-based and
   // Cocoa-based browsers, to allow this bubble to be used in either.
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 66c7dc8..2222d7e79 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -214,6 +214,7 @@
     "//components/url_formatter",
     "//components/variations",
     "//components/version_info",
+    "//components/version_info:version_string",
     "//components/visitedlink/common",
     "//content/public/common",
     "//crypto",
diff --git a/chrome/common/channel_info.cc b/chrome/common/channel_info.cc
index 878267f..0596f54 100644
--- a/chrome/common/channel_info.cc
+++ b/chrome/common/channel_info.cc
@@ -6,6 +6,7 @@
 
 #include "base/profiler/scoped_tracker.h"
 #include "components/version_info/version_info.h"
+#include "components/version_info/version_string.h"
 
 namespace chrome {
 
diff --git a/chrome/common/channel_info_android.cc b/chrome/common/channel_info_android.cc
index 11d64ed..3791b6e 100644
--- a/chrome/common/channel_info_android.cc
+++ b/chrome/common/channel_info_android.cc
@@ -7,6 +7,7 @@
 #include "base/android/build_info.h"
 #include "base/logging.h"
 #include "base/strings/string_util.h"
+#include "components/version_info/channel_android.h"
 #include "components/version_info/version_info.h"
 
 namespace chrome {
@@ -26,18 +27,7 @@
 version_info::Channel GetChannel() {
   const base::android::BuildInfo* bi = base::android::BuildInfo::GetInstance();
   DCHECK(bi && bi->package_name());
-
-  if (!strcmp(bi->package_name(), "com.android.chrome") ||
-      !strcmp(bi->package_name(), "com.chrome.work"))
-    return version_info::Channel::STABLE;
-  if (!strcmp(bi->package_name(), "com.chrome.beta"))
-    return version_info::Channel::BETA;
-  if (!strcmp(bi->package_name(), "com.chrome.dev"))
-    return version_info::Channel::DEV;
-  if (!strcmp(bi->package_name(), "com.chrome.canary"))
-    return version_info::Channel::CANARY;
-
-  return version_info::Channel::UNKNOWN;
+  return version_info::ChannelFromPackageName(bi->package_name());
 }
 
 }  // namespace chrome
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index 8fa03bbd5..2ad7728c 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -872,6 +872,10 @@
   skip.insert(APIPermission::kVirtualKeyboard);
   skip.insert(APIPermission::kLauncherSearchProvider);
 
+  // The lock screen apps are set by user through settings, no need to warn at
+  // installation time.
+  skip.insert(APIPermission::kLockScreen);
+
   // We already have a generic message for declaring externally_connectable.
   skip.insert(APIPermission::kExternallyConnectableAllUrls);
 
diff --git a/chrome/renderer/chrome_content_renderer_client_browsertest.cc b/chrome/renderer/chrome_content_renderer_client_browsertest.cc
index db18349..0020011 100644
--- a/chrome/renderer/chrome_content_renderer_client_browsertest.cc
+++ b/chrome/renderer/chrome_content_renderer_client_browsertest.cc
@@ -18,8 +18,8 @@
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "content/public/child/url_loader_throttle.h"
 #include "content/public/common/content_constants.h"
+#include "content/public/common/url_loader_throttle.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_view.h"
 #include "content/public/test/browser_test_utils.h"
diff --git a/chrome/renderer/safe_browsing/safe_browsing_url_loader_throttle.h b/chrome/renderer/safe_browsing/safe_browsing_url_loader_throttle.h
index 01a599f..aea48f7 100644
--- a/chrome/renderer/safe_browsing/safe_browsing_url_loader_throttle.h
+++ b/chrome/renderer/safe_browsing/safe_browsing_url_loader_throttle.h
@@ -7,7 +7,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/common/safe_browsing.mojom.h"
-#include "content/public/child/url_loader_throttle.h"
+#include "content/public/common/url_loader_throttle.h"
 
 namespace chrome {
 namespace mojom {
diff --git a/chrome/test/data/webui/md_bookmarks/app_test.js b/chrome/test/data/webui/md_bookmarks/app_test.js
index 364c359..4ded343 100644
--- a/chrome/test/data/webui/md_bookmarks/app_test.js
+++ b/chrome/test/data/webui/md_bookmarks/app_test.js
@@ -9,7 +9,7 @@
   function resetStore() {
     store = new bookmarks.TestStore({});
     store.acceptInitOnce();
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     chrome.bookmarks.getTree = function(fn) {
       fn([
diff --git a/chrome/test/data/webui/md_bookmarks/command_manager_test.js b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
index a862fbd..ca849ce 100644
--- a/chrome/test/data/webui/md_bookmarks/command_manager_test.js
+++ b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
@@ -39,7 +39,7 @@
                 createFolder('21', []),
               ]))
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     commandManager = new TestCommandManager();
     replaceBody(commandManager);
@@ -207,7 +207,7 @@
       selectedFolder: '1',
     });
     store.setReducersEnabled(true);
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     commandManager = document.createElement('bookmarks-command-manager');
 
diff --git a/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js b/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js
index b0bb7ad..1cc323b4 100644
--- a/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js
+++ b/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js
@@ -89,7 +89,7 @@
           createFolder('2', [])),
       selectedFolder: '1',
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     chrome.bookmarkManagerPrivate.startDrag = function(nodes, isTouch) {
       draggedIds = nodes;
diff --git a/chrome/test/data/webui/md_bookmarks/folder_node_test.js b/chrome/test/data/webui/md_bookmarks/folder_node_test.js
index 57f1e89..7a0a5f98 100644
--- a/chrome/test/data/webui/md_bookmarks/folder_node_test.js
+++ b/chrome/test/data/webui/md_bookmarks/folder_node_test.js
@@ -27,7 +27,7 @@
           createFolder('7', [])),
       selectedFolder: '1',
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     rootNode = document.createElement('bookmarks-folder-node');
     rootNode.itemId = '0';
diff --git a/chrome/test/data/webui/md_bookmarks/item_test.js b/chrome/test/data/webui/md_bookmarks/item_test.js
index d3d8ee38..94436d1 100644
--- a/chrome/test/data/webui/md_bookmarks/item_test.js
+++ b/chrome/test/data/webui/md_bookmarks/item_test.js
@@ -16,7 +16,7 @@
             createItem('3'),
           ])),
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     item = document.createElement('bookmarks-item');
     item.itemId = '2';
diff --git a/chrome/test/data/webui/md_bookmarks/list_test.js b/chrome/test/data/webui/md_bookmarks/list_test.js
index 3c7efa8e..755e1b624 100644
--- a/chrome/test/data/webui/md_bookmarks/list_test.js
+++ b/chrome/test/data/webui/md_bookmarks/list_test.js
@@ -18,7 +18,7 @@
           ])),
       selectedFolder: '10',
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     list = document.createElement('bookmarks-list');
     list.style.height = '100%';
@@ -90,7 +90,7 @@
           ])),
       selectedFolder: '10',
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
     store.setReducersEnabled(true);
 
     list = document.createElement('bookmarks-list');
diff --git a/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js b/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js
index 27072b0..d7957ff7 100644
--- a/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js
+++ b/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js
@@ -60,7 +60,7 @@
         selectedFolder: '1',
       });
       store.setReducersEnabled(true);
-      bookmarks.Store.instance_ = store;
+      store.replaceSingleton();
 
       rootNode = document.createElement('bookmarks-folder-node');
       rootNode.itemId = '0';
@@ -205,7 +205,7 @@
         selectedFolder: '1',
       });
       store.setReducersEnabled(true);
-      bookmarks.Store.instance_ = store;
+      store.replaceSingleton();
 
       list = document.createElement('bookmarks-list');
       list.style.height = '100%';
diff --git a/chrome/test/data/webui/md_bookmarks/policy_test.js b/chrome/test/data/webui/md_bookmarks/policy_test.js
index f04fe3df3..3654007 100644
--- a/chrome/test/data/webui/md_bookmarks/policy_test.js
+++ b/chrome/test/data/webui/md_bookmarks/policy_test.js
@@ -17,7 +17,7 @@
     });
     store.setReducersEnabled(true);
     store.expectAction('set-incognito-availability');
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     app = document.createElement('bookmarks-app');
     replaceBody(app);
diff --git a/chrome/test/data/webui/md_bookmarks/router_test.js b/chrome/test/data/webui/md_bookmarks/router_test.js
index ecba392..f118c12 100644
--- a/chrome/test/data/webui/md_bookmarks/router_test.js
+++ b/chrome/test/data/webui/md_bookmarks/router_test.js
@@ -19,7 +19,7 @@
         term: '',
       },
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     router = document.createElement('bookmarks-router');
     replaceBody(router);
diff --git a/chrome/test/data/webui/md_bookmarks/test_store.js b/chrome/test/data/webui/md_bookmarks/test_store.js
index cdf2571c..57f5505 100644
--- a/chrome/test/data/webui/md_bookmarks/test_store.js
+++ b/chrome/test/data/webui/md_bookmarks/test_store.js
@@ -40,6 +40,11 @@
         this.data_ = newData;
       },
 
+      /** Replace the global store instance with this TestStore. */
+      replaceSingleton: function() {
+        bookmarks.Store.instance_ = this;
+      },
+
       /**
        * Enable or disable calling bookmarks.reduceAction for each action.
        * With reducers disabled (the default), TestStore is a stub which
diff --git a/chrome/test/data/webui/md_bookmarks/toolbar_test.js b/chrome/test/data/webui/md_bookmarks/toolbar_test.js
index 8e5d470d..9144e55 100644
--- a/chrome/test/data/webui/md_bookmarks/toolbar_test.js
+++ b/chrome/test/data/webui/md_bookmarks/toolbar_test.js
@@ -22,7 +22,7 @@
         anchor: null,
       },
     });
-    bookmarks.Store.instance_ = store;
+    store.replaceSingleton();
 
     toolbar = document.createElement('bookmarks-toolbar');
     replaceBody(toolbar);
diff --git a/chromecast/android/BUILD.gn b/chromecast/android/BUILD.gn
index 6a591b7..1d29974 100644
--- a/chromecast/android/BUILD.gn
+++ b/chromecast/android/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//chromecast/chromecast.gni")
+import("//media/media_options.gni")
 
 # These targets shall only be referenced on Android builds.
 assert(is_android)
@@ -27,6 +28,7 @@
     ":platform_jni_loader",
     "//base",
     "//chromecast:cast_shell_lib",
+    "//chromecast:chromecast_features",
     "//chromecast/app",
     "//chromecast/app:cast_crash_client",
     "//chromecast/base",
@@ -42,4 +44,8 @@
   if (chromecast_branding == "public") {
     sources += [ "platform_jni_loader_stub.cc" ]
   }
+
+  if (is_cast_using_cma_backend) {
+    deps += [ "//chromecast/media/cma/backend/android:cast_media_android" ]
+  }
 }
diff --git a/chromecast/android/DEPS b/chromecast/android/DEPS
index c80012b5..8745a344 100644
--- a/chromecast/android/DEPS
+++ b/chromecast/android/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
+  "+chromecast/media/cma/backend",
   "+jni",
 ]
diff --git a/chromecast/android/cast_jni_registrar.cc b/chromecast/android/cast_jni_registrar.cc
index a2a0fd77..0004e6ab 100644
--- a/chromecast/android/cast_jni_registrar.cc
+++ b/chromecast/android/cast_jni_registrar.cc
@@ -9,6 +9,11 @@
 #include "base/macros.h"
 #include "chromecast/base/android/system_time_change_notifier_android.h"
 #include "chromecast/base/chromecast_config_android.h"
+#include "chromecast/chromecast_features.h"
+
+#if BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#include "chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h"
+#endif
 
 namespace chromecast {
 namespace android {
@@ -19,6 +24,10 @@
     {"ChromecastConfigAndroid", ChromecastConfigAndroid::RegisterJni},
     {"SystemTimeChangeNotifierAndroid",
      SystemTimeChangeNotifierAndroid::RegisterJni},
+#if BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+    {"AudioSinkAudioTrackImpl",
+     media::AudioSinkAndroidAudioTrackImpl::RegisterJni},
+#endif
 };
 
 }  // namespace
diff --git a/chromecast/browser/android/BUILD.gn b/chromecast/browser/android/BUILD.gn
index 54d84bf..48571fb 100644
--- a/chromecast/browser/android/BUILD.gn
+++ b/chromecast/browser/android/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//chromecast/chromecast.gni")
 import("//build/config/android/rules.gni")
+import("//media/media_options.gni")
 
 assert(is_android)
 
@@ -68,6 +69,10 @@
     "//third_party/android_tools:android_support_core_utils_java",
     "//ui/android:ui_java",
   ]
+
+  if (is_cast_using_cma_backend) {
+    deps += [ "//chromecast/media/cma/backend/android:audio_track_java" ]
+  }
 }
 
 junit_binary("cast_shell_junit_tests") {
diff --git a/chromecast/media/cma/backend/android/BUILD.gn b/chromecast/media/cma/backend/android/BUILD.gn
index 8f1ae62..c1b49df5 100644
--- a/chromecast/media/cma/backend/android/BUILD.gn
+++ b/chromecast/media/cma/backend/android/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/android/config.gni")
+import("//build/config/android/rules.gni")
 
 source_set("cast_media_android") {
   sources = [
@@ -10,6 +11,8 @@
     "audio_decoder_android.h",
     "audio_sink_android.cc",
     "audio_sink_android.h",
+    "audio_sink_android_audiotrack_impl.cc",
+    "audio_sink_android_audiotrack_impl.h",
     "audio_sink_android_dummy_impl.cc",
     "audio_sink_android_dummy_impl.h",
     "cast_media_android.cc",
@@ -19,6 +22,8 @@
   ]
 
   deps = [
+    ":audio_track_java",
+    ":audio_track_jni_headers",
     "//base",
     "//chromecast/base",
     "//chromecast/media/cma/backend:null",
@@ -28,3 +33,21 @@
     "//media",
   ]
 }
+
+generate_jni("audio_track_jni_headers") {
+  sources = [
+    "java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java",
+  ]
+
+  jni_package = "audio_track"
+}
+
+android_library("audio_track_java") {
+  java_files = [ "java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java" ]
+
+  deps = [
+    "//base:base_java",
+    "//chromecast/base:base_java",
+    "//third_party/android_tools:android_support_v13_java",
+  ]
+}
diff --git a/chromecast/media/cma/backend/android/DEPS b/chromecast/media/cma/backend/android/DEPS
index d17e221d..407d13c 100644
--- a/chromecast/media/cma/backend/android/DEPS
+++ b/chromecast/media/cma/backend/android/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
+  "+jni",
   "+media/filters",
 ]
diff --git a/chromecast/media/cma/backend/android/audio_sink_android.cc b/chromecast/media/cma/backend/android/audio_sink_android.cc
index 9599101..8bff62a7 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android.cc
+++ b/chromecast/media/cma/backend/android/audio_sink_android.cc
@@ -7,7 +7,8 @@
 #include <string>
 #include <utility>
 
-#include "chromecast/media/cma/backend/android/audio_sink_android_dummy_impl.h"
+#include "chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
 
 namespace chromecast {
 namespace media {
@@ -17,8 +18,8 @@
                                    bool primary,
                                    const std::string& device_id,
                                    AudioContentType content_type) {
-  impl_.reset(new AudioSinkAndroidDummyImpl(delegate, samples_per_second,
-                                            primary, device_id, content_type));
+  impl_.reset(new AudioSinkAndroidAudioTrackImpl(
+      delegate, samples_per_second, primary, device_id, content_type));
 }
 
 AudioSinkAndroid::~AudioSinkAndroid() {
diff --git a/chromecast/media/cma/backend/android/audio_sink_android.h b/chromecast/media/cma/backend/android/audio_sink_android.h
index 60b008e..97c3a3f 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android.h
+++ b/chromecast/media/cma/backend/android/audio_sink_android.h
@@ -19,7 +19,7 @@
 namespace media {
 
 class DecoderBufferBase;
-class AudioSinkAndroidDummyImpl;
+class AudioSinkAndroidAudioTrackImpl;
 
 // Input handle to the sink. All methods (including constructor and destructor)
 // must be called on the same thread.
@@ -71,7 +71,7 @@
   void SetVolumeMultiplier(float multiplier);
 
  private:
-  std::unique_ptr<AudioSinkAndroidDummyImpl> impl_;
+  std::unique_ptr<AudioSinkAndroidAudioTrackImpl> impl_;
   base::ThreadChecker thread_checker_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioSinkAndroid);
diff --git a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
new file mode 100644
index 0000000..aed4ab2e
--- /dev/null
+++ b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
@@ -0,0 +1,326 @@
+// 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.
+
+#include "chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "jni/AudioSinkAudioTrackImpl_jni.h"
+#include "media/base/audio_bus.h"
+
+#define RUN_ON_FEEDER_THREAD(callback, ...)                               \
+  if (!feeder_task_runner_->BelongsToCurrentThread()) {                   \
+    POST_TASK_TO_FEEDER_THREAD(&AudioSinkAndroidAudioTrackImpl::callback, \
+                               ##__VA_ARGS__);                            \
+    return;                                                               \
+  }
+
+#define POST_TASK_TO_FEEDER_THREAD(task, ...) \
+  feeder_task_runner_->PostTask(              \
+      FROM_HERE, base::BindOnce(task, base::Unretained(this), ##__VA_ARGS__));
+
+#define RUN_ON_CALLER_THREAD(callback, ...)                               \
+  if (!caller_task_runner_->BelongsToCurrentThread()) {                   \
+    POST_TASK_TO_CALLER_THREAD(&AudioSinkAndroidAudioTrackImpl::callback, \
+                               ##__VA_ARGS__);                            \
+    return;                                                               \
+  }
+
+#define POST_TASK_TO_CALLER_THREAD(task, ...) \
+  caller_task_runner_->PostTask(              \
+      FROM_HERE, base::BindOnce(task, weak_this_, ##__VA_ARGS__));
+
+using base::android::JavaParamRef;
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+const char* GetContentTypeName(const AudioContentType type) {
+  switch (type) {
+    case AudioContentType::kMedia:
+      return "kMedia";
+    case AudioContentType::kAlarm:
+      return "kAlarm";
+    case AudioContentType::kCommunication:
+      return "kCommunication";
+    default:
+      return "Unknown";
+  }
+}
+
+}  // namespace
+
+AudioSinkAndroidAudioTrackImpl::AudioSinkAndroidAudioTrackImpl(
+    AudioSinkAndroid::Delegate* delegate,
+    int input_samples_per_second,
+    bool primary,
+    const std::string& device_id,
+    AudioContentType content_type)
+    : delegate_(delegate),
+      input_samples_per_second_(input_samples_per_second),
+      primary_(primary),
+      device_id_(device_id),
+      content_type_(content_type),
+      feeder_thread_("AudioTrack feeder thread"),
+      feeder_task_runner_(nullptr),
+      caller_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      direct_pcm_buffer_address_(nullptr),
+      direct_rendering_delay_address_(nullptr),
+      state_(kStateUninitialized),
+      stream_volume_multiplier_(1.0f),
+      weak_factory_(this) {
+  LOG(INFO) << __func__ << "(" << this << "):"
+            << " input_samples_per_second_=" << input_samples_per_second_
+            << " primary_=" << primary_ << " device_id_=" << device_id_
+            << " content_type__=" << GetContentTypeName(content_type_);
+  DCHECK(delegate_);
+
+  // Create Java part and initialize.
+  DCHECK(j_audio_sink_audiotrack_impl_.is_null());
+  j_audio_sink_audiotrack_impl_.Reset(
+      Java_AudioSinkAudioTrackImpl_createAudioSinkAudioTrackImpl(
+          base::android::AttachCurrentThread(),
+          reinterpret_cast<intptr_t>(this)));
+  Java_AudioSinkAudioTrackImpl_init(
+      base::android::AttachCurrentThread(), j_audio_sink_audiotrack_impl_,
+      input_samples_per_second_, kDirectBufferSize);
+  base::Thread::Options options;
+  options.priority = base::ThreadPriority::REALTIME_AUDIO;
+  feeder_thread_.StartWithOptions(options);
+  feeder_task_runner_ = feeder_thread_.task_runner();
+  weak_this_ = weak_factory_.GetWeakPtr();
+}
+
+AudioSinkAndroidAudioTrackImpl::~AudioSinkAndroidAudioTrackImpl() {
+  LOG(INFO) << __func__ << "(" << this << "): device_id_=" << device_id_;
+  FinalizeOnFeederThread();
+  feeder_thread_.Stop();
+  feeder_task_runner_ = nullptr;
+}
+
+void AudioSinkAndroidAudioTrackImpl::FinalizeOnFeederThread() {
+  RUN_ON_FEEDER_THREAD(FinalizeOnFeederThread);
+  LOG(INFO) << __func__ << "(" << this << "):";
+  if (j_audio_sink_audiotrack_impl_.is_null()) {
+    LOG(WARNING) << "j_audio_sink_audiotrack_impl_ is NULL";
+    return;
+  }
+  Java_AudioSinkAudioTrackImpl_close(base::android::AttachCurrentThread(),
+                                     j_audio_sink_audiotrack_impl_);
+  j_audio_sink_audiotrack_impl_.Reset();
+}
+
+void AudioSinkAndroidAudioTrackImpl::PreventDelegateCalls() {
+  DCHECK(caller_task_runner_->BelongsToCurrentThread());
+  weak_factory_.InvalidateWeakPtrs();
+}
+
+// static
+bool AudioSinkAndroidAudioTrackImpl::RegisterJni(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+void AudioSinkAndroidAudioTrackImpl::CacheDirectBufferAddress(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jobject>& pcm_byte_buffer,
+    const JavaParamRef<jobject>& timestamp_byte_buffer) {
+  LOG(INFO) << __func__ << "(" << this << "):";
+  direct_pcm_buffer_address_ =
+      static_cast<uint8_t*>(env->GetDirectBufferAddress(pcm_byte_buffer));
+  direct_rendering_delay_address_ = static_cast<uint64_t*>(
+      env->GetDirectBufferAddress(timestamp_byte_buffer));
+}
+
+void AudioSinkAndroidAudioTrackImpl::WritePcm(
+    scoped_refptr<DecoderBufferBase> data) {
+  DCHECK(caller_task_runner_->BelongsToCurrentThread());
+  DCHECK(!pending_data_);
+  pending_data_ = std::move(data);
+  pending_data_bytes_already_fed_ = 0;
+  FeedData();
+}
+
+void AudioSinkAndroidAudioTrackImpl::FeedData() {
+  RUN_ON_FEEDER_THREAD(FeedData);
+
+  DCHECK(pending_data_);
+  if (pending_data_->end_of_stream()) {
+    LOG(INFO) << __func__ << "(" << this << "): EOS!";
+    state_ = kStateGotEos;
+    PostPcmCallback(sink_rendering_delay_);
+    return;
+  }
+
+  VLOG(3) << __func__ << "(" << this << "):"
+          << " [" << pending_data_->data_size() << "]"
+          << " @ts=" << pending_data_->timestamp();
+
+  if (pending_data_->data_size() == 0) {
+    LOG(INFO) << __func__ << "(" << this << "): empty data buffer!";
+    PostPcmCallback(sink_rendering_delay_);
+    return;
+  }
+
+  ReformatData();
+
+  int written = Java_AudioSinkAudioTrackImpl_writePcm(
+      base::android::AttachCurrentThread(), j_audio_sink_audiotrack_impl_,
+      pending_data_->data_size());
+
+  if (written < 0) {
+    LOG(ERROR) << __func__ << "(" << this << "): Cannot write PCM via JNI!";
+    SignalError(AudioSinkAndroid::SinkError::kInternalError);
+    return;
+  }
+
+  if (state_ == kStatePaused &&
+      written < static_cast<int>(pending_data_->data_size())) {
+    LOG(INFO) << "Audio Server is full while in PAUSED, "
+              << "will continue when entering PLAY mode.";
+    pending_data_bytes_already_fed_ = written;
+    return;
+  }
+
+  if (written != static_cast<int>(pending_data_->data_size())) {
+    LOG(ERROR) << __func__ << "(" << this << "): Wrote " << written
+               << " instead of " << pending_data_->data_size();
+    // continue anyway, better to do a best-effort than fail completely
+  }
+
+  // RenderingDelay was returned through JNI via direct buffers.
+  sink_rendering_delay_.delay_microseconds = direct_rendering_delay_address_[0];
+  sink_rendering_delay_.timestamp_microseconds =
+      direct_rendering_delay_address_[1];
+
+  TrackRawMonotonicClockDeviation();
+
+  PostPcmCallback(sink_rendering_delay_);
+}
+
+void AudioSinkAndroidAudioTrackImpl::ReformatData() {
+  // Data is in planar float format, i.e. all left samples first, then all
+  // right -> "LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR").
+  // AudioTrack needs interleaved format -> "LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR").
+  DCHECK(direct_pcm_buffer_address_);
+  DCHECK_EQ(0, static_cast<int>(pending_data_->data_size() % sizeof(float)));
+  CHECK(pending_data_->data_size() < kDirectBufferSize);
+  int num_of_samples = pending_data_->data_size() / sizeof(float);
+  int num_of_frames = num_of_samples / 2;
+  const float* src_left = reinterpret_cast<const float*>(pending_data_->data());
+  const float* src_right = src_left + num_of_samples / 2;
+  float* dst = reinterpret_cast<float*>(direct_pcm_buffer_address_);
+  for (int f = 0; f < num_of_frames; f++) {
+    *dst++ = *src_left++;
+    *dst++ = *src_right++;
+  }
+}
+
+void AudioSinkAndroidAudioTrackImpl::TrackRawMonotonicClockDeviation() {
+  timespec now = {0, 0};
+  clock_gettime(CLOCK_MONOTONIC, &now);
+  int64_t now_usec =
+      static_cast<int64_t>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
+
+  clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+  int64_t now_raw_usec =
+      static_cast<int64_t>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
+
+  // TODO(ckuiper): Eventually we want to use this to convert from non-RAW to
+  // RAW timestamps to improve accuracy.
+  VLOG(3) << __func__ << "(" << this << "):"
+          << " now - now_raw=" << (now_usec - now_raw_usec);
+}
+
+void AudioSinkAndroidAudioTrackImpl::FeedDataContinue() {
+  RUN_ON_FEEDER_THREAD(FeedData);
+
+  DCHECK(pending_data_);
+  DCHECK(pending_data_bytes_already_fed_);
+
+  int left_to_send =
+      pending_data_->data_size() - pending_data_bytes_already_fed_;
+  LOG(INFO) << __func__ << "(" << this << "): send remaining " << left_to_send
+            << "/" << pending_data_->data_size();
+
+  memmove(direct_pcm_buffer_address_,
+          direct_pcm_buffer_address_ + pending_data_bytes_already_fed_,
+          left_to_send);
+
+  int written = Java_AudioSinkAudioTrackImpl_writePcm(
+      base::android::AttachCurrentThread(), j_audio_sink_audiotrack_impl_,
+      left_to_send);
+
+  DCHECK(written == left_to_send);
+
+  PostPcmCallback(sink_rendering_delay_);
+}
+
+void AudioSinkAndroidAudioTrackImpl::PostPcmCallback(
+    const MediaPipelineBackendAndroid::RenderingDelay& delay) {
+  RUN_ON_CALLER_THREAD(PostPcmCallback, delay);
+  DCHECK(pending_data_);
+  VLOG(3) << __func__ << "(" << this << "): "
+          << " delay=" << delay.delay_microseconds
+          << " ts=" << delay.timestamp_microseconds;
+  pending_data_ = nullptr;
+  pending_data_bytes_already_fed_ = 0;
+  delegate_->OnWritePcmCompletion(MediaPipelineBackendAndroid::kBufferSuccess,
+                                  delay);
+}
+
+void AudioSinkAndroidAudioTrackImpl::SignalError(
+    AudioSinkAndroid::SinkError error) {
+  DCHECK(feeder_task_runner_->BelongsToCurrentThread());
+  state_ = kStateError;
+  PostError(error);
+}
+
+void AudioSinkAndroidAudioTrackImpl::PostError(
+    AudioSinkAndroid::SinkError error) {
+  RUN_ON_CALLER_THREAD(PostError, error);
+  delegate_->OnSinkError(error);
+}
+
+void AudioSinkAndroidAudioTrackImpl::SetPaused(bool paused) {
+  RUN_ON_FEEDER_THREAD(SetPaused, paused);
+
+  if (paused) {
+    LOG(INFO) << __func__ << "(" << this << "): Pausing";
+    state_ = kStatePaused;
+    Java_AudioSinkAudioTrackImpl_pause(base::android::AttachCurrentThread(),
+                                       j_audio_sink_audiotrack_impl_);
+  } else {
+    LOG(INFO) << __func__ << "(" << this << "): Unpausing";
+    state_ = kStateNormalPlayback;
+    Java_AudioSinkAudioTrackImpl_play(base::android::AttachCurrentThread(),
+                                      j_audio_sink_audiotrack_impl_);
+    if (pending_data_bytes_already_fed_) {
+      // The last data buffer was partially fed, complete it now.
+      FeedDataContinue();
+    }
+  }
+}
+
+void AudioSinkAndroidAudioTrackImpl::SetVolumeMultiplier(float multiplier) {
+  stream_volume_multiplier_ = std::max(0.0f, std::min(multiplier, 1.0f));
+  LOG(INFO) << __func__ << "(" << this << "): "
+            << " multiplier=" << multiplier << " device_id_=" << device_id_
+            << "(" << this << "): "
+            << "stream_volume_multiplier_= " << stream_volume_multiplier_;
+  // TODO(ckuiper): implement as part of volume control
+}
+
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
new file mode 100644
index 0000000..63e14e02
--- /dev/null
+++ b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
@@ -0,0 +1,145 @@
+// 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.
+
+#ifndef CHROMECAST_MEDIA_CMA_BACKEND_ANDROID_AUDIO_SINK_ANDROID_AUDIOTRACK_IMPL_H_
+#define CHROMECAST_MEDIA_CMA_BACKEND_ANDROID_AUDIO_SINK_ANDROID_AUDIOTRACK_IMPL_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "chromecast/media/cma/backend/android/audio_sink_android.h"
+#include "chromecast/media/cma/backend/android/media_pipeline_backend_android.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}  // namespace base
+
+namespace chromecast {
+namespace media {
+
+class AudioSinkAndroidAudioTrackImpl {
+ public:
+  enum State {
+    kStateUninitialized,   // No data has been queued yet.
+    kStateNormalPlayback,  // Normal playback.
+    kStatePaused,          // Currently paused.
+    kStateGotEos,          // Got the end-of-stream buffer (normal playback).
+    kStateError,           // A sink error occurred, this is unusable now.
+  };
+
+  // TODO(ckuiper): There doesn't seem to be a maximum size for the buffers
+  // sent through the media pipeline, so we need to add code to break up a
+  // buffer larger than this size and feed it in in smaller chunks.
+  static const int kDirectBufferSize = 512 * 1024;
+
+  AudioSinkAndroidAudioTrackImpl(AudioSinkAndroid::Delegate* delegate,
+                                 int input_samples_per_second,
+                                 bool primary,
+                                 const std::string& device_id,
+                                 AudioContentType content_type);
+
+  ~AudioSinkAndroidAudioTrackImpl();
+
+  static bool RegisterJni(JNIEnv* env);
+
+  // Called from Java so that we can cache the addresses of the Java-managed
+  // byte_buffers.
+  void CacheDirectBufferAddress(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      const base::android::JavaParamRef<jobject>& pcm_byte_buffer,
+      const base::android::JavaParamRef<jobject>& timestamp_byte_buffer);
+
+  // Feeds data through JNI into the AudioTrack. The data must be in planar
+  // float format.
+  void WritePcm(scoped_refptr<DecoderBufferBase> data);
+
+  // Sets the pause state of this stream.
+  void SetPaused(bool paused);
+
+  // Sets the volume multiplier for this stream. If |multiplier| < 0, sets the
+  // volume multiplier to 0. If |multiplier| > 1, sets the volume multiplier
+  // to 1.
+  void SetVolumeMultiplier(float multiplier);
+
+  // Prevents any further calls to the delegate (ie, called when the delegate
+  // is being destroyed).
+  void PreventDelegateCalls();
+
+  State state() const { return state_; }
+
+ private:
+  void FinalizeOnFeederThread();
+
+  void FeedData();
+  void FeedDataContinue();
+
+  // Reformats audio data from planar float into interleaved float for
+  // AudioTrack. I.e.:
+  // "LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR" -> "LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR".
+  void ReformatData();
+
+  void TrackRawMonotonicClockDeviation();
+
+  void PostPcmCallback(
+      const MediaPipelineBackendAndroid::RenderingDelay& delay);
+
+  void SignalError(AudioSinkAndroid::SinkError error);
+  void PostError(AudioSinkAndroid::SinkError error);
+
+  AudioSinkAndroid::Delegate* const delegate_;
+  const int input_samples_per_second_;
+  const bool primary_;
+  const std::string device_id_;
+  const AudioContentType content_type_;
+
+  // Java AudioSinkAudioTrackImpl instance.
+  base::android::ScopedJavaGlobalRef<jobject> j_audio_sink_audiotrack_impl_;
+
+  // Thread that feeds audio data into the Java instance though JNI,
+  // potentially blocking. When in Play mode the Java AudioTrack blocks as it
+  // waits for queue space to become available for the new data. In Pause mode
+  // it returns immediately once all queue space has been filled up. This case
+  // is handled separately via FeedDataContinue().
+  base::Thread feeder_thread_;
+  scoped_refptr<base::SingleThreadTaskRunner> feeder_task_runner_;
+
+  const scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
+
+  // Buffers shared between native and Java space to move data across the JNI.
+  // We use direct buffers so that the native class can have access to the
+  // underlying memory address. This avoids the need to copy from a jbyteArray
+  // to native memory. More discussion of this here:
+  // http://developer.android.com/training/articles/perf-jni.html Owned by
+  // j_audio_sink_audiotrack_impl_.
+  uint8_t* direct_pcm_buffer_address_;  // PCM audio data native->java
+  // rendering delay+timestamp return value, java->native
+  uint64_t* direct_rendering_delay_address_;
+
+  State state_;
+
+  float stream_volume_multiplier_;
+
+  scoped_refptr<DecoderBufferBase> pending_data_;
+  int pending_data_bytes_already_fed_;
+
+  MediaPipelineBackendAndroid::RenderingDelay sink_rendering_delay_;
+
+  base::WeakPtr<AudioSinkAndroidAudioTrackImpl> weak_this_;
+  base::WeakPtrFactory<AudioSinkAndroidAudioTrackImpl> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioSinkAndroidAudioTrackImpl);
+};
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_CMA_BACKEND_ANDROID_AUDIO_SINK_ANDROID_AUDIOTRACK_IMPL_H_
diff --git a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
new file mode 100644
index 0000000..79e38e9
--- /dev/null
+++ b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
@@ -0,0 +1,350 @@
+// 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.
+
+package org.chromium.chromecast.cma.backend.android;
+
+import android.annotation.TargetApi;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.os.Build;
+import android.os.SystemClock;
+
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Implements an audio sink object using Android's AudioTrack module to
+ * playback audio samples.
+ * It assumes the following fixed configuration parameters:
+ *   - 2-channel audio
+ *   - PCM audio format (i.e., no encoded data like mp3)
+ *   - samples are 4-byte floats, interleaved channels ("LRLRLRLRLR").
+ * The only configurable audio parameter is the sample rate (typically 44.1 or
+ * 48 KHz).
+ *
+ * PCM data is shared through the JNI using memory-mapped ByteBuffer objects.
+ * The AudioTrack.write() function is called in BLOCKING mode. That means when
+ * in PLAYING state the call will block until all data has been accepted
+ * (queued) by the Audio server. The native side feeding data in through the
+ * JNI is assumed to be running in a dedicated thread to avoid hanging other
+ * parts of the application.
+ *
+ * No locking of instance data is done as it is assumed to be called from a
+ * single thread in native code.
+ *
+ */
+@JNINamespace("chromecast::media")
+@TargetApi(Build.VERSION_CODES.N)
+class AudioSinkAudioTrackImpl {
+    private static final String TAG = "AudiotrackImpl";
+    private static final int DEBUG_LEVEL = 1;
+
+    // hardcoded AudioTrack config parameters
+    private static final int STREAM_TYPE = AudioManager.STREAM_MUSIC;
+    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_STEREO;
+    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
+    private static final int AUDIO_MODE = AudioTrack.MODE_STREAM;
+    private static final int BYTES_PER_FRAME = 2 * 4; // 2 channels, float (4-bytes)
+
+    private static final long NO_TIMESTAMP = Long.MIN_VALUE;
+
+    private static final long SEC_IN_NSEC = 1000000000L;
+    private static final long TIMESTAMP_UPDATE_PERIOD = 3 * SEC_IN_NSEC;
+
+    private final long mNativeAudioSinkAudioTrackImpl;
+
+    private boolean mIsInitialized;
+
+    // Dynamic AudioTrack config parameter.
+    private int mSampleRateInHz;
+
+    private AudioTrack mAudioTrack;
+
+    // Timestamping logic for RenderingDelay calculations.
+    private AudioTimestamp mLastPlayoutTStamp;
+    private long mLastTimestampUpdateNsec; // Last time we updated the timestamp.
+    private boolean mTriggerTimestampUpdateNow; // Set to true to trigger an early update.
+
+    private int mLastUnderrunCount;
+
+    // Statistics
+    private long mTotalFramesWritten;
+
+    // Sample Rate calculator
+    private long mSRWindowStartTimeNsec;
+    private long mSRWindowFramesWritten;
+
+    // Buffers shared between native and java space to move data across the JNI.
+    // We use a direct buffers so that the native class can have access to
+    // the underlying memory address. This avoids the need to copy from a
+    // jbyteArray to native memory. More discussion of this here:
+    // http://developer.android.com/training/articles/perf-jni.html
+    private ByteBuffer mPcmBuffer; // PCM audio data (native->java)
+    private ByteBuffer mRenderingDelayBuffer; // RenderingDelay return value
+                                              // (java->native)
+
+    /** Construction */
+    @CalledByNative
+    private static AudioSinkAudioTrackImpl createAudioSinkAudioTrackImpl(
+            long nativeAudioSinkAudioTrackImpl) {
+        Log.i(TAG, "Creating new AudioSinkAudioTrackImpl instance");
+        return new AudioSinkAudioTrackImpl(nativeAudioSinkAudioTrackImpl);
+    }
+
+    private AudioSinkAudioTrackImpl(long nativeAudioSinkAudioTrackImpl) {
+        Log.i(TAG, "Ctor called...");
+        mNativeAudioSinkAudioTrackImpl = nativeAudioSinkAudioTrackImpl;
+        mLastTimestampUpdateNsec = NO_TIMESTAMP;
+        mTriggerTimestampUpdateNow = false;
+        mLastUnderrunCount = 0;
+        mTotalFramesWritten = 0;
+    }
+
+    /**
+     * Initializes the instance by creating the AudioTrack object and allocating
+     * the shared memory buffers.
+     */
+    @CalledByNative
+    private void init(int sampleRateInHz, int bytesPerBuffer) {
+        Log.i(TAG,
+                "Init:"
+                        + " sampleRateInHz=" + sampleRateInHz
+                        + " API-version=" + android.os.Build.VERSION.SDK_INT);
+
+        if (mIsInitialized) {
+            Log.w(TAG, "Init: already initialized.");
+            return;
+        }
+
+        if (sampleRateInHz <= 0) {
+            Log.e(TAG, "Invalid sampleRateInHz=" + sampleRateInHz + " given!");
+            return;
+        }
+        mSampleRateInHz = sampleRateInHz;
+
+        // TODO(ckuiper): ALSA code uses a 90ms buffer size, we should do something
+        // similar.
+        int bufferSizeInBytes =
+                5 * AudioTrack.getMinBufferSize(mSampleRateInHz, CHANNEL_CONFIG, AUDIO_FORMAT);
+        Log.i(TAG, "Init: using an AudioTrack buffer_size=" + bufferSizeInBytes);
+
+        mAudioTrack = new AudioTrack(STREAM_TYPE, mSampleRateInHz, CHANNEL_CONFIG, AUDIO_FORMAT,
+                bufferSizeInBytes, AUDIO_MODE);
+        mLastPlayoutTStamp = new AudioTimestamp();
+
+        // Allocated shared buffers.
+        mPcmBuffer = ByteBuffer.allocateDirect(bytesPerBuffer);
+        mPcmBuffer.order(ByteOrder.nativeOrder());
+
+        mRenderingDelayBuffer = ByteBuffer.allocateDirect(2 * 8); // 2 long
+        mRenderingDelayBuffer.order(ByteOrder.nativeOrder());
+
+        nativeCacheDirectBufferAddress(
+                mNativeAudioSinkAudioTrackImpl, mPcmBuffer, mRenderingDelayBuffer);
+
+        // Put into PLAYING state so it starts playing right when data is fed in.
+        play();
+
+        mIsInitialized = true;
+    }
+
+    @CalledByNative
+    private void play() {
+        Log.i(TAG, "Start playback");
+        mSRWindowFramesWritten = 0;
+        mAudioTrack.play();
+        mTriggerTimestampUpdateNow = true; // Get a fresh timestamp asap.
+    }
+
+    @CalledByNative
+    private void pause() {
+        Log.i(TAG, "Pausing playback");
+        mAudioTrack.pause();
+    }
+
+    private boolean isPlaying() {
+        return mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING;
+    }
+
+    private boolean isPaused() {
+        return mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED;
+    }
+
+    @CalledByNative
+    /** Closes the instance by stopping playback and releasing the AudioTrack
+     * object. */
+    private void close() {
+        Log.i(TAG, "Close AudioSinkAudioTrackImpl!");
+        if (!mIsInitialized) {
+            Log.w(TAG, "Close: not initialized.");
+            return;
+        }
+        mAudioTrack.stop();
+        mAudioTrack.release();
+        mIsInitialized = false;
+    }
+
+    private String getPlayStateString() {
+        switch (mAudioTrack.getPlayState()) {
+            case AudioTrack.PLAYSTATE_PAUSED:
+                return "PAUSED";
+            case AudioTrack.PLAYSTATE_STOPPED:
+                return "STOPPED";
+            case AudioTrack.PLAYSTATE_PLAYING:
+                return "PLAYING";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    int getUnderrunCount() {
+        return mAudioTrack.getUnderrunCount();
+    }
+
+    /** Writes the PCM data of the given size into the AudioTrack object. The
+     * PCM data is provided through the memory-mapped ByteBuffer.
+     *
+     * Returns the number of bytes written into the AudioTrack object, -1 for
+     * error.
+     */
+    @CalledByNative
+    private int writePcm(int sizeInBytes) {
+        if (DEBUG_LEVEL >= 3) {
+            Log.i(TAG,
+                    "Writing new PCM data:"
+                            + " sizeInBytes=" + sizeInBytes + " state=" + getPlayStateString()
+                            + " underruns=" + mLastUnderrunCount);
+        }
+
+        if (!mIsInitialized) {
+            Log.e(TAG, "not initialized!");
+            return -1;
+        }
+
+        // Setup the PCM ByteBuffer correctly.
+        mPcmBuffer.limit(sizeInBytes);
+        mPcmBuffer.position(0);
+
+        // Feed into AudioTrack - blocking call.
+        long beforeMsecs = SystemClock.elapsedRealtime();
+        int bytesWritten = mAudioTrack.write(mPcmBuffer, sizeInBytes, AudioTrack.WRITE_BLOCKING);
+
+        int framesWritten = bytesWritten / BYTES_PER_FRAME;
+        mTotalFramesWritten += framesWritten;
+
+        if (DEBUG_LEVEL >= 3) {
+            Log.i(TAG,
+                    "  wrote " + bytesWritten + "/" + sizeInBytes
+                            + " total_bytes_written=" + (mTotalFramesWritten * BYTES_PER_FRAME)
+                            + " took:" + (SystemClock.elapsedRealtime() - beforeMsecs) + "ms");
+        }
+
+        if (bytesWritten <= 0 || isPaused()) {
+            // Either hit an error or we are in PAUSED state, in which case the
+            // write() is non-blocking. If not all data was written, we will come
+            // back here once we transition back into PLAYING state.
+            return bytesWritten;
+        }
+
+        updateSampleRateMeasure(framesWritten);
+
+        updateRenderingDelay();
+
+        // TODO(ckuiper): Log key statistics (SR and underruns, e.g.) in regular intervals
+
+        return bytesWritten;
+    }
+
+    /** Returns the elapsed time from the given start_time until now, in nsec. */
+    private long elapsedNsec(long startTimeNsec) {
+        return System.nanoTime() - startTimeNsec;
+    }
+
+    private void updateSampleRateMeasure(long framesWritten) {
+        if (mSRWindowFramesWritten == 0) {
+            // Start new window.
+            mSRWindowStartTimeNsec = System.nanoTime();
+            mSRWindowFramesWritten = framesWritten;
+            return;
+        }
+        mSRWindowFramesWritten += framesWritten;
+        long periodNsec = elapsedNsec(mSRWindowStartTimeNsec);
+        float sampleRate = 1e9f * mSRWindowFramesWritten / periodNsec;
+        if (DEBUG_LEVEL >= 3) {
+            Log.i(TAG,
+                    "SR=" + mSRWindowFramesWritten + "/" + (periodNsec / 1000)
+                            + "us = " + sampleRate);
+        }
+    }
+
+    private void updateRenderingDelay() {
+        updateTimestamp();
+        if (mLastTimestampUpdateNsec == NO_TIMESTAMP) {
+            // No timestamp available yet, just put dummy values and return.
+            mRenderingDelayBuffer.putLong(0, 0);
+            mRenderingDelayBuffer.putLong(8, NO_TIMESTAMP);
+            return;
+        }
+
+        // Interpolate to get proper Rendering delay.
+        long delta_frames = mTotalFramesWritten - mLastPlayoutTStamp.framePosition;
+        long delta_nsecs = 1000000000 * delta_frames / mSampleRateInHz;
+        long playout_time_nsecs = mLastPlayoutTStamp.nanoTime + delta_nsecs;
+        long now_nsecs = System.nanoTime();
+        long delay_nsecs = playout_time_nsecs - now_nsecs;
+
+        // Populate RenderingDelay return value for native land.
+        mRenderingDelayBuffer.putLong(0, delay_nsecs / 1000);
+        mRenderingDelayBuffer.putLong(8, now_nsecs / 1000);
+
+        if (DEBUG_LEVEL >= 3) {
+            Log.i(TAG,
+                    "RenderingDelay: "
+                            + " df=" + delta_frames + " dt=" + (delta_nsecs / 1000)
+                            + " delay=" + (delay_nsecs / 1000) + " play=" + (now_nsecs / 1000));
+        }
+    }
+
+    /** Gets a new timestamp from AudioTrack. For performance reasons we only
+     * read a new timestamp in certain intervals. */
+    private void updateTimestamp() {
+        int underruns = getUnderrunCount();
+        if (underruns != mLastUnderrunCount) {
+            Log.i(TAG,
+                    "Underrun detected (" + mLastUnderrunCount + "->" + underruns
+                            + ")! Resetting rendering delay logic.");
+            mLastTimestampUpdateNsec = NO_TIMESTAMP;
+            mLastUnderrunCount = underruns;
+        }
+        if (!mTriggerTimestampUpdateNow && mLastTimestampUpdateNsec != NO_TIMESTAMP
+                && elapsedNsec(mLastTimestampUpdateNsec) <= TIMESTAMP_UPDATE_PERIOD) {
+            // not time for an update yet
+            return;
+        }
+
+        if (mAudioTrack.getTimestamp(mLastPlayoutTStamp)) {
+            // Got a new value.
+            if (DEBUG_LEVEL >= 1) {
+                Log.i(TAG,
+                        "New AudioTrack timestamp:"
+                                + " pos=" + mLastPlayoutTStamp.framePosition
+                                + " ts=" + mLastPlayoutTStamp.nanoTime / 1000 + "us");
+            }
+            mLastTimestampUpdateNsec = System.nanoTime();
+            mTriggerTimestampUpdateNow = false;
+        }
+    }
+
+    //
+    // JNI functions in native land.
+    //
+    private native void nativeCacheDirectBufferAddress(long nativeAudioSinkAndroidAudioTrackImpl,
+            ByteBuffer mPcmBuffer, ByteBuffer mRenderingDelayBuffer);
+}
diff --git a/chromeos/components/tether/connect_tethering_operation.cc b/chromeos/components/tether/connect_tethering_operation.cc
index fe2c4a23..fd349fa 100644
--- a/chromeos/components/tether/connect_tethering_operation.cc
+++ b/chromeos/components/tether/connect_tethering_operation.cc
@@ -13,6 +13,11 @@
 
 namespace tether {
 
+// This value is quite large because first time
+// setup can take a long time.
+// static
+uint32_t ConnectTetheringOperation::kSetupRequiredResponseTimeoutSeconds = 90;
+
 // static
 ConnectTetheringOperation::Factory*
     ConnectTetheringOperation::Factory::factory_instance_ = nullptr;
@@ -22,12 +27,14 @@
 ConnectTetheringOperation::Factory::NewInstance(
     const cryptauth::RemoteDevice& device_to_connect,
     BleConnectionManager* connection_manager,
-    TetherHostResponseRecorder* tether_host_response_recorder) {
+    TetherHostResponseRecorder* tether_host_response_recorder,
+    bool setup_required) {
   if (!factory_instance_) {
     factory_instance_ = new Factory();
   }
   return factory_instance_->BuildInstance(device_to_connect, connection_manager,
-                                          tether_host_response_recorder);
+                                          tether_host_response_recorder,
+                                          setup_required);
 }
 
 // static
@@ -40,20 +47,24 @@
 ConnectTetheringOperation::Factory::BuildInstance(
     const cryptauth::RemoteDevice& device_to_connect,
     BleConnectionManager* connection_manager,
-    TetherHostResponseRecorder* tether_host_response_recorder) {
+    TetherHostResponseRecorder* tether_host_response_recorder,
+    bool setup_required) {
   return base::MakeUnique<ConnectTetheringOperation>(
-      device_to_connect, connection_manager, tether_host_response_recorder);
+      device_to_connect, connection_manager, tether_host_response_recorder,
+      setup_required);
 }
 
 ConnectTetheringOperation::ConnectTetheringOperation(
     const cryptauth::RemoteDevice& device_to_connect,
     BleConnectionManager* connection_manager,
-    TetherHostResponseRecorder* tether_host_response_recorder)
+    TetherHostResponseRecorder* tether_host_response_recorder,
+    bool setup_required)
     : MessageTransferOperation(
           std::vector<cryptauth::RemoteDevice>{device_to_connect},
           connection_manager),
       remote_device_(device_to_connect),
       tether_host_response_recorder_(tether_host_response_recorder),
+      setup_required_(setup_required),
       error_code_to_return_(
           ConnectTetheringResponse_ResponseCode::
               ConnectTetheringResponse_ResponseCode_UNKNOWN_ERROR) {}
@@ -159,6 +170,12 @@
   }
 }
 
+uint32_t ConnectTetheringOperation::GetResponseTimeoutSeconds() {
+  return (setup_required_)
+             ? ConnectTetheringOperation::kSetupRequiredResponseTimeoutSeconds
+             : MessageTransferOperation::GetResponseTimeoutSeconds();
+}
+
 }  // namespace tether
 
 }  // namespace chromeos
diff --git a/chromeos/components/tether/connect_tethering_operation.h b/chromeos/components/tether/connect_tethering_operation.h
index 01ba625..952b41a 100644
--- a/chromeos/components/tether/connect_tethering_operation.h
+++ b/chromeos/components/tether/connect_tethering_operation.h
@@ -24,8 +24,6 @@
 // Operation used to request that a tether host share its Internet connection.
 // Attempts a connection to the RemoteDevice passed to its constructor and
 // notifies observers when the RemoteDevice sends a response.
-// TODO(khorimoto): Add a timeout which gives up if no response is received in
-// a reasonable amount of time.
 class ConnectTetheringOperation : public MessageTransferOperation {
  public:
   class Factory {
@@ -33,7 +31,8 @@
     static std::unique_ptr<ConnectTetheringOperation> NewInstance(
         const cryptauth::RemoteDevice& device_to_connect,
         BleConnectionManager* connection_manager,
-        TetherHostResponseRecorder* tether_host_response_recorder);
+        TetherHostResponseRecorder* tether_host_response_recorder,
+        bool setup_required);
 
     static void SetInstanceForTesting(Factory* factory);
 
@@ -41,7 +40,8 @@
     virtual std::unique_ptr<ConnectTetheringOperation> BuildInstance(
         const cryptauth::RemoteDevice& devices_to_connect,
         BleConnectionManager* connection_manager,
-        TetherHostResponseRecorder* tether_host_response_recorder);
+        TetherHostResponseRecorder* tether_host_response_recorder,
+        bool setup_required);
 
    private:
     static Factory* factory_instance_;
@@ -61,7 +61,8 @@
   ConnectTetheringOperation(
       const cryptauth::RemoteDevice& device_to_connect,
       BleConnectionManager* connection_manager,
-      TetherHostResponseRecorder* tether_host_response_recorder);
+      TetherHostResponseRecorder* tether_host_response_recorder,
+      bool setup_required);
   ~ConnectTetheringOperation() override;
 
   void AddObserver(Observer* observer);
@@ -80,11 +81,18 @@
   void NotifyObserversOfConnectionFailure(
       ConnectTetheringResponse_ResponseCode error_code);
 
+  uint32_t GetResponseTimeoutSeconds() override;
+
  private:
   friend class ConnectTetheringOperationTest;
 
+  // The amount of time this operation will wait for if first time setup is
+  // required on the host device.
+  static uint32_t kSetupRequiredResponseTimeoutSeconds;
+
   cryptauth::RemoteDevice remote_device_;
   TetherHostResponseRecorder* tether_host_response_recorder_;
+  bool setup_required_;
 
   // These values are saved in OnMessageReceived() and returned in
   // OnOperationFinished().
diff --git a/chromeos/components/tether/connect_tethering_operation_unittest.cc b/chromeos/components/tether/connect_tethering_operation_unittest.cc
index cb736b2..3c05a2c 100644
--- a/chromeos/components/tether/connect_tethering_operation_unittest.cc
+++ b/chromeos/components/tether/connect_tethering_operation_unittest.cc
@@ -117,12 +117,14 @@
 
     operation_ = base::WrapUnique(new ConnectTetheringOperation(
         test_device_, fake_ble_connection_manager_.get(),
-        mock_tether_host_response_recorder_.get()));
+        mock_tether_host_response_recorder_.get(), false /* setup_required */));
     operation_->AddObserver(test_observer_.get());
     operation_->Initialize();
   }
 
   void SimulateDeviceAuthenticationAndVerifyMessageSent() {
+    VerifyResponseTimeoutSeconds(false /* setup_required */);
+
     operation_->OnDeviceAuthenticated(test_device_);
 
     // Verify that the message was sent successfully.
@@ -166,6 +168,16 @@
     }
   }
 
+  void VerifyResponseTimeoutSeconds(bool setup_required) {
+    uint32_t expected_response_timeout_seconds =
+        setup_required
+            ? ConnectTetheringOperation::kSetupRequiredResponseTimeoutSeconds
+            : MessageTransferOperation::kDefaultResponseTimeoutSeconds;
+
+    EXPECT_EQ(expected_response_timeout_seconds,
+              operation_->GetResponseTimeoutSeconds());
+  }
+
   const std::string connect_tethering_request_string_;
   const cryptauth::RemoteDevice test_device_;
 
@@ -252,6 +264,13 @@
             test_observer_->error_code);
 }
 
+TEST_F(ConnectTetheringOperationTest, TestOperation_SetupRequired) {
+  operation_ = base::WrapUnique(new ConnectTetheringOperation(
+      test_device_, fake_ble_connection_manager_.get(),
+      mock_tether_host_response_recorder_.get(), true /* setup_required */));
+  VerifyResponseTimeoutSeconds(true /* setup_required */);
+}
+
 }  // namespace tether
 
 }  // namespace cryptauth
diff --git a/chromeos/components/tether/tether_connector.cc b/chromeos/components/tether/tether_connector.cc
index 7eb23ad..ee34c45 100644
--- a/chromeos/components/tether/tether_connector.cc
+++ b/chromeos/components/tether/tether_connector.cc
@@ -199,12 +199,14 @@
 
   DCHECK(device_id == tether_host_to_connect->GetDeviceId());
 
-  // TODO (hansberry): Indicate to ConnectTetheringOperation if first-time setup
-  // is required, so that it can adjust its timeout duration.
+  const std::string& tether_network_guid =
+      device_id_tether_network_guid_map_->GetTetherNetworkGuidForDeviceId(
+          device_id);
   connect_tethering_operation_ =
       ConnectTetheringOperation::Factory::NewInstance(
           *tether_host_to_connect, connection_manager_,
-          tether_host_response_recorder_);
+          tether_host_response_recorder_,
+          host_scan_cache_->DoesHostRequireSetup(tether_network_guid));
   connect_tethering_operation_->AddObserver(this);
   connect_tethering_operation_->Initialize();
 }
diff --git a/chromeos/components/tether/tether_connector_unittest.cc b/chromeos/components/tether/tether_connector_unittest.cc
index 4910a09..1398d2a 100644
--- a/chromeos/components/tether/tether_connector_unittest.cc
+++ b/chromeos/components/tether/tether_connector_unittest.cc
@@ -55,10 +55,13 @@
   FakeConnectTetheringOperation(
       const cryptauth::RemoteDevice& device_to_connect,
       BleConnectionManager* connection_manager,
-      TetherHostResponseRecorder* tether_host_response_recorder)
+      TetherHostResponseRecorder* tether_host_response_recorder,
+      bool setup_required)
       : ConnectTetheringOperation(device_to_connect,
                                   connection_manager,
-                                  tether_host_response_recorder) {}
+                                  tether_host_response_recorder,
+                                  setup_required),
+        setup_required_(setup_required) {}
 
   ~FakeConnectTetheringOperation() override {}
 
@@ -75,6 +78,11 @@
     EXPECT_EQ(1u, remote_devices().size());
     return remote_devices()[0];
   }
+
+  bool setup_required() { return setup_required_; }
+
+ private:
+  bool setup_required_;
 };
 
 class FakeConnectTetheringOperationFactory
@@ -92,10 +100,12 @@
   std::unique_ptr<ConnectTetheringOperation> BuildInstance(
       const cryptauth::RemoteDevice& device_to_connect,
       BleConnectionManager* connection_manager,
-      TetherHostResponseRecorder* tether_host_response_recorder) override {
+      TetherHostResponseRecorder* tether_host_response_recorder,
+      bool setup_required) override {
     FakeConnectTetheringOperation* operation =
         new FakeConnectTetheringOperation(device_to_connect, connection_manager,
-                                          tether_host_response_recorder);
+                                          tether_host_response_recorder,
+                                          setup_required);
     created_operations_.push_back(operation);
     return base::WrapUnique(operation);
   }
@@ -281,6 +291,8 @@
   // Simulate a failed connection attempt (either the host cannot provide
   // tethering at this time or a timeout occurs).
   EXPECT_EQ(1u, fake_operation_factory_->created_operations().size());
+  EXPECT_FALSE(
+      fake_operation_factory_->created_operations()[0]->setup_required());
   tether_connector_->CancelConnectionAttempt(
       GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
 
@@ -305,6 +317,8 @@
   // Simulate a failed connection attempt (either the host cannot provide
   // tethering at this time or a timeout occurs).
   EXPECT_EQ(1u, fake_operation_factory_->created_operations().size());
+  EXPECT_FALSE(
+      fake_operation_factory_->created_operations()[0]->setup_required());
   fake_operation_factory_->created_operations()[0]->SendFailedResponse(
       ConnectTetheringResponse_ResponseCode::
           ConnectTetheringResponse_ResponseCode_UNKNOWN_ERROR);
@@ -328,6 +342,8 @@
 
   EXPECT_TRUE(
       fake_notification_presenter_->is_setup_required_notification_shown());
+  EXPECT_TRUE(
+      fake_operation_factory_->created_operations()[0]->setup_required());
 
   fake_operation_factory_->created_operations()[0]->SendFailedResponse(
       ConnectTetheringResponse_ResponseCode::
@@ -353,6 +369,8 @@
 
   // Receive a successful response. We should still be connecting.
   EXPECT_EQ(1u, fake_operation_factory_->created_operations().size());
+  EXPECT_FALSE(
+      fake_operation_factory_->created_operations()[0]->setup_required());
   fake_operation_factory_->created_operations()[0]->SendSuccessfulResponse(
       kSsid, kPassword);
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
@@ -387,6 +405,8 @@
 
   // Receive a successful response. We should still be connecting.
   EXPECT_EQ(1u, fake_operation_factory_->created_operations().size());
+  EXPECT_FALSE(
+      fake_operation_factory_->created_operations()[0]->setup_required());
   fake_operation_factory_->created_operations()[0]->SendSuccessfulResponse(
       kSsid, kPassword);
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
@@ -417,6 +437,8 @@
 
   // Receive a successful response. We should still be connecting.
   EXPECT_EQ(1u, fake_operation_factory_->created_operations().size());
+  EXPECT_FALSE(
+      fake_operation_factory_->created_operations()[0]->setup_required());
   fake_operation_factory_->created_operations()[0]->SendSuccessfulResponse(
       kSsid, kPassword);
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
@@ -456,6 +478,8 @@
 
   EXPECT_TRUE(
       fake_notification_presenter_->is_setup_required_notification_shown());
+  EXPECT_TRUE(
+      fake_operation_factory_->created_operations()[0]->setup_required());
 
   fake_operation_factory_->created_operations()[0]->SendSuccessfulResponse(
       kSsid, kPassword);
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 71bc853..482b3b8 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -85,7 +85,6 @@
     "//components/download:unit_tests",
     "//components/favicon/core:unit_tests",
     "//components/favicon_base:unit_tests",
-    "//components/feature_engagement_tracker:unit_tests",
     "//components/flags_ui:unit_tests",
     "//components/gcm_driver:unit_tests",
     "//components/gcm_driver/crypto:unit_tests",
@@ -244,6 +243,7 @@
   if (is_android) {
     deps += [
       "//components/cdm/browser:unit_tests",
+      "//components/feature_engagement_tracker:unit_tests",
       "//components/gcm_driver/instance_id:test_support",
       "//components/gcm_driver/instance_id/android:instance_id_driver_java",
       "//components/gcm_driver/instance_id/android:instance_id_driver_test_support_java",
diff --git a/components/feature_engagement_tracker/README.md b/components/feature_engagement_tracker/README.md
index d357494c99..e912703 100644
--- a/components/feature_engagement_tracker/README.md
+++ b/components/feature_engagement_tracker/README.md
@@ -13,6 +13,21 @@
 The backend is feature agnostic and have no special logic for any specific
 features, but instead provides a generic API.
 
+## Compiling for platforms other than Android
+
+For now the code for the Feature Engagement Tracker is only compiled in
+for Android, but only a shim layer is really dependent on Android to provide a
+JNI bridge. The goal is to keep all the business logic in the cross-platform
+part of the code.
+
+For local development, it is therefore possible to compile and run tests for
+the core of the tracker on other platforms. To do this, simply add the
+following line to the `//components:components_unittests` target:
+
+```python
+deps += [ "//components/feature_engagement_tracker:unit_tests" ]
+```
+
 ## Testing
 
 To compile and run tests, assuming the product out directory is `out/Debug`,
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
index b2d9374a..9d92884 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/feature_list.h"
-#include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/user_metrics.h"
@@ -36,10 +35,8 @@
 namespace feature_engagement_tracker {
 
 namespace {
-const base::FilePath::CharType kEventDBStorageDir[] =
-    FILE_PATH_LITERAL("EventDB");
-const base::FilePath::CharType kAvailabilityDBStorageDir[] =
-    FILE_PATH_LITERAL("AvailabilityDB");
+const char kEventDBStorageDir[] = "EventDB";
+const char kAvailabilityDBStorageDir[] = "AvailabilityDB";
 
 // Creates a FeatureEngagementTrackerImpl that is usable for a demo mode.
 std::unique_ptr<FeatureEngagementTracker>
diff --git a/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc b/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc
index e9d08c0..6bcf53c 100644
--- a/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc
+++ b/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc
@@ -17,7 +17,6 @@
   exploded_time.year = year;
   exploded_time.month = month;
   exploded_time.day_of_month = day;
-  exploded_time.day_of_week = 0;
   exploded_time.hour = 0;
   exploded_time.minute = 0;
   exploded_time.second = 0;
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
index 8cb72a3..0dcac70 100644
--- a/components/metrics/BUILD.gn
+++ b/components/metrics/BUILD.gn
@@ -90,6 +90,8 @@
     "system_memory_stats_recorder_win.cc",
     "url_constants.cc",
     "url_constants.h",
+    "version_utils.cc",
+    "version_utils.h",
   ]
 
   public_deps = [
@@ -102,6 +104,7 @@
     "//base:base_static",
     "//components/prefs",
     "//components/variations",
+    "//components/version_info:version_info",
     "//third_party/zlib/google:compression_utils",
   ]
 
@@ -151,8 +154,6 @@
     "net/net_metrics_log_uploader.h",
     "net/network_metrics_provider.cc",
     "net/network_metrics_provider.h",
-    "net/version_utils.cc",
-    "net/version_utils.h",
     "net/wifi_access_point_info_provider.cc",
     "net/wifi_access_point_info_provider.h",
   ]
@@ -166,7 +167,6 @@
     "//base",
     "//components/data_use_measurement/core",
     "//components/variations",
-    "//components/version_info",
     "//net",
     "//url",
   ]
diff --git a/components/metrics/net/version_utils.cc b/components/metrics/version_utils.cc
similarity index 89%
rename from components/metrics/net/version_utils.cc
rename to components/metrics/version_utils.cc
index 3745233..d24932b 100644
--- a/components/metrics/net/version_utils.cc
+++ b/components/metrics/version_utils.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/metrics/net/version_utils.h"
+#include "components/metrics/version_utils.h"
 
 #include "base/logging.h"
 #include "build/build_config.h"
@@ -20,8 +20,7 @@
   return version;
 }
 
-SystemProfileProto::Channel AsProtobufChannel(
-    version_info::Channel channel) {
+SystemProfileProto::Channel AsProtobufChannel(version_info::Channel channel) {
   switch (channel) {
     case version_info::Channel::UNKNOWN:
       return SystemProfileProto::CHANNEL_UNKNOWN;
diff --git a/components/metrics/net/version_utils.h b/components/metrics/version_utils.h
similarity index 71%
rename from components/metrics/net/version_utils.h
rename to components/metrics/version_utils.h
index 853c846a..3daecc7b 100644
--- a/components/metrics/net/version_utils.h
+++ b/components/metrics/version_utils.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_METRICS_NET_VERSION_UTILS_H_
-#define COMPONENTS_METRICS_NET_VERSION_UTILS_H_
+#ifndef COMPONENTS_METRICS_VERSION_UTILS_H_
+#define COMPONENTS_METRICS_VERSION_UTILS_H_
 
 #include <string>
 
@@ -21,9 +21,8 @@
 
 // Translates version_info::Channel to the equivalent
 // SystemProfileProto::Channel.
-SystemProfileProto::Channel AsProtobufChannel(
-    version_info::Channel channel);
+SystemProfileProto::Channel AsProtobufChannel(version_info::Channel channel);
 
 }  // namespace metrics
 
-#endif  // COMPONENTS_METRICS_NET_VERSION_UTILS_H_
+#endif  // COMPONENTS_METRICS_VERSION_UTILS_H_
diff --git a/components/payments_strings.grdp b/components/payments_strings.grdp
index 974c7a7..0c663a0 100644
--- a/components/payments_strings.grdp
+++ b/components/payments_strings.grdp
@@ -225,8 +225,8 @@
   </message>
   <message name="IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS" desc="The label in the Order Summary section of the Payment Sheet that indicates how many display items are hidden. [ICU Syntax]">
     {MORE_ITEMS, plural,
-      =1 {<ph name="ITEM_COUNT">#<ex>1</ex></ph> item}
-      other {<ph name="ITEM_COUNT">#<ex>2</ex></ph> items}}
+      =1 {<ph name="ITEM_COUNT">#<ex>1</ex></ph> more item}
+      other {<ph name="ITEM_COUNT">#<ex>2</ex></ph> more items}}
   </message>
   <message name="IDS_PAYMENT_REQUEST_SHIPPING_SECTION_NAME" desc="The name of the Shipping Address section in the Payment Sheet of the Payment Request dialog.">
     Shipping address
@@ -314,21 +314,25 @@
   <if expr="not is_android">
     <message name="IDS_PAYMENT_REQUEST_PAYMENT_METHODS_PREVIEW" desc="This is a snippet of a payment method a user has saved to Chrome, plus an indication of the number of additional payment methods the user has saved. Its function is to show the user has payment methods that can be used to complete a payment, and thus doesn't have to type the entire payment method. [ICU Syntax]">
     {PAYMENT_METHOD, plural,
+       =0 {<ph name="PAYMENT_METHOD_PREVIEW">{1}<ex>VISA ....1234</ex></ph>}
        =1 {<ph name="PAYMENT_METHOD_PREVIEW">{1}<ex>VISA ....1234</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_PAYMENT_METHODS">{2}<ex>1</ex></ph> more}
        other {<ph name="PAYMENT_METHOD_PREVIEW">{1}<ex>VISA ....1234</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_PAYMENT_METHODS">{2}<ex>2</ex></ph> more}}
     </message>
     <message name="IDS_PAYMENT_REQUEST_SHIPPING_ADDRESSES_PREVIEW" desc="This is a snippet of a shipping address a user has saved to Chrome, plus an indication of the number of additional shipping addresses the user has saved. Its function is to show the user has shipping addresses that can be used to complete a purchase, and thus doesn't have to type the entire address. [ICU Syntax]" formatter_data="android_java">
     {SHIPPING_ADDRESS, plural,
+      =0 {<ph name="SHIPPING_ADDRESS_PREVIEW">{1}<ex>Jerry, 1253 Mcgill college</ex></ph>}
       =1 {<ph name="SHIPPING_ADDRESS_PREVIEW">{1}<ex>Jerry, 1253 Mcgill college</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_ADDRESSES">{2}<ex>1</ex></ph> more}
       other {<ph name="SHIPPING_ADDRESS_PREVIEW">{1}<ex>Jerry, 1253 Mcgill college</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_ADDRESSES">{2}<ex>2</ex></ph> more}}
     </message>
     <message name="IDS_PAYMENT_REQUEST_SHIPPING_OPTIONS_PREVIEW" desc="This is a snippet of a shipping option a merchant supports, plus an indication of the number of additional shipping options the merchant supports. Its function is to show the user can choose different shipping options to complete a purchase. [ICU Syntax]">
     {SHIPPING_OPTIONS, plural,
+       =0 {<ph name="SHIPPING_OPTION_PREVIEW">{1}<ex>standard shipping</ex></ph>}
        =1 {<ph name="SHIPPING_OPTION_PREVIEW">{1}<ex>standard shipping</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_SHIPPING_OPTIONS">{2}<ex>1</ex></ph> more}
        other {<ph name="SHIPPING_OPTION_PREVIEW">{1}<ex>standard shipping</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_SHIPPING_OPTIONS">{2}<ex>2</ex></ph> more}}
     </message>
     <message name="IDS_PAYMENT_REQUEST_CONTACTS_PREVIEW" desc="This is a snippet of a contact a user has saved to Chrome, plus an indication of the number of additional contacts the user has saved. Its function is to show the user has contacts that can be used to complete a purchase, and thus doesn't have to type the entire contact info. [ICU Syntax]">
      {CONTACT, plural,
+       =0 {<ph name="CONTACT_PREVIEW">{1}<ex>Jerry, 438-123-1922</ex></ph>}
        =1 {<ph name="CONTACT_PREVIEW">{1}<ex>Jerry, 438-123-1922</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_CONTACTS">{2}<ex>1</ex></ph> more}
        other {<ph name="CONTACT_PREVIEW">{1}<ex>Jerry, 438-123-1922</ex></ph> and <ph name="NUMBER_OF_ADDITIONAL_CONTACTS">{2}<ex>2</ex></ph> more}}
     </message>
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index ae91ee0..0a8a5551 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -982,6 +982,7 @@
     "//components/sync_preferences",
     "//components/sync_preferences:test_support",
     "//components/version_info",
+    "//components/version_info:version_string",
     "//google_apis",
     "//google_apis:test_support",
     "//net",
diff --git a/components/sync/device_info/local_device_info_provider_impl_unittest.cc b/components/sync/device_info/local_device_info_provider_impl_unittest.cc
index e3fbf77..9319c0a 100644
--- a/components/sync/device_info/local_device_info_provider_impl_unittest.cc
+++ b/components/sync/device_info/local_device_info_provider_impl_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "components/sync/base/get_session_name.h"
+#include "components/version_info/version_string.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace syncer {
diff --git a/components/sync/model/entity_change.h b/components/sync/model/entity_change.h
index 408f370d..65349fb6 100644
--- a/components/sync/model/entity_change.h
+++ b/components/sync/model/entity_change.h
@@ -28,6 +28,10 @@
   std::string storage_key() const { return storage_key_; }
   ChangeType type() const { return type_; }
   const EntityData& data() const { return data_.value(); }
+  // TODO(pavely): data_ptr() is added temporarily to support converting
+  // ModelTypeSyncBridge::MergeSyncData from map to change list. Should be
+  // removed as part of crbug.com/719570.
+  const EntityDataPtr& data_ptr() const { return data_; }
 
  private:
   EntityChange(const std::string& storage_key,
diff --git a/components/sync/model/fake_model_type_change_processor.cc b/components/sync/model/fake_model_type_change_processor.cc
index 0c39665..6ca9f46 100644
--- a/components/sync/model/fake_model_type_change_processor.cc
+++ b/components/sync/model/fake_model_type_change_processor.cc
@@ -36,8 +36,8 @@
     MetadataChangeList* metadata_change_list) {}
 
 void FakeModelTypeChangeProcessor::UpdateStorageKey(
-    const std::string& old_storage_key,
-    const std::string& new_storage_key,
+    const EntityData& entity_data,
+    const std::string& storage_key,
     MetadataChangeList* metadata_change_list) {}
 
 void FakeModelTypeChangeProcessor::ModelReadyToSync(
diff --git a/components/sync/model/fake_model_type_change_processor.h b/components/sync/model/fake_model_type_change_processor.h
index 42886611..309c41f 100644
--- a/components/sync/model/fake_model_type_change_processor.h
+++ b/components/sync/model/fake_model_type_change_processor.h
@@ -33,8 +33,8 @@
            MetadataChangeList* metadata_change_list) override;
   void Delete(const std::string& client_tag,
               MetadataChangeList* metadata_change_list) override;
-  void UpdateStorageKey(const std::string& old_storage_key,
-                        const std::string& new_storage_key,
+  void UpdateStorageKey(const EntityData& entity_data,
+                        const std::string& storage_key,
                         MetadataChangeList* metadata_change_list) override;
   void ModelReadyToSync(std::unique_ptr<MetadataBatch> batch) override;
   void OnSyncStarting(const ModelErrorHandler& error_handler,
diff --git a/components/sync/model/fake_model_type_sync_bridge.cc b/components/sync/model/fake_model_type_sync_bridge.cc
index 4fbdbd28..5c0cb83 100644
--- a/components/sync/model/fake_model_type_sync_bridge.cc
+++ b/components/sync/model/fake_model_type_sync_bridge.cc
@@ -4,6 +4,7 @@
 
 #include "components/sync/model/fake_model_type_sync_bridge.h"
 
+#include <set>
 #include <utility>
 
 #include "base/bind.h"
@@ -27,6 +28,17 @@
   TestMetadataChangeList() {}
   ~TestMetadataChangeList() override {}
 
+  void UpdateMetadata(const std::string& storage_key,
+                      const sync_pb::EntityMetadata& metadata) override {
+    DCHECK(!storage_key.empty());
+    InMemoryMetadataChangeList::UpdateMetadata(storage_key, metadata);
+  }
+
+  void ClearMetadata(const std::string& storage_key) override {
+    DCHECK(!storage_key.empty());
+    InMemoryMetadataChangeList::ClearMetadata(storage_key);
+  }
+
   const std::map<std::string, MetadataChange>& GetMetadataChanges() const {
     return metadata_changes_;
   }
@@ -163,7 +175,7 @@
     const std::string& key,
     std::unique_ptr<EntityData> entity_data) {
   db_->PutData(key, *entity_data);
-  if (change_processor()) {
+  if (change_processor()->IsTrackingMetadata()) {
     auto change_list = CreateMetadataChangeList();
     change_processor()->Put(key, std::move(entity_data), change_list.get());
     ApplyMetadataChangeList(std::move(change_list));
@@ -172,7 +184,7 @@
 
 void FakeModelTypeSyncBridge::DeleteItem(const std::string& key) {
   db_->RemoveData(key);
-  if (change_processor()) {
+  if (change_processor()->IsTrackingMetadata()) {
     auto change_list = CreateMetadataChangeList();
     change_processor()->Delete(key, change_list.get());
     ApplyMetadataChangeList(std::move(change_list));
@@ -185,26 +197,45 @@
 }
 
 base::Optional<ModelError> FakeModelTypeSyncBridge::MergeSyncData(
-    std::unique_ptr<MetadataChangeList> metadata_changes,
-    EntityDataMap data_map) {
+    std::unique_ptr<MetadataChangeList> metadata_change_list,
+    EntityChangeList entity_data) {
   if (error_next_) {
     error_next_ = false;
     return ModelError(FROM_HERE, "boom");
   }
 
+  std::set<std::string> remote_storage_keys;
+  // Store any new remote entities.
+  for (const auto& change : entity_data) {
+    EXPECT_FALSE(change.data().is_deleted());
+    EXPECT_EQ(EntityChange::ACTION_ADD, change.type());
+    std::string storage_key = change.storage_key();
+    EXPECT_NE(SupportsGetStorageKey(), storage_key.empty());
+    if (storage_key.empty()) {
+      storage_key = GetStorageKeyImpl(change.data());
+      change_processor()->UpdateStorageKey(change.data(), storage_key,
+                                           metadata_change_list.get());
+    }
+    remote_storage_keys.insert(storage_key);
+    db_->PutData(storage_key, change.data());
+  }
+
   // Commit any local entities that aren't being overwritten by the server.
   for (const auto& kv : db_->all_data()) {
-    if (data_map.find(kv.first) == data_map.end()) {
+    if (remote_storage_keys.find(kv.first) == remote_storage_keys.end()) {
       change_processor()->Put(kv.first, CopyEntityData(*kv.second),
-                              metadata_changes.get());
+                              metadata_change_list.get());
     }
   }
-  // Store any new remote entities.
-  for (const auto& kv : data_map) {
-    EXPECT_FALSE(kv.second->is_deleted());
-    db_->PutData(kv.first, kv.second.value());
-  }
-  ApplyMetadataChangeList(std::move(metadata_changes));
+
+  ApplyMetadataChangeList(std::move(metadata_change_list));
+  return {};
+}
+
+base::Optional<ModelError> FakeModelTypeSyncBridge::MergeSyncData(
+    std::unique_ptr<MetadataChangeList> metadata_change_list,
+    EntityDataMap entity_data) {
+  NOTREACHED();
   return {};
 }
 
@@ -218,10 +249,18 @@
 
   for (const EntityChange& change : entity_changes) {
     switch (change.type()) {
-      case EntityChange::ACTION_ADD:
-        EXPECT_FALSE(db_->HasData(change.storage_key()));
-        db_->PutData(change.storage_key(), change.data());
+      case EntityChange::ACTION_ADD: {
+        std::string storage_key = change.storage_key();
+        EXPECT_NE(SupportsGetStorageKey(), storage_key.empty());
+        if (storage_key.empty()) {
+          storage_key = GetStorageKeyImpl(change.data());
+          change_processor()->UpdateStorageKey(change.data(), storage_key,
+                                               metadata_changes.get());
+        }
+        EXPECT_FALSE(db_->HasData(storage_key));
+        db_->PutData(storage_key, change.data());
         break;
+      }
       case EntityChange::ACTION_UPDATE:
         EXPECT_TRUE(db_->HasData(change.storage_key()));
         db_->PutData(change.storage_key(), change.data());
@@ -302,9 +341,24 @@
 
 std::string FakeModelTypeSyncBridge::GetStorageKey(
     const EntityData& entity_data) {
+  DCHECK(supports_get_storage_key_);
+  return GetStorageKeyImpl(entity_data);
+}
+
+std::string FakeModelTypeSyncBridge::GetStorageKeyImpl(
+    const EntityData& entity_data) {
   return entity_data.specifics.preference().name();
 }
 
+bool FakeModelTypeSyncBridge::SupportsGetStorageKey() const {
+  return supports_get_storage_key_;
+}
+
+void FakeModelTypeSyncBridge::SetSupportsGetStorageKey(
+    bool supports_get_storage_key) {
+  supports_get_storage_key_ = supports_get_storage_key;
+}
+
 ConflictResolution FakeModelTypeSyncBridge::ResolveConflict(
     const EntityData& local_data,
     const EntityData& remote_data) const {
diff --git a/components/sync/model/fake_model_type_sync_bridge.h b/components/sync/model/fake_model_type_sync_bridge.h
index f71b3472..b6e1226 100644
--- a/components/sync/model/fake_model_type_sync_bridge.h
+++ b/components/sync/model/fake_model_type_sync_bridge.h
@@ -106,7 +106,10 @@
   std::unique_ptr<MetadataChangeList> CreateMetadataChangeList() override;
   base::Optional<ModelError> MergeSyncData(
       std::unique_ptr<MetadataChangeList> metadata_change_list,
-      EntityDataMap entity_data_map) override;
+      EntityChangeList entity_data) override;
+  base::Optional<ModelError> MergeSyncData(
+      std::unique_ptr<MetadataChangeList> metadata_change_list,
+      EntityDataMap entity_data) override;
   base::Optional<ModelError> ApplySyncChanges(
       std::unique_ptr<MetadataChangeList> metadata_change_list,
       EntityChangeList entity_changes) override;
@@ -114,6 +117,8 @@
   void GetAllData(DataCallback callback) override;
   std::string GetClientTag(const EntityData& entity_data) override;
   std::string GetStorageKey(const EntityData& entity_data) override;
+  bool SupportsGetStorageKey() const override;
+  void SetSupportsGetStorageKey(bool supports_get_storage_key);
   ConflictResolution ResolveConflict(
       const EntityData& local_data,
       const EntityData& remote_data) const override;
@@ -140,11 +145,18 @@
   // Applies |change_list| to the metadata store.
   void ApplyMetadataChangeList(std::unique_ptr<MetadataChangeList> change_list);
 
+  std::string GetStorageKeyImpl(const EntityData& entity_data);
+
   // The conflict resolution to use for calls to ResolveConflict.
   std::unique_ptr<ConflictResolution> conflict_resolution_;
 
   // Whether an error should be produced on the next bridge call.
   bool error_next_ = false;
+
+  // Whether the bridge supports call to GetStorageKey. If it doesn't bridge is
+  // responsible for calling UpdateStorageKey when processing new entities in
+  // MergeSyncData/ApplySyncChanges.
+  bool supports_get_storage_key_ = true;
 };
 
 }  // namespace syncer
diff --git a/components/sync/model/model_type_change_processor.h b/components/sync/model/model_type_change_processor.h
index 78d00a3..4e83b00 100644
--- a/components/sync/model/model_type_change_processor.h
+++ b/components/sync/model/model_type_change_processor.h
@@ -48,12 +48,14 @@
   virtual void Delete(const std::string& storage_key,
                       MetadataChangeList* metadata_change_list) = 0;
 
-  // Inform the processor that storage key has chagned.
-  // TODO(gangwu): crbug.com/719570 should remove this after bug fixed.
-  // This function should only be called for the data type which does not create
-  // storage key based on syncer::EntityData.
-  virtual void UpdateStorageKey(const std::string& old_storage_key,
-                                const std::string& new_storage_key,
+  // Sets storage key for the new entity. This function only applies to
+  // datatypes that can't generate storage key based on EntityData. Bridge
+  // should call this function when handling MergeSyncData/ApplySyncChanges to
+  // inform the processor about |storage_key| of an entity identified by
+  // |entity_data|. Metadata changes about new entity will be appended to
+  // |metadata_change_list|.
+  virtual void UpdateStorageKey(const EntityData& entity_data,
+                                const std::string& storage_key,
                                 MetadataChangeList* metadata_change_list) = 0;
 
   // The bridge is expected to call this exactly once unless it encounters an
diff --git a/components/sync/model/model_type_sync_bridge.cc b/components/sync/model/model_type_sync_bridge.cc
index 0e5a1dd..e4c6f87b 100644
--- a/components/sync/model/model_type_sync_bridge.cc
+++ b/components/sync/model/model_type_sync_bridge.cc
@@ -8,6 +8,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "components/sync/model/metadata_batch.h"
+#include "components/sync/model/metadata_change_list.h"
 
 namespace syncer {
 
@@ -20,6 +21,23 @@
 
 ModelTypeSyncBridge::~ModelTypeSyncBridge() {}
 
+base::Optional<ModelError> ModelTypeSyncBridge::MergeSyncData(
+    std::unique_ptr<MetadataChangeList> metadata_change_list,
+    EntityChangeList entity_data) {
+  EntityDataMap entity_data_map;
+  for (const auto& change : entity_data) {
+    DCHECK_EQ(EntityChange::ACTION_ADD, change.type());
+    DCHECK(!change.storage_key().empty());
+    DCHECK(entity_data_map.find(change.storage_key()) == entity_data_map.end());
+    entity_data_map.emplace(change.storage_key(), change.data_ptr());
+  }
+  return MergeSyncData(std::move(metadata_change_list), entity_data_map);
+}
+
+bool ModelTypeSyncBridge::SupportsGetStorageKey() const {
+  return true;
+}
+
 ConflictResolution ModelTypeSyncBridge::ResolveConflict(
     const EntityData& local_data,
     const EntityData& remote_data) const {
diff --git a/components/sync/model/model_type_sync_bridge.h b/components/sync/model/model_type_sync_bridge.h
index eaa5355..20340ad0 100644
--- a/components/sync/model/model_type_sync_bridge.h
+++ b/components/sync/model/model_type_sync_bridge.h
@@ -50,17 +50,41 @@
 
   // Perform the initial merge between local and sync data. This should only be
   // called when a data type is first enabled to start syncing, and there is no
+  // sync metadata. Best effort should be made to match local and sync data.
+  // Storage key in entity_data elements will be set to result of
+  // GetStorageKey() call if the bridge supports it. Otherwise it will be left
+  // empty, bridge is responsible for updating storage keys of new entities with
+  // change_processor()->UpdateStorageKey() in this case. If a local and sync
+  // data should match/merge but disagree on storage key, the bridge should
+  // delete one of the records (preferably local). Any local pieces of data that
+  // are not present in sync should immediately be Put(...) to the processor
+  // before returning. The same MetadataChangeList that was passed into this
+  // function can be passed to Put(...) calls. Delete(...) can also be called
+  // but should not be needed for most model types. Durable storage writes, if
+  // not able to combine all change atomically, should save the metadata after
+  // the data changes, so that this merge will be re-driven by sync if is not
+  // completely saved during the current run.
+  virtual base::Optional<ModelError> MergeSyncData(
+      std::unique_ptr<MetadataChangeList> metadata_change_list,
+      EntityChangeList entity_data);
+
+  // Perform the initial merge between local and sync data. This should only be
+  // called when a data type is first enabled to start syncing, and there is no
   // sync metadata. Best effort should be made to match local and sync data. The
-  // keys in the |entity_data_map| will have been created via GetClientTag(...),
-  // and if a local and sync data should match/merge but disagree on tags, the
-  // bridge should use the sync data's tag. Any local pieces of data that are
-  // not present in sync should immediately be Put(...) to the processor before
-  // returning. The same MetadataChangeList that was passed into this function
-  // can be passed to Put(...) calls. Delete(...) can also be called but should
-  // not be needed for most model types. Durable storage writes, if not able to
-  // combine all change atomically, should save the metadata after the data
-  // changes, so that this merge will be re-driven by sync if is not completely
-  // saved during the current run.
+  // keys in the |entity_data_map| will have been created via
+  // GetStorageKey(...), and if a local and sync data should match/merge but
+  // disagree on storage key, the bridge should delete one of the records
+  // (preferably local). Any local pieces of data that are not present in sync
+  // should immediately be Put(...) to the processor before returning. The same
+  // MetadataChangeList that was passed into this function can be passed to
+  // Put(...) calls. Delete(...) can also be called but should not be needed for
+  // most model types. Durable storage writes, if not able to combine all change
+  // atomically, should save the metadata after the data changes, so that this
+  // merge will be re-driven by sync if is not completely saved during the
+  // current run.
+  // TODO(pavely): This function should be removed as part of crbug.com/719570
+  // once all bridge implementations are switched to the other MergeSyncData
+  // signature.
   virtual base::Optional<ModelError> MergeSyncData(
       std::unique_ptr<MetadataChangeList> metadata_change_list,
       EntityDataMap entity_data_map) = 0;
@@ -102,6 +126,20 @@
   // type should strive to keep these keys as small as possible.
   virtual std::string GetStorageKey(const EntityData& entity_data) = 0;
 
+  // By returning true in this function datatype indicates that it can generate
+  // storage key from EntityData. In this case for all new entities received
+  // from server, change processor will call GetStorageKey and update
+  // EntityChange structures before passing them to MergeSyncData and
+  // ApplySyncChanges.
+  //
+  // This function should return false when datatype's native storage is not
+  // indexed by some combination of values from EntityData, when key into the
+  // storage is obtained at the time the record is inserted into it (e.g. ROWID
+  // in SQLite). In this case entity changes for new entities passed to
+  // MergeSyncData and ApplySyncChanges will have empty storage_key. It is
+  // datatype's responsibility to call UpdateStorageKey for such entities.
+  virtual bool SupportsGetStorageKey() const;
+
   // Resolve a conflict between the client and server versions of data. They are
   // guaranteed not to match (both be deleted or have identical specifics). A
   // default implementation chooses the server data unless it is a deletion.
diff --git a/components/sync/model_impl/processor_entity_tracker.cc b/components/sync/model_impl/processor_entity_tracker.cc
index 7d0b6b3..f6fe203 100644
--- a/components/sync/model_impl/processor_entity_tracker.cc
+++ b/components/sync/model_impl/processor_entity_tracker.cc
@@ -45,6 +45,7 @@
 std::unique_ptr<ProcessorEntityTracker>
 ProcessorEntityTracker::CreateFromMetadata(const std::string& storage_key,
                                            sync_pb::EntityMetadata* metadata) {
+  DCHECK(!storage_key.empty());
   return std::unique_ptr<ProcessorEntityTracker>(
       new ProcessorEntityTracker(storage_key, metadata));
 }
@@ -61,8 +62,10 @@
 
 ProcessorEntityTracker::~ProcessorEntityTracker() {}
 
-void ProcessorEntityTracker::SetStorageKey(const std::string& new_key) {
-  storage_key_ = new_key;
+void ProcessorEntityTracker::SetStorageKey(const std::string& storage_key) {
+  DCHECK(storage_key_.empty());
+  DCHECK(!storage_key.empty());
+  storage_key_ = storage_key;
 }
 
 void ProcessorEntityTracker::SetCommitData(EntityData* data) {
diff --git a/components/sync/model_impl/processor_entity_tracker.h b/components/sync/model_impl/processor_entity_tracker.h
index 8f262f3..7b76f09 100644
--- a/components/sync/model_impl/processor_entity_tracker.h
+++ b/components/sync/model_impl/processor_entity_tracker.h
@@ -100,11 +100,10 @@
   // Clears any in-memory sync state associated with outstanding commits.
   void ClearTransientSyncState();
 
-  // Update storage_key_.
-  // This function should only be called by
-  // ModelTypeChangeProcessor::UpdateStorageKey for the data type which does not
-  // create storage key based on syncer::EntityData.
-  void SetStorageKey(const std::string& new_key);
+  // Update storage_key_. Allows setting storage key for datatypes that don't
+  // generate storage key from syncer::EntityData. Should only be called for
+  // tracker initialized with empty storage key.
+  void SetStorageKey(const std::string& storage_key);
 
   // Takes the passed commit data updates its fields with values from metadata
   // and caches it in the instance. The data is swapped from the input struct
diff --git a/components/sync/model_impl/processor_entity_tracker_unittest.cc b/components/sync/model_impl/processor_entity_tracker_unittest.cc
index e23b9f5..e79e285 100644
--- a/components/sync/model_impl/processor_entity_tracker_unittest.cc
+++ b/components/sync/model_impl/processor_entity_tracker_unittest.cc
@@ -107,6 +107,9 @@
   std::unique_ptr<ProcessorEntityTracker> CreateNew() {
     return ProcessorEntityTracker::CreateNew(kKey, kHash, "", ctime_);
   }
+  std::unique_ptr<ProcessorEntityTracker> CreateNewWithEmptyStorageKey() {
+    return ProcessorEntityTracker::CreateNew("", kHash, "", ctime_);
+  }
 
   std::unique_ptr<ProcessorEntityTracker> CreateSynced() {
     std::unique_ptr<ProcessorEntityTracker> entity = CreateNew();
@@ -242,6 +245,21 @@
   EXPECT_FALSE(entity->HasCommitData());
 }
 
+// Test creating tracker for new server item with empty storage key, applying
+// update and updating storage key.
+TEST_F(ProcessorEntityTrackerTest, NewServerItem_EmptyStorageKey) {
+  std::unique_ptr<ProcessorEntityTracker> entity =
+      CreateNewWithEmptyStorageKey();
+
+  EXPECT_EQ("", entity->storage_key());
+
+  const base::Time mtime = base::Time::Now();
+  entity->RecordAcceptedUpdate(
+      GenerateUpdate(*entity, kHash, kId, kName, kValue1, mtime, 10));
+  entity->SetStorageKey(kKey);
+  EXPECT_EQ(kKey, entity->storage_key());
+}
+
 // Test state for a tombstone received for a previously unknown item.
 TEST_F(ProcessorEntityTrackerTest, NewServerTombstone) {
   std::unique_ptr<ProcessorEntityTracker> entity = CreateNew();
diff --git a/components/sync/model_impl/shared_model_type_processor.cc b/components/sync/model_impl/shared_model_type_processor.cc
index e24917d..322c6ca 100644
--- a/components/sync/model_impl/shared_model_type_processor.cc
+++ b/components/sync/model_impl/shared_model_type_processor.cc
@@ -133,8 +133,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   std::unique_ptr<MetadataChangeList> change_list =
       bridge_->CreateMetadataChangeList();
-  for (auto it = entities_.begin(); it != entities_.end(); ++it) {
-    change_list->ClearMetadata(it->second->storage_key());
+  for (const auto& kv : entities_) {
+    change_list->ClearMetadata(kv.second->storage_key());
   }
   change_list->ClearModelTypeState();
   // Nothing to do if this fails, so just ignore the error it might return.
@@ -193,8 +193,8 @@
   weak_ptr_factory_.InvalidateWeakPtrs();
   worker_.reset();
 
-  for (auto it = entities_.begin(); it != entities_.end(); ++it) {
-    it->second->ClearTransientSyncState();
+  for (const auto& kv : entities_) {
+    kv.second->ClearTransientSyncState();
   }
 }
 
@@ -260,26 +260,21 @@
 }
 
 void SharedModelTypeProcessor::UpdateStorageKey(
-    const std::string& old_storage_key,
-    const std::string& new_storage_key,
+    const EntityData& entity_data,
+    const std::string& storage_key,
     MetadataChangeList* metadata_change_list) {
-  ProcessorEntityTracker* entity = GetEntityForStorageKey(old_storage_key);
-  if (entity == nullptr) {
-    DLOG(WARNING) << "Attempted to update missing item."
-                  << " storage key: " << old_storage_key;
-    return;
-  }
+  const std::string& client_tag_hash = entity_data.client_tag_hash;
+  DCHECK(!client_tag_hash.empty());
+  ProcessorEntityTracker* entity = GetEntityForTagHash(client_tag_hash);
+  DCHECK(entity);
 
-  entity->SetStorageKey(new_storage_key);
+  DCHECK(entity->storage_key().empty());
+  DCHECK(storage_key_to_tag_hash_.find(storage_key) ==
+         storage_key_to_tag_hash_.end());
 
-  // Update storage key to tag hash map.
-  storage_key_to_tag_hash_.erase(old_storage_key);
-  storage_key_to_tag_hash_[new_storage_key] =
-      entity->metadata().client_tag_hash();
-
-  // Update metadata store.
-  metadata_change_list->ClearMetadata(old_storage_key);
-  metadata_change_list->UpdateMetadata(new_storage_key, entity->metadata());
+  storage_key_to_tag_hash_[storage_key] = client_tag_hash;
+  entity->SetStorageKey(storage_key);
+  metadata_change_list->UpdateMetadata(storage_key, entity->metadata());
 }
 
 void SharedModelTypeProcessor::FlushPendingCommitRequests() {
@@ -294,8 +289,8 @@
     return;
 
   // TODO(rlarocque): Do something smarter than iterate here.
-  for (auto it = entities_.begin(); it != entities_.end(); ++it) {
-    ProcessorEntityTracker* entity = it->second.get();
+  for (const auto& kv : entities_) {
+    ProcessorEntityTracker* entity = kv.second.get();
     if (entity->RequiresCommitRequest() && !entity->RequiresCommitData()) {
       CommitRequestData request;
       entity->InitializeCommitRequestData(&request);
@@ -372,10 +367,15 @@
     ProcessorEntityTracker* entity = ProcessUpdate(update, &entity_changes);
 
     if (!entity) {
-      // The update should be ignored.
+      // The update is either tombstone of entity that didn't exist locally or
+      // reflection, thus should be ignored.
       continue;
     }
-
+    if (entity->storage_key().empty()) {
+      // Storage key of this entity is not known yet. Don't update metadata, it
+      // will be done from UpdateStorageKey.
+      continue;
+    }
     if (entity->CanClearMetadata()) {
       metadata_changes->ClearMetadata(entity->storage_key());
       storage_key_to_tag_hash_.erase(entity->storage_key());
@@ -391,19 +391,24 @@
   }
 
   if (got_new_encryption_requirements) {
+    // TODO(pavely): Currently we recommit all entities. We should instead
+    // recommit only the ones whose encryption key doesn't match the one in
+    // DataTypeState. Work is tracked in http://crbug.com/727874.
     RecommitAllForEncryption(already_updated, metadata_changes.get());
   }
-
   // Inform the bridge of the new or updated data.
   base::Optional<ModelError> error =
       bridge_->ApplySyncChanges(std::move(metadata_changes), entity_changes);
-
   if (error) {
     ReportError(error.value());
-  } else {
-    // There may be new reasons to commit by the time this function is done.
-    FlushPendingCommitRequests();
+    return;
   }
+
+  // If there were trackers with empty storage keys, they should have been
+  // updated by bridge as part of ApplySyncChanges.
+  DCHECK(AllStorageKeysPopulated());
+  // There may be new reasons to commit by the time this function is done.
+  FlushPendingCommitRequests();
 }
 
 ProcessorEntityTracker* SharedModelTypeProcessor::ProcessUpdate(
@@ -412,40 +417,43 @@
   const EntityData& data = update.entity.value();
   const std::string& client_tag_hash = data.client_tag_hash;
   ProcessorEntityTracker* entity = GetEntityForTagHash(client_tag_hash);
-  if (entity == nullptr) {
-    if (data.is_deleted()) {
-      DLOG(WARNING) << "Received remote delete for a non-existing item."
-                    << " client_tag_hash: " << client_tag_hash;
-      return nullptr;
-    }
 
-    entity = CreateEntity(data);
-    entity_changes->push_back(
-        EntityChange::CreateAdd(entity->storage_key(), update.entity));
-    entity->RecordAcceptedUpdate(update);
-  } else if (entity->UpdateIsReflection(update.response_version)) {
+  // Handle corner cases first.
+  if (entity == nullptr && data.is_deleted()) {
+    // Local entity doesn't exist and update is tombstone.
+    DLOG(WARNING) << "Received remote delete for a non-existing item."
+                  << " client_tag_hash: " << client_tag_hash;
+    return nullptr;
+  }
+  if (entity && entity->UpdateIsReflection(update.response_version)) {
     // Seen this update before; just ignore it.
     return nullptr;
-  } else if (entity->IsUnsynced()) {
+  }
+
+  if (entity && entity->IsUnsynced()) {
+    // Handle conflict resolution.
     ConflictResolution::Type resolution_type =
         ResolveConflict(update, entity, entity_changes);
     UMA_HISTOGRAM_ENUMERATION("Sync.ResolveConflict", resolution_type,
                               ConflictResolution::TYPE_SIZE);
-  } else if (data.is_deleted()) {
-    // The entity was deleted; inform the bridge. Note that the local data
-    // can never be deleted at this point because it would have either been
-    // acked (the add case) or pending (the conflict case).
-    DCHECK(!entity->metadata().is_deleted());
-    entity_changes->push_back(
-        EntityChange::CreateDelete(entity->storage_key()));
-    entity->RecordAcceptedUpdate(update);
-  } else if (!entity->MatchesData(data)) {
-    // Specifics have changed, so update the bridge.
-    entity_changes->push_back(
-        EntityChange::CreateUpdate(entity->storage_key(), update.entity));
-    entity->RecordAcceptedUpdate(update);
   } else {
-    // No data change; still record that the update was received.
+    // Handle simple create/delete/update.
+    if (entity == nullptr) {
+      entity = CreateEntity(data);
+      entity_changes->push_back(
+          EntityChange::CreateAdd(entity->storage_key(), update.entity));
+    } else if (data.is_deleted()) {
+      // The entity was deleted; inform the bridge. Note that the local data
+      // can never be deleted at this point because it would have either been
+      // acked (the add case) or pending (the conflict case).
+      DCHECK(!entity->metadata().is_deleted());
+      entity_changes->push_back(
+          EntityChange::CreateDelete(entity->storage_key()));
+    } else if (!entity->MatchesData(data)) {
+      // Specifics have changed, so update the bridge.
+      entity_changes->push_back(
+          EntityChange::CreateUpdate(entity->storage_key(), update.entity));
+    }
     entity->RecordAcceptedUpdate(update);
   }
 
@@ -543,9 +551,14 @@
     MetadataChangeList* metadata_changes) {
   ModelTypeSyncBridge::StorageKeyList entities_needing_data;
 
-  for (auto it = entities_.begin(); it != entities_.end(); ++it) {
-    ProcessorEntityTracker* entity = it->second.get();
-    if (already_updated.find(entity->storage_key()) != already_updated.end()) {
+  for (const auto& kv : entities_) {
+    ProcessorEntityTracker* entity = kv.second.get();
+    if (entity->storage_key().empty() ||
+        (already_updated.find(entity->storage_key()) !=
+         already_updated.end())) {
+      // Entities with empty storage key were already processed. ProcessUpdate()
+      // incremented their sequence numbers and cached commit data. Their
+      // metadata will be persisted in UpdateStorageKey().
       continue;
     }
     entity->IncrementSequenceNumber();
@@ -574,7 +587,7 @@
 
   std::unique_ptr<MetadataChangeList> metadata_changes =
       bridge_->CreateMetadataChangeList();
-  EntityDataMap data_map;
+  EntityChangeList entity_data;
 
   model_type_state_ = model_type_state;
   metadata_changes->UpdateModelTypeState(model_type_state_);
@@ -586,22 +599,27 @@
       continue;
     }
     ProcessorEntityTracker* entity = CreateEntity(update.entity.value());
-    const std::string& storage_key = entity->storage_key();
     entity->RecordAcceptedUpdate(update);
-    metadata_changes->UpdateMetadata(storage_key, entity->metadata());
-    data_map[storage_key] = update.entity;
+    const std::string& storage_key = entity->storage_key();
+    entity_data.push_back(EntityChange::CreateAdd(storage_key, update.entity));
+    if (!storage_key.empty())
+      metadata_changes->UpdateMetadata(storage_key, entity->metadata());
   }
 
   // Let the bridge handle associating and merging the data.
   base::Optional<ModelError> error =
-      bridge_->MergeSyncData(std::move(metadata_changes), data_map);
-
+      bridge_->MergeSyncData(std::move(metadata_changes), entity_data);
   if (error) {
     ReportError(error.value());
-  } else {
-    // We may have new reasons to commit by the time this function is done.
-    FlushPendingCommitRequests();
+    return;
   }
+
+  // If there were trackers with empty storage keys, they should have been
+  // updated by bridge as part of MergeSyncData.
+  DCHECK(AllStorageKeysPopulated());
+
+  // We may have new reasons to commit by the time this function is done.
+  FlushPendingCommitRequests();
 }
 
 void SharedModelTypeProcessor::OnInitialPendingDataLoaded(
@@ -673,14 +691,16 @@
     const std::string& storage_key,
     const EntityData& data) {
   DCHECK(entities_.find(data.client_tag_hash) == entities_.end());
-  DCHECK(storage_key_to_tag_hash_.find(storage_key) ==
-         storage_key_to_tag_hash_.end());
+  DCHECK(!bridge_->SupportsGetStorageKey() || !storage_key.empty());
+  DCHECK(storage_key.empty() || storage_key_to_tag_hash_.find(storage_key) ==
+                                    storage_key_to_tag_hash_.end());
   std::unique_ptr<ProcessorEntityTracker> entity =
       ProcessorEntityTracker::CreateNew(storage_key, data.client_tag_hash,
                                         data.id, data.creation_time);
   ProcessorEntityTracker* entity_ptr = entity.get();
   entities_[data.client_tag_hash] = std::move(entity);
-  storage_key_to_tag_hash_[storage_key] = data.client_tag_hash;
+  if (!storage_key.empty())
+    storage_key_to_tag_hash_[storage_key] = data.client_tag_hash;
   return entity_ptr;
 }
 
@@ -688,7 +708,19 @@
     const EntityData& data) {
   // Verify the tag hash matches, may be relaxed in the future.
   DCHECK_EQ(data.client_tag_hash, GetHashForTag(bridge_->GetClientTag(data)));
-  return CreateEntity(bridge_->GetStorageKey(data), data);
+  std::string storage_key;
+  if (bridge_->SupportsGetStorageKey())
+    storage_key = bridge_->GetStorageKey(data);
+  return CreateEntity(storage_key, data);
+}
+
+bool SharedModelTypeProcessor::AllStorageKeysPopulated() const {
+  for (const auto& kv : entities_) {
+    ProcessorEntityTracker* entity = kv.second.get();
+    if (entity->storage_key().empty())
+      return false;
+  }
+  return true;
 }
 
 size_t SharedModelTypeProcessor::EstimateMemoryUsage() const {
diff --git a/components/sync/model_impl/shared_model_type_processor.h b/components/sync/model_impl/shared_model_type_processor.h
index d8b54483..17ac9e9 100644
--- a/components/sync/model_impl/shared_model_type_processor.h
+++ b/components/sync/model_impl/shared_model_type_processor.h
@@ -56,8 +56,8 @@
            MetadataChangeList* metadata_change_list) override;
   void Delete(const std::string& storage_key,
               MetadataChangeList* metadata_change_list) override;
-  void UpdateStorageKey(const std::string& old_storage_key,
-                        const std::string& new_storage_key,
+  void UpdateStorageKey(const EntityData& entity_data,
+                        const std::string& storage_key,
                         MetadataChangeList* metadata_change_list) override;
   void ModelReadyToSync(std::unique_ptr<MetadataBatch> batch) override;
   void OnSyncStarting(const ModelErrorHandler& error_handler,
@@ -145,6 +145,9 @@
   // Version of the above that generates a tag for |data|.
   ProcessorEntityTracker* CreateEntity(const EntityData& data);
 
+  // Returns true if all processor entity trackers have non-empty storage keys.
+  bool AllStorageKeysPopulated() const;
+
   /////////////////////
   // Processor state //
   /////////////////////
@@ -204,10 +207,15 @@
   // entities may not always contain model type data/specifics.
   std::map<std::string, std::unique_ptr<ProcessorEntityTracker>> entities_;
 
-  // The bridge wants to communicate entirely via storage keys that is free to
-  // define and can understand more easily. All of the sync machinery wants to
-  // use client tag hash. This mapping allows us to convert from storage key to
-  // client tag hash. The other direction can use |entities_|.
+  // The bridge wants to communicate entirely via storage keys that it is free
+  // to define and can understand more easily. All of the sync machinery wants
+  // to use client tag hash. This mapping allows us to convert from storage key
+  // to client tag hash. The other direction can use |entities_|.
+  // Entity is temporarily not included in this map for the duration of
+  // MergeSyncData/ApplySyncChanges call when the bridge doesn't support
+  // GetStorageKey(). In this case the bridge is responsible for updating
+  // storage key with UpdateStorageKey() call from within
+  // MergeSyncData/ApplySyncChanges.
   std::map<std::string, std::string> storage_key_to_tag_hash_;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/components/sync/model_impl/shared_model_type_processor_unittest.cc b/components/sync/model_impl/shared_model_type_processor_unittest.cc
index c7341b58..7715747 100644
--- a/components/sync/model_impl/shared_model_type_processor_unittest.cc
+++ b/components/sync/model_impl/shared_model_type_processor_unittest.cc
@@ -31,10 +31,7 @@
 
 namespace {
 
-// TODO(gangwu): crbug.com/719570 should assign kKey1 as "key1" after bug fixed.
-// Assign a prefix 'l' for kKey1 to let TestModelTypeSyncBridge::GetStorageKey
-// generate a random storage key for it.
-const char kKey1[] = "lkey1";
+const char kKey1[] = "key1";
 const char kKey2[] = "key2";
 const char kKey3[] = "key3";
 const char kKey4[] = "key4";
@@ -52,10 +49,6 @@
 // worker/processor will not have been initialized and thus empty.
 const EntitySpecifics kEmptySpecifics;
 
-const int invalidStorageKeySize = 64;
-
-static char InvalidStorageKeyPrefix[] = "InvalidStorageKey";
-
 EntitySpecifics GenerateSpecifics(const std::string& key,
                                   const std::string& value) {
   return FakeModelTypeSyncBridge::GenerateSpecifics(key, value);
@@ -66,15 +59,6 @@
   return FakeModelTypeSyncBridge::GenerateEntityData(key, value);
 }
 
-std::string GenerateInvalidStorageKey() {
-  return InvalidStorageKeyPrefix +
-         base::RandBytesAsString(invalidStorageKeySize);
-}
-
-bool IsInvalidStorageKey(const std::string& storage_key) {
-  return 0 == storage_key.find(InvalidStorageKeyPrefix);
-}
-
 class TestModelTypeSyncBridge : public FakeModelTypeSyncBridge {
  public:
   TestModelTypeSyncBridge()
@@ -92,6 +76,11 @@
     EXPECT_FALSE(data_callback_);
   }
 
+  std::string GetStorageKey(const EntityData& entity_data) override {
+    get_storage_key_call_count_++;
+    return FakeModelTypeSyncBridge::GetStorageKey(entity_data);
+  }
+
   void OnPendingCommitDataLoaded() {
     ASSERT_TRUE(data_callback_);
     data_callback_.Run();
@@ -114,66 +103,26 @@
   void ExpectSynchronousDataCallback() { synchronous_data_callback_ = true; }
 
   int merge_call_count() const { return merge_call_count_; }
+  int apply_call_count() const { return apply_call_count_; }
+  int get_storage_key_call_count() const { return get_storage_key_call_count_; }
 
   // FakeModelTypeSyncBridge overrides.
 
   base::Optional<ModelError> MergeSyncData(
-      std::unique_ptr<MetadataChangeList> metadata_changes,
-      EntityDataMap entity_data_map) override {
+      std::unique_ptr<MetadataChangeList> metadata_change_list,
+      EntityChangeList entity_data) override {
     merge_call_count_++;
-
-    std::map<std::string, std::string> updated_keys;
-
-    // Update storage key for entities with invalid one.
-    for (const auto& kv : entity_data_map) {
-      std::string storage_key = kv.first;
-      if (IsInvalidStorageKey(storage_key)) {
-        change_processor()->UpdateStorageKey(
-            storage_key, kv.second.value().specifics.preference().name(),
-            metadata_changes.get());
-        updated_keys[storage_key] =
-            kv.second.value().specifics.preference().name();
-      }
-    }
-
-    for (const auto& kv : updated_keys) {
-      DCHECK_NE(kv.first, kv.second);
-      entity_data_map[kv.second] = entity_data_map[kv.first];
-      entity_data_map.erase(kv.first);
-    }
-
-    return FakeModelTypeSyncBridge::MergeSyncData(std::move(metadata_changes),
-                                                  entity_data_map);
+    return FakeModelTypeSyncBridge::MergeSyncData(
+        std::move(metadata_change_list), entity_data);
   }
-
   base::Optional<ModelError> ApplySyncChanges(
-      std::unique_ptr<MetadataChangeList> metadata_changes,
+      std::unique_ptr<MetadataChangeList> metadata_change_list,
       EntityChangeList entity_changes) override {
-    EntityChangeList new_changes;
-    for (EntityChangeList::iterator iter = entity_changes.begin();
-         iter != entity_changes.end();) {
-      std::string storage_key = iter->storage_key();
-      if (IsInvalidStorageKey(storage_key)) {
-        EXPECT_TRUE(iter->type() == EntityChange::ACTION_ADD);
-
-        change_processor()->UpdateStorageKey(
-            storage_key, iter->data().specifics.preference().name(),
-            metadata_changes.get());
-        new_changes.push_back(EntityChange::CreateAdd(
-            iter->data().specifics.preference().name(),
-            FakeModelTypeSyncBridge::CopyEntityData(iter->data())
-                ->PassToPtr()));
-        iter = entity_changes.erase(iter);
-      } else {
-        ++iter;
-      }
-    }
-    entity_changes.insert(entity_changes.end(), new_changes.begin(),
-                          new_changes.end());
-
+    apply_call_count_++;
     return FakeModelTypeSyncBridge::ApplySyncChanges(
-        std::move(metadata_changes), entity_changes);
+        std::move(metadata_change_list), entity_changes);
   }
+
   void GetData(StorageKeyList keys, DataCallback callback) override {
     if (synchronous_data_callback_) {
       synchronous_data_callback_ = false;
@@ -185,14 +134,6 @@
     }
   }
 
-  std::string GetStorageKey(const EntityData& entity_data) override {
-    std::string name = entity_data.specifics.preference().name();
-    if (name.length() > 0 && name[0] <= 'k') {
-      return entity_data.specifics.preference().name();
-    }
-    return GenerateInvalidStorageKey();
-  }
-
  private:
   void CaptureDataCallback(DataCallback callback,
                            std::unique_ptr<DataBatch> data) {
@@ -202,6 +143,8 @@
 
   // The number of times MergeSyncData has been called.
   int merge_call_count_ = 0;
+  int apply_call_count_ = 0;
+  int get_storage_key_call_count_ = 0;
 
   // Stores the data callback between GetData() and OnPendingCommitDataLoaded().
   base::Closure data_callback_;
@@ -767,7 +710,7 @@
   InitializeToReadyState();
   EXPECT_EQ(0U, worker()->GetNumPendingCommits());
 
-  std::unique_ptr<EntityData> entity_data = base::WrapUnique(new EntityData());
+  std::unique_ptr<EntityData> entity_data = base::MakeUnique<EntityData>();
   entity_data->specifics.mutable_preference()->set_name(kKey1);
   entity_data->specifics.mutable_preference()->set_value(kValue1);
 
@@ -1244,9 +1187,7 @@
 
   // Once we're ready to commit, all three local items should consider
   // themselves uncommitted and pending for commit.
-  // The hashes need to be in alphabet order of their storage keys since
-  // enabling sync trigered merge and it will reorder the commits.
-  worker()->VerifyPendingCommits({kHash2, kHash3, kHash1});
+  worker()->VerifyPendingCommits({kHash1, kHash2, kHash3});
 }
 
 // Test re-encrypt everything when desired encryption key changes.
@@ -1452,4 +1393,54 @@
   worker()->VerifyNthPendingCommit(1, kHash1, specifics2);
 }
 
+// Tests that UpdateStorageKey propagates storage key to ProcessorEntityTracker
+// and updates corresponding entity's metadata in MetadataChangeList.
+TEST_F(SharedModelTypeProcessorTest, UpdateStorageKey) {
+  // Setup bridge to not support calls to GetStorageKey. This will cause
+  // FakeModelTypeSyncBridge to call UpdateStorageKey for new entities and will
+  // DCHECK if GetStorageKey gets called.
+  bridge()->SetSupportsGetStorageKey(false);
+  ModelReadyToSync();
+  OnSyncStarting();
+
+  // Initial update from server should be handled by MergeSyncData.
+  worker()->UpdateFromServer(kHash1, GenerateSpecifics(kKey1, kValue1));
+  EXPECT_EQ(1, bridge()->merge_call_count());
+  EXPECT_EQ(1U, ProcessorEntityCount());
+  // Metadata should be written under kKey1. This means that UpdateStorageKey
+  // was called and value of storage key got propagated to MetadataChangeList.
+  EXPECT_TRUE(db().HasMetadata(kKey1));
+  EXPECT_EQ(1U, db().metadata_count());
+  EXPECT_EQ(0, bridge()->get_storage_key_call_count());
+
+  // Local update of kKey1 should affect the same entity. This ensures that
+  // storage key to client tag hash mapping was updated on the previous step.
+  bridge()->WriteItem(kKey1, kValue2);
+  EXPECT_EQ(1U, ProcessorEntityCount());
+  EXPECT_EQ(1U, db().metadata_count());
+
+  // Second update from server should be handled by ApplySyncChanges. Similarly
+  // It should call UpdateStorageKey, not GetStorageKey.
+  worker()->UpdateFromServer(kHash2, GenerateSpecifics(kKey2, kValue2));
+  EXPECT_EQ(1, bridge()->apply_call_count());
+  EXPECT_TRUE(db().HasMetadata(kKey2));
+  EXPECT_EQ(2U, db().metadata_count());
+  EXPECT_EQ(0, bridge()->get_storage_key_call_count());
+}
+
+// Tests that reencryption scenario works correctly for types that don't support
+// GetStorageKey(). When update from server delivers updated encryption key, all
+// entities should be reencrypted including new entity that just got received
+// from server.
+TEST_F(SharedModelTypeProcessorTest, ReencryptionWithEmptyStorageKeys) {
+  bridge()->SetSupportsGetStorageKey(false);
+  InitializeToReadyState();
+
+  UpdateResponseDataList update;
+  update.push_back(worker()->GenerateUpdateData(
+      kHash1, GenerateSpecifics(kKey1, kValue1), 1, "ek1"));
+  worker()->UpdateWithEncryptionKey("ek2", update);
+  worker()->VerifyPendingCommits({kHash1});
+}
+
 }  // namespace syncer
diff --git a/components/toolbar/BUILD.gn b/components/toolbar/BUILD.gn
index b540a4d..0f1cc9f0 100644
--- a/components/toolbar/BUILD.gn
+++ b/components/toolbar/BUILD.gn
@@ -24,6 +24,16 @@
   ]
 }
 
+static_library("vector_icons") {
+  sources = get_target_outputs(":toolbar_vector_icons")
+  deps = [
+    ":toolbar_vector_icons",
+    "//skia",
+    "//ui/gfx",
+    "//ui/vector_icons",
+  ]
+}
+
 static_library("toolbar") {
   sources = [
     "toolbar_model.h",
@@ -50,11 +60,7 @@
   ]
 
   if (!is_android && !is_ios) {
-    sources += get_target_outputs(":toolbar_vector_icons")
-    deps += [
-      ":toolbar_vector_icons",
-      "//ui/vector_icons",
-    ]
+    deps += [ ":vector_icons" ]
   }
 }
 
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index e669699..1affabc 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -372,7 +372,6 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 
   StartRepeatedVariationsSeedFetch();
-  client_->OnInitialStartup();
 }
 
 void VariationsService::StartRepeatedVariationsSeedFetch() {
diff --git a/components/variations/service/variations_service_client.h b/components/variations/service/variations_service_client.h
index b3758a6..89a27c1 100644
--- a/components/variations/service/variations_service_client.h
+++ b/components/variations/service/variations_service_client.h
@@ -56,9 +56,6 @@
   // |parameter| is an out-param that will contain the value of the restrict
   // parameter if true is returned.
   virtual bool OverridesRestrictParameter(std::string* parameter) = 0;
-
-  // Called from VariationsService::PerformPreMainMessageLoopStartup().
-  virtual void OnInitialStartup() {}
 };
 
 }  // namespace variations
diff --git a/components/variations/service/variations_service_unittest.cc b/components/variations/service/variations_service_unittest.cc
index d629387..880e6c4 100644
--- a/components/variations/service/variations_service_unittest.cc
+++ b/components/variations/service/variations_service_unittest.cc
@@ -64,7 +64,6 @@
     *parameter = restrict_parameter_;
     return true;
   }
-  void OnInitialStartup() override {}
 
   void set_restrict_parameter(const std::string& value) {
     restrict_parameter_ = value;
diff --git a/components/version_info/BUILD.gn b/components/version_info/BUILD.gn
index 1fe5cfb1..17b24f4 100644
--- a/components/version_info/BUILD.gn
+++ b/components/version_info/BUILD.gn
@@ -19,12 +19,24 @@
   deps = [
     ":generate_version_info",
     "//base",
-    "//components/strings",
   ]
 
   public_deps = [
     ":channel",
   ]
+}
+
+# Isolate the //ui/base dependency in this target.
+static_library("version_string") {
+  sources = [
+    "version_string.cc",
+    "version_string.h",
+  ]
+
+  deps = [
+    ":version_info",
+    "//components/strings",
+  ]
 
   if (use_unofficial_version_number) {
     defines = [ "USE_UNOFFICIAL_VERSION_NUMBER" ]
@@ -35,6 +47,8 @@
 source_set("channel") {
   sources = [
     "channel.h",
+    "channel_android.cc",
+    "channel_android.h",
   ]
 }
 
diff --git a/components/version_info/channel_android.cc b/components/version_info/channel_android.cc
new file mode 100644
index 0000000..b63b5ac
--- /dev/null
+++ b/components/version_info/channel_android.cc
@@ -0,0 +1,25 @@
+// 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.
+
+#include "components/version_info/channel_android.h"
+
+#include <cstring>
+
+namespace version_info {
+
+Channel ChannelFromPackageName(const char* package_name) {
+  if (!strcmp(package_name, "com.android.chrome") ||
+      !strcmp(package_name, "com.chrome.work"))
+    return Channel::STABLE;
+  if (!strcmp(package_name, "com.chrome.beta"))
+    return Channel::BETA;
+  if (!strcmp(package_name, "com.chrome.dev"))
+    return Channel::DEV;
+  if (!strcmp(package_name, "com.chrome.canary"))
+    return Channel::CANARY;
+
+  return Channel::UNKNOWN;
+}
+
+}  // namespace version_info
diff --git a/components/version_info/channel_android.h b/components/version_info/channel_android.h
new file mode 100644
index 0000000..d4e5cf20
--- /dev/null
+++ b/components/version_info/channel_android.h
@@ -0,0 +1,17 @@
+// 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.
+
+#ifndef COMPONENTS_VERSION_INFO_CHANNEL_ANDROID_H_
+#define COMPONENTS_VERSION_INFO_CHANNEL_ANDROID_H_
+
+#include "components/version_info/channel.h"
+
+namespace version_info {
+
+// Determine channel based on Chrome's package name.
+Channel ChannelFromPackageName(const char* package_name);
+
+}  // namespace version_info
+
+#endif  // COMPONENTS_VERSION_INFO_CHANNEL_ANDROID_H_
diff --git a/components/version_info/version_info.cc b/components/version_info/version_info.cc
index b12ad0ff..0b28f152 100644
--- a/components/version_info/version_info.cc
+++ b/components/version_info/version_info.cc
@@ -5,7 +5,6 @@
 #include "components/version_info/version_info.h"
 
 #include "build/build_config.h"
-#include "components/strings/grit/components_strings.h"
 #include "components/version_info/version_info_values.h"
 
 #if defined(USE_UNOFFICIAL_VERSION_NUMBER)
@@ -83,21 +82,4 @@
   return std::string();
 }
 
-std::string GetVersionStringWithModifier(const std::string& modifier) {
-  std::string current_version;
-  current_version += GetVersionNumber();
-#if defined(USE_UNOFFICIAL_VERSION_NUMBER)
-  current_version += " (";
-  current_version += l10n_util::GetStringUTF8(IDS_VERSION_UI_UNOFFICIAL);
-  current_version += " ";
-  current_version += GetLastChange();
-  current_version += " ";
-  current_version += GetOSType();
-  current_version += ")";
-#endif  // USE_UNOFFICIAL_VERSION_NUMBER
-  if (!modifier.empty())
-    current_version += " " + modifier;
-  return current_version;
-}
-
 }  // namespace version_info
diff --git a/components/version_info/version_info.h b/components/version_info/version_info.h
index 6212541..f25fc5ce 100644
--- a/components/version_info/version_info.h
+++ b/components/version_info/version_info.h
@@ -36,12 +36,6 @@
 // is branded or not and without any additional modifiers.
 std::string GetChannelString(Channel channel);
 
-// Returns a version string to be displayed in "About Chromium" dialog.
-// |modifier| is a string representation of the channel with system specific
-// information, e.g. "dev SyzyASan". It is appended to the returned version
-// information if non-empty.
-std::string GetVersionStringWithModifier(const std::string& modifier);
-
 }  // namespace version_info
 
 #endif  // COMPONENTS_VERSION_INFO_VERSION_INFO_H_
diff --git a/components/version_info/version_string.cc b/components/version_info/version_string.cc
new file mode 100644
index 0000000..c1e9dcda5
--- /dev/null
+++ b/components/version_info/version_string.cc
@@ -0,0 +1,33 @@
+// 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.
+
+#include "components/version_info/version_string.h"
+
+#include "components/strings/grit/components_strings.h"
+#include "components/version_info/version_info.h"
+
+#if defined(USE_UNOFFICIAL_VERSION_NUMBER)
+#include "ui/base/l10n/l10n_util.h"  // nogncheck
+#endif  // USE_UNOFFICIAL_VERSION_NUMBER
+
+namespace version_info {
+
+std::string GetVersionStringWithModifier(const std::string& modifier) {
+  std::string current_version;
+  current_version += GetVersionNumber();
+#if defined(USE_UNOFFICIAL_VERSION_NUMBER)
+  current_version += " (";
+  current_version += l10n_util::GetStringUTF8(IDS_VERSION_UI_UNOFFICIAL);
+  current_version += " ";
+  current_version += GetLastChange();
+  current_version += " ";
+  current_version += GetOSType();
+  current_version += ")";
+#endif  // USE_UNOFFICIAL_VERSION_NUMBER
+  if (!modifier.empty())
+    current_version += " " + modifier;
+  return current_version;
+}
+
+}  // namespace version_info
diff --git a/components/version_info/version_string.h b/components/version_info/version_string.h
new file mode 100644
index 0000000..083cfbf
--- /dev/null
+++ b/components/version_info/version_string.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef COMPONENTS_VERSION_INFO_VERSION_STRING_H_
+#define COMPONENTS_VERSION_INFO_VERSION_STRING_H_
+
+#include <string>
+
+namespace version_info {
+
+// Returns a version string to be displayed in "About Chromium" dialog.
+// |modifier| is a string representation of the channel with system specific
+// information, e.g. "dev SyzyASan". It is appended to the returned version
+// information if non-empty.
+std::string GetVersionStringWithModifier(const std::string& modifier);
+
+}  // namespace version_info
+
+#endif  // COMPONENTS_VERSION_INFO_VERSION_STRING_H_
diff --git a/content/child/BUILD.gn b/content/child/BUILD.gn
index 771e200..37f961b1 100644
--- a/content/child/BUILD.gn
+++ b/content/child/BUILD.gn
@@ -175,8 +175,6 @@
     "sync_load_response.h",
     "thread_safe_sender.cc",
     "thread_safe_sender.h",
-    "throttling_url_loader.cc",
-    "throttling_url_loader.h",
     "url_loader_client_impl.cc",
     "url_loader_client_impl.h",
     "url_response_body_consumer.cc",
diff --git a/content/child/request_extra_data.h b/content/child/request_extra_data.h
index 79fafdc..2855b49 100644
--- a/content/child/request_extra_data.h
+++ b/content/child/request_extra_data.h
@@ -13,7 +13,7 @@
 #include "content/common/content_export.h"
 #include "content/common/navigation_params.h"
 #include "content/common/url_loader_factory.mojom.h"
-#include "content/public/child/url_loader_throttle.h"
+#include "content/public/common/url_loader_throttle.h"
 #include "third_party/WebKit/public/platform/WebPageVisibilityState.h"
 #include "third_party/WebKit/public/platform/WebString.h"
 #include "third_party/WebKit/public/platform/WebURLRequest.h"
diff --git a/content/child/resource_dispatcher.cc b/content/child/resource_dispatcher.cc
index 218efb1..f9768451 100644
--- a/content/child/resource_dispatcher.cc
+++ b/content/child/resource_dispatcher.cc
@@ -27,13 +27,13 @@
 #include "content/child/shared_memory_received_data_factory.h"
 #include "content/child/site_isolation_stats_gatherer.h"
 #include "content/child/sync_load_response.h"
-#include "content/child/throttling_url_loader.h"
 #include "content/child/url_loader_client_impl.h"
 #include "content/common/inter_process_time_ticks_converter.h"
 #include "content/common/navigation_params.h"
 #include "content/common/resource_messages.h"
 #include "content/common/resource_request.h"
 #include "content/common/resource_request_completion_status.h"
+#include "content/common/throttling_url_loader.h"
 #include "content/public/child/fixed_received_data.h"
 #include "content/public/child/request_peer.h"
 #include "content/public/child/resource_dispatcher_delegate.h"
diff --git a/content/child/resource_dispatcher.h b/content/child/resource_dispatcher.h
index 82708ba6..091bf39 100644
--- a/content/child/resource_dispatcher.h
+++ b/content/child/resource_dispatcher.h
@@ -23,8 +23,8 @@
 #include "base/time/time.h"
 #include "content/common/content_export.h"
 #include "content/common/url_loader.mojom.h"
-#include "content/public/child/url_loader_throttle.h"
 #include "content/public/common/resource_type.h"
+#include "content/public/common/url_loader_throttle.h"
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_sender.h"
 #include "mojo/public/cpp/system/data_pipe.h"
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index d5a4acb..5a37ace 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -342,6 +342,8 @@
     "text_input_client_messages.h",
     "text_input_state.cc",
     "text_input_state.h",
+    "throttling_url_loader.cc",
+    "throttling_url_loader.h",
     "unique_name_helper.cc",
     "unique_name_helper.h",
     "url_request_struct_traits.cc",
diff --git a/content/child/throttling_url_loader.cc b/content/common/throttling_url_loader.cc
similarity index 99%
rename from content/child/throttling_url_loader.cc
rename to content/common/throttling_url_loader.cc
index fc77bc1..9f258c9d 100644
--- a/content/child/throttling_url_loader.cc
+++ b/content/common/throttling_url_loader.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 "content/child/throttling_url_loader.h"
+#include "content/common/throttling_url_loader.h"
 
 namespace content {
 
diff --git a/content/child/throttling_url_loader.h b/content/common/throttling_url_loader.h
similarity index 94%
rename from content/child/throttling_url_loader.h
rename to content/common/throttling_url_loader.h
index 49615f0..202d51dc 100644
--- a/content/child/throttling_url_loader.h
+++ b/content/common/throttling_url_loader.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_CHILD_THROTTLING_URL_LOADER_H_
-#define CONTENT_CHILD_THROTTLING_URL_LOADER_H_
+#ifndef CONTENT_COMMON_THROTTLING_URL_LOADER_H_
+#define CONTENT_COMMON_THROTTLING_URL_LOADER_H_
 
 #include <memory>
 
 #include "content/common/content_export.h"
 #include "content/common/url_loader.mojom.h"
 #include "content/common/url_loader_factory.mojom.h"
-#include "content/public/child/url_loader_throttle.h"
+#include "content/public/common/url_loader_throttle.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
 namespace content {
@@ -114,4 +114,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_CHILD_THROTTLING_URL_LOADER_H_
+#endif  // CONTENT_COMMON_THROTTLING_URL_LOADER_H_
diff --git a/content/child/throttling_url_loader_unittest.cc b/content/common/throttling_url_loader_unittest.cc
similarity index 99%
rename from content/child/throttling_url_loader_unittest.cc
rename to content/common/throttling_url_loader_unittest.cc
index 96b8b919..b361baf 100644
--- a/content/child/throttling_url_loader_unittest.cc
+++ b/content/common/throttling_url_loader_unittest.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/child/throttling_url_loader.h"
+#include "content/common/throttling_url_loader.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "content/common/url_loader.mojom.h"
 #include "content/common/url_loader_factory.mojom.h"
-#include "content/public/child/url_loader_throttle.h"
+#include "content/public/common/url_loader_throttle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
diff --git a/content/public/child/BUILD.gn b/content/public/child/BUILD.gn
index dd0b85e..36a0155 100644
--- a/content/public/child/BUILD.gn
+++ b/content/public/child/BUILD.gn
@@ -35,7 +35,6 @@
     "image_decoder_utils.h",
     "request_peer.h",
     "resource_dispatcher_delegate.h",
-    "url_loader_throttle.h",
     "v8_value_converter.h",
     "worker_thread.h",
   ]
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 18bdf6c0..fe2f2e5 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -238,6 +238,7 @@
     "url_constants.cc",
     "url_constants.h",
     "url_fetcher.h",
+    "url_loader_throttle.h",
     "url_utils.cc",
     "url_utils.h",
     "user_agent.h",
diff --git a/content/public/child/url_loader_throttle.h b/content/public/common/url_loader_throttle.h
similarity index 92%
rename from content/public/child/url_loader_throttle.h
rename to content/public/common/url_loader_throttle.h
index e72a4ff..e288f5f0 100644
--- a/content/public/child/url_loader_throttle.h
+++ b/content/public/common/url_loader_throttle.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 CONTENT_PUBLIC_RENDERER_URL_LOADER_THROTTLE_H_
-#define CONTENT_PUBLIC_RENDERER_URL_LOADER_THROTTLE_H_
+#ifndef CONTENT_PUBLIC_COMMON_URL_LOADER_THROTTLE_H_
+#define CONTENT_PUBLIC_COMMON_URL_LOADER_THROTTLE_H_
 
 #include "content/common/content_export.h"
 #include "content/public/common/resource_type.h"
@@ -64,4 +64,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_PUBLIC_RENDERER_URL_LOADER_THROTTLE_H_
+#endif  // CONTENT_PUBLIC_COMMON_URL_LOADER_THROTTLE_H_
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 8ff540e..b80afe9 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -71,7 +71,6 @@
 #include "content/common/swapped_out_messages.h"
 #include "content/common/view_messages.h"
 #include "content/common/worker_url_loader_factory_provider.mojom.h"
-#include "content/public/child/url_loader_throttle.h"
 #include "content/public/common/appcache_info.h"
 #include "content/public/common/associated_interface_provider.h"
 #include "content/public/common/bindings_policy.h"
@@ -87,6 +86,7 @@
 #include "content/public/common/resource_response.h"
 #include "content/public/common/service_manager_connection.h"
 #include "content/public/common/url_constants.h"
+#include "content/public/common/url_loader_throttle.h"
 #include "content/public/common/url_utils.h"
 #include "content/public/renderer/browser_plugin_delegate.h"
 #include "content/public/renderer/content_renderer_client.h"
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index f756353..cd76192 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1349,7 +1349,6 @@
     "../child/site_isolation_stats_gatherer_unittest.cc",
     "../child/test_request_peer.cc",
     "../child/test_request_peer.h",
-    "../child/throttling_url_loader_unittest.cc",
     "../child/url_loader_client_impl_unittest.cc",
     "../child/url_response_body_consumer_unittest.cc",
     "../child/v8_value_converter_impl_unittest.cc",
@@ -1396,6 +1395,7 @@
     "../common/sandbox_mac_unittest_helper.mm",
     "../common/service_manager/service_manager_connection_impl_unittest.cc",
     "../common/service_worker/service_worker_utils_unittest.cc",
+    "../common/throttling_url_loader_unittest.cc",
     "../common/webplugininfo_unittest.cc",
     "../network/url_loader_unittest.cc",
     "../public/test/referrer_unittest.cc",
diff --git a/device/vr/vr_service_impl.cc b/device/vr/vr_service_impl.cc
index 81223fa..db03bea 100644
--- a/device/vr/vr_service_impl.cc
+++ b/device/vr/vr_service_impl.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/auto_reset.h"
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "device/vr/vr_device.h"
@@ -16,7 +17,9 @@
 
 VRServiceImpl::VRServiceImpl()
     : listening_for_activate_(false),
+      in_set_client_(false),
       connected_devices_(0),
+      handled_devices_(0),
       weak_ptr_factory_(this) {}
 
 VRServiceImpl::~VRServiceImpl() {
@@ -36,13 +39,28 @@
 void VRServiceImpl::SetClient(mojom::VRServiceClientPtr service_client,
                               SetClientCallback callback) {
   DCHECK(!client_.get());
+
+  // Set a scoped variable to true so we can verify we are in the same stack.
+  base::AutoReset<bool> set_client(&in_set_client_, true);
+
   client_ = std::move(service_client);
   // Once a client has been connected AddService will force any VRDisplays to
   // send ConnectDevice to it so that it's populated with the currently active
   // displays. Thereafter it will stay up to date by virtue of listening for new
   // connected events.
-  VRDeviceManager::GetInstance()->AddService(this);
-  std::move(callback).Run(connected_devices_);
+
+  VRDeviceManager* device_manager = VRDeviceManager::GetInstance();
+  device_manager->AddService(this);
+  unsigned expected_devices = device_manager->GetNumberOfConnectedDevices();
+  // TODO(amp): Remove this count based synchronization.
+  // If libraries are not loaded, new devices will immediatly be handled but not
+  // connect, return only those devices which have already connected.
+  // If libraries were loaded then all devices may not be handled yet so return
+  // the number we expect to eventually connect.
+  if (expected_devices == handled_devices_) {
+    expected_devices = connected_devices_;
+  }
+  std::move(callback).Run(expected_devices);
 }
 
 void VRServiceImpl::ConnectDevice(VRDevice* device) {
@@ -75,12 +93,15 @@
   if (!display_info) {
     // If we get passed a null display info it means the device does not exist.
     // This can happen for example if VR services are not installed. We will not
-    // instantiate a display in this case and don't count it as connected.
-    return;
+    // instantiate a display in this case and don't count it as connected, but
+    // we do mark that we have handled it and verify we haven't changed stacks.
+    DCHECK(in_set_client_);
+  } else {
+    displays_[device] = base::MakeUnique<VRDisplayImpl>(
+        device, this, client_.get(), std::move(display_info));
+    connected_devices_++;
   }
-  displays_[device] = base::MakeUnique<VRDisplayImpl>(
-      device, this, client_.get(), std::move(display_info));
-  connected_devices_++;
+  handled_devices_++;
 }
 
 VRDisplayImpl* VRServiceImpl::GetVRDisplayImplForTesting(VRDevice* device) {
diff --git a/device/vr/vr_service_impl.h b/device/vr/vr_service_impl.h
index e66e54c..6c4c2a62 100644
--- a/device/vr/vr_service_impl.h
+++ b/device/vr/vr_service_impl.h
@@ -60,7 +60,9 @@
   mojom::VRServiceClientPtr client_;
 
   bool listening_for_activate_;
+  bool in_set_client_;
   unsigned connected_devices_;
+  unsigned handled_devices_;
 
   base::WeakPtrFactory<VRServiceImpl> weak_ptr_factory_;
 
diff --git a/docs/testing/layout_tests.md b/docs/testing/layout_tests.md
index 02294ff..23c104dd 100644
--- a/docs/testing/layout_tests.md
+++ b/docs/testing/layout_tests.md
@@ -454,6 +454,42 @@
 When you rebaseline a test, make sure your commit description explains why the
 test is being re-baselined.
 
+### Rebaselining flag-specific expectations
+
+Though we prefer the Rebaseline Tool to local rebaselining, the Rebaseline Tool
+doesn't support rebaselining flag-specific expectations.
+
+```bash
+cd src/third_party/WebKit
+Tools/Script/run-webkit-tests --additional-driver-flag=--enable-flag --new-flag-specific-baseline foo/bar/test.html
+```
+
+New baselines will be created in the flag-specific baselines directory, e.g.
+`LayoutTests/flag-specific/enable-flag/foo/bar/test-expected.{txt,png}`.
+
+Then you can commit the new baselines and upload the patch for review.
+
+However, it's difficult for reviewers to review the patch containing only new
+files. You can follow the steps below for easier review. The steps require a
+try bot already setup for the flag-specific tests (e.g.
+`linux_layout_tests_slimming_paint_v2` for `--enable-slimming-paint-v2`).
+
+1. Before the rebaseline, upload a patch for which the tests to be rebaselined
+   will fail. If the tests are expected to fail in
+   `LayoutTests/FlagExpectations/<flag>`, remove the failure expectation lines
+   in the patch.
+
+2. Schedule a try job on the try bot for the flag.
+
+3. Rebaseline locally, and upload a new version of patch containing the new
+   baselines to the same CL.
+
+4. After the try job finishes, request review of the CL and tell the reviewer
+   the URL of the `layout_test_result` link under the `archive_webkit_tests_results`
+   step of the try job. The reviewer should review the layout test result
+   assuming that the new baselines in the latest version of the CL are the same
+   as the actual results in the linked page.
+
 ## web-platform-tests
 
 In addition to layout tests developed and run just by the Blink team, there is
diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json
index 47e389b..4c5b65e 100644
--- a/extensions/common/api/_permission_features.json
+++ b/extensions/common/api/_permission_features.json
@@ -249,6 +249,15 @@
     "channel": "stable",
     "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
   },
+  "lockScreen": {
+    "channel": "trunk",
+    "extension_types": ["platform_app"],
+    "platforms": ["chromeos"],
+    "whitelist":  [
+      "6F9C741B8E0E546652134F1138DF0284A7C9B21E",  // http://crbug.com/728309
+      "47448626CB266C60AA2404E4EB426E025DF497DF"   // http://crbug.com/728309
+    ]
+  },
   "mediaPerceptionPrivate": [{
     "channel": "dev",
     "extension_types": ["platform_app"],
diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h
index 73576b3..8350d43 100644
--- a/extensions/common/permissions/api_permission.h
+++ b/extensions/common/permissions/api_permission.h
@@ -245,6 +245,7 @@
     kVirtualKeyboard,
     kNetworkingCastPrivate,
     kMediaPerceptionPrivate,
+    kLockScreen,
     // Last entry: Add new entries above and ensure to update the
     // "ExtensionPermission3" enum in tools/metrics/histograms/histograms.xml
     // (by running update_extension_permission.py).
diff --git a/extensions/common/permissions/extensions_api_permissions.cc b/extensions/common/permissions/extensions_api_permissions.cc
index 12d69914..e9eba198 100644
--- a/extensions/common/permissions/extensions_api_permissions.cc
+++ b/extensions/common/permissions/extensions_api_permissions.cc
@@ -63,6 +63,7 @@
       {APIPermission::kOverrideEscFullscreen,
        "app.window.fullscreen.overrideEsc"},
       {APIPermission::kIdle, "idle"},
+      {APIPermission::kLockScreen, "lockScreen"},
       {APIPermission::kMediaPerceptionPrivate, "mediaPerceptionPrivate"},
       {APIPermission::kMetricsPrivate, "metricsPrivate",
        APIPermissionInfo::kFlagCannotBeOptional},
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
index 59fce4f3..709cd325 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
@@ -34,12 +34,12 @@
 #include "components/metrics/net/cellular_logic_helper.h"
 #include "components/metrics/net/net_metrics_log_uploader.h"
 #include "components/metrics/net/network_metrics_provider.h"
-#include "components/metrics/net/version_utils.h"
 #include "components/metrics/profiler/profiler_metrics_provider.h"
 #include "components/metrics/profiler/tracking_synchronizer.h"
 #include "components/metrics/stability_metrics_helper.h"
 #include "components/metrics/ui/screen_info_metrics_provider.h"
 #include "components/metrics/url_constants.h"
+#include "components/metrics/version_utils.h"
 #include "components/omnibox/browser/omnibox_metrics_provider.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
diff --git a/ios/chrome/browser/translate/translate_egtest.mm b/ios/chrome/browser/translate/translate_egtest.mm
index 1e40ad97..5b3dee8 100644
--- a/ios/chrome/browser/translate/translate_egtest.mm
+++ b/ios/chrome/browser/translate/translate_egtest.mm
@@ -83,14 +83,15 @@
     "<meta http-equiv=\"content-language\" content=\"it\">";
 
 // Various link components.
-const char kHttpServerDomain[] = "localhost";
+// TODO(crbug.com/729195): Re-write the hardcoded address.
+const char kHttpServerDomain[] = "127.0.0.1";
 const char kLanguagePath[] = "/languagepath/";
 const char kLinkPath[] = "/linkpath/";
 const char kSubresourcePath[] = "/subresourcepath/";
 const char kSomeLanguageUrl[] = "http://languagepath/?http=es";
 const char kFrenchPagePath[] = "/frenchpage/";
 const char kFrenchPageWithLinkPath[] = "/frenchpagewithlink/";
-const char kTranslateScriptPath[] = "/translatescript";
+const char kTranslateScriptPath[] = "/translatescript/";
 const char kTranslateScript[] = "Fake Translate Script";
 
 // Builds a HTML document with a French text and the given |html| and |meta|
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm b/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
index d1e78e8a..a5bffdb8 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
@@ -337,7 +337,9 @@
 
   // Test Omnibox URL
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
-                                          "localhost:8080/potato/")]
+                                          web::test::HttpServer::MakeUrl(
+                                              kDistillableURL)
+                                              .GetContent())]
       assertWithMatcher:grey_notNil()];
 
   // Test presence of online page
@@ -448,7 +450,8 @@
 // Tests that sharing a web page to the Reading List results in a snackbar
 // appearing, and that the Reading List entry is present in the Reading List.
 // Loads online version by tapping on entry.
-- (void)testSavingToReadingListAndLoadNormal {
+// TODO(crbug.com/724555): Re-enable the test.
+- (void)DISABLED_testSavingToReadingListAndLoadNormal {
   auto network_change_disabler =
       base::MakeUnique<net::NetworkChangeNotifier::DisableForTest>();
   auto wifi_network = base::MakeUnique<WifiNetworkChangeNotifier>();
@@ -487,7 +490,8 @@
 // Tests that sharing a web page to the Reading List results in a snackbar
 // appearing, and that the Reading List entry is present in the Reading List.
 // Loads offline version by tapping on entry without web server.
-- (void)testSavingToReadingListAndLoadNoNetwork {
+// TODO(crbug.com/724555): Re-enable the test.
+- (void)DISABLED_testSavingToReadingListAndLoadNoNetwork {
   auto network_change_disabler =
       base::MakeUnique<net::NetworkChangeNotifier::DisableForTest>();
   auto wifi_network = base::MakeUnique<WifiNetworkChangeNotifier>();
@@ -558,14 +562,18 @@
   TapEntry(pageTitle);
 
   AssertIsShowingDistillablePage(false);
-  // Reload should load online page.
-  chrome_test_util::GetCurrentWebState()->GetNavigationManager()->Reload(
-      web::ReloadType::NORMAL, false);
-  AssertIsShowingDistillablePage(true);
-  // Reload should load offline page.
-  chrome_test_util::GetCurrentWebState()->GetNavigationManager()->Reload(
-      web::ReloadType::NORMAL, false);
-  AssertIsShowingDistillablePage(false);
+
+  // TODO(crbug.com/724555): Re-enable the reload checks.
+  if (false) {
+    // Reload should load online page.
+    chrome_test_util::GetCurrentWebState()->GetNavigationManager()->Reload(
+        web::ReloadType::NORMAL, false);
+    AssertIsShowingDistillablePage(true);
+    // Reload should load offline page.
+    chrome_test_util::GetCurrentWebState()->GetNavigationManager()->Reload(
+        web::ReloadType::NORMAL, false);
+    AssertIsShowingDistillablePage(false);
+  }
 }
 
 // Tests that only the "Edit" button is showing when not editing.
diff --git a/ios/chrome/browser/variations/ios_chrome_variations_service_client.cc b/ios/chrome/browser/variations/ios_chrome_variations_service_client.cc
index 066d53e9..3953743 100644
--- a/ios/chrome/browser/variations/ios_chrome_variations_service_client.cc
+++ b/ios/chrome/browser/variations/ios_chrome_variations_service_client.cc
@@ -58,5 +58,3 @@
     std::string* parameter) {
   return false;
 }
-
-void IOSChromeVariationsServiceClient::OnInitialStartup() {}
diff --git a/ios/chrome/browser/variations/ios_chrome_variations_service_client.h b/ios/chrome/browser/variations/ios_chrome_variations_service_client.h
index 6be4a28..6f6a3150 100644
--- a/ios/chrome/browser/variations/ios_chrome_variations_service_client.h
+++ b/ios/chrome/browser/variations/ios_chrome_variations_service_client.h
@@ -25,7 +25,6 @@
   network_time::NetworkTimeTracker* GetNetworkTimeTracker() override;
   version_info::Channel GetChannel() override;
   bool OverridesRestrictParameter(std::string* parameter) override;
-  void OnInitialStartup() override;
 
   DISALLOW_COPY_AND_ASSIGN(IOSChromeVariationsServiceClient);
 };
diff --git a/ios/chrome/common/BUILD.gn b/ios/chrome/common/BUILD.gn
index d0b2a47..f38e270 100644
--- a/ios/chrome/common/BUILD.gn
+++ b/ios/chrome/common/BUILD.gn
@@ -21,6 +21,7 @@
   deps = [
     "//base",
     "//components/version_info",
+    "//components/version_info:version_string",
     "//ios/chrome/common/app_group:main_app",
     "//ios/chrome/common/physical_web",
     "//net",
diff --git a/ios/chrome/common/channel_info.mm b/ios/chrome/common/channel_info.mm
index 6e40f812..1ee7a6b 100644
--- a/ios/chrome/common/channel_info.mm
+++ b/ios/chrome/common/channel_info.mm
@@ -11,6 +11,7 @@
 #include "base/profiler/scoped_tracker.h"
 #import "base/strings/sys_string_conversions.h"
 #include "components/version_info/version_info.h"
+#include "components/version_info/version_string.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
diff --git a/ios/chrome/test/earl_grey/chrome_test_case.mm b/ios/chrome/test/earl_grey/chrome_test_case.mm
index 07383641..6da92b8 100644
--- a/ios/chrome/test/earl_grey/chrome_test_case.mm
+++ b/ios/chrome/test/earl_grey/chrome_test_case.mm
@@ -260,7 +260,6 @@
 
 + (void)startHTTPServer {
   web::test::HttpServer& server = web::test::HttpServer::GetSharedInstance();
-  DCHECK(!server.IsRunning());
   server.StartOrDie();
 }
 
diff --git a/ios/chrome/today_extension/today_metrics_logger.mm b/ios/chrome/today_extension/today_metrics_logger.mm
index cde4862..55c2ddc 100644
--- a/ios/chrome/today_extension/today_metrics_logger.mm
+++ b/ios/chrome/today_extension/today_metrics_logger.mm
@@ -22,7 +22,7 @@
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_provider.h"
 #include "components/metrics/metrics_service_client.h"
-#include "components/metrics/net/version_utils.h"
+#include "components/metrics/version_utils.h"
 #include "components/prefs/json_pref_store.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
diff --git a/ios/web/public/test/http_server/BUILD.gn b/ios/web/public/test/http_server/BUILD.gn
index 76175f2f..d96c5b40 100644
--- a/ios/web/public/test/http_server/BUILD.gn
+++ b/ios/web/public/test/http_server/BUILD.gn
@@ -10,6 +10,7 @@
     "//base/test:test_support",
     "//ios/third_party/gcdwebserver",
     "//net",
+    "//net:test_support",
     "//url",
   ]
 
@@ -20,10 +21,6 @@
     "delayed_response_provider.mm",
     "error_page_response_provider.h",
     "error_page_response_provider.mm",
-    "file_based_response_provider.h",
-    "file_based_response_provider.mm",
-    "file_based_response_provider_impl.h",
-    "file_based_response_provider_impl.mm",
     "html_response_provider.h",
     "html_response_provider.mm",
     "html_response_provider_impl.h",
diff --git a/ios/web/public/test/http_server/data_response_provider.h b/ios/web/public/test/http_server/data_response_provider.h
index d5c999b4..2372c7e 100644
--- a/ios/web/public/test/http_server/data_response_provider.h
+++ b/ios/web/public/test/http_server/data_response_provider.h
@@ -14,14 +14,15 @@
 
 namespace web {
 
-// An abstract ResponseProvider that returns a GCDWebServerDataResponse for a
+// An abstract ResponseProvider that returns a test_server::HttpResponse for a
 // request. This class encapsulates the logic to convert the response headers
 // and body received from |GetResponseHeadersAndBody| into a
 // GCDWebServerDataResponse.
 class DataResponseProvider : public ResponseProvider {
  public:
   // ResponseProvider implementation.
-  GCDWebServerResponse* GetGCDWebServerResponse(const Request& request) final;
+  std::unique_ptr<net::test_server::HttpResponse> GetEmbeddedTestServerResponse(
+      const Request& request) final;
 
   // Returns the headers and the response body. Will only be called if the
   // provider can handle the request.
diff --git a/ios/web/public/test/http_server/data_response_provider.mm b/ios/web/public/test/http_server/data_response_provider.mm
index 3b282e7a..3838e2a 100644
--- a/ios/web/public/test/http_server/data_response_provider.mm
+++ b/ios/web/public/test/http_server/data_response_provider.mm
@@ -4,19 +4,24 @@
 
 #import "ios/web/public/test/http_server/data_response_provider.h"
 
+#include "base/memory/ptr_util.h"
 #include "base/strings/sys_string_conversions.h"
-#import "ios/third_party/gcdwebserver/src/GCDWebServer/Responses/GCDWebServerDataResponse.h"
 
 namespace web {
 
-GCDWebServerResponse* DataResponseProvider::GetGCDWebServerResponse(
-    const Request& request) {
+std::unique_ptr<net::test_server::HttpResponse>
+DataResponseProvider::GetEmbeddedTestServerResponse(const Request& request) {
   std::string response_body;
   scoped_refptr<net::HttpResponseHeaders> response_headers;
   GetResponseHeadersAndBody(request, &response_headers, &response_body);
-  GCDWebServerDataResponse* data_response = [GCDWebServerDataResponse
-      responseWithHTML:base::SysUTF8ToNSString(response_body)];
-  data_response.statusCode = response_headers->response_code();
+
+  std::unique_ptr<net::test_server::BasicHttpResponse> data_response =
+      base::MakeUnique<net::test_server::BasicHttpResponse>();
+
+  data_response->set_code(
+      static_cast<net::HttpStatusCode>(response_headers->response_code()));
+  data_response->set_content(response_body);
+
   size_t iter = 0;
   std::string name;
   std::string value;
@@ -24,13 +29,12 @@
     // TODO(crbug.com/435350): Extract out other names that can't be set by
     // using the |setValue:forAdditionalHeader:| API such as "ETag" etc.
     if (name == "Content-type") {
-      data_response.contentType = base::SysUTF8ToNSString(value);
+      data_response->set_content_type(value);
       continue;
     }
-    [data_response setValue:base::SysUTF8ToNSString(value)
-        forAdditionalHeader:base::SysUTF8ToNSString(name)];
+    data_response->AddCustomHeader(name, value);
   }
-  return data_response;
+  return std::move(data_response);
 }
 
 }  // namespace web
diff --git a/ios/web/public/test/http_server/delayed_response_provider.h b/ios/web/public/test/http_server/delayed_response_provider.h
index d196911..955b3da1 100644
--- a/ios/web/public/test/http_server/delayed_response_provider.h
+++ b/ios/web/public/test/http_server/delayed_response_provider.h
@@ -25,10 +25,9 @@
   // Forwards to |delayed_provider_|.
   bool CanHandleRequest(const Request& request) override;
 
-  // Creates a GCDWebServerResponse that will proxy the object returned by
-  // Forwards to |delayed_provider_->GetGCDWebServerResponse(request)|.
-  // The read operation will be delayed by |delay_| seconds.
-  GCDWebServerResponse* GetGCDWebServerResponse(
+  // Creates a test_server::HttpResponse that will delay the read operation
+  // by |delay_| seconds.
+  std::unique_ptr<net::test_server::HttpResponse> GetEmbeddedTestServerResponse(
       const Request& request) override;
 
  private:
diff --git a/ios/web/public/test/http_server/delayed_response_provider.mm b/ios/web/public/test/http_server/delayed_response_provider.mm
index fd20ac1..bf7ba92 100644
--- a/ios/web/public/test/http_server/delayed_response_provider.mm
+++ b/ios/web/public/test/http_server/delayed_response_provider.mm
@@ -6,100 +6,35 @@
 
 #import <Foundation/Foundation.h>
 
+#include "base/bind.h"
 #import "base/ios/weak_nsobject.h"
 #import "base/mac/foundation_util.h"
 #import "base/mac/scoped_nsobject.h"
-#import "ios/third_party/gcdwebserver/src/GCDWebServer/Responses/GCDWebServerDataResponse.h"
-
-// A proxy class that will forward all messages to the |_response|.
-// It will delay read operation  |_delay| seconds.
-@interface DelayedGCDWebServerResponse : NSProxy {
-  base::scoped_nsobject<GCDWebServerResponse> _response;
-  NSTimeInterval _delay;
-}
-
-+ (instancetype)responseWithServerResponse:(GCDWebServerResponse*)response
-                                     delay:(NSTimeInterval)delay;
-- (instancetype)initWithServerResponse:(GCDWebServerResponse*)response
-                                 delay:(NSTimeInterval)delay;
-@end
-
-@implementation DelayedGCDWebServerResponse
-+ (instancetype)responseWithServerResponse:(GCDWebServerResponse*)response
-                                     delay:(NSTimeInterval)delay {
-  return [[[DelayedGCDWebServerResponse alloc] initWithServerResponse:response
-                                                                delay:delay]
-      autorelease];
-}
-
-- (instancetype)initWithServerResponse:(GCDWebServerResponse*)response
-                                 delay:(NSTimeInterval)delay {
-  _response.reset([response retain]);
-  _delay = delay;
-  return self;
-}
-
-#pragma mark - GCDWebServerResponse
-
-- (void)performReadDataWithCompletion:
-    (GCDWebServerBodyReaderCompletionBlock)block {
-  [self asyncReadDataWithCompletion:block];
-}
-
-- (void)asyncReadDataWithCompletion:
-    (GCDWebServerBodyReaderCompletionBlock)block {
-  dispatch_async(dispatch_get_main_queue(), ^{
-    base::WeakNSObject<GCDWebServerResponse> weakResponse(_response);
-    if ([_response
-            respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
-      dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
-                                   static_cast<int64_t>(_delay * NSEC_PER_SEC)),
-                     dispatch_get_main_queue(), ^{
-                       base::scoped_nsobject<GCDWebServerResponse> response(
-                           [weakResponse retain]);
-                       [response asyncReadDataWithCompletion:block];
-                     });
-    } else {
-      dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
-                                   static_cast<int64_t>(_delay * NSEC_PER_SEC)),
-                     dispatch_get_main_queue(), ^{
-                       base::scoped_nsobject<GCDWebServerResponse> response(
-                           [weakResponse retain]);
-                       if (!response) {
-                         return;
-                       }
-                       NSError* error = nil;
-                       NSData* data = [response readData:&error];
-                       block(data, error);
-                     });
-    }
-  });
-}
-
-#pragma mark - NSProxy
-
-- (BOOL)conformsToProtocol:(Protocol*)protocol {
-  return [[_response class] conformsToProtocol:protocol];
-}
-
-- (BOOL)respondsToSelector:(SEL)selector {
-  return [[_response class] instancesRespondToSelector:selector];
-}
-
-- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
-  return [[_response class] instanceMethodSignatureForSelector:selector];
-}
-
-- (void)forwardInvocation:(NSInvocation*)invocation {
-  SEL selector = [invocation selector];
-  if ([_response respondsToSelector:selector])
-    [invocation invokeWithTarget:_response];
-}
-
-@end
+#include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/test/embedded_test_server/http_response.h"
 
 namespace web {
 
+// Delays |delay| seconds before sending a response to the client.
+class DelayedHttpResponse : public net::test_server::BasicHttpResponse {
+ public:
+  explicit DelayedHttpResponse(double delay) : delay_(delay) {}
+
+  void SendResponse(
+      const net::test_server::SendBytesCallback& send,
+      const net::test_server::SendCompleteCallback& done) override {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, base::Bind(send, ToResponseString(), done),
+        base::TimeDelta::FromSecondsD(delay_));
+  }
+
+ private:
+  const double delay_;
+
+  DISALLOW_COPY_AND_ASSIGN(DelayedHttpResponse);
+};
+
 DelayedResponseProvider::DelayedResponseProvider(
     std::unique_ptr<web::ResponseProvider> delayed_provider,
     double delay)
@@ -113,13 +48,12 @@
   return delayed_provider_->CanHandleRequest(request);
 }
 
-GCDWebServerResponse* DelayedResponseProvider::GetGCDWebServerResponse(
-    const Request& request) {
-  DelayedGCDWebServerResponse* response = [DelayedGCDWebServerResponse
-      responseWithServerResponse:delayed_provider_->GetGCDWebServerResponse(
-                                     request)
-                           delay:delay_];
-  return base::mac::ObjCCastStrict<GCDWebServerResponse>(response);
+std::unique_ptr<net::test_server::HttpResponse>
+DelayedResponseProvider::GetEmbeddedTestServerResponse(const Request& request) {
+  std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+      base::MakeUnique<DelayedHttpResponse>(delay_));
+  http_response->set_content_type("text/html");
+  http_response->set_content("Slow Page");
+  return std::move(http_response);
 }
-
 }  // namespace web
diff --git a/ios/web/public/test/http_server/file_based_response_provider.h b/ios/web/public/test/http_server/file_based_response_provider.h
deleted file mode 100644
index 5ed6455b..0000000
--- a/ios/web/public/test/http_server/file_based_response_provider.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2015 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_PUBLIC_TEST_HTTP_SERVER_FILE_BASED_RESPONSE_PROVIDER_H_
-#define IOS_WEB_PUBLIC_TEST_HTTP_SERVER_FILE_BASED_RESPONSE_PROVIDER_H_
-
-#include <memory>
-
-#include "base/compiler_specific.h"
-#import "ios/web/public/test/http_server/file_based_response_provider_impl.h"
-#import "ios/web/public/test/http_server/response_provider.h"
-
-namespace base {
-class FilePath;
-}
-
-namespace web {
-
-// FileBasedResponseProvider tries to resolve URL as if it were a path relative
-// to |path| on the filesystem.
-class FileBasedResponseProvider : public ResponseProvider {
- public:
-  explicit FileBasedResponseProvider(const base::FilePath& path);
-  ~FileBasedResponseProvider() override;
-
-  // web::ReponseProvider implementation.
-  bool CanHandleRequest(const Request& request) override;
-  GCDWebServerResponse* GetGCDWebServerResponse(
-      const Request& request) override;
-
- private:
-  std::unique_ptr<FileBasedResponseProviderImpl> response_provider_impl_;
-};
-}
-
-#endif  // IOS_WEB_PUBLIC_TEST_HTTP_SERVER_FILE_BASED_RESPONSE_PROVIDER_H_
diff --git a/ios/web/public/test/http_server/file_based_response_provider.mm b/ios/web/public/test/http_server/file_based_response_provider.mm
deleted file mode 100644
index 20ac8d8e..0000000
--- a/ios/web/public/test/http_server/file_based_response_provider.mm
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/web/public/test/http_server/file_based_response_provider.h"
-
-#include "base/files/file_path.h"
-#include "base/strings/sys_string_conversions.h"
-#import "ios/third_party/gcdwebserver/src/GCDWebServer/Responses/GCDWebServerFileResponse.h"
-
-namespace web {
-
-FileBasedResponseProvider::FileBasedResponseProvider(const base::FilePath& path)
-    : response_provider_impl_(new FileBasedResponseProviderImpl(path)) {}
-
-FileBasedResponseProvider::~FileBasedResponseProvider() {}
-
-bool FileBasedResponseProvider::CanHandleRequest(const Request& request) {
-  return response_provider_impl_->CanHandleRequest(request);
-}
-
-GCDWebServerResponse* FileBasedResponseProvider::GetGCDWebServerResponse(
-    const Request& request) {
-  const base::FilePath file_path =
-      response_provider_impl_->BuildTargetPath(request.url);
-  NSString* path = base::SysUTF8ToNSString(file_path.value());
-  return [GCDWebServerFileResponse responseWithFile:path];
-}
-
-}  // namespace web
diff --git a/ios/web/public/test/http_server/file_based_response_provider_impl.h b/ios/web/public/test/http_server/file_based_response_provider_impl.h
deleted file mode 100644
index b6e98a4e..0000000
--- a/ios/web/public/test/http_server/file_based_response_provider_impl.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2015 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_PUBLIC_TEST_HTTP_SERVER_FILE_BASED_RESPONSE_PROVIDER_IMPL_H_
-#define IOS_WEB_PUBLIC_TEST_HTTP_SERVER_FILE_BASED_RESPONSE_PROVIDER_IMPL_H_
-
-#include "base/files/file_path.h"
-#import "ios/web/public/test/http_server/response_provider.h"
-
-class GURL;
-
-namespace web {
-
-// FileBasedMockResponseProvider tries to resolve URL as if it were a path
-// relative to |path| on the filesystem. Use |BuildTargetPath| to resolve URLs
-// to paths on the filesystem.
-class FileBasedResponseProviderImpl {
- public:
-  explicit FileBasedResponseProviderImpl(const base::FilePath& path);
-  virtual ~FileBasedResponseProviderImpl();
-
-  // Returns true if the request's URL when converted to a path on the app
-  // bundle has a file present.
-  bool CanHandleRequest(const ResponseProvider::Request& request);
-  // Converts |url| to a path on the app bundle. Used to resolve URLs on to the
-  // app bundle.
-  base::FilePath BuildTargetPath(const GURL& url);
-
- private:
-  // The path according to which URLs are resolved from.
-  base::FilePath path_;
-};
-}
-
-#endif  // IOS_WEB_PUBLIC_TEST_HTTP_SERVER_FILE_BASED_RESPONSE_PROVIDER_IMPL_H_
diff --git a/ios/web/public/test/http_server/file_based_response_provider_impl.mm b/ios/web/public/test/http_server/file_based_response_provider_impl.mm
deleted file mode 100644
index 42b39f7..0000000
--- a/ios/web/public/test/http_server/file_based_response_provider_impl.mm
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/web/public/test/http_server/file_based_response_provider_impl.h"
-
-#include "base/files/file_util.h"
-#import "ios/web/public/test/http_server/response_provider.h"
-#include "url/gurl.h"
-
-namespace web {
-
-FileBasedResponseProviderImpl::FileBasedResponseProviderImpl(
-    const base::FilePath& path)
-    : path_(path) {}
-
-FileBasedResponseProviderImpl::~FileBasedResponseProviderImpl() {}
-
-bool FileBasedResponseProviderImpl::CanHandleRequest(
-    const ResponseProvider::Request& request) {
-  return base::PathExists(BuildTargetPath(request.url));
-}
-
-base::FilePath FileBasedResponseProviderImpl::BuildTargetPath(const GURL& url) {
-  base::FilePath result = path_;
-  const std::string kLocalhostHost = "localhost";
-  if (url.host() != kLocalhostHost) {
-    result = result.Append(url.host());
-  }
-  std::string url_path = url.path();
-  // Remove the leading slash in url path.
-  if (url_path.length() > 0 && url_path[0] == '/') {
-    url_path.erase(0, 1);
-  }
-  if (!url_path.empty())
-    result = result.Append(url_path);
-  return result;
-}
-
-}  // namespace web
diff --git a/ios/web/public/test/http_server/http_server.h b/ios/web/public/test/http_server/http_server.h
index e1a584f..c565eb6 100644
--- a/ios/web/public/test/http_server/http_server.h
+++ b/ios/web/public/test/http_server/http_server.h
@@ -14,7 +14,16 @@
 #include "base/synchronization/lock.h"
 #import "ios/web/public/test/http_server/response_provider.h"
 
-@class GCDWebServer;
+namespace net {
+namespace test_server {
+class EmbeddedTestServer;
+struct HttpRequest;
+}
+}
+
+namespace web {
+class ResponseProvider;
+}
 
 namespace web {
 namespace test {
@@ -41,12 +50,12 @@
 };
 
 // The HttpServer is an in-process web server that is used to service requests.
-// It is a singleton and backed by a GCDWebServer.
+// It is a singleton and backed by a EmbeddedTestServer.
 // HttpServer can be configured to serve requests by registering
 // web::ResponseProviders.
 // This class is not thread safe on the whole and only certain methods are
 // thread safe.
-class HttpServer {
+class HttpServer : public base::RefCountedThreadSafe<HttpServer> {
  public:
   typedef std::vector<std::unique_ptr<ResponseProvider>> ProviderList;
 
@@ -61,14 +70,10 @@
   // A convenience method for the longer form of
   // |web::test::HttpServer::GetSharedInstance().MakeUrlForHttpServer|
   static GURL MakeUrl(const std::string& url);
-
   // Starts the server on the default port 8080. CHECKs if the server can not be
   // started.
   // Must be called from the main thread.
   void StartOrDie();
-  // Starts the server on |port|. Returns true on success, false otherwise.
-  // Must be called from the main thread.
-  bool StartOnPort(NSUInteger port);
   // Stops the server and prevents it from accepting new requests.
   // Must be called from the main thread.
   void Stop();
@@ -92,9 +97,7 @@
   void RemoveAllResponseProviders();
 
  private:
-  // Initializes the server by registering for a GCDWebServer servlet. Must be
-  // called from the main thread.
-  void InitHttpServer();
+  friend class base::RefCountedThreadSafe<HttpServer>;
   HttpServer();
   ~HttpServer();
 
@@ -120,10 +123,19 @@
   mutable base::Lock port_lock_;
   // The port that the server is running on. 0 if the server is not running.
   NSUInteger port_;
-  // The GCDWebServer backing the HttpServer.
-  base::scoped_nsobject<GCDWebServer> gcd_web_server_;
+  // The EmbeddedTestServer backing the HttpServer.
+  std::unique_ptr<net::test_server::EmbeddedTestServer> embedded_test_server_;
   // The list of providers to service a request.
   std::vector<scoped_refptr<RefCountedResponseProviderWrapper>> providers_;
+  // Returns the response providers for a request.
+  ResponseProvider* GetResponseProviderForProviderRequest(
+      const web::ResponseProvider::Request& request);
+  // Provides a shim between EmbeddedTestServer and ResponseProvider. This
+  // handler converts an EmbeddedTestServer request to ResponseProvider
+  // request, look up the corresponding providers, and then converts the
+  // ResponseProvider response back to EmbeddedTestServer response.
+  std::unique_ptr<net::test_server::HttpResponse> GetResponse(
+      const net::test_server::HttpRequest& request);
   DISALLOW_COPY_AND_ASSIGN(HttpServer);
 };
 
diff --git a/ios/web/public/test/http_server/http_server.mm b/ios/web/public/test/http_server/http_server.mm
index 3b6972ff..6c6219f 100644
--- a/ios/web/public/test/http_server/http_server.mm
+++ b/ios/web/public/test/http_server/http_server.mm
@@ -9,38 +9,31 @@
 #include <string>
 
 #include "base/logging.h"
-#include "base/memory/ref_counted.h"
+#include "base/memory/ptr_util.h"
+#include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/sys_string_conversions.h"
-#import "ios/third_party/gcdwebserver/src/GCDWebServer/Core/GCDWebServer.h"
-#import "ios/third_party/gcdwebserver/src/GCDWebServer/Core/GCDWebServerResponse.h"
-#import "ios/third_party/gcdwebserver/src/GCDWebServer/Requests/GCDWebServerDataRequest.h"
 #import "net/base/mac/url_conversions.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
 
 #include "url/gurl.h"
 
 namespace {
 
-// The default port on which the GCDWebServer is brought up on.
-const NSUInteger kDefaultPort = 8080;
-
-// Converts a GCDWebServerDataRequest (received from the GCDWebServer servlet)
-// to a request object that the ResponseProvider expects.
-web::ResponseProvider::Request ResponseProviderRequestFromGCDWebServerRequest(
-    GCDWebServerDataRequest* request) {
-  GURL url(net::GURLWithNSURL(request.URL));
-  std::string method(base::SysNSStringToUTF8(request.method));
-  base::scoped_nsobject<NSString> body([[NSString alloc]
-      initWithData:request.data
-          encoding:NSUTF8StringEncoding]);
-  __block net::HttpRequestHeaders headers;
-  [[request headers] enumerateKeysAndObjectsUsingBlock:^(
-                         NSString* header_key, NSString* header_value, BOOL*) {
-    headers.SetHeader(base::SysNSStringToUTF8(header_key),
-                      base::SysNSStringToUTF8(header_value));
-  }];
-  return web::ResponseProvider::Request(url, method,
-                                        base::SysNSStringToUTF8(body), headers);
+// Converts a net::test_server::HttpRequest (received from the
+// EmbeddedTestServer servlet) to a request object that the ResponseProvider
+// expects.
+web::ResponseProvider::Request
+ResponseProviderRequestFromEmbeddedTestServerRequest(
+    const net::test_server::HttpRequest& request) {
+  net::HttpRequestHeaders headers;
+  for (auto it = request.headers.begin(); it != request.headers.end(); ++it) {
+    headers.SetHeader(it->first, it->second);
+  }
+  return web::ResponseProvider::Request(request.GetURL(), request.method_string,
+                                        request.content, headers);
 }
 
 }  // namespace
@@ -75,64 +68,47 @@
   return server;
 }
 
-void HttpServer::InitHttpServer() {
-  DCHECK(gcd_web_server_);
-  // Note: This block is called from an arbitrary GCD thread.
-  id process_request =
-      ^GCDWebServerResponse*(GCDWebServerDataRequest* request) {
-    // Relax the cross-thread access restriction to non-thread-safe RefCount.
-    // TODO(crbug.com/707010): Remove ScopedAllowCrossThreadRefCountAccess.
-    base::ScopedAllowCrossThreadRefCountAccess
-        allow_cross_thread_ref_count_access;
+std::unique_ptr<net::test_server::HttpResponse> HttpServer::GetResponse(
+    const net::test_server::HttpRequest& request) {
+  ResponseProvider::Request provider_request =
+      ResponseProviderRequestFromEmbeddedTestServerRequest(request);
+  ResponseProvider* response_provider =
+      GetResponseProviderForProviderRequest(provider_request);
 
-    ResponseProvider::Request provider_request =
-        ResponseProviderRequestFromGCDWebServerRequest(request);
-    scoped_refptr<RefCountedResponseProviderWrapper>
-        ref_counted_response_provider =
-            GetResponseProviderForRequest(provider_request);
-
-    if (!ref_counted_response_provider) {
-      return [GCDWebServerResponse response];
-    }
-    ResponseProvider* response_provider =
-        ref_counted_response_provider->GetResponseProvider();
-    if (!response_provider) {
-      return [GCDWebServerResponse response];
-    }
-
-    return response_provider->GetGCDWebServerResponse(provider_request);
-  };
-  [gcd_web_server_ removeAllHandlers];
-  // Register a servlet for all HTTP GET, POST methods.
-  [gcd_web_server_ addDefaultHandlerForMethod:@"GET"
-                                 requestClass:[GCDWebServerDataRequest class]
-                                 processBlock:process_request];
-  [gcd_web_server_ addDefaultHandlerForMethod:@"POST"
-                                 requestClass:[GCDWebServerDataRequest class]
-                                 processBlock:process_request];
+  if (!response_provider) {
+    return nullptr;
+  }
+  return response_provider->GetEmbeddedTestServerResponse(provider_request);
 }
 
-HttpServer::HttpServer() : port_(0) {
-  gcd_web_server_.reset([[GCDWebServer alloc] init]);
-  InitHttpServer();
+ResponseProvider* HttpServer::GetResponseProviderForProviderRequest(
+    const web::ResponseProvider::Request& request) {
+  auto response_provider = GetResponseProviderForRequest(request);
+  if (!response_provider) {
+    return nullptr;
+  }
+  return response_provider->GetResponseProvider();
 }
 
+HttpServer::HttpServer() : port_(0) {}
+
 HttpServer::~HttpServer() {}
 
-bool HttpServer::StartOnPort(NSUInteger port) {
-  DCHECK([NSThread isMainThread]);
-  DCHECK(!IsRunning()) << "The server is already running."
-                       << " Please stop it before starting it again.";
-  BOOL success = [gcd_web_server_ startWithPort:port bonjourName:@""];
-  if (success) {
-    SetPort(port);
-  }
-  return success;
-}
-
 void HttpServer::StartOrDie() {
   DCHECK([NSThread isMainThread]);
-  StartOnPort(kDefaultPort);
+
+  // Registers request handler which serves files from the http test files
+  // directory. The current tests calls full path relative to DIR_SOURCE_ROOT.
+  // Registers the DIR_SOURCE_ROOT to avoid massive test changes.
+  embedded_test_server_ = base::MakeUnique<net::EmbeddedTestServer>();
+  embedded_test_server_->ServeFilesFromSourceDirectory(".");
+
+  embedded_test_server_->RegisterDefaultHandler(
+      base::Bind(&HttpServer::GetResponse, this));
+
+  if (embedded_test_server_->Start()) {
+    SetPort((NSUInteger)embedded_test_server_->port());
+  }
   CHECK(IsRunning());
 }
 
@@ -140,13 +116,18 @@
   DCHECK([NSThread isMainThread]);
   DCHECK(IsRunning()) << "Cannot stop an already stopped server.";
   RemoveAllResponseProviders();
-  [gcd_web_server_ stop];
+  // TODO(crbug.com/711723): Re-write the Stop() function when shutting down
+  // server works for iOS.
+  embedded_test_server_.release();
   SetPort(0);
 }
 
 bool HttpServer::IsRunning() const {
   DCHECK([NSThread isMainThread]);
-  return [gcd_web_server_ isRunning];
+  if (embedded_test_server_ == nullptr) {
+    return false;
+  }
+  return embedded_test_server_->Started();
 }
 
 NSUInteger HttpServer::GetPort() const {
@@ -162,24 +143,7 @@
 GURL HttpServer::MakeUrlForHttpServer(const std::string& url) const {
   GURL result(url);
   DCHECK(result.is_valid());
-  const std::string kLocalhostHost = std::string("localhost");
-  if (result.port() == base::IntToString(GetPort()) &&
-      result.host() == kLocalhostHost) {
-    return result;
-  }
-
-  GURL::Replacements replacements;
-  replacements.SetHostStr(kLocalhostHost);
-
-  const std::string port = base::IntToString(static_cast<int>(GetPort()));
-  replacements.SetPortStr(port);
-
-  // It is necessary to prepend the host of the input URL so that URLs such
-  // as http://origin/foo, http://destination/foo can be disamgiguated.
-  const std::string new_path = std::string(result.host() + result.path());
-  replacements.SetPathStr(new_path);
-
-  return result.ReplaceComponents(replacements);
+  return embedded_test_server_->GetURL("/" + result.GetContent());
 }
 
 scoped_refptr<RefCountedResponseProviderWrapper>
diff --git a/ios/web/public/test/http_server/http_server_util.mm b/ios/web/public/test/http_server/http_server_util.mm
index f9601445..2e54d4c 100644
--- a/ios/web/public/test/http_server/http_server_util.mm
+++ b/ios/web/public/test/http_server/http_server_util.mm
@@ -6,7 +6,6 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/path_service.h"
-#import "ios/web/public/test/http_server/file_based_response_provider.h"
 #import "ios/web/public/test/http_server/html_response_provider.h"
 #import "ios/web/public/test/http_server/http_server.h"
 
@@ -22,11 +21,9 @@
   SetUpHttpServer(base::MakeUnique<HtmlResponseProvider>(responses));
 }
 
-void SetUpFileBasedHttpServer() {
-  base::FilePath path;
-  PathService::Get(base::DIR_MODULE, &path);
-  SetUpHttpServer(base::MakeUnique<FileBasedResponseProvider>(path));
-}
+// TODO(crbug.com/694859): Cleanup tests and remove the function. Not
+// necessary after switching to EmbeddedTestServer.
+void SetUpFileBasedHttpServer() {}
 
 void SetUpHttpServer(std::unique_ptr<web::ResponseProvider> provider) {
   web::test::HttpServer& server = web::test::HttpServer::GetSharedInstance();
diff --git a/ios/web/public/test/http_server/response_provider.h b/ios/web/public/test/http_server/response_provider.h
index d27914b..5cbb0876 100644
--- a/ios/web/public/test/http_server/response_provider.h
+++ b/ios/web/public/test/http_server/response_provider.h
@@ -13,6 +13,7 @@
 #include "net/http/http_request_headers.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_response.h"
 #include "url/gurl.h"
 
 @class GCDWebServerResponse;
@@ -20,7 +21,7 @@
 namespace web {
 
 // An abstract class for a provider that services a request and returns a
-// GCDWebServerResponse.
+// EmbeddedTestServerResponse.
 // Note: The ResponseProviders can be called from any arbitrary GCD thread.
 class ResponseProvider {
  public:
@@ -46,10 +47,10 @@
   // Returns true if the request is handled by the provider.
   virtual bool CanHandleRequest(const Request& request) = 0;
 
-  // Returns the GCDWebServerResponse as a reply to the request. Will only be
-  // called if the provider can handle the request.
-  virtual GCDWebServerResponse* GetGCDWebServerResponse(
-      const Request& request) = 0;
+  // Returns the test_server::HttpResponse as a reply to the request. Will only
+  // be called if the provider can handle the request.
+  virtual std::unique_ptr<net::test_server::HttpResponse>
+  GetEmbeddedTestServerResponse(const Request& request) = 0;
 
   // Gets default response headers with a text/html content type and a 200
   // response code.
diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index 3f0660a..79756a1d4 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -208,6 +208,7 @@
       "android/audio_manager_android.h",
       "android/audio_record_input.cc",
       "android/audio_record_input.h",
+      "android/muteable_audio_output_stream.h",
       "android/opensles_input.cc",
       "android/opensles_input.h",
       "android/opensles_output.cc",
diff --git a/media/audio/android/audio_manager_android.cc b/media/audio/android/audio_manager_android.cc
index 5f6e2669..1756788 100644
--- a/media/audio/android/audio_manager_android.cc
+++ b/media/audio/android/audio_manager_android.cc
@@ -171,7 +171,7 @@
   AudioOutputStream* stream = AudioManagerBase::MakeAudioOutputStream(
       params, std::string(), AudioManager::LogCallback());
   if (stream)
-    streams_.insert(static_cast<OpenSLESOutputStream*>(stream));
+    streams_.insert(static_cast<MuteableAudioOutputStream*>(stream));
   return stream;
 }
 
@@ -197,7 +197,7 @@
 
 void AudioManagerAndroid::ReleaseOutputStream(AudioOutputStream* stream) {
   DCHECK(GetTaskRunner()->BelongsToCurrentThread());
-  streams_.erase(static_cast<OpenSLESOutputStream*>(stream));
+  streams_.erase(static_cast<MuteableAudioOutputStream*>(stream));
   AudioManagerBase::ReleaseOutputStream(stream);
 }
 
@@ -235,6 +235,15 @@
   return new OpenSLESOutputStream(this, params, stream_type);
 }
 
+AudioOutputStream* AudioManagerAndroid::MakeBitstreamOutputStream(
+    const AudioParameters& params,
+    const std::string& device_id,
+    const LogCallback& log_callback) {
+  // TODO(tsunghung): add output stream for audio bitstream formats.
+  NOTREACHED();
+  return nullptr;
+}
+
 AudioInputStream* AudioManagerAndroid::MakeLinearInputStream(
     const AudioParameters& params,
     const std::string& device_id,
diff --git a/media/audio/android/audio_manager_android.h b/media/audio/android/audio_manager_android.h
index 20707bc..c65b634 100644
--- a/media/audio/android/audio_manager_android.h
+++ b/media/audio/android/audio_manager_android.h
@@ -15,7 +15,7 @@
 
 namespace media {
 
-class OpenSLESOutputStream;
+class MuteableAudioOutputStream;
 
 // Android implemention of AudioManager.
 class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase {
@@ -54,6 +54,10 @@
       const AudioParameters& params,
       const std::string& device_id,
       const LogCallback& log_callback) override;
+  AudioOutputStream* MakeBitstreamOutputStream(
+      const AudioParameters& params,
+      const std::string& device_id,
+      const LogCallback& log_callback) override;
   AudioInputStream* MakeLinearInputStream(
       const AudioParameters& params,
       const std::string& device_id,
@@ -96,7 +100,7 @@
   // Java AudioManager instance.
   base::android::ScopedJavaGlobalRef<jobject> j_audio_manager_;
 
-  typedef std::set<OpenSLESOutputStream*> OutputStreams;
+  typedef std::set<MuteableAudioOutputStream*> OutputStreams;
   OutputStreams streams_;
 
   // Enabled when first input stream is created and set to false when last
diff --git a/media/audio/android/muteable_audio_output_stream.h b/media/audio/android/muteable_audio_output_stream.h
new file mode 100644
index 0000000..8ef13a64
--- /dev/null
+++ b/media/audio/android/muteable_audio_output_stream.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef MEDIA_MUTEABLE_AUDIO_OUTPUT_STREAM_H_
+#define MEDIA_MUTEABLE_AUDIO_OUTPUT_STREAM_H_
+
+#include "media/audio/audio_io.h"
+
+namespace media {
+
+class MEDIA_EXPORT MuteableAudioOutputStream : public AudioOutputStream {
+ public:
+  // Volume control coming from hardware. It overrides volume when it's
+  // true. Otherwise, use SetVolume(double volume) for scaling.
+  // This is needed because platform voice volume never goes to zero in
+  // COMMUNICATION mode on Android.
+  virtual void SetMute(bool muted) = 0;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_MUTEABLE_AUDIO_OUTPUT_STREAM_H_
diff --git a/media/audio/android/opensles_output.h b/media/audio/android/opensles_output.h
index 80d8929..17a91aaa 100644
--- a/media/audio/android/opensles_output.h
+++ b/media/audio/android/opensles_output.h
@@ -16,8 +16,8 @@
 #include "base/macros.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
+#include "media/audio/android/muteable_audio_output_stream.h"
 #include "media/audio/android/opensles_util.h"
-#include "media/audio/audio_io.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/audio_timestamp_helper.h"
 
@@ -48,7 +48,7 @@
 // This class is created and lives on the Audio Manager thread but recorded
 // audio buffers are given to us from an internal OpenSLES audio thread.
 // All public methods should be called on the Audio Manager thread.
-class OpenSLESOutputStream : public AudioOutputStream {
+class OpenSLESOutputStream : public MuteableAudioOutputStream {
  public:
   static const int kMaxNumOfBuffersInQueue = 2;
 
@@ -58,7 +58,7 @@
 
   ~OpenSLESOutputStream() override;
 
-  // Implementation of AudioOutputStream.
+  // Implementation of MuteableAudioOutputStream.
   bool Open() override;
   void Close() override;
   void Start(AudioSourceCallback* callback) override;
@@ -68,7 +68,7 @@
 
   // Set the value of |muted_|. It does not affect |volume_| which can be
   // got by calling GetVolume(). See comments for |muted_| below.
-  void SetMute(bool muted);
+  void SetMute(bool muted) override;
 
  private:
   bool CreatePlayer();
diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc
index 29c2679..92a255d 100644
--- a/media/audio/audio_manager_base.cc
+++ b/media/audio/audio_manager_base.cc
@@ -166,9 +166,7 @@
       break;
     case AudioParameters::AUDIO_BITSTREAM_AC3:
     case AudioParameters::AUDIO_BITSTREAM_EAC3:
-      // TODO(tsunghung): create passthrough output stream.
-      NOTREACHED();
-      stream = nullptr;
+      stream = MakeBitstreamOutputStream(params, device_id, log_callback);
       break;
     case AudioParameters::AUDIO_FAKE:
       stream = FakeAudioOutputStream::MakeFakeStream(this, params);
@@ -185,6 +183,13 @@
   return stream;
 }
 
+AudioOutputStream* AudioManagerBase::MakeBitstreamOutputStream(
+    const AudioParameters& params,
+    const std::string& device_id,
+    const LogCallback& log_callback) {
+  return nullptr;
+}
+
 AudioInputStream* AudioManagerBase::MakeAudioInputStream(
     const AudioParameters& params,
     const std::string& device_id,
@@ -286,7 +291,8 @@
   const base::TimeDelta kCloseDelay =
       base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds);
   std::unique_ptr<AudioOutputDispatcher> dispatcher;
-  if (output_params.format() != AudioParameters::AUDIO_FAKE) {
+  if (output_params.format() != AudioParameters::AUDIO_FAKE &&
+      !output_params.IsBitstreamFormat()) {
     // Using unretained for |debug_recording_manager_| is safe since it
     // outlives the dispatchers (cleared in ShutdownOnAudioThread()).
     dispatcher = base::MakeUnique<AudioOutputResampler>(
diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h
index cc1d10a..8e2fc15b 100644
--- a/media/audio/audio_manager_base.h
+++ b/media/audio/audio_manager_base.h
@@ -75,6 +75,12 @@
       const std::string& device_id,
       const LogCallback& log_callback) = 0;
 
+  // Creates the output stream for the |AUDIO_BITSTREAM_XXX| format.
+  virtual AudioOutputStream* MakeBitstreamOutputStream(
+      const AudioParameters& params,
+      const std::string& device_id,
+      const LogCallback& log_callback);
+
   // Creates the input stream for the |AUDIO_PCM_LINEAR| format. The legacy
   // name is also from |AUDIO_PCM_LINEAR|.
   virtual AudioInputStream* MakeLinearInputStream(
diff --git a/net/quic/core/crypto/crypto_secret_boxer.cc b/net/quic/core/crypto/crypto_secret_boxer.cc
index 0ce5872..65691d2 100644
--- a/net/quic/core/crypto/crypto_secret_boxer.cc
+++ b/net/quic/core/crypto/crypto_secret_boxer.cc
@@ -7,12 +7,11 @@
 #include <memory>
 
 #include "base/logging.h"
-#include "net/quic/core/crypto/aes_128_gcm_12_decrypter.h"
-#include "net/quic/core/crypto/aes_128_gcm_12_encrypter.h"
-#include "net/quic/core/crypto/crypto_protocol.h"
+#include "base/strings/string_util.h"
 #include "net/quic/core/crypto/quic_decrypter.h"
 #include "net/quic/core/crypto/quic_encrypter.h"
 #include "net/quic/core/crypto/quic_random.h"
+#include "third_party/boringssl/src/include/openssl/aead.h"
 
 using std::string;
 
@@ -22,18 +21,14 @@
 static const size_t kKeySize = 16;
 
 // kBoxNonceSize contains the number of bytes of nonce that we use in each box.
-// TODO(rtenneti): Add support for kBoxNonceSize to be 16 bytes.
-//
-// From agl@:
-//   96-bit nonces are on the edge. An attacker who can collect 2^41
-//   source-address tokens has a 1% chance of finding a duplicate.
-//
-//   The "average" DDoS is now 32.4M PPS. That's 2^25 source-address tokens
-//   per second. So one day of that DDoS botnot would reach the 1% mark.
-//
-//   It's not terrible, but it's not a "forget about it" margin.
 static const size_t kBoxNonceSize = 12;
 
+struct CryptoSecretBoxer::State {
+  // ctxs are the initialised AEAD contexts. These objects contain the
+  // scheduled AES state for each of the keys.
+  std::vector<bssl::UniquePtr<EVP_AEAD_CTX>> ctxs;
+};
+
 CryptoSecretBoxer::CryptoSecretBoxer() {}
 
 CryptoSecretBoxer::~CryptoSecretBoxer() {}
@@ -43,90 +38,94 @@
   return kKeySize;
 }
 
+// kAEAD is the AEAD used for boxing: AES-128-GCM-SIV.
+static const EVP_AEAD* (*const kAEAD)() = EVP_aead_aes_128_gcm_siv;
+
 void CryptoSecretBoxer::SetKeys(const std::vector<string>& keys) {
   DCHECK(!keys.empty());
-  std::vector<string> copy = keys;
+  const EVP_AEAD* const aead = kAEAD();
+  std::unique_ptr<State> new_state(new State);
+
   for (const string& key : keys) {
     DCHECK_EQ(kKeySize, key.size());
+    bssl::UniquePtr<EVP_AEAD_CTX> ctx(
+        EVP_AEAD_CTX_new(aead, reinterpret_cast<const uint8_t*>(key.data()),
+                         key.size(), EVP_AEAD_DEFAULT_TAG_LENGTH));
+    if (!ctx) {
+      LOG(DFATAL) << "EVP_AEAD_CTX_init failed";
+      return;
+    }
+
+    new_state->ctxs.push_back(std::move(ctx));
   }
+
   QuicWriterMutexLock l(&lock_);
-  keys_.swap(copy);
+  state_ = std::move(new_state);
 }
 
 string CryptoSecretBoxer::Box(QuicRandom* rand,
                               QuicStringPiece plaintext) const {
-  std::unique_ptr<Aes128Gcm12Encrypter> encrypter(new Aes128Gcm12Encrypter());
-  {
-    QuicReaderMutexLock l(&lock_);
-    DCHECK_EQ(kKeySize, keys_[0].size());
-    if (!encrypter->SetKey(keys_[0])) {
-      DLOG(DFATAL) << "CryptoSecretBoxer's encrypter->SetKey failed.";
-      return string();
-    }
-  }
-  size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length());
+  // The box is formatted as:
+  //   12 bytes of random nonce
+  //   n bytes of ciphertext
+  //   16 bytes of authenticator
+  size_t out_len =
+      kBoxNonceSize + plaintext.size() + EVP_AEAD_max_overhead(kAEAD());
 
   string ret;
-  const size_t len = kBoxNonceSize + ciphertext_size;
-  ret.resize(len);
-  char* data = &ret[0];
+  uint8_t* out = reinterpret_cast<uint8_t*>(base::WriteInto(&ret, out_len + 1));
 
-  // Generate nonce.
-  rand->RandBytes(data, kBoxNonceSize);
-  memcpy(data + kBoxNonceSize, plaintext.data(), plaintext.size());
+  // Write kBoxNonceSize bytes of random nonce to the beginning of the output
+  // buffer.
+  rand->RandBytes(out, kBoxNonceSize);
+  const uint8_t* const nonce = out;
+  out += kBoxNonceSize;
+  out_len -= kBoxNonceSize;
 
-  if (!encrypter->Encrypt(
-          QuicStringPiece(data, kBoxNonceSize), QuicStringPiece(), plaintext,
-          reinterpret_cast<unsigned char*>(data + kBoxNonceSize))) {
-    DLOG(DFATAL) << "CryptoSecretBoxer's Encrypt failed.";
-    return string();
+  size_t bytes_written;
+  {
+    QuicReaderMutexLock l(&lock_);
+    if (!EVP_AEAD_CTX_seal(state_->ctxs[0].get(), out, &bytes_written, out_len,
+                           nonce, kBoxNonceSize,
+                           reinterpret_cast<const uint8_t*>(plaintext.data()),
+                           plaintext.size(), nullptr, 0)) {
+      LOG(DFATAL) << "EVP_AEAD_CTX_seal failed";
+      return "";
+    }
   }
 
+  DCHECK_EQ(out_len, bytes_written);
+
   return ret;
 }
 
-bool CryptoSecretBoxer::Unbox(QuicStringPiece ciphertext,
+bool CryptoSecretBoxer::Unbox(QuicStringPiece in_ciphertext,
                               string* out_storage,
                               QuicStringPiece* out) const {
-  if (ciphertext.size() < kBoxNonceSize) {
+  if (in_ciphertext.size() <= kBoxNonceSize) {
     return false;
   }
 
-  QuicStringPiece nonce(ciphertext.data(), kBoxNonceSize);
-  ciphertext.remove_prefix(kBoxNonceSize);
-  QuicPacketNumber packet_number;
-  QuicStringPiece nonce_prefix(nonce.data(),
-                               nonce.size() - sizeof(packet_number));
-  memcpy(&packet_number, nonce.data() + nonce_prefix.size(),
-         sizeof(packet_number));
+  const uint8_t* const nonce =
+      reinterpret_cast<const uint8_t*>(in_ciphertext.data());
+  const uint8_t* const ciphertext = nonce + kBoxNonceSize;
+  const size_t ciphertext_len = in_ciphertext.size() - kBoxNonceSize;
 
-  std::unique_ptr<Aes128Gcm12Decrypter> decrypter(new Aes128Gcm12Decrypter());
-  char plaintext[kMaxPacketSize];
-  size_t plaintext_length = 0;
-  bool ok = false;
-  {
-    QuicReaderMutexLock l(&lock_);
-    for (const string& key : keys_) {
-      if (decrypter->SetKey(key)) {
-        decrypter->SetNoncePrefix(nonce_prefix);
-        if (decrypter->DecryptPacket(QUIC_VERSION_36, packet_number,
-                                     /*associated data=*/QuicStringPiece(),
-                                     ciphertext, plaintext, &plaintext_length,
-                                     kMaxPacketSize)) {
-          ok = true;
-          break;
-        }
-      }
+  uint8_t* out_data = reinterpret_cast<uint8_t*>(
+      base::WriteInto(out_storage, ciphertext_len + 1));
+
+  QuicReaderMutexLock l(&lock_);
+  for (const bssl::UniquePtr<EVP_AEAD_CTX>& ctx : state_->ctxs) {
+    size_t bytes_written;
+    if (EVP_AEAD_CTX_open(ctx.get(), out_data, &bytes_written, ciphertext_len,
+                          nonce, kBoxNonceSize, ciphertext, ciphertext_len,
+                          nullptr, 0)) {
+      *out = QuicStringPiece(out_storage->data(), bytes_written);
+      return true;
     }
   }
-  if (!ok) {
-    return false;
-  }
 
-  out_storage->resize(plaintext_length);
-  out_storage->assign(plaintext, plaintext_length);
-  out->set(out_storage->data(), plaintext_length);
-  return true;
+  return false;
 }
 
 }  // namespace net
diff --git a/net/quic/core/crypto/crypto_secret_boxer.h b/net/quic/core/crypto/crypto_secret_boxer.h
index d955814..f838e0b 100644
--- a/net/quic/core/crypto/crypto_secret_boxer.h
+++ b/net/quic/core/crypto/crypto_secret_boxer.h
@@ -6,6 +6,7 @@
 #define NET_QUIC_CORE_CRYPTO_CRYPTO_SECRET_BOXER_H_
 
 #include <cstddef>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -51,8 +52,13 @@
              QuicStringPiece* out) const;
 
  private:
+  struct State;
+
   mutable QuicMutex lock_;
-  std::vector<std::string> keys_ GUARDED_BY(lock_);
+
+  // state_ is an opaque pointer to whatever additional state the concrete
+  // implementation of CryptoSecretBoxer requires.
+  std::unique_ptr<State> state_ GUARDED_BY(lock_);
 
   DISALLOW_COPY_AND_ASSIGN(CryptoSecretBoxer);
 };
diff --git a/printing/backend/win_helper.cc b/printing/backend/win_helper.cc
index 3527aee..04990a9a 100644
--- a/printing/backend/win_helper.cc
+++ b/printing/backend/win_helper.cc
@@ -471,12 +471,7 @@
   return ticket;
 }
 
-bool IsPrinterRPCSOnly(HANDLE printer) {
-  PrinterInfo5 info_5;
-  if (!info_5.Init(printer))
-    return false;
-  const wchar_t* name = info_5.get()->pPrinterName;
-  const wchar_t* port = info_5.get()->pPortName;
+bool IsPrinterRPCSOnly(const wchar_t* name, const wchar_t* port) {
   int num_languages =
       DeviceCapabilities(name, port, DC_PERSONALITY, NULL, NULL);
   if (num_languages != 1)
@@ -487,12 +482,16 @@
   return wcscmp(buf.data(), kRPCSLanguage) == 0;
 }
 
+bool PrinterHasValidPaperSize(const wchar_t* name, const wchar_t* port) {
+  return DeviceCapabilities(name, port, DC_PAPERSIZE, nullptr, nullptr) > 0;
+}
+
 std::unique_ptr<DEVMODE, base::FreeDeleter> CreateDevMode(HANDLE printer,
                                                           DEVMODE* in) {
   LONG buffer_size = DocumentProperties(
       NULL, printer, const_cast<wchar_t*>(L""), NULL, NULL, 0);
   if (buffer_size < static_cast<int>(sizeof(DEVMODE)))
-    return std::unique_ptr<DEVMODE, base::FreeDeleter>();
+    return nullptr;
 
   // Some drivers request buffers with size smaller than dmSize + dmDriverExtra.
   // crbug.com/421402
@@ -502,17 +501,27 @@
       reinterpret_cast<DEVMODE*>(calloc(buffer_size, 1)));
   DWORD flags = (in ? (DM_IN_BUFFER) : 0) | DM_OUT_BUFFER;
 
+  PrinterInfo5 info_5;
+  if (!info_5.Init(printer))
+    return nullptr;
+  const wchar_t* name = info_5.get()->pPrinterName;
+  const wchar_t* port = info_5.get()->pPortName;
+
   // Check for RPCS drivers on Windows 8+ as DocumentProperties will crash if
-  // called on one of these printers. See crbug.com/679160
-  if (base::win::GetVersion() >= base::win::VERSION_WIN8 &&
-      IsPrinterRPCSOnly(printer)) {
-    return std::unique_ptr<DEVMODE, base::FreeDeleter>();
+  // called on one of these printers. See crbug.com/679160.
+  // Also check that valid paper sizes exist; some old drivers return no paper
+  // sizes and crash in DocumentProperties if used with Win10. See
+  // crbug.com/724595
+  if ((base::win::GetVersion() >= base::win::VERSION_WIN8 &&
+       IsPrinterRPCSOnly(name, port)) ||
+      !PrinterHasValidPaperSize(name, port)) {
+    return nullptr;
   }
 
   if (DocumentProperties(
           NULL, printer, const_cast<wchar_t*>(L""), out.get(), in, flags) !=
       IDOK) {
-    return std::unique_ptr<DEVMODE, base::FreeDeleter>();
+    return nullptr;
   }
   int size = out->dmSize;
   int extra_size = out->dmDriverExtra;
diff --git a/remoting/host/input_injector_chromeos.cc b/remoting/host/input_injector_chromeos.cc
index 1398e3f8..d3bccd0b 100644
--- a/remoting/host/input_injector_chromeos.cc
+++ b/remoting/host/input_injector_chromeos.cc
@@ -15,9 +15,10 @@
 #include "remoting/host/chromeos/point_transformer.h"
 #include "remoting/host/clipboard.h"
 #include "remoting/proto/internal.pb.h"
+#include "ui/base/ime/chromeos/ime_keyboard.h"
+#include "ui/base/ime/chromeos/input_method_manager.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
-#include "ui/ozone/public/input_controller.h"
 #include "ui/ozone/public/ozone_platform.h"
 #include "ui/ozone/public/system_input_injector.h"
 
@@ -45,6 +46,28 @@
   }
 }
 
+bool shouldSetLockStates(ui::DomCode dom_code, bool key_pressed) {
+  if (!key_pressed)
+    return false;
+  switch (dom_code) {
+    // Ignores all the keys that could possibly be mapped to Caps Lock in event
+    // rewriter. Please refer to ui::EventRewriterChromeOS::RewriteModifierKeys.
+    case ui::DomCode::F16:
+    case ui::DomCode::CAPS_LOCK:
+    case ui::DomCode::META_LEFT:
+    case ui::DomCode::META_RIGHT:
+    case ui::DomCode::CONTROL_LEFT:
+    case ui::DomCode::CONTROL_RIGHT:
+    case ui::DomCode::ALT_LEFT:
+    case ui::DomCode::ALT_RIGHT:
+    case ui::DomCode::ESCAPE:
+    case ui::DomCode::BACKSPACE:
+      return false;
+    default:
+      return true;
+  }
+}
+
 }  // namespace
 
 // This class is run exclusively on the UI thread of the browser process.
@@ -85,13 +108,14 @@
   DCHECK(event.has_pressed());
   DCHECK(event.has_usb_keycode());
 
-  if (event.has_lock_states()) {
-    SetLockStates(event.lock_states());
-  }
-
   ui::DomCode dom_code =
       ui::KeycodeConverter::UsbKeycodeToDomCode(event.usb_keycode());
 
+  if (event.has_lock_states() &&
+      shouldSetLockStates(dom_code, event.pressed())) {
+    SetLockStates(event.lock_states());
+  }
+
   // Ignore events which can't be mapped.
   if (dom_code != ui::DomCode::NONE) {
     delegate_->InjectKeyEvent(dom_code, event.pressed(),
@@ -131,9 +155,9 @@
 }
 
 void InputInjectorChromeos::Core::SetLockStates(uint32_t states) {
-  ui::InputController* input_controller =
-      ui::OzonePlatform::GetInstance()->GetInputController();
-  input_controller->SetCapsLockEnabled(
+  chromeos::input_method::InputMethodManager* ime =
+      chromeos::input_method::InputMethodManager::Get();
+  ime->GetImeKeyboard()->SetCapsLockEnabled(
       states & protocol::KeyEvent::LOCK_STATES_CAPSLOCK);
 }
 
diff --git a/remoting/ios/app/app_delegate.mm b/remoting/ios/app/app_delegate.mm
index 9383cf5..d6c87e0 100644
--- a/remoting/ios/app/app_delegate.mm
+++ b/remoting/ios/app/app_delegate.mm
@@ -55,7 +55,10 @@
 
 - (void)launchRemotingViewController {
   RemotingViewController* vc = [[RemotingViewController alloc] init];
-  self.window.rootViewController = vc;
+  UINavigationController* navController =
+      [[UINavigationController alloc] initWithRootViewController:vc];
+  navController.navigationBarHidden = true;
+  self.window.rootViewController = navController;
   [self.window makeKeyAndVisible];
 }
 
diff --git a/remoting/ios/app/client_connection_view_controller.mm b/remoting/ios/app/client_connection_view_controller.mm
index 31ad6d8..657ccf9 100644
--- a/remoting/ios/app/client_connection_view_controller.mm
+++ b/remoting/ios/app/client_connection_view_controller.mm
@@ -300,15 +300,12 @@
       [[HostViewController alloc] initWithClient:_client];
   _client = nil;
 
-  __weak UIViewController* parentController = self.presentingViewController;
-
-  [self dismissViewControllerAnimated:NO
-                           completion:^{
-                             [parentController
-                                 presentViewController:hostViewController
-                                              animated:NO
-                                            completion:nil];
-                           }];
+  // Replaces current (topmost) view controller with |hostViewController|.
+  NSMutableArray* controllers =
+      [self.navigationController.viewControllers mutableCopy];
+  [controllers removeLastObject];
+  [controllers addObject:hostViewController];
+  [self.navigationController setViewControllers:controllers animated:NO];
 }
 
 - (void)didProvidePin:(NSString*)pin createPairing:(BOOL)createPairing {
@@ -323,7 +320,7 @@
 
 - (void)didTapCancel:(id)sender {
   _client = nil;
-  [self dismissViewControllerAnimated:YES completion:nil];
+  [self.navigationController popViewControllerAnimated:YES];
 }
 
 - (void)hostSessionStatusChanged:(NSNotification*)notification {
diff --git a/remoting/ios/app/host_view_controller.mm b/remoting/ios/app/host_view_controller.mm
index 07fa9f4..4aa780f 100644
--- a/remoting/ios/app/host_view_controller.mm
+++ b/remoting/ios/app/host_view_controller.mm
@@ -258,7 +258,7 @@
 
   void (^disconnectHandler)(UIAlertAction*) = ^(UIAlertAction*) {
     [_client disconnectFromHost];
-    [self dismissViewControllerAnimated:YES completion:nil];
+    [self.navigationController popViewControllerAnimated:YES];
   };
   [alert addAction:[UIAlertAction actionWithTitle:@"Disconnect"
                                             style:UIAlertActionStyleDefault
diff --git a/remoting/ios/app/remoting_view_controller.mm b/remoting/ios/app/remoting_view_controller.mm
index 73da201..d6e6e9c 100644
--- a/remoting/ios/app/remoting_view_controller.mm
+++ b/remoting/ios/app/remoting_view_controller.mm
@@ -194,9 +194,8 @@
 
   ClientConnectionViewController* clientConnectionViewController =
       [[ClientConnectionViewController alloc] initWithHostInfo:cell.hostInfo];
-  [self presentViewController:clientConnectionViewController
-                     animated:YES
-                   completion:nil];
+  [self.navigationController pushViewController:clientConnectionViewController
+                                       animated:YES];
   completionBlock();
 }
 
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 04dd979..8e2ec27 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1553,6 +1553,8 @@
 crbug.com/472330 fast/writing-mode/box-shadow-vertical-lr.html [ Failure ]
 crbug.com/472330 fast/writing-mode/box-shadow-vertical-rl.html [ Failure ]
 
+crbug.com/729279 external/wpt/scroll-anchoring/start-edge-in-block-layout-direction.html [ Failure ]
+
 crbug.com/700795 [ Mac ] inspector/animation/animation-transition-setTiming-crash.html [ Timeout Pass ]
 
 # These are the failing tests because Chrome hasn't implemented according to the spec.
@@ -2684,6 +2686,15 @@
 crbug.com/576815 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-03b.html [ Failure ]
 crbug.com/576815 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-04.html [ Failure ]
 
+# Producing incorrect results due to crbug.com/729352
+# TODO(ccameron): Re-enable and re-baseline when fixed.
+crbug.com/729352 images/color-profile-background-image-cross-fade.html [ Pass Failure Crash Timeout ]
+crbug.com/729352 images/color-profile-background-image-space.html [ Pass Failure Crash Timeout ]
+crbug.com/729352 images/color-profile-group.html [ Pass Failure Crash Timeout ]
+crbug.com/729352 images/color-profile-layer-filter.html [ Pass Failure Crash Timeout ]
+crbug.com/729352 images/color-profile-svg.html [ Pass Failure Crash Timeout ]
+crbug.com/729352 images/color-profile-svg-fill-text.html [ Pass Failure Crash Timeout ]
+
 # TODO(chrishall): this is a temporary mediation step as part of the P0 issue crbug.com/657646
 # this is not meant to be here for more than a few days (from 2016-11-03 SYD)
 crbug.com/657646 [ Win ] fast/text/font-weight.html [ Failure Pass ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/abspos-containing-block-outside-scroller.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/abspos-containing-block-outside-scroller.html
new file mode 100644
index 0000000..76a49523
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/abspos-containing-block-outside-scroller.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { margin: 0; }
+#scroller { overflow: scroll; width: 500px; height: 400px; }
+#space { height: 1000px; }
+#abs {
+  position: absolute; background-color: red;
+  width: 100px; height: 100px;
+  left: 25px; top: 25px;
+}
+#rel {
+  position: relative; background-color: green;
+  left: 50px; top: 100px; width: 100px; height: 75px;
+}
+
+</style>
+<div id="scroller">
+  <div id="space">
+    <div id="abs"></div>
+    <div id="before"></div>
+    <div id="rel"></div>
+  </div>
+</div>
+<script>
+
+// Tests that anchor node selection skips an absolute-positioned descendant of
+// the scroller if and only if its containing block is outside the scroller.
+
+test(() => {
+  var scroller = document.querySelector("#scroller");
+  var abs = document.querySelector("#abs");
+  var before = document.querySelector("#before");
+  var rel = document.querySelector("#rel");
+
+  // We should not anchor to #abs, because it does not move with the scroller.
+  scroller.scrollTop = 25;
+  before.style.height = "25px";
+  assert_equals(scroller.scrollTop, 50);
+
+  // Reset, make #scroller a containing block.
+  before.style.height = "0";
+  scroller.scrollTop = 0;
+  scroller.style.position = "relative";
+
+  // This time we should anchor to #abs.
+  scroller.scrollTop = 25;
+  before.style.height = "25px";
+  assert_equals(scroller.scrollTop, 25);
+
+}, "Abs-pos descendant with containing block outside the scroller.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/abspos-contributes-to-static-parent-bounds.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/abspos-contributes-to-static-parent-bounds.html
new file mode 100644
index 0000000..5d8ff9a91
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/abspos-contributes-to-static-parent-bounds.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body, html, #static { height: 0; }
+#abs {
+  position: absolute;
+  left: 50px;
+  top: 50px;
+  height: 1200px;
+  padding: 50px;
+  border: 5px solid gray;
+}
+#anchor {
+  background-color: #afa;
+  width: 100px;
+  height: 100px;
+}
+
+</style>
+<div id="static">
+  <div id="abs">
+    <div id="changer"></div>
+    <div id="anchor"></div>
+  </div>
+</div>
+<script>
+
+// Tests that the "bounds" of an element, for the purpose of visibility in the
+// anchor node selection algorithm, include any space occupied by the element's
+// positioned descendants.
+
+test(() => {
+  document.scrollingElement.scrollTop = 120;
+  document.querySelector("#changer").style.height = "100px";
+  assert_equals(document.scrollingElement.scrollTop, 220);
+}, "Abs-pos with zero-height static parent.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/ancestor-change-heuristic.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/ancestor-change-heuristic.html
new file mode 100644
index 0000000..ffe60ad
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/ancestor-change-heuristic.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+#space { height: 1000px; }
+#ancestor { position: relative; }
+#before, #anchor { height: 100px; }
+#anchor { background-color: green; }
+
+.layout1 { padding-top: 20px; }
+.layout2 { margin-right: 20px; }
+.layout3 { max-width: 100px; }
+.layout4 { min-height: 400px; }
+.layout5 { position: static !important; }
+.layout6 { left: 20px; }
+.layout7 { transform: matrix(1, 0, 0, 1, 50, 50); }
+.nonLayout1 { color: red; }
+.nonLayout2 { font-size: 200%; }
+.nonLayout3 { box-shadow: 10px 10px 10px gray; }
+.nonLayout4 { opacity: 0.5; }
+.nonLayout5 { z-index: -1; }
+
+.scroller {
+  overflow: scroll;
+  width: 600px;
+  height: 600px;
+}
+
+</style>
+<div id="maybeScroller">
+  <div id="space">
+    <div id="ancestor">
+      <div id="before"></div>
+      <div id="anchor"></div>
+    </div>
+  </div>
+</div>
+<script>
+
+// Tests that scroll anchoring is suppressed when one of the "layout-affecting"
+// properties is modified on an ancestor of the anchor node.
+
+var scroller;
+var ancestor = document.querySelector("#ancestor");
+var before = document.querySelector("#before");
+
+function runCase(classToApply, expectSuppression) {
+  // Reset.
+  scroller.scrollTop = 0;
+  ancestor.className = "";
+  before.style.height = "";
+  scroller.scrollTop = 150;
+
+  ancestor.className = classToApply;
+  before.style.height = "150px";
+
+  var expectedTop = expectSuppression ? 150 : 200;
+  assert_equals(scroller.scrollTop, expectedTop);
+}
+
+function runAll() {
+  for (var i = 1; i <= 7; i++)
+    runCase("layout" + i, true);
+  for (var i = 1; i <= 5; i++)
+    runCase("nonLayout" + i, false);
+}
+
+test(() => {
+  document.querySelector("#maybeScroller").className = "";
+  scroller = document.scrollingElement;
+  runAll();
+}, "Ancestor changes in document scroller.");
+
+test(() => {
+  scroller = document.querySelector("#maybeScroller");
+  scroller.className = "scroller";
+  runAll();
+}, "Ancestor changes in scrollable <div>.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchor-updates-after-explicit-scroll.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchor-updates-after-explicit-scroll.html
new file mode 100644
index 0000000..7f0c54d1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchor-updates-after-explicit-scroll.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+#scroller {
+  height: 200px;
+  width: 200px;
+  overflow: scroll;
+}
+#a1, #space1, #a2, #space2 {
+  height: 200px;
+}
+#a1, #a2 {
+  background-color: #8f8;
+}
+
+</style>
+<div id="scroller">
+  <div id="space0"></div>
+  <div id="a1"></div>
+  <div id="space1"></div>
+  <div id="a2"></div>
+  <div id="space2"></div>
+</div>
+<script>
+
+// Tests that the anchor node is recomputed after an explicit change to the
+// scroll position.
+
+test(() => {
+  var scroller = document.querySelector("#scroller");
+  scroller.scrollTop = 500;
+
+  // We should now be anchored to #a2.
+  document.querySelector("#space1").style.height = "300px";
+  assert_equals(scroller.scrollTop, 600);
+
+  scroller.scrollTop = 100;
+
+  // We should now be anchored to #a1. Make sure there is no adjustment from
+  // moving #a2.
+  document.querySelector("#space1").style.height = "400px";
+  assert_equals(scroller.scrollTop, 100);
+
+  // Moving #a1 should produce an adjustment.
+  document.querySelector("#space0").style.height = "100px";
+  assert_equals(scroller.scrollTop, 200);
+}, "Anchor node recomputed after an explicit scroll occurs.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchoring-with-bounds-clamping-div.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchoring-with-bounds-clamping-div.html
new file mode 100644
index 0000000..3de725e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchoring-with-bounds-clamping-div.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+#scroller {
+  height: 500px;
+  width: 200px;
+  overflow: scroll;
+}
+#changer { height: 1500px; }
+#anchor {
+  width: 150px;
+  height: 1000px;
+  overflow: scroll;
+}
+
+</style>
+<div id="scroller">
+  <div id="changer"></div>
+  <div id="anchor"></div>
+</div>
+<script>
+
+// Test that scroll anchoring interacts correctly with scroll bounds clamping
+// inside a scrollable <div> element.
+//
+// There should be no visible jump even if the content shrinks such that the
+// new max scroll position is less than the previous scroll position.
+
+test(() => {
+  var scroller = document.querySelector("#scroller");
+  scroller.scrollTop = 1600;
+  document.querySelector("#changer").style.height = "0";
+  assert_equals(scroller.scrollTop, 100);
+}, "Anchoring combined with scroll bounds clamping in a <div>.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchoring-with-bounds-clamping.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchoring-with-bounds-clamping.html
index 9d0040f..3adcfe5 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchoring-with-bounds-clamping.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anchoring-with-bounds-clamping.html
@@ -2,20 +2,27 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <style>
-  #changer { height: 1500px; }
-  #anchor {
-    width: 150px; height: 1000px; background-color: pink;
-  }
+
+#changer { height: 1500px; }
+#anchor {
+  width: 150px;
+  height: 1000px;
+  background-color: pink;
+}
+
 </style>
 <div id="changer"></div>
 <div id="anchor"></div>
 <script>
+
 // Test that scroll anchoring interacts correctly with scroll bounds clamping:
 // There should be no visible jump even if the content shrinks such that the
 // new max scroll position is less than the previous scroll position.
+
 test(() => {
   document.scrollingElement.scrollTop = 1600;
   document.querySelector("#changer").style.height = "0";
   assert_equals(document.scrollingElement.scrollTop, 100);
-});
+}, "Anchoring combined with scroll bounds clamping in the document.");
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anonymous-block-box.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anonymous-block-box.html
new file mode 100644
index 0000000..97542e2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/anonymous-block-box.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { height: 2000px; margin: 0 10px; }
+#before, #after { height: 100px; }
+#before { margin-bottom: 100px; }
+#container { line-height: 100px; vertical-align: top; }
+
+</style>
+<div id="container">
+  <div id="before">before</div>
+  <span id="inline">inline</span>
+  <div id="after">after</div>
+</div>
+<script>
+
+// Tests anchoring inside an anonymous block box. The anchor selection algorithm
+// should descend into the children of the anonymous box even though it is fully
+// contained in the viewport.
+
+test(() => {
+  document.scrollingElement.scrollTop = 150;
+
+  var span = document.querySelector("#inline");
+  var newSpan = document.createElement("span");
+  newSpan.innerHTML = "inserted<br>";
+  span.parentNode.insertBefore(newSpan, span);
+
+  assert_equals(document.scrollingElement.scrollTop, 250);
+}, "Anchor selection descent into anonymous block boxes.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/basic.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/basic.html
index d4ec4ff7..2c46c28 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/basic.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/basic.html
@@ -2,15 +2,22 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <style>
-  body { height: 1000px }
-  div { height: 100px }
+
+body { height: 1000px; }
+div { height: 100px; }
+
 </style>
 <div id="block1">abc</div>
 <div id="block2">def</div>
 <script>
+
+// Tests that growing an element above the viewport produces a scroll
+// anchoring adjustment equal to the amount by which it grew.
+
 test(() => {
   document.scrollingElement.scrollTop = 150;
   document.querySelector("#block1").style.height = "200px";
   assert_equals(document.scrollingElement.scrollTop, 250);
-});
+}, "Minimal scroll anchoring example.");
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/clipped-scrollers-skipped.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/clipped-scrollers-skipped.html
index bd44dd92..594cd60 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/clipped-scrollers-skipped.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/clipped-scrollers-skipped.html
@@ -2,13 +2,15 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <style>
-  body { height: 2000px; }
-  #scroller { overflow: scroll; width: 500px; height: 300px; }
-  .anchor {
-    position: relative; height: 100px; width: 150px;
-    background-color: #afa; border: 1px solid gray;
-  }
-  #forceScrolling { height: 500px; background-color: #fcc; }
+
+body { height: 2000px; }
+#scroller { overflow: scroll; width: 500px; height: 300px; }
+.anchor {
+  position: relative; height: 100px; width: 150px;
+  background-color: #afa; border: 1px solid gray;
+}
+#forceScrolling { height: 500px; background-color: #fcc; }
+
 </style>
 <div id="scroller">
   <div id="innerChanger"></div>
@@ -18,8 +20,10 @@
 <div id="outerChanger"></div>
 <div id="outerAnchor" class="anchor"></div>
 <script>
+
 // Test that we ignore the clipped content when computing visibility otherwise
 // we may end up with an anchor that we think is in the viewport but is not.
+
 test(() => {
   document.querySelector("#scroller").scrollTop = 100;
   document.scrollingElement.scrollTop = 350;
@@ -29,5 +33,6 @@
 
   assert_equals(document.querySelector("#scroller").scrollTop, 300);
   assert_equals(document.scrollingElement.scrollTop, 500);
-});
+}, "Anchor selection with nested scrollers.");
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/descend-into-container-with-float.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/descend-into-container-with-float.html
new file mode 100644
index 0000000..a86634f9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/descend-into-container-with-float.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { height: 1000px; }
+#outer { width: 300px; }
+#outer:after { content: " "; clear:both; display: table; }
+#float {
+  float: left; background-color: #ccc;
+  height: 500px; width: 100%;
+}
+#inner { height: 100px; background-color: green; }
+
+</style>
+<div id="outer">
+  <div id="zeroheight">
+    <div id="float">
+       <div id="changer"></div>
+       <div id="inner"></div>
+    </div>
+  </div>
+</div>
+<div>after</div>
+<script>
+
+// Tests that we descend into zero-height containers that have floating content.
+
+test(() => {
+  document.scrollingElement.scrollTop = 50;
+  assert_equals(document.querySelector("#zeroheight").offsetHeight, 0);
+  document.querySelector("#changer").style.height = "50px";
+  assert_equals(document.scrollingElement.scrollTop, 100);
+}, "Zero-height container with float.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/descend-into-container-with-overflow.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/descend-into-container-with-overflow.html
new file mode 100644
index 0000000..3b469157
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/descend-into-container-with-overflow.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { height: 1000px; }
+#outer { width: 300px; }
+#zeroheight { height: 0px; }
+#changer { height: 100px; background-color: red; }
+#bottom { margin-top: 600px; }
+
+</style>
+<div id="outer">
+  <div id="zeroheight">
+    <div id="changer"></div>
+    <div id="bottom">bottom</div>
+  </div>
+</div>
+<script>
+
+// Tests that the anchor selection algorithm descends into zero-height
+// containers that have overflowing content.
+
+test(() => {
+  document.scrollingElement.scrollTop = 200;
+  document.querySelector("#changer").style.height = "200px";
+  assert_equals(document.scrollingElement.scrollTop, 300);
+}, "Zero-height container with visible overflow.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/exclude-fixed-position.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/exclude-fixed-position.html
new file mode 100644
index 0000000..99686bde
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/exclude-fixed-position.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { height: 1000px; margin: 0; }
+#fixed, #content { width: 200px; height: 100px; }
+#fixed { position: fixed; left: 100px; top: 50px; }
+#before { height: 50px; }
+#content { margin-top: 100px; }
+
+</style>
+<div id="fixed">fixed</div>
+<div id="before"></div>
+<div id="content">content</div>
+<script>
+
+// Tests that the anchor selection algorithm skips fixed-positioned elements.
+
+test(() => {
+  document.scrollingElement.scrollTop = 100;
+  document.querySelector("#before").style.height = "100px";
+  assert_equals(document.scrollingElement.scrollTop, 150);
+}, "Fixed-position header.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/inline-block.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/inline-block.html
new file mode 100644
index 0000000..fa7655bc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/inline-block.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { height: 1000px }
+#outer { line-height: 100px }
+#ib1, #ib2 { display: inline-block }
+
+</style>
+<span id=outer>
+  <span id=ib1>abc</span>
+  <br><br>
+  <span id=ib2>def</span>
+</span>
+<script>
+
+// Tests anchoring to an inline block inside a <span>.
+
+test(() => {
+  document.scrollingElement.scrollTop = 150;
+  document.querySelector("#ib1").style.lineHeight = "150px";
+  assert_equals(document.scrollingElement.scrollTop, 200);
+}, "Anchor selection descent into inline blocks.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/negative-layout-overflow.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/negative-layout-overflow.html
new file mode 100644
index 0000000..e1ce331
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/negative-layout-overflow.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body {
+  height: 1200px;
+}
+#header {
+  position: relative;
+  height: 100px;
+}
+#evil {
+  position: relative;
+  top: -900px;
+  height: 1000px;
+  width: 100px;
+}
+#changer {
+  height: 100px;
+}
+#anchor {
+  height: 100px;
+  background-color: green;
+}
+
+</style>
+<div id="header">
+  <div id="evil"></div>
+</div>
+<div id="changer"></div>
+<div id="anchor"></div>
+<script>
+
+// Tests that the anchor selection algorithm correctly accounts for negative
+// positioning when computing bounds for visibility.
+
+test(() => {
+  document.scrollingElement.scrollTop = 250;
+  document.querySelector("#changer").style.height = "200px";
+  assert_equals(document.scrollingElement.scrollTop, 350);
+}, "Anchor selection accounts for negative positioning.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/opt-out.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/opt-out.html
new file mode 100644
index 0000000..12d46c1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/opt-out.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { height: 2000px; overflow-anchor: none; }
+#scroller { overflow: scroll; width: 500px; height: 300px; }
+.anchor {
+  position:relative; height: 100px; width: 150px;
+  background-color: #afa; border: 1px solid gray;
+}
+#forceScrolling { height: 500px; background-color: #fcc; }
+
+</style>
+<div id="outerChanger"></div>
+<div id="outerAnchor" class="anchor"></div>
+<div id="scroller">
+  <div id="innerChanger"></div>
+  <div id="innerAnchor" class="anchor"></div>
+  <div id="forceScrolling"></div>
+</div>
+<script>
+
+// Tests that scroll anchoring can be disabled per-scroller with the
+// overflow-anchor property.
+
+var divScroller = document.querySelector("#scroller");
+var docScroller = document.scrollingElement;
+var innerChanger = document.querySelector("#innerChanger");
+var outerChanger = document.querySelector("#outerChanger");
+
+function setup() {
+  divScroller.scrollTop = 100;
+  docScroller.scrollTop = 100;
+  innerChanger.style.height = "200px";
+  outerChanger.style.height = "150px";
+}
+
+function reset() {
+  document.body.style.overflowAnchor = "";
+  divScroller.style.overflowAnchor = "";
+  divScroller.scrollTop = 0;
+  docScroller.scrollTop = 0;
+  innerChanger.style.height = "";
+  outerChanger.style.height = "";
+}
+
+test(() => {
+  setup();
+
+  assert_equals(divScroller.scrollTop, 300,
+      "Scroll anchoring should apply to #scroller.");
+
+  assert_equals(docScroller.scrollTop, 100,
+      "Scroll anchoring should not apply to the document scroller.");
+
+  reset();
+}, "Disabled on document, enabled on div.");
+
+test(() => {
+  document.body.style.overflowAnchor = "auto";
+  divScroller.style.overflowAnchor = "none";
+  setup();
+
+  assert_equals(divScroller.scrollTop, 100,
+      "Scroll anchoring should not apply to #scroller.");
+
+  assert_equals(docScroller.scrollTop, 250,
+      "Scroll anchoring should apply to the document scroller.");
+
+  reset();
+}, "Enabled on document, disabled on div.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/position-change-heuristic.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/position-change-heuristic.html
new file mode 100644
index 0000000..a7ef5525
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/position-change-heuristic.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+#space {
+  height: 1000px;
+}
+#header {
+  background-color: #F5B335;
+  height: 50px;
+  width: 100%;
+}
+#content {
+  background-color: #D3D3D3;
+  height: 400px;
+}
+.scroller {
+  overflow: scroll;
+  position: relative;
+  width: 600px;
+  height: 600px;
+}
+
+</style>
+<div id="maybeScroller">
+  <div id="space">
+    <div id="header"></div>
+    <div id="before"></div>
+    <div id="content"></div>
+  </div>
+</div>
+<script>
+
+// Tests that scroll anchoring is suppressed when an element in the scroller
+// changes its in-flow state.
+
+var scroller;
+
+function runCase(oldPos, newPos, expectSuppression, skipInverse) {
+  var header = document.querySelector("#header");
+  var before = document.querySelector("#before");
+
+  header.style.position = oldPos;
+  before.style.height = "0";
+  scroller.scrollTop = 200;
+
+  header.style.position = newPos;
+  before.style.height = "25px";
+
+  var expectedTop = expectSuppression ? 200 : 225;
+  assert_equals(scroller.scrollTop, expectedTop);
+
+  if (!skipInverse)
+    runCase(newPos, oldPos, expectSuppression, true);
+}
+
+test(() => {
+  scroller = document.scrollingElement;
+  document.querySelector("#maybeScroller").className = "";
+
+  runCase("static", "fixed", true);
+  runCase("static", "absolute", true);
+  runCase("static", "relative", false);
+  runCase("fixed", "absolute", false);
+  runCase("fixed", "relative", true);
+  runCase("absolute", "relative", true);
+}, "Position changes in document scroller.");
+
+test(() => {
+  scroller = document.querySelector("#maybeScroller");
+  scroller.className = "scroller";
+
+  runCase("static", "fixed", true);
+  runCase("static", "absolute", true);
+  runCase("static", "relative", false);
+  runCase("fixed", "absolute", false);
+  runCase("fixed", "relative", true);
+  runCase("absolute", "relative", true);
+}, "Position changes in scrollable <div>.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/start-edge-in-block-layout-direction.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/start-edge-in-block-layout-direction.html
new file mode 100644
index 0000000..4607c37b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/start-edge-in-block-layout-direction.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { margin: 0; }
+html {
+  line-height: 0;
+  width: 200vw;
+  height: 200vh;
+}
+
+html.ltr { direction: ltr; }
+html.rtl { direction: rtl; }
+
+html.horz { writing-mode: horizontal-tb; }
+html.vlr { writing-mode: vertical-lr; }
+html.vrl { writing-mode: vertical-rl; }
+
+.horz.ltr .cx2, .vlr .cx2 { left: 100vw; }
+.horz.rtl .cx2, .vrl .cx2 { right: 100vw; }
+.horz .cy2, .ltr .cy2 { top: 100vh; }
+.vlr.rtl .cy2, .vrl.rtl .cy2 { bottom: 100vh; }
+
+#block_pusher, #inline_pusher {
+  display: inline-block;
+  width: 100px;
+  height: 100px;
+}
+#block_pusher { background-color: #e88; }
+#inline_pusher { background-color: #88e; }
+.vpush { height: 80px !important; }
+.hpush { width: 70px !important; }
+
+#anchor {
+  position: relative;
+  display: inline-block;
+  background-color: #8e8;
+  min-width: 100px;
+  min-height: 100px;
+}
+
+#grower { width: 0; height: 0; }
+.grow {
+  width: 180px !important;
+  height: 160px !important;
+}
+
+</style>
+<div id="container">
+  <div id="block_pusher"></div><br>
+  <div id="inline_pusher"></div><div id="anchor">
+    <div id="grower"></div>
+  </div>
+</div>
+<script>
+
+// Tests that anchoring adjustments are only on the block layout axis and that
+// their magnitude is based on the movement of the block start edge of the
+// anchor node, for all 6 combinations of text direction and writing mode,
+// regardless of which corner of the viewport the anchor node overlaps.
+
+var CORNERS = ["cx1 cy1", "cx2 cy1", "cx1 cy2", "cx2 cy2"];
+var docEl = document.documentElement;
+var scroller = document.scrollingElement;
+var blockPusher = document.querySelector("#block_pusher");
+var inlinePusher = document.querySelector("#inline_pusher");
+var grower = document.querySelector("#grower");
+var anchor = document.querySelector("#anchor");
+
+function reset() {
+  scroller.scrollLeft = 0;
+  scroller.scrollTop = 0;
+  blockPusher.className = "";
+  inlinePusher.className = "";
+  grower.className = "";
+}
+
+function runCase(docClass, xDir, yDir, vert, expectXAdj, expectYAdj, corner) {
+  docEl.className = docClass;
+  anchor.className = corner;
+
+  var initX = 150 * xDir;
+  var initY = 150 * yDir;
+
+  scroller.scrollLeft = initX;
+  scroller.scrollTop = initY;
+
+  // Each corner moves a different distance.
+  block_pusher.className = vert ? "hpush" : "vpush";
+  inline_pusher.className = vert ? "vpush" : "hpush";
+  grower.className = "grow";
+
+  assert_equals(scroller.scrollLeft, initX + expectXAdj);
+  assert_equals(scroller.scrollTop, initY + expectYAdj);
+
+  reset();
+}
+
+test(() => {
+  CORNERS.forEach((corner) => {
+    runCase("horz ltr", 1, 1, false, 0, -20, corner);
+  });
+}, "Horizontal LTR.");
+
+test(() => {
+  CORNERS.forEach((corner) => {
+    runCase("horz rtl", -1, 1, false, 0, -20, corner);
+  });
+}, "Horizontal RTL.");
+
+test(() => {
+  CORNERS.forEach((corner) => {
+    runCase("vlr ltr", 1, 1, true, -30, 0, corner);
+  });
+}, "Vertical-LR LTR.");
+
+test(() => {
+  CORNERS.forEach((corner) => {
+    runCase("vlr rtl", 1, -1, true, -30, 0, corner);
+  });
+}, "Vertical-LR RTL.");
+
+test(() => {
+  CORNERS.forEach((corner) => {
+    runCase("vrl ltr", -1, 1, true, 30, 0, corner);
+  });
+}, "Vertical-RL LTR.");
+
+test(() => {
+  CORNERS.forEach((corner) => {
+    runCase("vrl rtl", -1, -1, true, 30, 0, corner);
+  });
+}, "Vertical-RL RTL.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/subtree-exclusion.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/subtree-exclusion.html
new file mode 100644
index 0000000..c384280f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/subtree-exclusion.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body { height: 1000px }
+#A, #B { width: 100px; background-color: #afa; }
+#B { height: 100px; }
+#inner { width: 100px; height: 100px; background-color: pink; }
+#A { overflow-anchor: none; }
+
+</style>
+<div id="changer1"></div>
+<div id="A">
+  <div id="inner"></div>
+  <div id="changer2"></div>
+</div>
+<div id="B"></div>
+<script>
+
+// Tests that an element with overflow-anchor: none is excluded, along with its
+// DOM descendants, from the anchor selection algorithm.
+
+test(() => {
+  var changer1 = document.querySelector("#changer1");
+  var changer2 = document.querySelector("#changer2");
+
+  document.scrollingElement.scrollTop = 50;
+  changer1.style.height = "100px";
+  changer2.style.height = "50px";
+
+  // We should anchor to #B, not #A or #inner, despite them being visible.
+  assert_equals(document.scrollingElement.scrollTop, 200);
+
+  document.querySelector("#A").style.overflowAnchor = "auto";
+  document.scrollingElement.scrollTop = 150;
+
+  changer1.style.height = "200px";
+  changer2.style.height = "100px";
+
+  // We should now anchor to #inner, which is moved only by #changer1.
+  assert_equals(document.scrollingElement.scrollTop, 250);
+}, "Subtree exclusion with overflow-anchor.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/wrapped-text.html b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/wrapped-text.html
new file mode 100644
index 0000000..4bd2cdb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/scroll-anchoring/wrapped-text.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+body {
+  position: absolute;
+  font-size: 100px;
+  width: 200px;
+  height: 1000px;
+  line-height: 100px;
+}
+
+</style>
+abc <b id=b>def</b> ghi
+<script>
+
+// Tests anchoring to a text node that is moved by preceding text.
+
+test(() => {
+  var b = document.querySelector("#b");
+  var preText = b.previousSibling;
+  document.scrollingElement.scrollTop = 150;
+  preText.nodeValue = "abcd efg ";
+  assert_equals(document.scrollingElement.scrollTop, 250);
+}, "Anchoring with text wrapping changes.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/load_wasm.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/load_wasm.js
index eca4b8d..5123246 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/load_wasm.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/load_wasm.js
@@ -3,12 +3,6 @@
 // found in the LICENSE file.
 
 function createWasmModule() {
-    // the file incrementer.wasm is copied from
-    // //v8/test/mjsunit/wasm. This is because currently we cannot
-    // reference files outside the LayoutTests folder. When wasm format
-    // changes require that file to be updated, there is a test on the
-    // v8 side (same folder), ensure-wasm-binaries-up-to-date.js, which
-    // fails and will require incrementer.wasm to be updated on that side.
     return fetch('incrementer.wasm')
         .then(response => {
             if (!response.ok) throw new Error(response.statusText);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.html b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.html
index 0011095..49766c77 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.html
@@ -5,5 +5,4 @@
 <script src="wasm_serialization_tests.js"></script>
 <script>
   promise_test(TestInstantiateInWorker, "serialize wasm to worker");
-  promise_test(TestIncompatibleDowngrade, "incompatible downgrade");
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.js
index 60aec80..3cc41661 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.js
@@ -16,48 +16,3 @@
     .then(data => assert_equals(data, 43))
     .catch(error => assert_unreached(error));
 }
-
-function ascii(a) { return a.charCodeAt(0); }
-
-function findStartOfWasmHeader(byteView) {
-  for (var i = 0; i < byteView.length - 2; ++i) {
-    if (byteView[i] === ascii('a') &&
-        byteView[i+1] === ascii('s') &&
-        byteView[i+2] === ascii('m')) {
-      return i;
-    }
-  }
-  return -1;
-}
-
-function TestIncompatibleDowngrade() {
-  return createWasmModule()
-    .then((mod) => {
-      var buffer = window.internals.serializeWithInlineWasm(mod);
-      var byteView = new Uint8Array(buffer);
-      // The serialized payload starts with some serialization header, followed
-      // by the wasm wire bytes. Those should start with the characters
-      // 'a' 's' 'm'.
-      // Find the start of that sequence and invalidate the wire bytes by
-      // clearing the first byte.
-      var startOfWasmHeader = findStartOfWasmHeader(byteView);
-      assert_greater_than(startOfWasmHeader, 0,
-                          "The wire format should contain a wasm header.");
-      byteView[startOfWasmHeader] = 0;
-      // Also invalidate the serialized blob. That follows the wire bytes.
-      // Start from the end and clear the first non-null byte.
-      var invalidalidated = false;
-      for (var i = byteView.length - 1; i >= startOfWasmHeader + 3; --i) {
-        if (byteView[i] != 0) {
-          byteView[i] = 0;
-          invalidated = true;
-          break;
-        }
-      }
-      assert_true(invalidated,
-                  "the serialized blob should contain some non-null bytes.");
-
-      var deserialized = window.internals.deserializeBufferContainingWasm(byteView.buffer);
-      assert_equals(deserialized, null);
-    });
-}
diff --git a/third_party/WebKit/LayoutTests/http/tests/csspaint/paint2d-filter-expected.html b/third_party/WebKit/LayoutTests/http/tests/csspaint/paint2d-filter-expected.html
new file mode 100644
index 0000000..4ca6383
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/csspaint/paint2d-filter-expected.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    canvas { display: inline-block; }
+</style>
+</head>
+<body>
+<canvas id='output0' width='100' height='100'></canvas>
+<canvas id='output1' width='100' height='100'></canvas>
+<canvas id='output2' width='100' height='100'></canvas>
+<canvas id='output3' width='100' height='100'></canvas>
+<canvas id='output4' width='100' height='100'></canvas>
+<br>
+<canvas id='output5' width='100' height='100'></canvas>
+<canvas id='output6' width='100' height='100'></canvas>
+<canvas id='output7' width='100' height='100'></canvas>
+<canvas id='output8' width='100' height='100'></canvas>
+<canvas id='output9' width='100' height='100'></canvas>
+<br>
+<canvas id='output10' width='100' height='100'></canvas>
+<canvas id='output11' width='100' height='100'></canvas>
+<canvas id='output12' width='100' height='100'></canvas>
+<canvas id='output13' width='100' height='100'></canvas>
+<canvas id='output14' width='100' height='100'></canvas>
+
+<script>
+var paint = function(id, filter) {
+  var c = document.getElementById(id);
+  var ctx = c.getContext('2d');
+  ctx.filter = filter;
+  ctx.fillStyle = '#A00';
+  ctx.fillRect(0, 0, 15, 15);
+  ctx.fillStyle = '#0A0';
+  ctx.fillRect(15, 0, 15, 15);
+  ctx.fillStyle = '#00A';
+  ctx.fillRect(0, 15, 15, 15);
+  ctx.fillStyle = "#AA0";
+  ctx.fillRect(15, 15, 15, 15);
+  return ctx;
+};
+
+paint('output0', "none");
+paint('output1', "blur(10px)");
+paint('output2', "blur(0px)");
+paint('output3', "blur(16px)");
+paint('output4', "blur(0px)");
+paint('output5', "brightness(40%)");
+paint('output6', "contrast(20%)");
+paint('output7', "drop-shadow(0 0 5px green)");
+paint('output8', "grayscale(100%)");
+paint('output9', "invert(100%)");
+paint('output10', "opacity(50%)");
+paint('output11', "saturate(20%)");
+paint('output12', "sepia(100%)");
+paint('output13', "sepia(1) hue-rotate(200deg)");
+paint('output14', "url(#url)");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/csspaint/paint2d-filter.html b/third_party/WebKit/LayoutTests/http/tests/csspaint/paint2d-filter.html
new file mode 100644
index 0000000..9f37be2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/csspaint/paint2d-filter.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../resources/run-after-layout-and-paint.js"></script>
+<script src="resources/test-runner-paint-worklet.js"></script>
+<style>
+    div {
+        display: inline-block;
+        width: 100px;
+        height: 100px;
+    }
+    #filter-none { background-image: paint(filter-none); }
+    #filter-blur-10px { background-image: paint(filter-blur-10px); }
+    #filter-blur-50vh { background-image: paint(filter-blur-50vh); }
+    #filter-blur-1em { background-image: paint(filter-blur-1em); }
+    #filter-blur-2percent { background-image: paint(filter-blur-2percent); }
+    #filter-brightness { background-image: paint(filter-brightness); }
+    #filter-contrast { background-image: paint(filter-contrast); }
+    #filter-drop-shadow { background-image: paint(filter-drop-shadow); }
+    #filter-grayscale { background-image: paint(filter-grayscale); }
+    #filter-invert { background-image: paint(filter-invert); }
+    #filter-opacity { background-image: paint(filter-opacity); }
+    #filter-saturate { background-image: paint(filter-saturate); }
+    #filter-sepia { background-image: paint(filter-sepia); }
+    #filter-hue-rotate { background-image: paint(filter-hue-rotate); }
+    #filter-url { background-image: paint(filter-url); }
+</style>
+</head>
+<body>
+<div id="filter-none"></div>
+<div id="filter-blur-10px"></div>
+<div id="filter-blur-50vh"></div>
+<div id="filter-blur-1em"></div>
+<div id="filter-blur-2percent"></div>
+<br>
+<div id="filter-brightness"></div>
+<div id="filter-contrast"></div>
+<div id="filter-drop-shadow"></div>
+<div id="filter-grayscale"></div>
+<div id="filter-invert"></div>
+<br>
+<div id="filter-opacity"></div>
+<div id="filter-saturate"></div>
+<div id="filter-sepia"></div>
+<div id="filter-hue-rotate"></div>
+<div id="filter-url"></div>
+
+<script id="code" type="text/worklet">
+var paintNames = [
+    'filter-none',
+    'filter-blur-10px',
+    'filter-blur-50vh',
+    'filter-blur-1em',
+    'filter-blur-2percent',
+    'filter-brightness',
+    'filter-contrast',
+    'filter-drop-shadow',
+    'filter-grayscale',
+    'filter-invert',
+    'filter-opacity',
+    'filter-saturate',
+    'filter-sepia',
+    'filter-hue-rotate',
+    'filter-url'
+];
+
+var filterOps = [
+    'none',
+    'blur(10px)',
+    'blur(50vh)',
+    'blur(1em)',
+    'blur(2%)',
+    'brightness(40%)',
+    'contrast(20%)',
+    'drop-shadow(0 0 5px green)',
+    'grayscale(100%)',
+    'invert(100%)',
+    'opacity(50%)',
+    'saturate(20%)',
+    'sepia(100%)',
+    'sepia(1) hue-rotate(200deg)',
+    'url(#url)'
+];
+
+function doPaint(ctx, op) {
+  ctx.filter = op;
+  console.log(op);
+  ctx.fillStyle = '#A00';
+  ctx.fillRect(0, 0, 15, 15);
+  ctx.fillStyle = '#0A0';
+  ctx.fillRect(15, 0, 15, 15);
+  ctx.fillStyle = '#00A';
+  ctx.fillRect(0, 15, 15, 15);
+  ctx.fillStyle = "#AA0";
+  ctx.fillRect(15, 15, 15, 15);
+};
+
+for (var i = 0; i < filterOps.length; i++) {
+  let op = filterOps[i];
+  registerPaint(paintNames[i], class { paint(ctx, geom) { doPaint(ctx, op); } });
+}
+</script>
+
+<script>
+    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_downgrade_test.html b/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_downgrade_test.html
new file mode 100644
index 0000000..6169c55d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_downgrade_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../resources/get-host-info.js"></script>
+<script src="resources/load_wasm.js"></script>
+<script src="wasm_downgrade_test.js"></script>
+<script>
+  promise_test(TestIncompatibleDowngrade, "incompatible downgrade");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_downgrade_test.js b/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_downgrade_test.js
new file mode 100644
index 0000000..ad735b8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_downgrade_test.js
@@ -0,0 +1,48 @@
+// 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.
+
+function ascii(a) { return a.charCodeAt(0); }
+
+function findStartOfWasmHeader(byteView) {
+  for (var i = 0; i < byteView.length - 2; ++i) {
+    if (byteView[i] === ascii('a') &&
+        byteView[i+1] === ascii('s') &&
+        byteView[i+2] === ascii('m')) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+function TestIncompatibleDowngrade() {
+  return createWasmModule()
+    .then((mod) => {
+      var buffer = window.internals.serializeWithInlineWasm(mod);
+      var byteView = new Uint8Array(buffer);
+      // The serialized payload starts with some serialization header, followed
+      // by the wasm wire bytes. Those should start with the characters
+      // 'a' 's' 'm'.
+      // Find the start of that sequence and invalidate the wire bytes by
+      // clearing the first byte.
+      var startOfWasmHeader = findStartOfWasmHeader(byteView);
+      assert_greater_than(startOfWasmHeader, 0,
+                          "The wire format should contain a wasm header.");
+      byteView[startOfWasmHeader] = 0;
+      // Also invalidate the serialized blob. That follows the wire bytes.
+      // Start from the end and clear the first non-null byte.
+      var invalidalidated = false;
+      for (var i = byteView.length - 1; i >= startOfWasmHeader + 3; --i) {
+        if (byteView[i] != 0) {
+          byteView[i] = 0;
+          invalidated = true;
+          break;
+        }
+      }
+      assert_true(invalidated,
+                  "the serialized blob should contain some non-null bytes.");
+
+      var deserialized = window.internals.deserializeBufferContainingWasm(byteView.buffer);
+      assert_equals(deserialized, null);
+    });
+}
diff --git a/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt b/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
index fe80529..35294a2 100644
--- a/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
@@ -120,6 +120,7 @@
 CONSOLE MESSAGE: line 147: interface PaintRenderingContext2D
 CONSOLE MESSAGE: line 147:     getter currentTransform
 CONSOLE MESSAGE: line 147:     getter fillStyle
+CONSOLE MESSAGE: line 147:     getter filter
 CONSOLE MESSAGE: line 147:     getter globalAlpha
 CONSOLE MESSAGE: line 147:     getter globalCompositeOperation
 CONSOLE MESSAGE: line 147:     getter imageSmoothingEnabled
@@ -169,6 +170,7 @@
 CONSOLE MESSAGE: line 147:     method translate
 CONSOLE MESSAGE: line 147:     setter currentTransform
 CONSOLE MESSAGE: line 147:     setter fillStyle
+CONSOLE MESSAGE: line 147:     setter filter
 CONSOLE MESSAGE: line 147:     setter globalAlpha
 CONSOLE MESSAGE: line 147:     setter globalCompositeOperation
 CONSOLE MESSAGE: line 147:     setter imageSmoothingEnabled
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/cHRM_color_spin-expected.png b/third_party/WebKit/LayoutTests/images/cHRM_color_spin-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/cHRM_color_spin-expected.png
rename to third_party/WebKit/LayoutTests/images/cHRM_color_spin-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-jpeg-with-color-profile-expected.png b/third_party/WebKit/LayoutTests/images/color-jpeg-with-color-profile-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-jpeg-with-color-profile-expected.png
rename to third_party/WebKit/LayoutTests/images/color-jpeg-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-animate-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-animate-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-animate-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-animate-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-animate-rotate-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-animate-rotate-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-animate-rotate-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-animate-rotate-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-clip-text-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-background-clip-text-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-clip-text-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-background-clip-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-image-cover-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-background-image-cover-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-image-cover-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-background-image-cover-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-image-repeat-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-background-image-repeat-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-image-repeat-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-border-fade-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-border-fade-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-border-fade-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-border-fade-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-border-image-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-border-image-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-border-image-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-border-image-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-clip-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-clip-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-clip-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-clip-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-iframe-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-iframe-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-iframe-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-iframe-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-canvas-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-image-canvas-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-canvas-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-canvas-pattern-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-image-canvas-pattern-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-canvas-pattern-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-image-canvas-pattern-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-object-fit-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-image-object-fit-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-object-fit-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-image-object-fit-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-profile-match-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-image-profile-match-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-profile-match-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-image-profile-match-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-pseudo-content-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-image-pseudo-content-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-pseudo-content-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-image-pseudo-content-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-svg-resource-url-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-image-svg-resource-url-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-image-svg-resource-url-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-image-svg-resource-url-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-mask-image-svg-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-mask-image-svg-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-mask-image-svg-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-mask-image-svg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-munsell-adobe-to-srgb-expected.txt b/third_party/WebKit/LayoutTests/images/color-profile-munsell-adobe-to-srgb-expected.txt
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-munsell-adobe-to-srgb-expected.txt
rename to third_party/WebKit/LayoutTests/images/color-profile-munsell-adobe-to-srgb-expected.txt
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-munsell-srgb-to-srgb-expected.txt b/third_party/WebKit/LayoutTests/images/color-profile-munsell-srgb-to-srgb-expected.txt
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-munsell-srgb-to-srgb-expected.txt
rename to third_party/WebKit/LayoutTests/images/color-profile-munsell-srgb-to-srgb-expected.txt
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-object-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-object-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-object-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-object-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-svg-foreign-object-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-svg-foreign-object-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-svg-foreign-object-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-svg-foreign-object-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/paint-subrect-expected.png b/third_party/WebKit/LayoutTests/images/paint-subrect-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/paint-subrect-expected.png
rename to third_party/WebKit/LayoutTests/images/paint-subrect-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/paletted-png-with-color-profile-expected.png b/third_party/WebKit/LayoutTests/images/paletted-png-with-color-profile-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/paletted-png-with-color-profile-expected.png
rename to third_party/WebKit/LayoutTests/images/paletted-png-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/png-color-profile-ignore-gamma-expected.png b/third_party/WebKit/LayoutTests/images/png-color-profile-ignore-gamma-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/png-color-profile-ignore-gamma-expected.png
rename to third_party/WebKit/LayoutTests/images/png-color-profile-ignore-gamma-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/webp-color-profile-lossless-expected.png b/third_party/WebKit/LayoutTests/images/webp-color-profile-lossless-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/webp-color-profile-lossless-expected.png
rename to third_party/WebKit/LayoutTests/images/webp-color-profile-lossless-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/webp-color-profile-lossy-alpha-expected.png b/third_party/WebKit/LayoutTests/images/webp-color-profile-lossy-alpha-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/webp-color-profile-lossy-alpha-expected.png
rename to third_party/WebKit/LayoutTests/images/webp-color-profile-lossy-alpha-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/webp-color-profile-lossy-expected.png b/third_party/WebKit/LayoutTests/images/webp-color-profile-lossy-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/webp-color-profile-lossy-expected.png
rename to third_party/WebKit/LayoutTests/images/webp-color-profile-lossy-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-adobe-to-srgb-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-adobe-to-srgb-expected.png
index 3c1b162..c2f6927 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-adobe-to-srgb-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-adobe-to-srgb-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-srgb-to-srgb-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-srgb-to-srgb-expected.png
index 14138ce3..1f00131 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-srgb-to-srgb-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-munsell-srgb-to-srgb-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/cHRM_color_spin-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/cHRM_color_spin-expected.png
deleted file mode 100644
index b372065..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/cHRM_color_spin-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-jpeg-with-color-profile-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-jpeg-with-color-profile-expected.png
deleted file mode 100644
index 693dca60..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-jpeg-with-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-animate-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-animate-expected.png
deleted file mode 100644
index d069fe8..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-animate-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-animate-rotate-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-animate-rotate-expected.png
deleted file mode 100644
index b9eb5a7..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-animate-rotate-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-clip-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-clip-text-expected.png
deleted file mode 100644
index 844fdc5..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-clip-text-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cover-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cover-expected.png
deleted file mode 100644
index 6b0791d..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cover-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-png-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-png-expected.png
index 099eeb2..62c0608 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-png-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-png-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-repeat-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-repeat-expected.png
deleted file mode 100644
index 7f26bde..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-repeat-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-fade-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-fade-expected.png
deleted file mode 100644
index 04668ce..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-fade-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-image-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-image-expected.png
deleted file mode 100644
index fb69fdb9..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-image-source-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-image-source-expected.png
index 5efd3da..d6c4262e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-image-source-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-image-source-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-radius-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-radius-expected.png
index 8d03d28..d9557de 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-radius-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-border-radius-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-clip-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-clip-expected.png
deleted file mode 100644
index b0b6200..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-clip-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-drag-image-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-drag-image-expected.png
index 58f7f2c..1fc9fe1 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-drag-image-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-drag-image-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-filter-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-filter-expected.png
index 1b517126..3422705 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-iframe-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-iframe-expected.png
deleted file mode 100644
index 7cd1803a..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-iframe-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-expected.png
deleted file mode 100644
index da3dedd..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-pattern-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-pattern-expected.png
deleted file mode 100644
index be3ad53..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-pattern-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-svg-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-svg-expected.png
index 039f7477..d359ffc 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-svg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-canvas-svg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-expected.png
index a5cdce3..8d47b0ff 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-filter-all-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-filter-all-expected.png
index 304dfb0..fcd0652f 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-filter-all-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-filter-all-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-object-fit-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-object-fit-expected.png
deleted file mode 100644
index 3ede6368..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-object-fit-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-profile-match-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-profile-match-expected.png
deleted file mode 100644
index 556dd78a..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-profile-match-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-pseudo-content-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-pseudo-content-expected.png
deleted file mode 100644
index f9ee2404..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-pseudo-content-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-shape-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-shape-expected.png
index bb179135..1cbc88ff3 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-shape-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-shape-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-svg-resource-url-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-svg-resource-url-expected.png
deleted file mode 100644
index 4b2e18c..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-image-svg-resource-url-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-expected.png
index 26765c9..69ad073 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-mask-image-svg-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index c413acca..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-adobe-to-srgb-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-adobe-to-srgb-expected.png
index 591b87b..7c29dbb0 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-adobe-to-srgb-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-adobe-to-srgb-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-adobe-to-srgb-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-adobe-to-srgb-expected.txt
deleted file mode 100644
index 236d02d..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-adobe-to-srgb-expected.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-
-Color         Actual        Expected      dE
---------------------------------------------
-Dark Skin     95,62,49      115,80,64     31
-Light Skin    181,132,111   195,151,130   30
-Blue Sky      76,103,139    94,123,156    32
-Foliage       71,91,50      88,108,65     28
-Blue Flower   111,108,162   130,129,177   32
-Bluish Green  85,179,154    100,190,171   25
---------------------------------------------
-Orange        205,101,29    217,122,37    25
-Purplish Blue 56,69,149     72,91,165     32
-Moderate Red  178,62,79     194,84,98     33
-Purple        72,43,87      91,59,107     32
-Yellow Green  144,177,46    160,188,60    24
-Orange Yellow 223,147,32    230,163,42    20
---------------------------------------------
-Blue          35,40,135     46,60,153     29
-Green         58,134,54     71,150,69     25
-Red           159,26,43     177,44,56     29
-Yellow        234,191,24    238,200,27    10
-Magenta       171,58,129    187,82,148    35
-Cyan (*)      0,116,149     0,135,166     25
---------------------------------------------
-White         240,238,232   243,242,237   7
-Neutral 8     189,190,188   201,201,201   21
-Neutral 6.5   143,144,143   161,161,161   31
-Neutral 5     104,102,102   122,122,121   33
-Neutral 3.5   66,66,66      83,83,83      29
-Black         37,37,37      50,49,50      22
---------------------------------------------
-
-Result: total RMS color error: 27.52
- * Munsell Cyan is outside 255 sRGB gamut
-
-  
-
-
-
-
-
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-srgb-to-srgb-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-srgb-to-srgb-expected.png
index f378248e8d..ba977700 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-srgb-to-srgb-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-srgb-to-srgb-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-srgb-to-srgb-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-srgb-to-srgb-expected.txt
deleted file mode 100644
index 6b8c026e..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-munsell-srgb-to-srgb-expected.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-
-Color         Actual        Expected      dE
---------------------------------------------
-Dark Skin     95,62,49      115,80,64     31
-Light Skin    181,133,111   195,151,130   30
-Blue Sky      76,103,138    94,123,156    32
-Foliage       71,90,50      88,108,65     29
-Blue Flower   111,108,161   130,129,177   33
-Bluish Green  85,178,155    100,190,171   25
---------------------------------------------
-Orange        206,102,29    217,122,37    24
-Purplish Blue 56,69,148     72,91,165     32
-Moderate Red  179,62,80     194,84,98     32
-Purple        72,42,88      91,59,107     32
-Yellow Green  144,178,47    160,188,60    23
-Orange Yellow 222,147,33    230,163,42    20
---------------------------------------------
-Blue          35,40,135     46,60,153     29
-Green         58,135,53     71,150,69     25
-Red           159,26,43     177,44,56     29
-Yellow        233,190,24    238,200,27    12
-Magenta       171,58,130    187,82,148    34
-Cyan (*)      15,116,149    0,135,166     30
---------------------------------------------
-White         240,239,233   243,242,237   6
-Neutral 8     189,189,189   201,201,201   21
-Neutral 6.5   144,144,144   161,161,161   29
-Neutral 5     103,103,102   122,122,121   33
-Neutral 3.5   65,65,65      83,83,83      31
-Black         38,37,38      50,49,50      21
---------------------------------------------
-
-Result: total RMS color error: 27.64
- * Munsell Cyan is outside 255 sRGB gamut
-
-  
-
-
-
-
-
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-object-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-object-expected.png
deleted file mode 100644
index eea719bc..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-object-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-foreign-object-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-foreign-object-expected.png
deleted file mode 100644
index a7050797..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-foreign-object-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/embed-image-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/embed-image-expected.png
deleted file mode 100644
index 19fdf4d..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/embed-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/jpeg-with-color-profile-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/jpeg-with-color-profile-expected.png
index b92f1c6..5f1c56e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/jpeg-with-color-profile-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/jpeg-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/object-image-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/object-image-expected.png
deleted file mode 100644
index 19fdf4d..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/object-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/paint-subrect-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/paint-subrect-expected.png
deleted file mode 100644
index 64b6ca2..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/paint-subrect-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/paletted-png-with-color-profile-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/paletted-png-with-color-profile-expected.png
deleted file mode 100644
index 90d0d43f..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/paletted-png-with-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/png-color-profile-ignore-gamma-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/png-color-profile-ignore-gamma-expected.png
deleted file mode 100644
index 193267c8..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/png-color-profile-ignore-gamma-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/png-suite/test-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/png-suite/test-expected.png
index ae61580e..5205383 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/png-suite/test-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/png-suite/test-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/png-with-color-profile-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/png-with-color-profile-expected.png
index b92f1c6..5f1c56e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/png-with-color-profile-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/png-with-color-profile-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/png_per_row_alpha_decoding-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/png_per_row_alpha_decoding-expected.png
deleted file mode 100644
index e9e5ba441..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/png_per_row_alpha_decoding-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossless-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossless-expected.png
deleted file mode 100644
index 255c145..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossless-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossy-alpha-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossy-alpha-expected.png
deleted file mode 100644
index 92ffe60..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossy-alpha-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossy-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossy-expected.png
deleted file mode 100644
index 717906ca4..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/webp-color-profile-lossy-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/Source/build/scripts/templates/fields/storage_only.tmpl b/third_party/WebKit/Source/build/scripts/templates/fields/storage_only.tmpl
index 5d7c25f..a4ba01f 100644
--- a/third_party/WebKit/Source/build/scripts/templates/fields/storage_only.tmpl
+++ b/third_party/WebKit/Source/build/scripts/templates/fields/storage_only.tmpl
@@ -7,7 +7,9 @@
 
 {% macro decl_protected_methods(field) -%}
 {{base.decl_internal_getter_method(field)}}
+{% if not field.wrapper_pointer_name %}
 {{base.decl_internal_setter_method(field)}}
+{% endif %}
 {% if not field.is_bit_field -%}
 void {{field.internal_setter_method_name}}({{field.type_name}}&& v) {
 {% if field.group_member_name %}
diff --git a/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5 b/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
index a68b70f..9d3fd3e 100644
--- a/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
+++ b/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
@@ -546,7 +546,7 @@
       field_group: "rare-inherited",
     },
     {
-      name: "Variables",
+      name: "InheritedVariables",
       inherited: true,
       field_template: "storage_only",
       type_name: "StyleInheritedVariables",
diff --git a/third_party/WebKit/Source/core/layout/ScrollAnchorTest.cpp b/third_party/WebKit/Source/core/layout/ScrollAnchorTest.cpp
index 42864d8..bd2ed6e 100644
--- a/third_party/WebKit/Source/core/layout/ScrollAnchorTest.cpp
+++ b/third_party/WebKit/Source/core/layout/ScrollAnchorTest.cpp
@@ -100,6 +100,8 @@
             GetScrollAnchor(viewport).AnchorObject());
 }
 
+// TODO(skobes): Convert this to web-platform-tests when visual viewport API is
+// launched (http://crbug.com/635031).
 TEST_P(ScrollAnchorTest, VisualViewportAnchors) {
   SetBodyInnerHTML(
       "<style>"
@@ -137,35 +139,6 @@
   EXPECT_EQ(nullptr, GetScrollAnchor(l_viewport).AnchorObject());
 }
 
-// Test that scroll anchoring causes no visible jump when a layout change
-// (such as removal of a DOM element) changes the scroll bounds of a scrolling
-// div.
-TEST_P(ScrollAnchorTest, AnchoringWhenContentRemovedFromScrollingDiv) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    #scroller { height: 500px; width: 200px; overflow: scroll; }"
-      "    #changer { height: 1500px; }"
-      "    #anchor {"
-      "        width: 150px; height: 1000px; overflow: scroll;"
-      "    }"
-      "</style>"
-      "<div id='scroller'>"
-      "    <div id='changer'></div>"
-      "    <div id='anchor'></div>"
-      "</div>");
-
-  ScrollableArea* scroller =
-      ScrollerForElement(GetDocument().getElementById("scroller"));
-
-  GetDocument().getElementById("scroller")->setScrollTop(1600);
-
-  SetHeight(GetDocument().getElementById("changer"), 0);
-
-  EXPECT_EQ(100, scroller->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("anchor")->GetLayoutObject(),
-            GetScrollAnchor(scroller).AnchorObject());
-}
-
 // Test that a non-anchoring scroll on scroller clears scroll anchors for all
 // parent scrollers.
 TEST_P(ScrollAnchorTest, ClearScrollAnchorsOnAncestors) {
@@ -326,319 +299,6 @@
   Update();
 }
 
-TEST_P(ScrollAnchorTest, ExcludeAnonymousCandidates) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 3500px }"
-      "    #div {"
-      "        position: relative; background-color: pink;"
-      "        top: 5px; left: 5px; width: 100px; height: 3500px;"
-      "    }"
-      "    #inline { padding-left: 10px }"
-      "</style>"
-      "<div id='div'>"
-      "    <a id='inline'>text</a>"
-      "    <p id='block'>Some text</p>"
-      "</div>"
-      "<div id=a>after</div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-  Element* inline_elem = GetDocument().getElementById("inline");
-  EXPECT_TRUE(inline_elem->GetLayoutObject()->Parent()->IsAnonymous());
-
-  // Scroll #div into view, making anonymous block a viable candidate.
-  GetDocument().getElementById("div")->scrollIntoView();
-
-  // Trigger layout and verify that we don't anchor to the anonymous block.
-  SetHeight(GetDocument().getElementById("a"), 100);
-  Update();
-  EXPECT_EQ(inline_elem->GetLayoutObject()->SlowFirstChild(),
-            GetScrollAnchor(viewport).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, FullyContainedInlineBlock) {
-  // Exercises every WalkStatus value:
-  // html, body -> Constrain
-  // #outer -> Continue
-  // #ib1, br -> Skip
-  // #ib2 -> Return
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 1000px }"
-      "    #outer { line-height: 100px }"
-      "    #ib1, #ib2 { display: inline-block }"
-      "</style>"
-      "<span id=outer>"
-      "    <span id=ib1>abc</span>"
-      "    <br><br>"
-      "    <span id=ib2>def</span>"
-      "</span>");
-
-  ScrollLayoutViewport(ScrollOffset(0, 150));
-
-  Element* ib1 = GetDocument().getElementById("ib1");
-  ib1->setAttribute(HTMLNames::styleAttr, "line-height: 150px");
-  Update();
-  EXPECT_EQ(GetDocument().getElementById("ib2")->GetLayoutObject(),
-            GetScrollAnchor(LayoutViewport()).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, TextBounds) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body {"
-      "        position: absolute;"
-      "        font-size: 100px;"
-      "        width: 200px;"
-      "        height: 1000px;"
-      "        line-height: 100px;"
-      "    }"
-      "</style>"
-      "abc <b id=b>def</b> ghi"
-      "<div id=a>after</div>");
-
-  ScrollLayoutViewport(ScrollOffset(0, 150));
-
-  SetHeight(GetDocument().getElementById("a"), 100);
-  EXPECT_EQ(
-      GetDocument().getElementById("b")->GetLayoutObject()->SlowFirstChild(),
-      GetScrollAnchor(LayoutViewport()).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, ExcludeFixedPosition) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 1000px; padding: 20px; }"
-      "    div { position: relative; top: 100px; }"
-      "    #f { position: fixed }"
-      "</style>"
-      "<div id=f>fixed</div>"
-      "<div id=c>content</div>"
-      "<div id=a>after</div>");
-
-  ScrollLayoutViewport(ScrollOffset(0, 50));
-
-  SetHeight(GetDocument().getElementById("a"), 100);
-  EXPECT_EQ(GetDocument().getElementById("c")->GetLayoutObject(),
-            GetScrollAnchor(LayoutViewport()).AnchorObject());
-}
-
-// This test verifies that position:absolute elements that stick to the viewport
-// are not selected as anchors.
-TEST_P(ScrollAnchorTest, ExcludeAbsolutePositionThatSticksToViewport) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { margin: 0; }"
-      "    #scroller { overflow: scroll; width: 500px; height: 400px; }"
-      "    #space { height: 1000px; }"
-      "    #abs {"
-      "        position: absolute; background-color: red;"
-      "        width: 100px; height: 100px;"
-      "    }"
-      "    #rel {"
-      "        position: relative; background-color: green;"
-      "        left: 50px; top: 100px; width: 100px; height: 75px;"
-      "    }"
-      "</style>"
-      "<div id='scroller'><div id='space'>"
-      "    <div id='abs'></div>"
-      "    <div id='rel'></div>"
-      "    <div id=a>after</div>"
-      "</div></div>");
-
-  Element* scroller_element = GetDocument().getElementById("scroller");
-  ScrollableArea* scroller = ScrollerForElement(scroller_element);
-  Element* abs_pos = GetDocument().getElementById("abs");
-  Element* rel_pos = GetDocument().getElementById("rel");
-
-  scroller->ScrollBy(ScrollOffset(0, 25), kUserScroll);
-  SetHeight(GetDocument().getElementById("a"), 100);
-
-  // When the scroller is position:static, the anchor cannot be
-  // position:absolute.
-  EXPECT_EQ(rel_pos->GetLayoutObject(),
-            GetScrollAnchor(scroller).AnchorObject());
-
-  scroller_element->setAttribute(HTMLNames::styleAttr, "position: relative");
-  Update();
-  scroller->ScrollBy(ScrollOffset(0, 25), kUserScroll);
-  SetHeight(GetDocument().getElementById("a"), 125);
-
-  // When the scroller is position:relative, the anchor may be
-  // position:absolute.
-  EXPECT_EQ(abs_pos->GetLayoutObject(),
-            GetScrollAnchor(scroller).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, DescendsIntoAbsPosWithOffscreenStaticParent) {
-  SetBodyInnerHTML(
-      "<style>"
-      "  body, html { height: 0; }"
-      "  #abs {"
-      "    position: absolute;"
-      "    left: 50px;"
-      "    top: 50px;"
-      "    height: 1200px;"
-      "    padding: 50px;"
-      "    border: 5px solid gray;"
-      "  }"
-      "  #anchor {"
-      "    background-color: #afa;"
-      "    width: 100px;"
-      "    height: 100px;"
-      "  }"
-      "</style>"
-      "<div id='abs'>"
-      "  <div id='changer'></div>"
-      "  <div id='anchor'></div>"
-      "</div>");
-
-  ScrollLayoutViewport(ScrollOffset(0, 120));
-  SetHeight(GetDocument().getElementById("changer"), 100);
-  EXPECT_EQ(220, LayoutViewport()->ScrollOffsetInt().Height());
-}
-
-// Test that we descend into zero-height containers that have overflowing
-// content.
-TEST_P(ScrollAnchorTest, DescendsIntoContainerWithOverflow) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 1000; }"
-      "    #outer { width: 300px; }"
-      "    #zeroheight { height: 0px; }"
-      "    #changer { height: 100px; background-color: red; }"
-      "    #bottom { margin-top: 600px; }"
-      "</style>"
-      "<div id='outer'>"
-      "    <div id='zeroheight'>"
-      "      <div id='changer'></div>"
-      "      <div id='bottom'>bottom</div>"
-      "    </div>"
-      "</div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-
-  ScrollLayoutViewport(ScrollOffset(0, 200));
-  SetHeight(GetDocument().getElementById("changer"), 200);
-
-  EXPECT_EQ(300, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("bottom")->GetLayoutObject(),
-            GetScrollAnchor(viewport).AnchorObject());
-}
-
-// Test that we account for the origin of the layout overflow rect when
-// computing bounds for possible descent.
-TEST_P(ScrollAnchorTest, NegativeLayoutOverflow) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 1200px; }"
-      "    #header { position: relative; height: 100px; }"
-      "    #evil { position: relative; "
-      "      top: -900px; height: 1000px; width: 100px; }"
-      "    #changer { height: 100px; }"
-      "    #anchor { height: 100px; background-color: green }"
-      "</style>"
-      "<div id='header'>"
-      "    <div id='evil'></div>"
-      "</div>"
-      "<div id='changer'></div>"
-      "<div id='anchor'></div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-
-  ScrollLayoutViewport(ScrollOffset(0, 250));
-  SetHeight(GetDocument().getElementById("changer"), 200);
-  EXPECT_EQ(350, viewport->ScrollOffsetInt().Height());
-}
-
-// Test that we descend into zero-height containers that have floating content.
-TEST_P(ScrollAnchorTest, DescendsIntoContainerWithFloat) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 1000; }"
-      "    #outer { width: 300px; }"
-      "    #outer:after { content: ' '; clear:both; display: table; }"
-      "    #float {"
-      "         float: left; background-color: #ccc;"
-      "         height: 500px; width: 100%;"
-      "    }"
-      "    #inner { height: 21px; background-color:#7f0; }"
-      "</style>"
-      "<div id='outer'>"
-      "    <div id='zeroheight'>"
-      "      <div id='float'>"
-      "         <div id='inner'></div>"
-      "      </div>"
-      "    </div>"
-      "</div>"
-      "<div id=a>after</div>");
-
-  EXPECT_EQ(
-      0,
-      ToLayoutBox(GetDocument().getElementById("zeroheight")->GetLayoutObject())
-          ->Size()
-          .Height());
-
-  ScrollableArea* viewport = LayoutViewport();
-
-  ScrollLayoutViewport(ScrollOffset(0, 200));
-  SetHeight(GetDocument().getElementById("a"), 100);
-
-  EXPECT_EQ(200, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("float")->GetLayoutObject(),
-            GetScrollAnchor(viewport).AnchorObject());
-}
-
-// This test verifies that scroll anchoring is disabled when any element within
-// the main scroller changes its in-flow state.
-TEST_P(ScrollAnchorTest, ChangeInFlowStateDisablesAnchoringForMainScroller) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 1000px; }"
-      "    #header { background-color: #F5B335; height: 50px; width: 100%; }"
-      "    #content { background-color: #D3D3D3; height: 200px; }"
-      "</style>"
-      "<div id='header'></div>"
-      "<div id='content'></div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-  ScrollLayoutViewport(ScrollOffset(0, 200));
-
-  GetDocument().getElementById("header")->setAttribute(HTMLNames::styleAttr,
-                                                       "position: fixed;");
-  Update();
-
-  EXPECT_EQ(200, viewport->ScrollOffsetInt().Height());
-}
-
-// This test verifies that scroll anchoring is disabled when any element within
-// a scrolling div changes its in-flow state.
-TEST_P(ScrollAnchorTest, ChangeInFlowStateDisablesAnchoringForScrollingDiv) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    #container { position: relative; width: 500px; }"
-      "    #scroller { height: 200px; overflow: scroll; }"
-      "    #changer { background-color: #F5B335; height: 50px; width: 100%; }"
-      "    #anchor { background-color: #D3D3D3; height: 300px; }"
-      "</style>"
-      "<div id='container'>"
-      "    <div id='scroller'>"
-      "      <div id='changer'></div>"
-      "      <div id='anchor'></div>"
-      "    </div>"
-      "</div>");
-
-  ScrollableArea* scroller =
-      ScrollerForElement(GetDocument().getElementById("scroller"));
-  GetDocument().getElementById("scroller")->setScrollTop(100);
-
-  GetDocument().getElementById("changer")->setAttribute(HTMLNames::styleAttr,
-                                                        "position: absolute;");
-  Update();
-
-  EXPECT_EQ(100, scroller->ScrollOffsetInt().Height());
-}
-
 TEST_P(ScrollAnchorTest, FlexboxDelayedClampingAlsoDelaysAdjustment) {
   SetBodyInnerHTML(
       "<style>"
@@ -700,186 +360,8 @@
   EXPECT_EQ(100, ScrollerForElement(scroller)->ScrollOffsetInt().Height());
 }
 
-// Test then an element and its children are not selected as the anchor when
-// it has the overflow-anchor property set to none.
-TEST_P(ScrollAnchorTest, OptOutElement) {
-  SetBodyInnerHTML(
-      "<style>"
-      "     body { height: 1000px }"
-      "     .div {"
-      "          height: 100px; width: 100px;"
-      "          border: 1px solid gray; background-color: #afa;"
-      "     }"
-      "     #innerDiv {"
-      "          height: 50px; width: 50px;"
-      "          border: 1px solid gray; background-color: pink;"
-      "     }"
-      "</style>"
-      "<div id='changer'></div>"
-      "<div class='div' id='firstDiv'><div id='innerDiv'></div></div>"
-      "<div class='div' id='secondDiv'></div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-  ScrollLayoutViewport(ScrollOffset(0, 50));
-
-  // No opt-out.
-  SetHeight(GetDocument().getElementById("changer"), 100);
-  EXPECT_EQ(150, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("innerDiv")->GetLayoutObject(),
-            GetScrollAnchor(viewport).AnchorObject());
-
-  // Clear anchor and opt-out element.
-  ScrollLayoutViewport(ScrollOffset(0, 10));
-  GetDocument()
-      .getElementById("firstDiv")
-      ->setAttribute(HTMLNames::styleAttr,
-                     AtomicString("overflow-anchor: none"));
-  Update();
-
-  // Opted out element and it's children skipped.
-  SetHeight(GetDocument().getElementById("changer"), 200);
-  EXPECT_EQ(260, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("secondDiv")->GetLayoutObject(),
-            GetScrollAnchor(viewport).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest,
-       SuppressAnchorNodeAncestorChangingLayoutAffectingProperty) {
-  SetBodyInnerHTML(
-      "<style> body { height: 1000px } div { height: 100px } </style>"
-      "<div id='block1'>abc</div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-
-  ScrollLayoutViewport(ScrollOffset(0, 50));
-  GetDocument().body()->setAttribute(HTMLNames::styleAttr, "padding-top: 20px");
-  Update();
-
-  EXPECT_EQ(50, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(nullptr, GetScrollAnchor(viewport).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, AnchorNodeAncestorChangingNonLayoutAffectingProperty) {
-  SetBodyInnerHTML(
-      "<style> body { height: 1000px } div { height: 100px } </style>"
-      "<div id='block1'>abc</div>"
-      "<div id='block2'>def</div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-  ScrollLayoutViewport(ScrollOffset(0, 150));
-
-  GetDocument().body()->setAttribute(HTMLNames::styleAttr, "color: red");
-  SetHeight(GetDocument().getElementById("block1"), 200);
-
-  EXPECT_EQ(250, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("block2")->GetLayoutObject(),
-            GetScrollAnchor(viewport).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, TransformIsLayoutAffecting) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 1000px }"
-      "    #block1 { height: 100px }"
-      "</style>"
-      "<div id='block1'>abc</div>"
-      "<div id=a>after</div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-
-  ScrollLayoutViewport(ScrollOffset(0, 50));
-  GetDocument().getElementById("block1")->setAttribute(
-      HTMLNames::styleAttr, "transform: matrix(1, 0, 0, 1, 25, 25);");
-  Update();
-
-  GetDocument().getElementById("block1")->setAttribute(
-      HTMLNames::styleAttr, "transform: matrix(1, 0, 0, 1, 50, 50);");
-  SetHeight(GetDocument().getElementById("a"), 100);
-  Update();
-
-  EXPECT_EQ(50, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(nullptr, GetScrollAnchor(viewport).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, OptOutBody) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 2000px; overflow-anchor: none; }"
-      "    #scroller { overflow: scroll; width: 500px; height: 300px; }"
-      "    .anchor {"
-      "        position:relative; height: 100px; width: 150px;"
-      "        background-color: #afa; border: 1px solid gray;"
-      "    }"
-      "    #forceScrolling { height: 500px; background-color: #fcc; }"
-      "</style>"
-      "<div id='outerChanger'></div>"
-      "<div id='outerAnchor' class='anchor'></div>"
-      "<div id='scroller'>"
-      "    <div id='innerChanger'></div>"
-      "    <div id='innerAnchor' class='anchor'></div>"
-      "    <div id='forceScrolling'></div>"
-      "</div>");
-
-  ScrollableArea* scroller =
-      ScrollerForElement(GetDocument().getElementById("scroller"));
-  ScrollableArea* viewport = LayoutViewport();
-
-  GetDocument().getElementById("scroller")->setScrollTop(100);
-  ScrollLayoutViewport(ScrollOffset(0, 100));
-
-  SetHeight(GetDocument().getElementById("innerChanger"), 200);
-  SetHeight(GetDocument().getElementById("outerChanger"), 150);
-
-  // Scroll anchoring should apply within #scroller.
-  EXPECT_EQ(300, scroller->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("innerAnchor")->GetLayoutObject(),
-            GetScrollAnchor(scroller).AnchorObject());
-  // Scroll anchoring should not apply within main frame.
-  EXPECT_EQ(100, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(nullptr, GetScrollAnchor(viewport).AnchorObject());
-}
-
-TEST_P(ScrollAnchorTest, OptOutScrollingDiv) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { height: 2000px; }"
-      "    #scroller {"
-      "        overflow: scroll; width: 500px; height: 300px;"
-      "        overflow-anchor: none;"
-      "    }"
-      "    .anchor {"
-      "        position:relative; height: 100px; width: 150px;"
-      "        background-color: #afa; border: 1px solid gray;"
-      "    }"
-      "    #forceScrolling { height: 500px; background-color: #fcc; }"
-      "</style>"
-      "<div id='outerChanger'></div>"
-      "<div id='outerAnchor' class='anchor'></div>"
-      "<div id='scroller'>"
-      "    <div id='innerChanger'></div>"
-      "    <div id='innerAnchor' class='anchor'></div>"
-      "    <div id='forceScrolling'></div>"
-      "</div>");
-
-  ScrollableArea* scroller =
-      ScrollerForElement(GetDocument().getElementById("scroller"));
-  ScrollableArea* viewport = LayoutViewport();
-
-  GetDocument().getElementById("scroller")->setScrollTop(100);
-  ScrollLayoutViewport(ScrollOffset(0, 100));
-
-  SetHeight(GetDocument().getElementById("innerChanger"), 200);
-  SetHeight(GetDocument().getElementById("outerChanger"), 150);
-
-  // Scroll anchoring should not apply within #scroller.
-  EXPECT_EQ(100, scroller->ScrollOffsetInt().Height());
-  EXPECT_EQ(nullptr, GetScrollAnchor(scroller).AnchorObject());
-  // Scroll anchoring should apply within main frame.
-  EXPECT_EQ(250, viewport->ScrollOffsetInt().Height());
-  EXPECT_EQ(GetDocument().getElementById("outerAnchor")->GetLayoutObject(),
-            GetScrollAnchor(viewport).AnchorObject());
-}
-
+// TODO(skobes): Convert this to web-platform-tests when document.rootScroller
+// is launched (http://crbug.com/505516).
 TEST_P(ScrollAnchorTest, NonDefaultRootScroller) {
   SetBodyInnerHTML(
       "<style>"
@@ -953,148 +435,4 @@
   EXPECT_EQ(nullptr, GetScrollAnchor(viewport).AnchorObject());
 }
 
-class ScrollAnchorCornerTest : public ScrollAnchorTest {
- protected:
-  void CheckCorner(Corner corner,
-                   ScrollOffset start_offset,
-                   ScrollOffset expected_adjustment) {
-    ScrollableArea* viewport = LayoutViewport();
-    Element* element = GetDocument().getElementById("changer");
-
-    viewport->SetScrollOffset(start_offset, kUserScroll);
-    element->setAttribute(HTMLNames::classAttr, "change");
-    Update();
-
-    ScrollOffset end_pos = start_offset;
-    end_pos += expected_adjustment;
-
-    EXPECT_EQ(end_pos, viewport->GetScrollOffset());
-    EXPECT_EQ(GetDocument().getElementById("a")->GetLayoutObject(),
-              GetScrollAnchor(viewport).AnchorObject());
-    EXPECT_EQ(corner, GetScrollAnchor(viewport).GetCorner());
-
-    element->removeAttribute(HTMLNames::classAttr);
-    Update();
-  }
-};
-
-// Verify that we anchor to the top left corner of an element for LTR.
-TEST_P(ScrollAnchorCornerTest, CornersLTR) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body { position: relative; width: 1220px; height: 920px; }"
-      "    #a { width: 400px; height: 300px; }"
-      "    .change { height: 100px; }"
-      "</style>"
-      "<div id='changer'></div>"
-      "<div id='a'></div>");
-
-  CheckCorner(Corner::kTopLeft, ScrollOffset(20, 20), ScrollOffset(0, 100));
-}
-
-// Verify that we anchor to the top left corner of an anchor element for
-// vertical-lr writing mode.
-TEST_P(ScrollAnchorCornerTest, CornersVerticalLR) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    html { writing-mode: vertical-lr; }"
-      "    body { position: relative; width: 1220px; height: 920px; }"
-      "    #a { width: 400px; height: 300px; }"
-      "    .change { width: 100px; }"
-      "</style>"
-      "<div id='changer'></div>"
-      "<div id='a'></div>");
-
-  CheckCorner(Corner::kTopLeft, ScrollOffset(20, 20), ScrollOffset(100, 0));
-}
-
-// Verify that we anchor to the top right corner of an anchor element for RTL.
-TEST_P(ScrollAnchorCornerTest, CornersRTL) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    html { direction: rtl; }"
-      "    body { position: relative; width: 1220px; height: 920px; }"
-      "    #a { width: 400px; height: 300px; }"
-      "    .change { height: 100px; }"
-      "</style>"
-      "<div id='changer'></div>"
-      "<div id='a'></div>");
-
-  CheckCorner(Corner::kTopRight, ScrollOffset(-20, 20), ScrollOffset(0, 100));
-}
-
-// Verify that we anchor to the top right corner of an anchor element for
-// vertical-lr writing mode.
-TEST_P(ScrollAnchorCornerTest, CornersVerticalRL) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    html { writing-mode: vertical-rl; }"
-      "    body { position: relative; width: 1220px; height: 920px; }"
-      "    #a { width: 400px; height: 300px; }"
-      "    .change { width: 100px; }"
-      "</style>"
-      "<div id='changer'></div>"
-      "<div id='a'></div>");
-
-  CheckCorner(Corner::kTopRight, ScrollOffset(-20, 20), ScrollOffset(-100, 0));
-}
-
-TEST_P(ScrollAnchorTest, IgnoreNonBlockLayoutAxis) {
-  SetBodyInnerHTML(
-      "<style>"
-      "    body {"
-      "        margin: 0; line-height: 0;"
-      "        width: 1200px; height: 1200px;"
-      "    }"
-      "    div {"
-      "        width: 100px; height: 100px;"
-      "        border: 5px solid gray;"
-      "        display: inline-block;"
-      "        box-sizing: border-box;"
-      "    }"
-      "</style>"
-      "<div id='a'></div><br>"
-      "<div id='b'></div><div id='c'></div>");
-
-  ScrollableArea* viewport = LayoutViewport();
-  ScrollLayoutViewport(ScrollOffset(150, 0));
-
-  Element* a = GetDocument().getElementById("a");
-  Element* b = GetDocument().getElementById("b");
-  Element* c = GetDocument().getElementById("c");
-
-  a->setAttribute(HTMLNames::styleAttr, "height: 150px");
-  Update();
-  EXPECT_EQ(ScrollOffset(150, 0), viewport->GetScrollOffset());
-  EXPECT_EQ(nullptr, GetScrollAnchor(viewport).AnchorObject());
-
-  ScrollLayoutViewport(ScrollOffset(0, 50));
-
-  a->setAttribute(HTMLNames::styleAttr, "height: 200px");
-  b->setAttribute(HTMLNames::styleAttr, "width: 150px");
-  Update();
-  EXPECT_EQ(ScrollOffset(150, 100), viewport->GetScrollOffset());
-  EXPECT_EQ(c->GetLayoutObject(), GetScrollAnchor(viewport).AnchorObject());
-
-  a->setAttribute(HTMLNames::styleAttr, "height: 100px");
-  b->setAttribute(HTMLNames::styleAttr, "width: 100px");
-  GetDocument().documentElement()->setAttribute(HTMLNames::styleAttr,
-                                                "writing-mode: vertical-rl");
-  GetDocument().scrollingElement()->setScrollLeft(0);
-  GetDocument().scrollingElement()->setScrollTop(0);
-  ScrollLayoutViewport(ScrollOffset(0, 150));
-
-  a->setAttribute(HTMLNames::styleAttr, "width: 150px");
-  Update();
-  EXPECT_EQ(ScrollOffset(0, 150), viewport->GetScrollOffset());
-  EXPECT_EQ(nullptr, GetScrollAnchor(viewport).AnchorObject());
-
-  ScrollLayoutViewport(ScrollOffset(-50, 0));
-
-  a->setAttribute(HTMLNames::styleAttr, "width: 200px");
-  b->setAttribute(HTMLNames::styleAttr, "height: 150px");
-  Update();
-  EXPECT_EQ(ScrollOffset(-100, 150), viewport->GetScrollOffset());
-  EXPECT_EQ(c->GetLayoutObject(), GetScrollAnchor(viewport).AnchorObject());
-}
 }
diff --git a/third_party/WebKit/Source/core/style/ComputedStyle.cpp b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
index dc0ba2a..b76088b 100644
--- a/third_party/WebKit/Source/core/style/ComputedStyle.cpp
+++ b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
@@ -922,7 +922,7 @@
 }
 
 void ComputedStyle::SetQuotes(RefPtr<QuotesData> q) {
-  SetQuotesInternal(q);
+  SetQuotesInternal(std::move(q));
 }
 
 bool ComputedStyle::QuotesDataEquivalent(const ComputedStyle& other) const {
@@ -1207,7 +1207,7 @@
 }
 
 void ComputedStyle::SetTextShadow(RefPtr<ShadowList> s) {
-  SetTextShadowInternal(s);
+  SetTextShadowInternal(std::move(s));
 }
 
 bool ComputedStyle::TextShadowDataEquivalent(const ComputedStyle& other) const {
@@ -1564,15 +1564,16 @@
 }
 
 StyleInheritedVariables* ComputedStyle::InheritedVariables() const {
-  return VariablesInternal().Get();
+  return InheritedVariablesInternal().Get();
 }
 
 StyleNonInheritedVariables* ComputedStyle::NonInheritedVariables() const {
-  return rare_non_inherited_data_->variables_.get();
+  return rare_non_inherited_data_->non_inherited_variables_.get();
 }
 
 StyleInheritedVariables& ComputedStyle::MutableInheritedVariables() {
-  RefPtr<StyleInheritedVariables>& variables = MutableVariablesInternal();
+  RefPtr<StyleInheritedVariables>& variables =
+      MutableInheritedVariablesInternal();
   if (!variables)
     variables = StyleInheritedVariables::Create();
   else if (!variables->HasOneRef())
@@ -1582,7 +1583,7 @@
 
 StyleNonInheritedVariables& ComputedStyle::MutableNonInheritedVariables() {
   std::unique_ptr<StyleNonInheritedVariables>& variables =
-      rare_non_inherited_data_.Access()->variables_;
+      rare_non_inherited_data_.Access()->non_inherited_variables_;
   if (!variables)
     variables = StyleNonInheritedVariables::Create();
   return *variables;
@@ -1877,8 +1878,8 @@
   SetHasSimpleUnderlineInternal(parent_style.HasSimpleUnderlineInternal());
   if (AppliedTextDecorationsInternal() !=
       parent_style.AppliedTextDecorationsInternal()) {
-    SetAppliedTextDecorationsInternal(
-        parent_style.AppliedTextDecorationsInternal());
+    SetAppliedTextDecorationsInternal(RefPtr<AppliedTextDecorationList>(
+        parent_style.AppliedTextDecorationsInternal()));
   }
 }
 
diff --git a/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.cpp b/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.cpp
index 72b1d5b..8032c0f 100644
--- a/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.cpp
+++ b/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.cpp
@@ -93,7 +93,7 @@
       visited_link_border_right_color_(StyleColor::CurrentColor()),
       visited_link_border_top_color_(StyleColor::CurrentColor()),
       visited_link_border_bottom_color_(StyleColor::CurrentColor()),
-      variables_(ComputedStyle::InitialNonInheritedVariables()),
+      non_inherited_variables_(ComputedStyle::InitialNonInheritedVariables()),
       align_content_(ComputedStyle::InitialContentAlignment()),
       align_items_(ComputedStyle::InitialDefaultAlignment()),
       align_self_(ComputedStyle::InitialSelfAlignment()),
@@ -179,7 +179,9 @@
       callback_selectors_(o.callback_selectors_),
       paint_images_(o.paint_images_ ? new PaintImages(*o.paint_images_)
                                     : nullptr),
-      variables_(o.variables_ ? o.variables_->Clone() : nullptr),
+      non_inherited_variables_(o.non_inherited_variables_
+                                   ? o.non_inherited_variables_->Clone()
+                                   : nullptr),
       align_content_(o.align_content_),
       align_items_(o.align_items_),
       align_self_(o.align_self_),
@@ -262,7 +264,7 @@
          visited_link_border_bottom_color_ ==
              o.visited_link_border_bottom_color_ &&
          callback_selectors_ == o.callback_selectors_ &&
-         DataEquivalent(variables_, o.variables_) &&
+         DataEquivalent(non_inherited_variables_, o.non_inherited_variables_) &&
          align_content_ == o.align_content_ && align_items_ == o.align_items_ &&
          align_self_ == o.align_self_ &&
          justify_content_ == o.justify_content_ &&
diff --git a/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.h b/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.h
index 0ea0316..97263604 100644
--- a/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.h
+++ b/third_party/WebKit/Source/core/style/StyleRareNonInheritedData.h
@@ -150,7 +150,7 @@
 
   std::unique_ptr<PaintImages> paint_images_;
 
-  std::unique_ptr<StyleNonInheritedVariables> variables_;
+  std::unique_ptr<StyleNonInheritedVariables> non_inherited_variables_;
 
   StyleContentAlignmentData align_content_;
   StyleSelfAlignmentData align_items_;
diff --git a/third_party/WebKit/Source/core/workers/WorkletGlobalScope.cpp b/third_party/WebKit/Source/core/workers/WorkletGlobalScope.cpp
index 4b3443a9..dbd8255 100644
--- a/third_party/WebKit/Source/core/workers/WorkletGlobalScope.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkletGlobalScope.cpp
@@ -45,6 +45,16 @@
   return v8::Local<v8::Object>();
 }
 
+bool WorkletGlobalScope::HasPendingActivity() const {
+  // The worklet global scope wrapper is kept alive as longs as its execution
+  // context is active.
+  return !ExecutionContext::IsContextDestroyed();
+}
+
+ExecutionContext* WorkletGlobalScope::GetExecutionContext() const {
+  return const_cast<WorkletGlobalScope*>(this);
+}
+
 bool WorkletGlobalScope::IsSecureContext(String& error_message) const {
   // Until there are APIs that are available in worklets and that
   // require a privileged context test that checks ancestors, just do
diff --git a/third_party/WebKit/Source/core/workers/WorkletGlobalScope.h b/third_party/WebKit/Source/core/workers/WorkletGlobalScope.h
index e3b7e4c..b8c97e6 100644
--- a/third_party/WebKit/Source/core/workers/WorkletGlobalScope.h
+++ b/third_party/WebKit/Source/core/workers/WorkletGlobalScope.h
@@ -11,6 +11,7 @@
 #include "core/dom/SecurityContext.h"
 #include "core/inspector/ConsoleMessage.h"
 #include "core/workers/WorkerOrWorkletGlobalScope.h"
+#include "platform/bindings/ActiveScriptWrappable.h"
 #include "platform/bindings/ScriptWrappable.h"
 #include "platform/heap/Handle.h"
 
@@ -22,7 +23,8 @@
     : public GarbageCollectedFinalized<WorkletGlobalScope>,
       public SecurityContext,
       public WorkerOrWorkletGlobalScope,
-      public ScriptWrappable {
+      public ScriptWrappable,
+      public ActiveScriptWrappable<WorkletGlobalScope> {
   DEFINE_WRAPPERTYPEINFO();
   USING_GARBAGE_COLLECTED_MIXIN(WorkletGlobalScope);
 
@@ -47,6 +49,9 @@
       v8::Isolate*,
       const WrapperTypeInfo*,
       v8::Local<v8::Object> wrapper) final;
+  bool HasPendingActivity() const override;
+
+  ExecutionContext* GetExecutionContext() const;
 
   // ExecutionContext
   String UserAgent() const final { return user_agent_; }
diff --git a/third_party/WebKit/Source/core/workers/WorkletGlobalScope.idl b/third_party/WebKit/Source/core/workers/WorkletGlobalScope.idl
index 418ddb4..8da4a9c 100644
--- a/third_party/WebKit/Source/core/workers/WorkletGlobalScope.idl
+++ b/third_party/WebKit/Source/core/workers/WorkletGlobalScope.idl
@@ -5,6 +5,8 @@
 // https://drafts.css-houdini.org/worklets/#workletglobalscope
 
 [
+    DependentLifetime,
+    ActiveScriptWrappable,
     Exposed=Worklet,
     RuntimeEnabled=Worklet,
     ImmutablePrototype,
diff --git a/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.cpp b/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.cpp
index fdf5d51..fe52473 100644
--- a/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.cpp
+++ b/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.cpp
@@ -76,4 +76,12 @@
 #endif
 }
 
+bool PaintRenderingContext2D::StateHasFilter() {
+  return GetState().HasFilterForOffscreenCanvas(IntSize(Width(), Height()));
+}
+
+sk_sp<SkImageFilter> PaintRenderingContext2D::StateGetFilter() {
+  return GetState().GetFilterForOffscreenCanvas(IntSize(Width(), Height()));
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.h b/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.h
index ceb1967..105a582f 100644
--- a/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.h
+++ b/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.h
@@ -59,12 +59,8 @@
 
   void DidDraw(const SkIRect& dirty_rect) final;
 
-  // TODO(ikilpatrick): We'll need to either only accept resolved filters
-  // from a typed-om <filter> object, or use the appropriate style resolution
-  // host to determine 'em' units etc in filters. At the moment just pretend
-  // that we don't have a filter set.
-  bool StateHasFilter() final { return false; }
-  sk_sp<SkImageFilter> StateGetFilter() final { return nullptr; }
+  bool StateHasFilter() final;
+  sk_sp<SkImageFilter> StateGetFilter() final;
   void SnapshotStateForFilter() final {}
 
   void ValidateStateStack() const final;
diff --git a/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.idl b/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.idl
index 27228cb..f537f20 100644
--- a/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.idl
+++ b/third_party/WebKit/Source/modules/csspaint/PaintRenderingContext2D.idl
@@ -24,6 +24,7 @@
     // compositing
     attribute unrestricted double globalAlpha; // (default 1.0)
     attribute DOMString globalCompositeOperation; // (default source-over)
+    attribute DOMString filter;
 
     // image smoothing
     attribute boolean imageSmoothingEnabled; // (default True)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py
index 378b4e78..b7d5354 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py
@@ -108,7 +108,7 @@
         # The image hash is used to avoid doing an image dump if the
         # checksums match, so it should be set to a blank value if we
         # are generating a new baseline.  (Otherwise, an image from a
-        # previous run will be copied into the baseline."""
+        # previous run will be copied into the baseline.)
         image_hash = None
         if self._should_fetch_expected_checksum():
             image_hash = self._port.expected_checksum(self._test_name)
@@ -194,6 +194,8 @@
 
         if self._options.add_platform_exceptions:
             output_dir = fs.join(port.baseline_version_dir(), fs.dirname(self._test_name))
+        elif self._options.new_flag_specific_baseline:
+            output_dir = fs.join(port.baseline_flag_specific_dir(), fs.dirname(self._test_name))
         else:
             output_dir = fs.dirname(port.expected_filename(self._test_name, extension))
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
index 3642c8a..13afce6f 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
@@ -257,6 +257,12 @@
         baseline_search_paths = self.baseline_search_path()
         return baseline_search_paths[0]
 
+    def baseline_flag_specific_dir(self):
+        """If --additional-driver-flag is specified, returns the absolute path to the flag-specific
+           platform-independent results. Otherwise returns None."""
+        flag_specific_path = self._flag_specific_baseline_search_path()
+        return flag_specific_path[-1] if flag_specific_path else None
+
     def virtual_baseline_search_path(self, test_name):
         suite = self.lookup_virtual_suite(test_name)
         if not suite:
@@ -1484,10 +1490,12 @@
         pass
 
     def physical_test_suites(self):
+        color_correct_rendering_flags = ['--enable-color-correct-rendering', '--force-color-profile=srgb']
         return [
             # For example, to turn on force-compositing-mode in the svg/ directory:
             # PhysicalTestSuite('svg', ['--force-compositing-mode']),
             PhysicalTestSuite('fast/text', ['--enable-direct-write', '--enable-font-antialiasing']),
+            PhysicalTestSuite('images', color_correct_rendering_flags),
         ]
 
     def virtual_test_suites(self):
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
index df94420..fe7bd16 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
@@ -121,8 +121,9 @@
                 '--add-platform-exceptions',
                 action='store_true',
                 default=False,
-                help=('Save generated results into the *most-specific-platform* directory rather '
-                      'than the *generic-platform* directory')),
+                help=('For --reset-results and --new-flag-specific-baseline, save generated '
+                      'results into the *most-specific-platform* directory rather than the '
+                      'current baseline directory or *generic-platform* directory')),
             optparse.make_option(
                 '--additional-driver-flag',
                 '--additional-drt-flag',
@@ -182,7 +183,14 @@
                 callback=deprecate,
                 help=('Deprecated. Use "webkit-patch rebaseline-cl" instead, or '
                       '"--reset-results --add-platform-exceptions" if you do want to create '
-                      'platform-version-specific new baselines locally.')),
+                      'new baselines for the *most-specific-platform* locally.')),
+            optparse.make_option(
+                '--new-flag-specific-baseline',
+                action='store_true',
+                default=False,
+                help=('Together with --addtional-driver-flag, if actual results are '
+                      'different from expected, save actual results as new baselines '
+                      'into the flag-specific generic-platform directory.')),
             optparse.make_option(
                 '--new-test-results',
                 action='callback',
@@ -484,7 +492,12 @@
         option_group.add_options(group_options)
         option_parser.add_option_group(option_group)
 
-    return option_parser.parse_args(args)
+    (options, args) = option_parser.parse_args(args)
+
+    if options.new_flag_specific_baseline and not options.additional_driver_flag:
+        option_parser.error('--new-flag-specific-baseline requires --additional-driver-flag')
+
+    return (options, args)
 
 
 def _set_up_derived_options(port, options, args):
@@ -513,6 +526,9 @@
             additional_platform_directories.append(port.host.filesystem.abspath(path))
         options.additional_platform_directory = additional_platform_directories
 
+    if options.new_flag_specific_baseline:
+        options.reset_results = True
+
     if options.pixel_test_directories:
         options.pixel_tests = True
         verified_dirs = set()
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
index aefcb5c..caf2f0c 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
@@ -1144,6 +1144,32 @@
                               'platform/test-mac-mac10.10/failures/unexpected/text-image-checksum',
                               ['.png'], err)
 
+    def test_new_flag_specific_baseline(self):
+        # Test that we create new baselines under flag-specific directory if the actual results
+        # are different from the current expectations.
+        host = MockHost()
+        host.filesystem.write_text_file(
+            test.LAYOUT_TEST_DIR + '/failures/unexpected/text-image-checksum-expected.txt',
+            # Make the current text expectation the same as the actual text result of the test.
+            # The value is the same as actual_result of the test defined in
+            # webkitpy.layout_tests.port.test.
+            'text-image-checksum_fail-txt')
+        details, err, _ = logging_run(
+            ['--additional-driver-flag=--flag',
+             '--new-flag-specific-baseline',
+             'failures/unexpected/text-image-checksum.html'],
+            tests_included=True, host=host)
+        file_list = host.filesystem.written_files.keys()
+        self.assertEqual(details.exit_code, 0)
+        self.assertEqual(len(file_list), 8)
+        # We should create new pixel baseline only.
+        self.assertFalse(
+            host.filesystem.exists(
+                test.LAYOUT_TEST_DIR + '/flag-specific/flag/failures/unexpected/text-image-checksum-expected.txt'))
+        self.assert_baselines(file_list,
+                              'flag-specific/flag/failures/unexpected/text-image-checksum',
+                              ['.png'], err)
+
     def test_reftest_reset_results(self):
         # Test rebaseline of reftests.
         # Should ignore reftests without text expectations.
diff --git a/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1 b/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1
index 5496f33d..c69cf50 100644
--- a/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1
+++ b/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1
@@ -1 +1 @@
-510b3f34e3a9ee4128e4329608ba59464549f6ca
\ No newline at end of file
+81cecc241ebe1e9d07f09850d0f83082ad52f225
\ No newline at end of file
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index eaae8b92..fd4087b 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -13080,6 +13080,7 @@
   <int value="202" label="kVirtualKeyboard"/>
   <int value="203" label="kNetworkingCastPrivate"/>
   <int value="204" label="kMediaPerceptionPrivate"/>
+  <int value="205" label="kLockScreen"/>
 </enum>
 
 <enum name="ExtensionServiceVerifyAllSuccess" type="int">
diff --git a/tools/perf/benchmarks/v8_browsing.py b/tools/perf/benchmarks/v8_browsing.py
index 6e1defa..eeb39c3 100644
--- a/tools/perf/benchmarks/v8_browsing.py
+++ b/tools/perf/benchmarks/v8_browsing.py
@@ -30,7 +30,25 @@
     r'total_)')
 
 
-class _V8BrowsingBenchmark(perf_benchmark.PerfBenchmark):
+class _v8BrowsingBenchmarkBaseClass(perf_benchmark.PerfBenchmark):
+  """Base class for all v8 browsing benchmarks."""
+  @classmethod
+  def ShouldTearDownStateAfterEachStoryRun(cls):
+    return True
+
+  def CreateStorySet(self, options):
+    return page_sets.SystemHealthStorySet(platform=self.PLATFORM, case='browse')
+
+  def GetExpectations(self):
+    if self.PLATFORM is 'desktop':
+      return page_sets.V8BrowsingDesktopExpecations()
+    if self.PLATFORM is 'mobile':
+      return page_sets.V8BrowsingMobileExpecations()
+    raise NotImplementedError, ('Only have expectations for mobile and desktop '
+                                'platforms for v8_browsing tests.')
+
+
+class _V8BrowsingBenchmark(_v8BrowsingBenchmarkBaseClass):
   """Base class for V8 browsing benchmarks.
   This benchmark measures memory usage with periodic memory dumps and v8 times.
   See browsing_stories._BrowsingStory for workload description.
@@ -71,17 +89,6 @@
       'expectedQueueingTimeMetric', 'v8AndMemoryMetrics'])
     return options
 
-  def CreateStorySet(self, options):
-    return page_sets.SystemHealthStorySet(platform=self.PLATFORM, case='browse')
-
-  def GetExpectations(self):
-    if self.PLATFORM is 'desktop':
-      return page_sets.V8BrowsingDesktopExpecations()
-    if self.PLATFORM is 'mobile':
-      return page_sets.V8BrowsingMobileExpecations()
-    raise NotImplementedError, ('Only have expectations for mobile and desktop '
-                                'platforms for v8_browsing tests.')
-
   @classmethod
   def ValueCanBeAddedPredicate(cls, value, is_first_result):
     # TODO(crbug.com/610962): Remove this stopgap when the perf dashboard
@@ -95,12 +102,8 @@
     # Allow all other metrics.
     return True
 
-  @classmethod
-  def ShouldTearDownStateAfterEachStoryRun(cls):
-    return True
 
-
-class _V8RuntimeStatsBrowsingBenchmark(perf_benchmark.PerfBenchmark):
+class _V8RuntimeStatsBrowsingBenchmark(_v8BrowsingBenchmarkBaseClass):
   """Base class for V8 browsing benchmarks that measure RuntimeStats.
   RuntimeStats measure the time spent by v8 in different phases like
   compile, JS execute, runtime etc.,
@@ -142,13 +145,6 @@
       'expectedQueueingTimeMetric', 'runtimeStatsTotalMetric', 'gcMetric'])
     return options
 
-  def CreateStorySet(self, options):
-    return page_sets.SystemHealthStorySet(platform=self.PLATFORM, case='browse')
-
-  @classmethod
-  def ShouldTearDownStateAfterEachStoryRun(cls):
-    return True
-
 
 @benchmark.Owner(emails=['ulan@chromium.org'])
 @benchmark.Disabled('android')
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
index be51029..ca2f3a9 100644
--- a/ui/events/BUILD.gn
+++ b/ui/events/BUILD.gn
@@ -460,6 +460,7 @@
         "ozone/evdev/tablet_event_converter_evdev_unittest.cc",
         "ozone/evdev/touch_event_converter_evdev_unittest.cc",
         "ozone/evdev/touch_filter/false_touch_finder_unittest.cc",
+        "ozone/gamepad/generic_gamepad_mapping_unittest.cc",
       ]
 
       if (use_xkbcommon) {
diff --git a/ui/events/ozone/BUILD.gn b/ui/events/ozone/BUILD.gn
index 8809831..aa0ad649 100644
--- a/ui/events/ozone/BUILD.gn
+++ b/ui/events/ozone/BUILD.gn
@@ -129,6 +129,10 @@
       "gamepad/gamepad_observer.h",
       "gamepad/gamepad_provider_ozone.cc",
       "gamepad/gamepad_provider_ozone.h",
+      "gamepad/generic_gamepad_mapping.cc",
+      "gamepad/generic_gamepad_mapping.h",
+      "gamepad/static_gamepad_mapping.cc",
+      "gamepad/static_gamepad_mapping.h",
       "gamepad/webgamepad_constants.h",
     ]
 
diff --git a/ui/events/ozone/evdev/event_device_test_util.cc b/ui/events/ozone/evdev/event_device_test_util.cc
index 886fcf4..2477945 100644
--- a/ui/events/ozone/evdev/event_device_test_util.cc
+++ b/ui/events/ozone/evdev/event_device_test_util.cc
@@ -79,6 +79,36 @@
 
 }  // namespace
 
+// Captured from HJC Game ZD - V gamepad.
+const DeviceAbsoluteAxis kHJCGamepadAbsAxes[] = {
+    {ABS_X, {128, 0, 255, 15, 0}}, {ABS_Y, {128, 0, 255, 15, 0}},
+    {ABS_Z, {128, 0, 255, 15, 0}}, {ABS_RZ, {128, 0, 255, 15, 0}},
+    {ABS_HAT0X, {0, -1, 1, 0, 0}}, {ABS_HAT0Y, {0, 1, 1, 0, 0}}};
+
+const DeviceCapabilities kHJCGamepad = {
+    /* path */
+    "/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2.2/1-2.2:1.0/"
+    "input/input38/event11",
+    /* name */ "HJC Game ZD - V",
+    /* phys */ "usb-0000:00:14.0-2.2/input0",
+    /* uniq */ "",
+    /* bustype */ "0011",
+    /* vendor */ "11c5",
+    /* product */ "5506",
+    /* version */ "0111",
+    /* prop */ "0",
+    /* ev */ "1b",
+    /* key */ "fff000000000000 0 0 0 0",
+    /* rel */ "0",
+    /* abs */ "30027",
+    /* msc */ "10",
+    /* sw */ "0",
+    /* led */ "0",
+    /* ff */ "0",
+    kHJCGamepadAbsAxes,
+    arraysize(kHJCGamepadAbsAxes),
+};
+
 // Captured from Xbox 360 gamepad.
 const DeviceAbsoluteAxis kXboxGamepadAbsAxes[] = {
     {ABS_X, {0, -32768, 32767, 16, 128}},
diff --git a/ui/events/ozone/evdev/event_device_test_util.h b/ui/events/ozone/evdev/event_device_test_util.h
index baa8654..a8de08f0 100644
--- a/ui/events/ozone/evdev/event_device_test_util.h
+++ b/ui/events/ozone/evdev/event_device_test_util.h
@@ -60,6 +60,7 @@
                               EventDeviceInfo* devinfo);
 
 extern const DeviceCapabilities kXboxGamepad;
+extern const DeviceCapabilities kHJCGamepad;
 extern const DeviceCapabilities kiBuffaloGamepad;
 extern const DeviceCapabilities kLinkKeyboard;
 extern const DeviceCapabilities kLinkTouchscreen;
diff --git a/ui/events/ozone/evdev/gamepad_event_converter_evdev.cc b/ui/events/ozone/evdev/gamepad_event_converter_evdev.cc
index ecd4513..1af45d07 100644
--- a/ui/events/ozone/evdev/gamepad_event_converter_evdev.cc
+++ b/ui/events/ozone/evdev/gamepad_event_converter_evdev.cc
@@ -107,7 +107,7 @@
       last_hat_right_press_(false),
       last_hat_up_press_(false),
       last_hat_down_press_(false),
-      mapper_(GetGamepadMapper(devinfo.vendor_id(), devinfo.product_id())),
+      mapper_(GetGamepadMapper(devinfo)),
       input_device_fd_(std::move(fd)),
       dispatcher_(dispatcher) {
   input_absinfo abs_info;
@@ -125,7 +125,7 @@
       if (abs_info.fuzz == 0) {
         abs_info.fuzz = abs_info.flat * 0.25f;
       }
-      mapper_(EV_ABS, code, &mapped_type, &mapped_code);
+      mapper_->Map(EV_ABS, code, &mapped_type, &mapped_code);
       axes_[code] = Axis(abs_info, mapped_type, mapped_code);
     }
   }
@@ -190,7 +190,7 @@
   GamepadEventType mapped_type;
   uint16_t mapped_code;
 
-  bool found_map = mapper_(EV_KEY, code, &mapped_type, &mapped_code);
+  bool found_map = mapper_->Map(EV_KEY, code, &mapped_type, &mapped_code);
 
   // If we cannot find a map for this event, it will be discarded.
   if (!found_map) {
diff --git a/ui/events/ozone/evdev/gamepad_event_converter_evdev.h b/ui/events/ozone/evdev/gamepad_event_converter_evdev.h
index d1f233a..cb10ab68 100644
--- a/ui/events/ozone/evdev/gamepad_event_converter_evdev.h
+++ b/ui/events/ozone/evdev/gamepad_event_converter_evdev.h
@@ -107,7 +107,7 @@
   bool last_hat_up_press_;
   bool last_hat_down_press_;
 
-  GamepadMapper mapper_;
+  std::unique_ptr<GamepadMapper> mapper_;
 
   // Input device file descriptor.
   ScopedInputDevice input_device_fd_;
diff --git a/ui/events/ozone/gamepad/gamepad_mapping.cc b/ui/events/ozone/gamepad/gamepad_mapping.cc
index be20d00..6e1d96c 100644
--- a/ui/events/ozone/gamepad/gamepad_mapping.cc
+++ b/ui/events/ozone/gamepad/gamepad_mapping.cc
@@ -2,447 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <linux/input.h>
-#include <cstdint>
-#include <map>
-
-#include "base/macros.h"
 #include "ui/events/ozone/gamepad/gamepad_mapping.h"
-#include "ui/events/ozone/gamepad/webgamepad_constants.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "ui/events/ozone/evdev/event_device_info.h"
+#include "ui/events/ozone/gamepad/generic_gamepad_mapping.h"
+#include "ui/events/ozone/gamepad/static_gamepad_mapping.h"
 
 namespace ui {
 
-// KeyMap maps evdev key code to web gamepad code.
-struct KeyMapEntry {
-  uint16_t evdev_code;
-  uint16_t mapped_code;
-};
-
-// AbsMap maps evdev abs code to web gamepad (type, code).
-struct AbsMapEntry {
-  uint16_t evdev_code;
-  GamepadEventType mapped_type;
-  uint16_t mapped_code;
-};
-
-using KeyMapType = const KeyMapEntry[];
-using AbsMapType = const AbsMapEntry[];
-
-#define TO_BTN(code, mapped_code) \
-  { code, GamepadEventType::BUTTON, mapped_code }
-
-#define TO_ABS(code, mapped_code) \
-  { code, GamepadEventType::AXIS, mapped_code }
-
-#define DO_MAPPING                                                   \
-  DoGamepadMapping(key_mapping, arraysize(key_mapping), abs_mapping, \
-                   arraysize(abs_mapping), type, code, mapped_type,  \
-                   mapped_code)
-
-bool DoGamepadMapping(const KeyMapEntry* key_mapping,
-                      size_t key_map_size,
-                      const AbsMapEntry* abs_mapping,
-                      size_t abs_map_size,
-                      uint16_t type,
-                      uint16_t code,
-                      GamepadEventType* mapped_type,
-                      uint16_t* mapped_code) {
-  if (type == EV_KEY) {
-    const KeyMapEntry* entry = nullptr;
-    for (size_t i = 0; i < key_map_size; i++) {
-      if (key_mapping[i].evdev_code == code) {
-        entry = key_mapping + i;
-      }
-    }
-    if (!entry) {
-      return false;
-    }
-    *mapped_type = GamepadEventType::BUTTON;
-    *mapped_code = entry->mapped_code;
-    return true;
+std::unique_ptr<GamepadMapper> GetGamepadMapper(
+    const EventDeviceInfo& devinfo) {
+  std::unique_ptr<GamepadMapper> result(
+      GetStaticGamepadMapper(devinfo.vendor_id(), devinfo.product_id()));
+  if (!result) {
+    return BuildGenericGamepadMapper(devinfo);
   }
-
-  if (type == EV_ABS) {
-    const AbsMapEntry* entry = nullptr;
-    for (size_t i = 0; i < abs_map_size; i++) {
-      if (abs_mapping[i].evdev_code == code) {
-        entry = abs_mapping + i;
-      }
-    }
-    if (!entry) {
-      return false;
-    }
-    *mapped_type = entry->mapped_type;
-    *mapped_code = entry->mapped_code;
-    return true;
-  }
-  return false;
-}
-
-// this mapper mapps gamepads compatible with xbox gamepad.
-bool XInputStyleMapper(uint16_t type,
-                       uint16_t code,
-                       GamepadEventType* mapped_type,
-                       uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_A, WG_BUTTON_A},            // btn_a = 304 / 0x130
-      {BTN_B, WG_BUTTON_B},            // btn_b = 305 / 0x131
-      {BTN_X, WG_BUTTON_X},            // btn_x = 307 / 0x133
-      {BTN_Y, WG_BUTTON_Y},            // btn_y = 308 / 0x134
-      {BTN_TL, WG_BUTTON_L1},          // btn_tl = 310 / 0x136
-      {BTN_TR, WG_BUTTON_R1},          // btn_tr = 311 / 0x137
-      {BTN_SELECT, WG_BUTTON_SELECT},  // btn_select = 314 / 0x13a
-      {BTN_START, WG_BUTTON_START},    // btn_start = 315 / 0x13b
-      {BTN_THUMBL, WG_BUTTON_THUMBL},  // btn_thumbl = 317 / 0x13d
-      {BTN_THUMBR, WG_BUTTON_THUMBR},  // btn_thumbr = 318 / 0x13e
-      {BTN_MODE, WG_BUTTON_MODE}       // btn_mode = 316 / 0x13c
-  };
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),       // ABS_X = 0x00
-      TO_ABS(ABS_Y, WG_ABS_Y),       // ABS_Y = 0x01
-      TO_ABS(ABS_RX, WG_ABS_RX),     // ABS_RX = 0x03
-      TO_ABS(ABS_RY, WG_ABS_RY),     // ABS_RZ = 0x04
-      TO_BTN(ABS_Z, WG_BUTTON_LT),   // ABS_Z = 0x02
-      TO_BTN(ABS_RZ, WG_BUTTON_RT),  // ABS_RZ = 0x05
-      TO_BTN(ABS_HAT0X, kHAT_X),     // HAT0X = 0x10
-      TO_BTN(ABS_HAT0Y, kHAT_Y)      // HAT0Y = 0x11
-  };
-  return DO_MAPPING;
-}
-
-bool PlaystationSixAxisMapper(uint16_t type,
-                              uint16_t code,
-                              GamepadEventType* mapped_type,
-                              uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {0x12e, WG_BUTTON_A},
-      {0x12d, WG_BUTTON_B},
-      {BTN_DEAD, WG_BUTTON_X},
-      {0x12c, WG_BUTTON_Y},
-      {BTN_BASE5, WG_BUTTON_L1},
-      {BTN_BASE6, WG_BUTTON_R1},
-      {BTN_BASE3, WG_BUTTON_LT},
-      {BTN_BASE4, WG_BUTTON_RT},
-      {BTN_TRIGGER, WG_BUTTON_SELECT},
-      {BTN_TOP, WG_BUTTON_START},
-      {BTN_THUMB, WG_BUTTON_THUMBL},
-      {BTN_THUMB2, WG_BUTTON_THUMBR},
-      {BTN_TOP2, WG_BUTTON_DPAD_UP},
-      {BTN_BASE, WG_BUTTON_DPAD_DOWN},
-      {BTN_BASE2, WG_BUTTON_DPAD_LEFT},
-      {BTN_PINKIE, WG_BUTTON_DPAD_RIGHT},
-      {BTN_TRIGGER_HAPPY17, WG_BUTTON_MODE},
-  };
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),
-      TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_Z, WG_ABS_RX),
-      TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_MT_TOUCH_MAJOR, WG_BUTTON_LT),
-      TO_BTN(ABS_MT_TOUCH_MINOR, WG_BUTTON_RT)};
-  return DO_MAPPING;
-}
-
-bool IBuffalocClassicMapper(uint16_t type,
-                            uint16_t code,
-                            GamepadEventType* mapped_type,
-                            uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_TRIGGER, WG_BUTTON_A}, {BTN_THUMB, WG_BUTTON_B},
-      {BTN_THUMB2, WG_BUTTON_X},  {BTN_TOP, WG_BUTTON_Y},
-      {BTN_BASE, WG_BUTTON_L1},   {BTN_BASE2, WG_BUTTON_R1},
-      {BTN_TOP2, WG_BUTTON_LT},   {BTN_PINKIE, WG_BUTTON_RT}};
-
-  static const AbsMapType abs_mapping = {
-      TO_BTN(ABS_X, kHAT_X), TO_BTN(ABS_Y, kHAT_Y),
-  };
-  return DO_MAPPING;
-}
-
-bool ClassicNESMapper(uint16_t type,
-                      uint16_t code,
-                      GamepadEventType* mapped_type,
-                      uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_THUMB, WG_BUTTON_A},      {BTN_THUMB2, WG_BUTTON_B},
-      {BTN_TRIGGER, WG_BUTTON_X},    {BTN_TOP, WG_BUTTON_Y},
-      {BTN_TOP2, WG_BUTTON_L1},      {BTN_PINKIE, WG_BUTTON_R1},
-      {BTN_BASE3, WG_BUTTON_SELECT}, {BTN_BASE4, WG_BUTTON_START}};
-
-  static const AbsMapType abs_mapping = {
-      TO_BTN(ABS_X, kHAT_X), TO_BTN(ABS_Y, kHAT_Y),
-  };
-  return DO_MAPPING;
-}
-
-bool SNesRetroMapper(uint16_t type,
-                     uint16_t code,
-                     GamepadEventType* mapped_type,
-                     uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_C, WG_BUTTON_A},        {BTN_B, WG_BUTTON_B},
-      {BTN_X, WG_BUTTON_X},        {BTN_A, WG_BUTTON_Y},
-      {BTN_Y, WG_BUTTON_L1},       {BTN_Z, WG_BUTTON_R1},
-      {BTN_TL2, WG_BUTTON_SELECT}, {BTN_TR2, WG_BUTTON_START}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, kHAT_X), TO_ABS(ABS_Y, kHAT_Y),
-  };
-  return DO_MAPPING;
-}
-
-bool ADT1Mapper(uint16_t type,
-                uint16_t code,
-                GamepadEventType* mapped_type,
-                uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_A, WG_BUTTON_A},           {BTN_B, WG_BUTTON_B},
-      {BTN_X, WG_BUTTON_X},           {BTN_Y, WG_BUTTON_Y},
-      {BTN_TL, WG_BUTTON_L1},         {BTN_TR, WG_BUTTON_R1},
-      {BTN_THUMBL, WG_BUTTON_THUMBL}, {BTN_THUMBR, WG_BUTTON_THUMBR},
-      {BTN_MODE, WG_BUTTON_START},    {KEY_BACK, WG_BUTTON_SELECT},
-      {KEY_HOMEPAGE, WG_BUTTON_MODE}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),         TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_Z, WG_ABS_RX),        TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_BRAKE, WG_BUTTON_LT), TO_BTN(ABS_GAS, WG_BUTTON_RT),
-      TO_BTN(ABS_HAT0X, kHAT_X),       TO_BTN(ABS_HAT0Y, kHAT_Y)};
-  return DO_MAPPING;
-}
-
-bool Vendor_1d79Product_0009Mapper(uint16_t type,
-                                   uint16_t code,
-                                   GamepadEventType* mapped_type,
-                                   uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {{BTN_A, WG_BUTTON_A},
-                                         {BTN_B, WG_BUTTON_B},
-                                         {BTN_X, WG_BUTTON_X},
-                                         {BTN_Y, WG_BUTTON_Y},
-                                         {BTN_TL, WG_BUTTON_L1},
-                                         {BTN_TR, WG_BUTTON_R1},
-                                         {BTN_START, WG_BUTTON_START},
-                                         {BTN_THUMBL, WG_BUTTON_THUMBL},
-                                         {BTN_THUMBR, WG_BUTTON_THUMBR},
-                                         {KEY_UP, WG_BUTTON_DPAD_UP},
-                                         {KEY_DOWN, WG_BUTTON_DPAD_DOWN},
-                                         {KEY_LEFT, WG_BUTTON_DPAD_LEFT},
-                                         {KEY_RIGHT, WG_BUTTON_DPAD_RIGHT},
-                                         {KEY_BACK, WG_BUTTON_SELECT},
-                                         {KEY_HOMEPAGE, WG_BUTTON_MODE}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),         TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_Z, WG_ABS_RX),        TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_BRAKE, WG_BUTTON_LT), TO_BTN(ABS_GAS, WG_BUTTON_RT),
-      TO_BTN(ABS_HAT0X, kHAT_X),       TO_BTN(ABS_HAT0Y, kHAT_Y)};
-  return DO_MAPPING;
-}
-
-bool Vendor_046dProduct_b501Mapper(uint16_t type,
-                                   uint16_t code,
-                                   GamepadEventType* mapped_type,
-                                   uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {{BTN_A, WG_BUTTON_A},
-                                         {BTN_B, WG_BUTTON_B},
-                                         {BTN_X, WG_BUTTON_X},
-                                         {BTN_Y, WG_BUTTON_Y},
-                                         {BTN_TL, WG_BUTTON_L1},
-                                         {BTN_TR, WG_BUTTON_R1},
-                                         {BTN_TL2, WG_BUTTON_LT},
-                                         {BTN_TR2, WG_BUTTON_RT},
-                                         {BTN_SELECT, WG_BUTTON_SELECT},
-                                         {BTN_START, WG_BUTTON_START},
-                                         {BTN_THUMBL, WG_BUTTON_THUMBL},
-                                         {BTN_THUMBR, WG_BUTTON_THUMBR},
-                                         {KEY_UP, WG_BUTTON_DPAD_UP},
-                                         {KEY_DOWN, WG_BUTTON_DPAD_DOWN},
-                                         {KEY_LEFT, WG_BUTTON_DPAD_LEFT},
-                                         {KEY_RIGHT, WG_BUTTON_DPAD_RIGHT},
-                                         {BTN_MODE, WG_BUTTON_MODE}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),         TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_Z, WG_ABS_RX),        TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_BRAKE, WG_BUTTON_LT), TO_BTN(ABS_GAS, WG_BUTTON_RT),
-      TO_BTN(ABS_HAT0X, kHAT_X),       TO_BTN(ABS_HAT0Y, kHAT_Y)};
-  return DO_MAPPING;
-}
-
-bool Vendor_046dProduct_c216Mapper(uint16_t type,
-                                   uint16_t code,
-                                   GamepadEventType* mapped_type,
-                                   uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_TRIGGER, WG_BUTTON_A},    {BTN_TOP, WG_BUTTON_B},
-      {BTN_THUMB, WG_BUTTON_X},      {BTN_THUMB2, WG_BUTTON_Y},
-      {BTN_TOP2, WG_BUTTON_L1},      {BTN_PINKIE, WG_BUTTON_R1},
-      {BTN_BASE, WG_BUTTON_LT},      {BTN_BASE2, WG_BUTTON_RT},
-      {BTN_BASE3, WG_BUTTON_SELECT}, {BTN_BASE4, WG_BUTTON_START},
-      {BTN_BASE5, WG_BUTTON_THUMBL}, {BTN_BASE6, WG_BUTTON_THUMBR}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),   TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_Z, WG_ABS_RX),  TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_HAT0X, kHAT_X), TO_BTN(ABS_HAT0Y, kHAT_Y)};
-  return DO_MAPPING;
-}
-
-bool Vendor_046dProduct_c219Mapper(uint16_t type,
-                                   uint16_t code,
-                                   GamepadEventType* mapped_type,
-                                   uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_B, WG_BUTTON_A},          {BTN_C, WG_BUTTON_B},
-      {BTN_A, WG_BUTTON_X},          {BTN_X, WG_BUTTON_Y},
-      {BTN_Y, WG_BUTTON_L1},         {BTN_Z, WG_BUTTON_R1},
-      {BTN_TL, WG_BUTTON_LT},        {BTN_TR, WG_BUTTON_RT},
-      {BTN_TR2, WG_BUTTON_START},    {BTN_SELECT, WG_BUTTON_THUMBL},
-      {BTN_START, WG_BUTTON_THUMBR}, {BTN_TL2, WG_BUTTON_SELECT}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),   TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_Z, WG_ABS_RX),  TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_HAT0X, kHAT_X), TO_BTN(ABS_HAT0Y, kHAT_Y)};
-  return DO_MAPPING;
-}
-
-bool Vendor_1038Product_1412Mapper(uint16_t type,
-                                   uint16_t code,
-                                   GamepadEventType* mapped_type,
-                                   uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_A, WG_BUTTON_A},         {BTN_B, WG_BUTTON_B},
-      {BTN_X, WG_BUTTON_X},         {BTN_Y, WG_BUTTON_Y},
-      {BTN_TL, WG_BUTTON_L1},       {BTN_TR, WG_BUTTON_R1},
-      {BTN_MODE, WG_BUTTON_SELECT}, {BTN_START, WG_BUTTON_START},
-  };
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),   TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_Z, WG_ABS_RX),  TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_HAT0X, kHAT_X), TO_BTN(ABS_HAT0Y, kHAT_Y)};
-  return DO_MAPPING;
-}
-
-bool Vendor_1689Product_fd00Mapper(uint16_t type,
-                                   uint16_t code,
-                                   GamepadEventType* mapped_type,
-                                   uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {
-      {BTN_A, WG_BUTTON_A},
-      {BTN_B, WG_BUTTON_B},
-      {BTN_X, WG_BUTTON_X},
-      {BTN_Y, WG_BUTTON_Y},
-      {BTN_TL, WG_BUTTON_L1},
-      {BTN_TR, WG_BUTTON_R1},
-      {BTN_START, WG_BUTTON_START},
-      {BTN_THUMBL, WG_BUTTON_THUMBL},
-      {BTN_THUMBR, WG_BUTTON_THUMBR},
-      {BTN_TRIGGER_HAPPY3, WG_BUTTON_DPAD_UP},
-      {BTN_TRIGGER_HAPPY4, WG_BUTTON_DPAD_DOWN},
-      {BTN_TRIGGER_HAPPY1, WG_BUTTON_DPAD_LEFT},
-      {BTN_TRIGGER_HAPPY2, WG_BUTTON_DPAD_RIGHT},
-      {BTN_SELECT, WG_BUTTON_SELECT},
-      {BTN_MODE, WG_BUTTON_MODE}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),     TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_RX, WG_ABS_RX),   TO_ABS(ABS_RY, WG_ABS_RY),
-      TO_BTN(ABS_Z, WG_BUTTON_LT), TO_BTN(ABS_RZ, WG_BUTTON_RT)};
-  return DO_MAPPING;
-}
-
-bool GenericMapper(uint16_t type,
-                   uint16_t code,
-                   GamepadEventType* mapped_type,
-                   uint16_t* mapped_code) {
-  static const KeyMapType key_mapping = {{BTN_A, WG_BUTTON_A},
-                                         {BTN_B, WG_BUTTON_B},
-                                         {BTN_X, WG_BUTTON_X},
-                                         {BTN_Y, WG_BUTTON_Y},
-                                         {BTN_TL, WG_BUTTON_L1},
-                                         {BTN_TR, WG_BUTTON_R1},
-                                         {BTN_TL2, WG_BUTTON_LT},
-                                         {BTN_TR2, WG_BUTTON_RT},
-                                         {BTN_THUMBL, WG_BUTTON_THUMBL},
-                                         {BTN_THUMBR, WG_BUTTON_THUMBR},
-                                         {KEY_BACK, WG_BUTTON_SELECT},
-                                         {KEY_HOMEPAGE, WG_BUTTON_MODE},
-                                         {KEY_UP, WG_BUTTON_DPAD_UP},
-                                         {KEY_DOWN, WG_BUTTON_DPAD_DOWN},
-                                         {KEY_LEFT, WG_BUTTON_DPAD_LEFT},
-                                         {KEY_RIGHT, WG_BUTTON_DPAD_RIGHT},
-                                         {BTN_SELECT, WG_BUTTON_SELECT},
-                                         {BTN_START, WG_BUTTON_START},
-                                         {BTN_MODE, WG_BUTTON_MODE}};
-
-  static const AbsMapType abs_mapping = {
-      TO_ABS(ABS_X, WG_ABS_X),         TO_ABS(ABS_Y, WG_ABS_Y),
-      TO_ABS(ABS_RX, WG_ABS_RX),       TO_ABS(ABS_RY, WG_ABS_RY),
-      TO_ABS(ABS_Z, WG_ABS_RX),        TO_ABS(ABS_RZ, WG_ABS_RY),
-      TO_BTN(ABS_BRAKE, WG_BUTTON_LT), TO_BTN(ABS_GAS, WG_BUTTON_RT),
-      TO_BTN(ABS_HAT0X, kHAT_X),       TO_BTN(ABS_HAT0Y, kHAT_Y)};
-  return DO_MAPPING;
-}
-
-static const struct MappingData {
-  uint16_t vendor_id;
-  uint16_t product_id;
-  GamepadMapper mapper;
-} AvailableMappings[] = {
-    // Xbox style gamepad.
-    {0x045e, 0x028e, XInputStyleMapper},  // Xbox 360 wired.
-    {0x045e, 0x028f, XInputStyleMapper},  // Xbox 360 wireless.
-    {0x045e, 0x02a1, XInputStyleMapper},  // Xbox 360 wireless.
-    {0x045e, 0x02d1, XInputStyleMapper},  // Xbox one wired.
-    {0x045e, 0x02dd, XInputStyleMapper},  // Xbox one wired (2015 fw).
-    {0x045e, 0x02e3, XInputStyleMapper},  // Xbox elite wired.
-    {0x045e, 0x02ea, XInputStyleMapper},  // Xbox one s (usb).
-    {0x045e, 0x0719, XInputStyleMapper},  // Xbox 360 wireless.
-    {0x046d, 0xc21d, XInputStyleMapper},  // Logitech f310.
-    {0x046d, 0xc21e, XInputStyleMapper},  // Logitech f510.
-    {0x046d, 0xc21f, XInputStyleMapper},  // Logitech f710.
-    {0x2378, 0x1008, XInputStyleMapper},  // Onlive controller (bluetooth).
-    {0x2378, 0x100a, XInputStyleMapper},  // Onlive controller (wired).
-    {0x1bad, 0xf016, XInputStyleMapper},  // Mad catz gamepad.
-    {0x1bad, 0xf023, XInputStyleMapper},  // Mad catz mlg gamepad for Xbox360.
-    {0x1bad, 0xf027, XInputStyleMapper},  // Mad catz fps pro.
-    {0x1bad, 0xf036, XInputStyleMapper},  // Mad catz generic Xbox controller.
-    {0x1689, 0xfd01, XInputStyleMapper},  // Razer Xbox 360 gamepad.
-    {0x1689, 0xfe00, XInputStyleMapper},  // Razer sabertooth elite.
-    // Sony gamepads.
-    {0x054c, 0x0268, PlaystationSixAxisMapper},  // Playstation 3.
-    // NES style gamepad.
-    {0x0583, 0x2060, IBuffalocClassicMapper},  // iBuffalo Classic.
-    {0x0079, 0x0011, ClassicNESMapper},        // Classic NES controller.
-    {0x12bd, 0xd015, SNesRetroMapper},         // Hitgaming SNES retro.
-    // Android gamepad.
-    {0x0b05, 0x4500, ADT1Mapper},  // Nexus player controller (asus gamepad).
-    {0x1532, 0x0900, ADT1Mapper},  // Razer serval.
-    {0x18d1, 0x2c40, ADT1Mapper},  // ADT-1 controller (odie).
-    // Other gamepads.
-    {0x1d79, 0x0009,
-     Vendor_1d79Product_0009Mapper},  // Nyko playpad / Playpad pro.
-    {0x046d, 0xb501, Vendor_046dProduct_b501Mapper},  // Logitech redhawk.
-    // Logitech dual action controller.
-    {0x046d, 0xc216, Vendor_046dProduct_c216Mapper},
-    // Logitech cordless rumblepad2.
-    {0x046d, 0xc219, Vendor_046dProduct_c219Mapper},
-    {0x1038, 0x1412, Vendor_1038Product_1412Mapper},  // Steelseries free.
-    // Razer onza tournment edition.
-    {0x1689, 0xfd00, Vendor_1689Product_fd00Mapper}};
-
-GamepadMapper GetGamepadMapper(uint16_t vendor_id, uint16_t product_id) {
-  for (size_t i = 0; i < arraysize(AvailableMappings); i++) {
-    if (AvailableMappings[i].vendor_id == vendor_id &&
-        AvailableMappings[i].product_id == product_id) {
-      return AvailableMappings[i].mapper;
-    }
-  }
-  return GenericMapper;
+  return result;
 }
 
 }  // namespace ui
diff --git a/ui/events/ozone/gamepad/gamepad_mapping.h b/ui/events/ozone/gamepad/gamepad_mapping.h
index e864e97..0a739896 100644
--- a/ui/events/ozone/gamepad/gamepad_mapping.h
+++ b/ui/events/ozone/gamepad/gamepad_mapping.h
@@ -5,23 +5,54 @@
 #ifndef UI_EVENTS_OZONE_GAMEPAD_GAMEPAD_MAPPING_H_
 #define UI_EVENTS_OZONE_GAMEPAD_GAMEPAD_MAPPING_H_
 
+#include <memory>
+
 #include "ui/events/ozone/gamepad/webgamepad_constants.h"
 
 namespace ui {
 
+class EventDeviceInfo;
+
 // The following HATX and HATY is not part of web gamepad definition, but we
 // need to specially treat them cause HAT_Y can be mapped to DPAD_UP or
 // DPAD_DOWN, and HAT_X can be mapped to DAPD_LEFT or DPAD_RIGHT.
 constexpr int kHAT_X = 4;
 constexpr int kHAT_Y = 5;
 
-typedef bool (*GamepadMapper)(uint16_t key,
-                              uint16_t code,
-                              GamepadEventType* mapped_type,
-                              uint16_t* mapped_code);
+// KeyMap maps evdev key code to web gamepad code.
+struct KeyMapEntry {
+  uint16_t evdev_code;
+  uint16_t mapped_code;
+};
+
+// AbsMap maps evdev abs code to web gamepad (type, code).
+struct AbsMapEntry {
+  uint16_t evdev_code;
+  GamepadEventType mapped_type;
+  uint16_t mapped_code;
+};
+
+using KeyMapType = const KeyMapEntry[];
+using AbsMapType = const AbsMapEntry[];
+
+#define TO_BTN(code, mapped_code) \
+  { code, GamepadEventType::BUTTON, mapped_code }
+
+#define TO_ABS(code, mapped_code) \
+  { code, GamepadEventType::AXIS, mapped_code }
+
+class GamepadMapper {
+ public:
+  virtual bool Map(uint16_t key,
+                   uint16_t code,
+                   GamepadEventType* mapped_type,
+                   uint16_t* mapped_code) const = 0;
+
+  virtual ~GamepadMapper() {}
+};
 
 // This function gets the best mapper for the gamepad vendor_id and product_id.
-GamepadMapper GetGamepadMapper(uint16_t vendor_id, uint16_t product_id);
+std::unique_ptr<GamepadMapper> GetGamepadMapper(const EventDeviceInfo& devinfo);
 
 }  // namespace ui
 
diff --git a/ui/events/ozone/gamepad/generic_gamepad_mapping.cc b/ui/events/ozone/gamepad/generic_gamepad_mapping.cc
new file mode 100644
index 0000000..343b2a9
--- /dev/null
+++ b/ui/events/ozone/gamepad/generic_gamepad_mapping.cc
@@ -0,0 +1,250 @@
+// 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.
+
+#include <linux/input.h>
+#include <algorithm>
+#include <bitset>
+#include <cstdint>
+#include <list>
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "ui/events/ozone/evdev/event_device_info.h"
+#include "ui/events/ozone/gamepad/generic_gamepad_mapping.h"
+#include "ui/events/ozone/gamepad/webgamepad_constants.h"
+
+namespace {
+using ui::GamepadEventType;
+
+class GenericGamepadMapper : public ui::GamepadMapper {
+ public:
+  GenericGamepadMapper(std::vector<ui::AbsMapEntry> axis_mapping,
+                       std::vector<ui::KeyMapEntry> button_mapping) {
+    axis_mapping.swap(axis_mapping_);
+    button_mapping.swap(button_mapping_);
+  }
+
+  bool Map(uint16_t type,
+           uint16_t code,
+           ui::GamepadEventType* mapped_type,
+           uint16_t* mapped_code) const override {
+    if (type == EV_KEY) {
+      for (auto entry : button_mapping_) {
+        if (entry.evdev_code == code) {
+          *mapped_type = ui::GamepadEventType::BUTTON;
+          *mapped_code = entry.mapped_code;
+          return true;
+        }
+      }
+      return false;
+    }
+
+    if (type == EV_ABS) {
+      for (auto entry : axis_mapping_) {
+        if (entry.evdev_code == code) {
+          *mapped_type = entry.mapped_type;
+          *mapped_code = entry.mapped_code;
+          return true;
+        }
+      }
+      return false;
+    }
+    return false;
+  }
+
+  ~GenericGamepadMapper() override {}
+
+ private:
+  std::vector<ui::AbsMapEntry> axis_mapping_;
+  std::vector<ui::KeyMapEntry> button_mapping_;
+};
+
+// This helper class will be used to build generic mapping.
+class GamepadMapperBuilder {
+ public:
+  explicit GamepadMapperBuilder(const ui::EventDeviceInfo& devinfo)
+      : devinfo_(devinfo) {}
+
+  std::unique_ptr<ui::GamepadMapper> ToGamepadMapper() {
+    return base::MakeUnique<GenericGamepadMapper>(std::move(axis_mapping_),
+                                                  std::move(button_mapping_));
+  }
+
+  void MapButton(uint16_t from_button, uint16_t mapped_button) {
+    if (!devinfo_.HasKeyEvent(from_button)) {
+      return;
+    }
+    DCHECK(!IfEvdevButtonMappedFrom(from_button));
+    DCHECK(!IfWebgamepadButtonMappedTo(mapped_button));
+
+    button_mapping_.push_back({from_button, mapped_button});
+    evdev_buttons_.set(from_button);
+    webgamepad_buttons_.set(mapped_button);
+  }
+
+  void MapAxisToButton(uint16_t from_axis, uint16_t mapped_button) {
+    if (!devinfo_.HasAbsEvent(from_axis)) {
+      return;
+    }
+    DCHECK(!IfEvdevAxisMappedFrom(from_axis));
+    evdev_axes_.set(from_axis);
+    axis_mapping_.push_back(TO_BTN(from_axis, mapped_button));
+
+    if (mapped_button == ui::kHAT_X) {
+      DCHECK(!IfWebgamepadButtonMappedTo(ui::WG_BUTTON_DPAD_LEFT));
+      DCHECK(!IfWebgamepadButtonMappedTo(ui::WG_BUTTON_DPAD_RIGHT));
+
+      webgamepad_buttons_.set(ui::WG_BUTTON_DPAD_LEFT);
+      webgamepad_buttons_.set(ui::WG_BUTTON_DPAD_RIGHT);
+    } else if (mapped_button == ui::kHAT_Y) {
+      DCHECK(!IfWebgamepadButtonMappedTo(ui::WG_BUTTON_DPAD_UP));
+      DCHECK(!IfWebgamepadButtonMappedTo(ui::WG_BUTTON_DPAD_DOWN));
+
+      webgamepad_buttons_.set(ui::WG_BUTTON_DPAD_UP);
+      webgamepad_buttons_.set(ui::WG_BUTTON_DPAD_DOWN);
+    } else {
+      DCHECK(!IfWebgamepadButtonMappedTo(mapped_button));
+      webgamepad_buttons_.set(mapped_button);
+    }
+  }
+
+  void MapAxisToAxis(uint16_t from_axis, uint16_t mapped_axis) {
+    if (!devinfo_.HasAbsEvent(from_axis)) {
+      return;
+    }
+    DCHECK(!IfEvdevAxisMappedFrom(from_axis));
+    DCHECK(!IfWebgamepadAxisMappedTo(mapped_axis));
+
+    axis_mapping_.push_back(TO_ABS(from_axis, mapped_axis));
+    evdev_axes_.set(from_axis);
+    webgamepad_axes_.set(mapped_axis);
+  }
+
+  void MapButtonLikeJoydev() {
+    uint16_t next_unmapped_button = 0;
+    // In linux kernel, joydev.c map evdev events in the same way.
+    for (int i = BTN_JOYSTICK - BTN_MISC; i < KEY_MAX - BTN_MISC + 1; i++) {
+      int code = i + BTN_MISC;
+      if (devinfo_.HasKeyEvent(code) && !IfEvdevButtonMappedFrom(code) &&
+          FindNextUnmappedCode(webgamepad_buttons_, &next_unmapped_button)) {
+        MapButton(code, next_unmapped_button);
+      }
+    }
+
+    for (int i = 0; i < BTN_JOYSTICK - BTN_MISC; i++) {
+      int code = i + BTN_MISC;
+      if (devinfo_.HasKeyEvent(code) && !IfEvdevButtonMappedFrom(code) &&
+          FindNextUnmappedCode(webgamepad_buttons_, &next_unmapped_button)) {
+        MapButton(code, next_unmapped_button);
+      }
+    }
+  }
+
+  void MapAxisLikeJoydev() {
+    uint16_t next_unmapped_axis = 0;
+    for (int code = 0; code < ABS_CNT; ++code) {
+      if (devinfo_.HasAbsEvent(code) && !IfEvdevAxisMappedFrom(code) &&
+          FindNextUnmappedCode(webgamepad_axes_, &next_unmapped_axis)) {
+        MapAxisToAxis(code, next_unmapped_axis);
+      }
+    }
+  }
+
+ private:
+  // This function helps to find the next unmapped button or axis. Code is the
+  // last unmapped code and will be the next unmapped code when the function
+  // returns.
+  template <typename T>
+  bool FindNextUnmappedCode(const T& bitset, uint16_t* code) {
+    for (uint16_t i = *code; i < bitset.size(); ++i) {
+      if (!bitset.test(i)) {
+        *code = i;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool IfEvdevButtonMappedFrom(uint16_t code) {
+    DCHECK_LT(code, KEY_CNT);
+    return evdev_buttons_.test(code);
+  }
+
+  bool IfEvdevAxisMappedFrom(uint16_t code) {
+    DCHECK_LT(code, ABS_CNT);
+    return evdev_axes_.test(code);
+  }
+
+  bool IfWebgamepadButtonMappedTo(uint16_t code) {
+    DCHECK_LT(code, ui::WG_BUTTON_COUNT);
+    return webgamepad_buttons_.test(code);
+  }
+
+  bool IfWebgamepadAxisMappedTo(uint16_t code) {
+    DCHECK_LT(code, ui::WG_ABS_COUNT);
+    return webgamepad_axes_.test(code);
+  }
+
+  const ui::EventDeviceInfo& devinfo_;
+
+  // Mapped webgamepad buttons and axes will be marked as true.
+  std::bitset<ui::WG_BUTTON_COUNT> webgamepad_buttons_;
+  std::bitset<ui::WG_ABS_COUNT> webgamepad_axes_;
+  // Evdev buttons and axes that are already mapped will be marked as true.
+  std::bitset<KEY_CNT> evdev_buttons_;
+  std::bitset<ABS_CNT> evdev_axes_;
+
+  // Generated Mapping.
+  std::vector<ui::AbsMapEntry> axis_mapping_;
+  std::vector<ui::KeyMapEntry> button_mapping_;
+};
+
+void MapSpecialButtons(GamepadMapperBuilder* builder) {
+  // Map mode seperately.
+  builder->MapButton(BTN_MODE, ui::WG_BUTTON_MODE);
+  // Take care of ADT style back and start.
+  builder->MapButton(KEY_BACK, ui::WG_BUTTON_SELECT);
+  builder->MapButton(KEY_HOMEPAGE, ui::WG_BUTTON_START);
+}
+
+void MapSpecialAxes(const ui::EventDeviceInfo& devinfo,
+                    GamepadMapperBuilder* builder) {
+  // HAT0X and HAT0Y always map to DPAD.
+  builder->MapAxisToButton(ABS_HAT0X, ui::kHAT_X);
+  builder->MapAxisToButton(ABS_HAT0Y, ui::kHAT_Y);
+
+  // When ABS_BRAKE and ABS_GAS supported at the same time, they are mapped to
+  // l-trigger and r-trigger.
+  // Otherwise, when x,y,z,rx,ry,rz are all supported, z and rz are mapped to
+  // triggers (As XInput gamepad mapper).
+  if (devinfo.HasAbsEvent(ABS_BRAKE) && devinfo.HasAbsEvent(ABS_GAS)) {
+    builder->MapAxisToButton(ABS_BRAKE, ui::WG_BUTTON_LT);
+    builder->MapAxisToButton(ABS_GAS, ui::WG_BUTTON_RT);
+  } else if (devinfo.HasAbsEvent(ABS_X) && devinfo.HasAbsEvent(ABS_Y) &&
+             devinfo.HasAbsEvent(ABS_Z) && devinfo.HasAbsEvent(ABS_RX) &&
+             devinfo.HasAbsEvent(ABS_RY) && devinfo.HasAbsEvent(ABS_RZ)) {
+    builder->MapAxisToButton(ABS_Z, ui::WG_BUTTON_LT);
+    builder->MapAxisToButton(ABS_RZ, ui::WG_BUTTON_RT);
+  }
+}
+}  // namespace
+
+namespace ui {
+std::unique_ptr<GamepadMapper> BuildGenericGamepadMapper(
+    const EventDeviceInfo& info) {
+  GamepadMapperBuilder builder(info);
+  // Must map axes first as axis might be mapped to button and occupy some
+  // webgamepad button slots.
+  MapSpecialAxes(info, &builder);
+  builder.MapAxisLikeJoydev();
+
+  MapSpecialButtons(&builder);
+  builder.MapButtonLikeJoydev();
+
+  return builder.ToGamepadMapper();
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/gamepad/generic_gamepad_mapping.h b/ui/events/ozone/gamepad/generic_gamepad_mapping.h
new file mode 100644
index 0000000..e5030e9
--- /dev/null
+++ b/ui/events/ozone/gamepad/generic_gamepad_mapping.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef UI_EVENTS_OZONE_GENERIC_GAMEPAD_GAMEPAD_MAPPING_H_
+#define UI_EVENTS_OZONE_GENERIC_GAMEPAD_GAMEPAD_MAPPING_H_
+
+#include <vector>
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+#include "ui/events/ozone/gamepad/gamepad_mapping.h"
+
+namespace ui {
+
+class EventDeviceInfo;
+
+std::unique_ptr<GamepadMapper> EVENTS_OZONE_EVDEV_EXPORT
+BuildGenericGamepadMapper(const EventDeviceInfo& info);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_GENERIC_GAMEPAD_GAMEPAD_MAPPING_H_
diff --git a/ui/events/ozone/gamepad/generic_gamepad_mapping_unittest.cc b/ui/events/ozone/gamepad/generic_gamepad_mapping_unittest.cc
new file mode 100644
index 0000000..7ac37b4
--- /dev/null
+++ b/ui/events/ozone/gamepad/generic_gamepad_mapping_unittest.cc
@@ -0,0 +1,92 @@
+// 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.
+
+#include "ui/events/ozone/gamepad/generic_gamepad_mapping.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <unistd.h>
+
+#include <memory>
+#include <queue>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/ozone/device/device_manager.h"
+#include "ui/events/ozone/evdev/event_converter_test_util.h"
+#include "ui/events/ozone/evdev/event_device_info.h"
+#include "ui/events/ozone/evdev/event_device_test_util.h"
+#include "ui/events/ozone/evdev/event_device_util.h"
+#include "ui/events/ozone/evdev/event_factory_evdev.h"
+#include "ui/events/ozone/gamepad/gamepad_event.h"
+#include "ui/events/ozone/gamepad/gamepad_observer.h"
+#include "ui/events/ozone/gamepad/gamepad_provider_ozone.h"
+#include "ui/events/ozone/gamepad/static_gamepad_mapping.h"
+#include "ui/events/ozone/gamepad/webgamepad_constants.h"
+#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_source.h"
+
+namespace ui {
+
+class GenericGamepadMappingTest : public testing::Test {
+ public:
+  GenericGamepadMappingTest() {}
+
+  void CompareGamepadMapper(const GamepadMapper* l_mapper,
+                            const GamepadMapper* r_mapper) {
+    bool l_result, r_result;
+    GamepadEventType l_mapped_type, r_mapped_type;
+    uint16_t l_mapped_code, r_mapped_code;
+    for (uint16_t code = BTN_MISC; code < KEY_MAX; code++) {
+      l_result = l_mapper->Map(EV_KEY, code, &l_mapped_type, &l_mapped_code);
+      r_result = r_mapper->Map(EV_KEY, code, &r_mapped_type, &r_mapped_code);
+      EXPECT_EQ(l_result, r_result) << " Current Code: " << code;
+      if (l_result) {
+        EXPECT_EQ(l_mapped_type, r_mapped_type);
+        EXPECT_EQ(r_mapped_code, r_mapped_code);
+      }
+    }
+    for (uint16_t code = ABS_X; code < ABS_MAX; code++) {
+      l_result = l_mapper->Map(EV_ABS, code, &l_mapped_type, &l_mapped_code);
+      r_result = r_mapper->Map(EV_ABS, code, &r_mapped_type, &r_mapped_code);
+      EXPECT_EQ(l_result, r_result);
+      if (l_result) {
+        EXPECT_EQ(l_mapped_type, r_mapped_type);
+        EXPECT_EQ(r_mapped_code, r_mapped_code);
+      }
+    }
+  }
+
+  void TestCompatableWithCapabilities(const DeviceCapabilities& cap) {
+    ui::EventDeviceInfo devinfo;
+    CapabilitiesToDeviceInfo(cap, &devinfo);
+    std::unique_ptr<GamepadMapper> static_mapper(
+        GetStaticGamepadMapper(devinfo.vendor_id(), devinfo.product_id()));
+    std::unique_ptr<GamepadMapper> generic_mapper =
+        BuildGenericGamepadMapper(devinfo);
+    CompareGamepadMapper(static_mapper.get(), generic_mapper.get());
+  }
+};
+
+TEST_F(GenericGamepadMappingTest, XInputGamepad) {
+  TestCompatableWithCapabilities(kXboxGamepad);
+}
+
+TEST_F(GenericGamepadMappingTest, HJCGamepad) {
+  TestCompatableWithCapabilities(kHJCGamepad);
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/gamepad/static_gamepad_mapping.cc b/ui/events/ozone/gamepad/static_gamepad_mapping.cc
new file mode 100644
index 0000000..2c01d40
--- /dev/null
+++ b/ui/events/ozone/gamepad/static_gamepad_mapping.cc
@@ -0,0 +1,458 @@
+// 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.
+
+#include <linux/input.h>
+#include <cstdint>
+#include <list>
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "ui/events/ozone/evdev/event_device_info.h"
+#include "ui/events/ozone/gamepad/static_gamepad_mapping.h"
+#include "ui/events/ozone/gamepad/webgamepad_constants.h"
+
+namespace ui {
+
+typedef bool (*GamepadMapperFunction)(uint16_t key,
+                                      uint16_t code,
+                                      GamepadEventType* mapped_type,
+                                      uint16_t* mapped_code);
+
+#define DO_MAPPING                                                   \
+  DoGamepadMapping(key_mapping, arraysize(key_mapping), abs_mapping, \
+                   arraysize(abs_mapping), type, code, mapped_type,  \
+                   mapped_code)
+
+bool DoGamepadMapping(const KeyMapEntry* key_mapping,
+                      size_t key_map_size,
+                      const AbsMapEntry* abs_mapping,
+                      size_t abs_map_size,
+                      uint16_t type,
+                      uint16_t code,
+                      GamepadEventType* mapped_type,
+                      uint16_t* mapped_code) {
+  if (type == EV_KEY) {
+    const KeyMapEntry* entry = nullptr;
+    for (size_t i = 0; i < key_map_size; i++) {
+      if (key_mapping[i].evdev_code == code) {
+        entry = key_mapping + i;
+      }
+    }
+    if (!entry) {
+      return false;
+    }
+    *mapped_type = GamepadEventType::BUTTON;
+    *mapped_code = entry->mapped_code;
+    return true;
+  }
+
+  if (type == EV_ABS) {
+    const AbsMapEntry* entry = nullptr;
+    for (size_t i = 0; i < abs_map_size; i++) {
+      if (abs_mapping[i].evdev_code == code) {
+        entry = abs_mapping + i;
+      }
+    }
+    if (!entry) {
+      return false;
+    }
+    *mapped_type = entry->mapped_type;
+    *mapped_code = entry->mapped_code;
+    return true;
+  }
+  return false;
+}
+
+// this mapper mapps gamepads compatible with xbox gamepad.
+bool XInputStyleMapper(uint16_t type,
+                       uint16_t code,
+                       GamepadEventType* mapped_type,
+                       uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_A, WG_BUTTON_A},            // btn_a = 304 / 0x130
+      {BTN_B, WG_BUTTON_B},            // btn_b = 305 / 0x131
+      {BTN_X, WG_BUTTON_X},            // btn_x = 307 / 0x133
+      {BTN_Y, WG_BUTTON_Y},            // btn_y = 308 / 0x134
+      {BTN_TL, WG_BUTTON_L1},          // btn_tl = 310 / 0x136
+      {BTN_TR, WG_BUTTON_R1},          // btn_tr = 311 / 0x137
+      {BTN_SELECT, WG_BUTTON_SELECT},  // btn_select = 314 / 0x13a
+      {BTN_START, WG_BUTTON_START},    // btn_start = 315 / 0x13b
+      {BTN_THUMBL, WG_BUTTON_THUMBL},  // btn_thumbl = 317 / 0x13d
+      {BTN_THUMBR, WG_BUTTON_THUMBR},  // btn_thumbr = 318 / 0x13e
+      {BTN_MODE, WG_BUTTON_MODE}       // btn_mode = 316 / 0x13c
+  };
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),       // ABS_X = 0x00
+      TO_ABS(ABS_Y, WG_ABS_Y),       // ABS_Y = 0x01
+      TO_ABS(ABS_RX, WG_ABS_RX),     // ABS_RX = 0x03
+      TO_ABS(ABS_RY, WG_ABS_RY),     // ABS_RZ = 0x04
+      TO_BTN(ABS_Z, WG_BUTTON_LT),   // ABS_Z = 0x02
+      TO_BTN(ABS_RZ, WG_BUTTON_RT),  // ABS_RZ = 0x05
+      TO_BTN(ABS_HAT0X, kHAT_X),     // HAT0X = 0x10
+      TO_BTN(ABS_HAT0Y, kHAT_Y)      // HAT0Y = 0x11
+  };
+  return DO_MAPPING;
+}
+
+bool PlaystationSixAxisMapper(uint16_t type,
+                              uint16_t code,
+                              GamepadEventType* mapped_type,
+                              uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {0x12e, WG_BUTTON_A},
+      {0x12d, WG_BUTTON_B},
+      {BTN_DEAD, WG_BUTTON_X},
+      {0x12c, WG_BUTTON_Y},
+      {BTN_BASE5, WG_BUTTON_L1},
+      {BTN_BASE6, WG_BUTTON_R1},
+      {BTN_BASE3, WG_BUTTON_LT},
+      {BTN_BASE4, WG_BUTTON_RT},
+      {BTN_TRIGGER, WG_BUTTON_SELECT},
+      {BTN_TOP, WG_BUTTON_START},
+      {BTN_THUMB, WG_BUTTON_THUMBL},
+      {BTN_THUMB2, WG_BUTTON_THUMBR},
+      {BTN_TOP2, WG_BUTTON_DPAD_UP},
+      {BTN_BASE, WG_BUTTON_DPAD_DOWN},
+      {BTN_BASE2, WG_BUTTON_DPAD_LEFT},
+      {BTN_PINKIE, WG_BUTTON_DPAD_RIGHT},
+      {BTN_TRIGGER_HAPPY17, WG_BUTTON_MODE},
+  };
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),
+      TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),
+      TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_MT_TOUCH_MAJOR, WG_BUTTON_LT),
+      TO_BTN(ABS_MT_TOUCH_MINOR, WG_BUTTON_RT)};
+  return DO_MAPPING;
+}
+
+bool IBuffalocClassicMapper(uint16_t type,
+                            uint16_t code,
+                            GamepadEventType* mapped_type,
+                            uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_TRIGGER, WG_BUTTON_A}, {BTN_THUMB, WG_BUTTON_B},
+      {BTN_THUMB2, WG_BUTTON_X},  {BTN_TOP, WG_BUTTON_Y},
+      {BTN_BASE, WG_BUTTON_L1},   {BTN_BASE2, WG_BUTTON_R1},
+      {BTN_TOP2, WG_BUTTON_LT},   {BTN_PINKIE, WG_BUTTON_RT}};
+
+  static const AbsMapType abs_mapping = {
+      TO_BTN(ABS_X, kHAT_X), TO_BTN(ABS_Y, kHAT_Y),
+  };
+  return DO_MAPPING;
+}
+
+bool ClassicNESMapper(uint16_t type,
+                      uint16_t code,
+                      GamepadEventType* mapped_type,
+                      uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_THUMB, WG_BUTTON_A},      {BTN_THUMB2, WG_BUTTON_B},
+      {BTN_TRIGGER, WG_BUTTON_X},    {BTN_TOP, WG_BUTTON_Y},
+      {BTN_TOP2, WG_BUTTON_L1},      {BTN_PINKIE, WG_BUTTON_R1},
+      {BTN_BASE3, WG_BUTTON_SELECT}, {BTN_BASE4, WG_BUTTON_START}};
+
+  static const AbsMapType abs_mapping = {
+      TO_BTN(ABS_X, kHAT_X), TO_BTN(ABS_Y, kHAT_Y),
+  };
+  return DO_MAPPING;
+}
+
+bool SNesRetroMapper(uint16_t type,
+                     uint16_t code,
+                     GamepadEventType* mapped_type,
+                     uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_C, WG_BUTTON_A},        {BTN_B, WG_BUTTON_B},
+      {BTN_X, WG_BUTTON_X},        {BTN_A, WG_BUTTON_Y},
+      {BTN_Y, WG_BUTTON_L1},       {BTN_Z, WG_BUTTON_R1},
+      {BTN_TL2, WG_BUTTON_SELECT}, {BTN_TR2, WG_BUTTON_START}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, kHAT_X), TO_ABS(ABS_Y, kHAT_Y),
+  };
+  return DO_MAPPING;
+}
+
+bool ADT1Mapper(uint16_t type,
+                uint16_t code,
+                GamepadEventType* mapped_type,
+                uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_A, WG_BUTTON_A},           {BTN_B, WG_BUTTON_B},
+      {BTN_X, WG_BUTTON_X},           {BTN_Y, WG_BUTTON_Y},
+      {BTN_TL, WG_BUTTON_L1},         {BTN_TR, WG_BUTTON_R1},
+      {BTN_THUMBL, WG_BUTTON_THUMBL}, {BTN_THUMBR, WG_BUTTON_THUMBR},
+      {BTN_MODE, WG_BUTTON_MODE},     {KEY_BACK, WG_BUTTON_SELECT},
+      {KEY_HOMEPAGE, WG_BUTTON_START}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),         TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),        TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_BRAKE, WG_BUTTON_LT), TO_BTN(ABS_GAS, WG_BUTTON_RT),
+      TO_BTN(ABS_HAT0X, kHAT_X),       TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+bool Vendor_1d79Product_0009Mapper(uint16_t type,
+                                   uint16_t code,
+                                   GamepadEventType* mapped_type,
+                                   uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {{BTN_A, WG_BUTTON_A},
+                                         {BTN_B, WG_BUTTON_B},
+                                         {BTN_X, WG_BUTTON_X},
+                                         {BTN_Y, WG_BUTTON_Y},
+                                         {BTN_TL, WG_BUTTON_L1},
+                                         {BTN_TR, WG_BUTTON_R1},
+                                         {BTN_START, WG_BUTTON_START},
+                                         {BTN_THUMBL, WG_BUTTON_THUMBL},
+                                         {BTN_THUMBR, WG_BUTTON_THUMBR},
+                                         {KEY_UP, WG_BUTTON_DPAD_UP},
+                                         {KEY_DOWN, WG_BUTTON_DPAD_DOWN},
+                                         {KEY_LEFT, WG_BUTTON_DPAD_LEFT},
+                                         {KEY_RIGHT, WG_BUTTON_DPAD_RIGHT},
+                                         {KEY_BACK, WG_BUTTON_SELECT},
+                                         {KEY_HOMEPAGE, WG_BUTTON_MODE}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),         TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),        TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_BRAKE, WG_BUTTON_LT), TO_BTN(ABS_GAS, WG_BUTTON_RT),
+      TO_BTN(ABS_HAT0X, kHAT_X),       TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+bool Vendor_046dProduct_b501Mapper(uint16_t type,
+                                   uint16_t code,
+                                   GamepadEventType* mapped_type,
+                                   uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {{BTN_A, WG_BUTTON_A},
+                                         {BTN_B, WG_BUTTON_B},
+                                         {BTN_X, WG_BUTTON_X},
+                                         {BTN_Y, WG_BUTTON_Y},
+                                         {BTN_TL, WG_BUTTON_L1},
+                                         {BTN_TR, WG_BUTTON_R1},
+                                         {BTN_TL2, WG_BUTTON_LT},
+                                         {BTN_TR2, WG_BUTTON_RT},
+                                         {BTN_SELECT, WG_BUTTON_SELECT},
+                                         {BTN_START, WG_BUTTON_START},
+                                         {BTN_THUMBL, WG_BUTTON_THUMBL},
+                                         {BTN_THUMBR, WG_BUTTON_THUMBR},
+                                         {KEY_UP, WG_BUTTON_DPAD_UP},
+                                         {KEY_DOWN, WG_BUTTON_DPAD_DOWN},
+                                         {KEY_LEFT, WG_BUTTON_DPAD_LEFT},
+                                         {KEY_RIGHT, WG_BUTTON_DPAD_RIGHT},
+                                         {BTN_MODE, WG_BUTTON_MODE}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),         TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),        TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_BRAKE, WG_BUTTON_LT), TO_BTN(ABS_GAS, WG_BUTTON_RT),
+      TO_BTN(ABS_HAT0X, kHAT_X),       TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+bool Vendor_046dProduct_c216Mapper(uint16_t type,
+                                   uint16_t code,
+                                   GamepadEventType* mapped_type,
+                                   uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_TRIGGER, WG_BUTTON_A},    {BTN_TOP, WG_BUTTON_B},
+      {BTN_THUMB, WG_BUTTON_X},      {BTN_THUMB2, WG_BUTTON_Y},
+      {BTN_TOP2, WG_BUTTON_L1},      {BTN_PINKIE, WG_BUTTON_R1},
+      {BTN_BASE, WG_BUTTON_LT},      {BTN_BASE2, WG_BUTTON_RT},
+      {BTN_BASE3, WG_BUTTON_SELECT}, {BTN_BASE4, WG_BUTTON_START},
+      {BTN_BASE5, WG_BUTTON_THUMBL}, {BTN_BASE6, WG_BUTTON_THUMBR}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),   TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),  TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_HAT0X, kHAT_X), TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+bool Vendor_046dProduct_c219Mapper(uint16_t type,
+                                   uint16_t code,
+                                   GamepadEventType* mapped_type,
+                                   uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_B, WG_BUTTON_A},          {BTN_C, WG_BUTTON_B},
+      {BTN_A, WG_BUTTON_X},          {BTN_X, WG_BUTTON_Y},
+      {BTN_Y, WG_BUTTON_L1},         {BTN_Z, WG_BUTTON_R1},
+      {BTN_TL, WG_BUTTON_LT},        {BTN_TR, WG_BUTTON_RT},
+      {BTN_TR2, WG_BUTTON_START},    {BTN_SELECT, WG_BUTTON_THUMBL},
+      {BTN_START, WG_BUTTON_THUMBR}, {BTN_TL2, WG_BUTTON_SELECT}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),   TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),  TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_HAT0X, kHAT_X), TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+bool Vendor_1038Product_1412Mapper(uint16_t type,
+                                   uint16_t code,
+                                   GamepadEventType* mapped_type,
+                                   uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_A, WG_BUTTON_A},         {BTN_B, WG_BUTTON_B},
+      {BTN_X, WG_BUTTON_X},         {BTN_Y, WG_BUTTON_Y},
+      {BTN_TL, WG_BUTTON_L1},       {BTN_TR, WG_BUTTON_R1},
+      {BTN_MODE, WG_BUTTON_SELECT}, {BTN_START, WG_BUTTON_START},
+  };
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),   TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),  TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_HAT0X, kHAT_X), TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+bool Vendor_1689Product_fd00Mapper(uint16_t type,
+                                   uint16_t code,
+                                   GamepadEventType* mapped_type,
+                                   uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_A, WG_BUTTON_A},
+      {BTN_B, WG_BUTTON_B},
+      {BTN_X, WG_BUTTON_X},
+      {BTN_Y, WG_BUTTON_Y},
+      {BTN_TL, WG_BUTTON_L1},
+      {BTN_TR, WG_BUTTON_R1},
+      {BTN_START, WG_BUTTON_START},
+      {BTN_THUMBL, WG_BUTTON_THUMBL},
+      {BTN_THUMBR, WG_BUTTON_THUMBR},
+      {BTN_TRIGGER_HAPPY3, WG_BUTTON_DPAD_UP},
+      {BTN_TRIGGER_HAPPY4, WG_BUTTON_DPAD_DOWN},
+      {BTN_TRIGGER_HAPPY1, WG_BUTTON_DPAD_LEFT},
+      {BTN_TRIGGER_HAPPY2, WG_BUTTON_DPAD_RIGHT},
+      {BTN_SELECT, WG_BUTTON_SELECT},
+      {BTN_MODE, WG_BUTTON_MODE}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),     TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_RX, WG_ABS_RX),   TO_ABS(ABS_RY, WG_ABS_RY),
+      TO_BTN(ABS_Z, WG_BUTTON_LT), TO_BTN(ABS_RZ, WG_BUTTON_RT)};
+  return DO_MAPPING;
+}
+
+bool JoydevLikeMapper(uint16_t type,
+                      uint16_t code,
+                      GamepadEventType* mapped_type,
+                      uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_A, WG_BUTTON_A},           {BTN_B, WG_BUTTON_B},
+      {BTN_C, WG_BUTTON_X},           {BTN_X, WG_BUTTON_Y},
+      {BTN_Y, WG_BUTTON_L1},          {BTN_Z, WG_BUTTON_R1},
+      {BTN_TL, WG_BUTTON_LT},         {BTN_TR, WG_BUTTON_RT},
+      {BTN_TL2, WG_BUTTON_SELECT},    {BTN_TR2, WG_BUTTON_START},
+      {BTN_SELECT, WG_BUTTON_THUMBL}, {BTN_START, WG_BUTTON_THUMBR}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),   TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_Z, WG_ABS_RX),  TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_HAT0X, kHAT_X), TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+bool DualShock4(uint16_t type,
+                uint16_t code,
+                GamepadEventType* mapped_type,
+                uint16_t* mapped_code) {
+  static const KeyMapType key_mapping = {
+      {BTN_A, WG_BUTTON_A},           {BTN_B, WG_BUTTON_B},
+      {BTN_C, WG_BUTTON_X},           {BTN_X, WG_BUTTON_Y},
+      {BTN_Y, WG_BUTTON_L1},          {BTN_Z, WG_BUTTON_R1},
+      {BTN_TL, WG_BUTTON_LT},         {BTN_TR, WG_BUTTON_RT},
+      {BTN_TL2, WG_BUTTON_SELECT},    {BTN_TR2, WG_BUTTON_START},
+      {BTN_SELECT, WG_BUTTON_THUMBL}, {BTN_START, WG_BUTTON_THUMBR}};
+
+  static const AbsMapType abs_mapping = {
+      TO_ABS(ABS_X, WG_ABS_X),      TO_ABS(ABS_Y, WG_ABS_Y),
+      TO_ABS(ABS_RX, WG_BUTTON_LT), TO_ABS(ABS_RY, WG_BUTTON_RT),
+      TO_ABS(ABS_Z, WG_ABS_RX),     TO_ABS(ABS_RZ, WG_ABS_RY),
+      TO_BTN(ABS_HAT0X, kHAT_X),    TO_BTN(ABS_HAT0Y, kHAT_Y)};
+  return DO_MAPPING;
+}
+
+static const struct MappingData {
+  uint16_t vendor_id;
+  uint16_t product_id;
+  GamepadMapperFunction mapper;
+} AvailableMappings[] = {
+    // Xbox style gamepad.
+    {0x045e, 0x028e, XInputStyleMapper},  // Xbox 360 wired.
+    {0x045e, 0x028f, XInputStyleMapper},  // Xbox 360 wireless.
+    {0x045e, 0x02a1, XInputStyleMapper},  // Xbox 360 wireless.
+    {0x045e, 0x02d1, XInputStyleMapper},  // Xbox one wired.
+    {0x045e, 0x02dd, XInputStyleMapper},  // Xbox one wired (2015 fw).
+    {0x045e, 0x02e3, XInputStyleMapper},  // Xbox elite wired.
+    {0x045e, 0x02ea, XInputStyleMapper},  // Xbox one s (usb).
+    {0x045e, 0x0719, XInputStyleMapper},  // Xbox 360 wireless.
+    {0x046d, 0xc21d, XInputStyleMapper},  // Logitech f310.
+    {0x046d, 0xc21e, XInputStyleMapper},  // Logitech f510.
+    {0x046d, 0xc21f, XInputStyleMapper},  // Logitech f710.
+    {0x2378, 0x1008, XInputStyleMapper},  // Onlive controller (bluetooth).
+    {0x2378, 0x100a, XInputStyleMapper},  // Onlive controller (wired).
+    {0x1bad, 0xf016, XInputStyleMapper},  // Mad catz gamepad.
+    {0x1bad, 0xf023, XInputStyleMapper},  // Mad catz mlg gamepad for Xbox360.
+    {0x1bad, 0xf027, XInputStyleMapper},  // Mad catz fps pro.
+    {0x1bad, 0xf036, XInputStyleMapper},  // Mad catz generic Xbox controller.
+    {0x1689, 0xfd01, XInputStyleMapper},  // Razer Xbox 360 gamepad.
+    {0x1689, 0xfe00, XInputStyleMapper},  // Razer sabertooth elite.
+    // Sony gamepads.
+    {0x054c, 0x0268, PlaystationSixAxisMapper},  // Playstation 3.
+    {0x054c, 0x05c4, DualShock4},                // Dualshock 4.
+    // NES style gamepad.
+    {0x0583, 0x2060, IBuffalocClassicMapper},  // iBuffalo Classic.
+    {0x0079, 0x0011, ClassicNESMapper},        // Classic NES controller.
+    {0x12bd, 0xd015, SNesRetroMapper},         // Hitgaming SNES retro.
+    // Android gamepad.
+    {0x0b05, 0x4500, ADT1Mapper},  // Nexus player controller (asus gamepad).
+    {0x1532, 0x0900, ADT1Mapper},  // Razer serval.
+    {0x18d1, 0x2c40, ADT1Mapper},  // ADT-1 controller (odie).
+    // Other gamepads.
+    {0x1d79, 0x0009,
+     Vendor_1d79Product_0009Mapper},  // Nyko playpad / Playpad pro.
+    {0x046d, 0xb501, Vendor_046dProduct_b501Mapper},  // Logitech redhawk.
+    // Logitech dual action controller.
+    {0x046d, 0xc216, Vendor_046dProduct_c216Mapper},
+    // Logitech cordless rumblepad2.
+    {0x046d, 0xc219, Vendor_046dProduct_c219Mapper},
+    {0x1038, 0x1412, Vendor_1038Product_1412Mapper},  // Steelseries free.
+    // Razer onza tournment edition.
+    {0x1689, 0xfd00, Vendor_1689Product_fd00Mapper},
+    {0x11c5, 0x5506, JoydevLikeMapper}  // HJC Game ZD-V
+};
+
+class StaticGamepadMapper : public GamepadMapper {
+ public:
+  StaticGamepadMapper(GamepadMapperFunction fp) : mapper_fp_(fp) {}
+
+  bool Map(uint16_t type,
+           uint16_t code,
+           GamepadEventType* mapped_type,
+           uint16_t* mapped_code) const override {
+    return mapper_fp_(type, code, mapped_type, mapped_code);
+  };
+
+ private:
+  GamepadMapperFunction mapper_fp_;
+};
+
+GamepadMapper* GetStaticGamepadMapper(uint16_t vendor_id, uint16_t product_id) {
+  for (size_t i = 0; i < arraysize(AvailableMappings); i++) {
+    if (AvailableMappings[i].vendor_id == vendor_id &&
+        AvailableMappings[i].product_id == product_id) {
+      return new StaticGamepadMapper(AvailableMappings[i].mapper);
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/gamepad/static_gamepad_mapping.h b/ui/events/ozone/gamepad/static_gamepad_mapping.h
new file mode 100644
index 0000000..f1cb3bf
--- /dev/null
+++ b/ui/events/ozone/gamepad/static_gamepad_mapping.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef UI_EVENTS_OZONE_STATIC_GAMEPAD_GAMEPAD_MAPPING_H_
+#define UI_EVENTS_OZONE_STATIC_GAMEPAD_GAMEPAD_MAPPING_H_
+
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+#include "ui/events/ozone/gamepad/gamepad_mapping.h"
+#include "ui/events/ozone/gamepad/webgamepad_constants.h"
+
+namespace ui {
+
+// This function gets the static mapper for the gamepad vendor_id and
+// product_id.
+GamepadMapper* EVENTS_OZONE_EVDEV_EXPORT
+GetStaticGamepadMapper(uint16_t vendor_id, uint16_t product_id);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_STATIC_GAMEPAD_GAMEPAD_MAPPING_H_
diff --git a/ui/gfx/codec/jpeg_codec.cc b/ui/gfx/codec/jpeg_codec.cc
index 3dc5ad62..6d926378b 100644
--- a/ui/gfx/codec/jpeg_codec.cc
+++ b/ui/gfx/codec/jpeg_codec.cc
@@ -43,6 +43,17 @@
 
 }  // namespace
 
+// This method helps identify at run time which library chromium is using.
+JPEGCodec::LibraryVariant JPEGCodec::JpegLibraryVariant() {
+#if defined(USE_SYSTEM_LIBJPEG)
+  return SYSTEM_LIBJPEG;
+#elif defined(USE_LIBJPEG_TURBO)
+  return LIBJPEG_TURBO;
+#else
+  return IJG_LIBJPEG;
+#endif
+}
+
 // Encoder ---------------------------------------------------------------------
 //
 // This code is based on nsJPEGEncoder from Mozilla.
@@ -133,6 +144,33 @@
   state->out->resize(state->image_buffer_used);
 }
 
+#if !defined(JCS_EXTENSIONS)
+// Converts RGBA to RGB (removing the alpha values) to prepare to send data to
+// libjpeg. This converts one row of data in rgba with the given width in
+// pixels the the given rgb destination buffer (which should have enough space
+// reserved for the final data).
+void StripAlpha(const unsigned char* rgba, int pixel_width, unsigned char* rgb)
+{
+  for (int x = 0; x < pixel_width; x++)
+    memcpy(&rgb[x * 3], &rgba[x * 4], 3);
+}
+
+// Converts BGRA to RGB by reordering the color components and dropping the
+// alpha. This converts  one row of data in rgba with the given width in
+// pixels the the given rgb destination buffer (which should have enough space
+// reserved for the final data).
+void BGRAtoRGB(const unsigned char* bgra, int pixel_width, unsigned char* rgb)
+{
+  for (int x = 0; x < pixel_width; x++) {
+    const unsigned char* pixel_in = &bgra[x * 4];
+    unsigned char* pixel_out = &rgb[x * 3];
+    pixel_out[0] = pixel_in[2];
+    pixel_out[1] = pixel_in[1];
+    pixel_out[2] = pixel_in[0];
+  }
+}
+#endif  // !defined(JCS_EXTENSIONS)
+
 // This class destroys the given jpeg_compress object when it goes out of
 // scope. It simplifies the error handling in Encode (and even applies to the
 // success case).
@@ -166,6 +204,9 @@
   CompressDestroyer destroyer;
   destroyer.SetManagedObject(&cinfo);
   output->clear();
+#if !defined(JCS_EXTENSIONS)
+  unsigned char* row_buffer = NULL;
+#endif
 
   // We set up the normal JPEG error routines, then override error_exit.
   // This must be done before the call to create_compress.
@@ -181,6 +222,9 @@
     // goto using a call to longjmp."  So we delete the CompressDestroyer's
     // object manually instead.
     destroyer.DestroyManagedObject();
+#if !defined(JCS_EXTENSIONS)
+    delete[] row_buffer;
+#endif
     return false;
   }
 
@@ -189,16 +233,22 @@
 
   cinfo.image_width = w;
   cinfo.image_height = h;
-  cinfo.input_components = 4;
+  cinfo.input_components = 3;
+#ifdef JCS_EXTENSIONS
   // Choose an input colorspace and return if it is an unsupported one. Since
   // libjpeg-turbo supports all input formats used by Chromium (i.e. RGB, RGBA,
   // and BGRA), we just map the input parameters to a colorspace used by
   // libjpeg-turbo.
-  if (format == FORMAT_RGBA ||
-      (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
+  if (format == FORMAT_RGB) {
+    cinfo.input_components = 3;
+    cinfo.in_color_space = JCS_RGB;
+  } else if (format == FORMAT_RGBA ||
+             (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
+    cinfo.input_components = 4;
     cinfo.in_color_space = JCS_EXT_RGBX;
   } else if (format == FORMAT_BGRA ||
              (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
+    cinfo.input_components = 4;
     cinfo.in_color_space = JCS_EXT_BGRX;
   } else {
     // We can exit this function without calling jpeg_destroy_compress() because
@@ -206,6 +256,9 @@
     NOTREACHED() << "Invalid pixel format";
     return false;
   }
+#else
+  cinfo.in_color_space = JCS_RGB;
+#endif
   cinfo.data_precision = 8;
 
   jpeg_set_defaults(&cinfo);
@@ -224,6 +277,7 @@
   jpeg_start_compress(&cinfo, 1);
 
   // feed it the rows, doing necessary conversions for the color format
+#ifdef JCS_EXTENSIONS
   // This function already returns when the input format is not supported by
   // libjpeg-turbo and needs conversion. Therefore, we just encode lines without
   // conversions.
@@ -231,6 +285,37 @@
     const unsigned char* row = &input[cinfo.next_scanline * row_byte_width];
     jpeg_write_scanlines(&cinfo, const_cast<unsigned char**>(&row), 1);
   }
+#else
+  if (format == FORMAT_RGB) {
+    // no conversion necessary
+    while (cinfo.next_scanline < cinfo.image_height) {
+      const unsigned char* row = &input[cinfo.next_scanline * row_byte_width];
+      jpeg_write_scanlines(&cinfo, const_cast<unsigned char**>(&row), 1);
+    }
+  } else {
+    // get the correct format converter
+    void (*converter)(const unsigned char* in, int w, unsigned char* rgb);
+    if (format == FORMAT_RGBA ||
+        (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
+      converter = StripAlpha;
+    } else if (format == FORMAT_BGRA ||
+               (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
+      converter = BGRAtoRGB;
+    } else {
+      NOTREACHED() << "Invalid pixel format";
+      return false;
+    }
+
+    // output row after converting
+    row_buffer = new unsigned char[w * 3];
+
+    while (cinfo.next_scanline < cinfo.image_height) {
+      converter(&input[cinfo.next_scanline * row_byte_width], w, row_buffer);
+      jpeg_write_scanlines(&cinfo, &row_buffer, 1);
+    }
+    delete[] row_buffer;
+  }
+#endif
 
   jpeg_finish_compress(&cinfo);
   return true;
@@ -313,6 +398,31 @@
 void TermSource(j_decompress_ptr cinfo) {
 }
 
+#if !defined(JCS_EXTENSIONS)
+// Converts one row of rgb data to rgba data by adding a fully-opaque alpha
+// value.
+void AddAlpha(const unsigned char* rgb, int pixel_width, unsigned char* rgba) {
+  for (int x = 0; x < pixel_width; x++) {
+    memcpy(&rgba[x * 4], &rgb[x * 3], 3);
+    rgba[x * 4 + 3] = 0xff;
+  }
+}
+
+// Converts one row of RGB data to BGRA by reordering the color components and
+// adding alpha values of 0xff.
+void RGBtoBGRA(const unsigned char* bgra, int pixel_width, unsigned char* rgb)
+{
+  for (int x = 0; x < pixel_width; x++) {
+    const unsigned char* pixel_in = &bgra[x * 3];
+    unsigned char* pixel_out = &rgb[x * 4];
+    pixel_out[0] = pixel_in[2];
+    pixel_out[1] = pixel_in[1];
+    pixel_out[2] = pixel_in[0];
+    pixel_out[3] = 0xff;
+  }
+}
+#endif  // !defined(JCS_EXTENSIONS)
+
 // This class destroys the given jpeg_decompress object when it goes out of
 // scope. It simplifies the error handling in Decode (and even applies to the
 // success case).
@@ -386,12 +496,16 @@
     case JCS_GRAYSCALE:
     case JCS_RGB:
     case JCS_YCbCr:
+#ifdef JCS_EXTENSIONS
       // Choose an output colorspace and return if it is an unsupported one.
       // Same as JPEGCodec::Encode(), libjpeg-turbo supports all input formats
       // used by Chromium (i.e. RGB, RGBA, and BGRA) and we just map the input
       // parameters to a colorspace.
-      if (format == FORMAT_RGBA ||
-          (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
+      if (format == FORMAT_RGB) {
+        cinfo.out_color_space = JCS_RGB;
+        cinfo.output_components = 3;
+      } else if (format == FORMAT_RGBA ||
+                 (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
         cinfo.out_color_space = JCS_EXT_RGBX;
         cinfo.output_components = 4;
       } else if (format == FORMAT_BGRA ||
@@ -404,6 +518,9 @@
         NOTREACHED() << "Invalid pixel format";
         return false;
       }
+#else
+      cinfo.out_color_space = JCS_RGB;
+#endif
       break;
     case JCS_CMYK:
     case JCS_YCCK:
@@ -413,6 +530,9 @@
       // care about these anyway.
       return false;
   }
+#ifndef JCS_EXTENSIONS
+  cinfo.output_components = 3;
+#endif
 
   jpeg_calc_output_dimensions(&cinfo);
   *w = cinfo.output_width;
@@ -424,6 +544,7 @@
   // how to align row lengths as we do for the compressor.
   int row_read_stride = cinfo.output_width * cinfo.output_components;
 
+#ifdef JCS_EXTENSIONS
   // Create memory for a decoded image and write decoded lines to the memory
   // without conversions same as JPEGCodec::Encode().
   int row_write_stride = row_read_stride;
@@ -434,6 +555,49 @@
     if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
       return false;
   }
+#else
+  if (format == FORMAT_RGB) {
+    // easy case, row needs no conversion
+    int row_write_stride = row_read_stride;
+    output->resize(row_write_stride * cinfo.output_height);
+
+    for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
+      unsigned char* rowptr = &(*output)[row * row_write_stride];
+      if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
+        return false;
+    }
+  } else {
+    // Rows need conversion to output format: read into a temporary buffer and
+    // expand to the final one. Performance: we could avoid the extra
+    // allocation by doing the expansion in-place.
+    int row_write_stride;
+    void (*converter)(const unsigned char* rgb, int w, unsigned char* out);
+    if (format == FORMAT_RGBA ||
+        (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
+      row_write_stride = cinfo.output_width * 4;
+      converter = AddAlpha;
+    } else if (format == FORMAT_BGRA ||
+               (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
+      row_write_stride = cinfo.output_width * 4;
+      converter = RGBtoBGRA;
+    } else {
+      NOTREACHED() << "Invalid pixel format";
+      jpeg_destroy_decompress(&cinfo);
+      return false;
+    }
+
+    output->resize(row_write_stride * cinfo.output_height);
+
+    std::unique_ptr<unsigned char[]> row_data(
+        new unsigned char[row_read_stride]);
+    unsigned char* rowptr = row_data.get();
+    for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
+      if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
+        return false;
+      converter(rowptr, *w, &(*output)[row * row_write_stride]);
+    }
+  }
+#endif
 
   jpeg_finish_decompress(&cinfo);
   jpeg_destroy_decompress(&cinfo);
diff --git a/ui/gfx/codec/jpeg_codec.h b/ui/gfx/codec/jpeg_codec.h
index fbc07903..5d10be5 100644
--- a/ui/gfx/codec/jpeg_codec.h
+++ b/ui/gfx/codec/jpeg_codec.h
@@ -23,6 +23,10 @@
 class CODEC_EXPORT JPEGCodec {
  public:
   enum ColorFormat {
+    // 3 bytes per pixel (packed), in RGB order regardless of endianness.
+    // This is the native JPEG format.
+    FORMAT_RGB,
+
     // 4 bytes per pixel, in RGBA order in mem regardless of endianness.
     FORMAT_RGBA,
 
@@ -35,6 +39,15 @@
     FORMAT_SkBitmap
   };
 
+  enum LibraryVariant {
+    SYSTEM_LIBJPEG = 0,
+    LIBJPEG_TURBO,
+    IJG_LIBJPEG,
+  };
+
+  // This method helps identify at run time which library chromium is using.
+  static LibraryVariant JpegLibraryVariant();
+
   // Encodes the given raw 'input' data, with each pixel being represented as
   // given in 'format'. The encoded JPEG data will be written into the supplied
   // vector and true will be returned on success. On failure (false), the
diff --git a/ui/gfx/codec/jpeg_codec_unittest.cc b/ui/gfx/codec/jpeg_codec_unittest.cc
index 9c4c8f1..8849102e 100644
--- a/ui/gfx/codec/jpeg_codec_unittest.cc
+++ b/ui/gfx/codec/jpeg_codec_unittest.cc
@@ -88,26 +88,62 @@
   return acc / static_cast<double>(a.size());
 }
 
-static void MakeRGBAImage(int w, int h, std::vector<unsigned char>* dat) {
-  dat->resize(w * h * 4);
+static void MakeRGBImage(int w, int h, std::vector<unsigned char>* dat) {
+  dat->resize(w * h * 3);
   for (int y = 0; y < h; y++) {
     for (int x = 0; x < w; x++) {
-      unsigned char* org_px = &(*dat)[(y * w + x) * 4];
+      unsigned char* org_px = &(*dat)[(y * w + x) * 3];
       org_px[0] = x * 3;      // r
       org_px[1] = x * 3 + 1;  // g
       org_px[2] = x * 3 + 2;  // b
-      org_px[3] = 0xFF;       // a
     }
   }
 }
 
+TEST(JPEGCodec, EncodeDecodeRGB) {
+  int w = 20, h = 20;
+
+  // create an image with known values
+  std::vector<unsigned char> original;
+  MakeRGBImage(w, h, &original);
+
+  // encode, making sure it was compressed some
+  std::vector<unsigned char> encoded;
+  EXPECT_TRUE(JPEGCodec::Encode(&original[0], JPEGCodec::FORMAT_RGB, w, h,
+                                w * 3, jpeg_quality, &encoded));
+  EXPECT_GT(original.size(), encoded.size());
+
+  // decode, it should have the same size as the original
+  std::vector<unsigned char> decoded;
+  int outw, outh;
+  EXPECT_TRUE(JPEGCodec::Decode(&encoded[0], encoded.size(),
+                                JPEGCodec::FORMAT_RGB, &decoded,
+                                &outw, &outh));
+  ASSERT_EQ(w, outw);
+  ASSERT_EQ(h, outh);
+  ASSERT_EQ(original.size(), decoded.size());
+
+  // Images must be approximately equal (compression will have introduced some
+  // minor artifacts).
+  ASSERT_GE(jpeg_equality_threshold, AveragePixelDelta(original, decoded));
+}
+
 TEST(JPEGCodec, EncodeDecodeRGBA) {
   int w = 20, h = 20;
 
   // create an image with known values, a must be opaque because it will be
   // lost during compression
   std::vector<unsigned char> original;
-  MakeRGBAImage(w, h, &original);
+  original.resize(w * h * 4);
+  for (int y = 0; y < h; y++) {
+    for (int x = 0; x < w; x++) {
+      unsigned char* org_px = &original[(y * w + x) * 4];
+      org_px[0] = x * 3;      // r
+      org_px[1] = x * 3 + 1;  // g
+      org_px[2] = x * 3 + 2;  // b
+      org_px[3] = 0xFF;       // a (opaque)
+    }
+  }
 
   // encode, making sure it was compressed some
   std::vector<unsigned char> encoded;
@@ -136,31 +172,31 @@
 
   // some random data (an uncompressed image)
   std::vector<unsigned char> original;
-  MakeRGBAImage(w, h, &original);
+  MakeRGBImage(w, h, &original);
 
   // it should fail when given non-JPEG compressed data
   std::vector<unsigned char> output;
   int outw, outh;
   ASSERT_FALSE(JPEGCodec::Decode(&original[0], original.size(),
-                                 JPEGCodec::FORMAT_RGBA, &output, &outw,
-                                 &outh));
+                                 JPEGCodec::FORMAT_RGB, &output,
+                                 &outw, &outh));
 
   // make some compressed data
   std::vector<unsigned char> compressed;
-  ASSERT_TRUE(JPEGCodec::Encode(&original[0], JPEGCodec::FORMAT_RGBA, w, h,
+  ASSERT_TRUE(JPEGCodec::Encode(&original[0], JPEGCodec::FORMAT_RGB, w, h,
                                 w * 3, jpeg_quality, &compressed));
 
   // try decompressing a truncated version
   ASSERT_FALSE(JPEGCodec::Decode(&compressed[0], compressed.size() / 2,
-                                 JPEGCodec::FORMAT_RGBA, &output, &outw,
-                                 &outh));
+                                 JPEGCodec::FORMAT_RGB, &output,
+                                 &outw, &outh));
 
   // corrupt it and try decompressing that
   for (int i = 10; i < 30; i++)
     compressed[i] = i;
   ASSERT_FALSE(JPEGCodec::Decode(&compressed[0], compressed.size(),
-                                 JPEGCodec::FORMAT_RGBA, &output, &outw,
-                                 &outh));
+                                 JPEGCodec::FORMAT_RGB, &output,
+                                 &outw, &outh));
 }
 
 // Test that we can decode JPEG images without invalid-read errors on valgrind.
@@ -171,6 +207,11 @@
   int outw, outh;
   JPEGCodec::Decode(kTopSitesMigrationTestImage,
                     arraysize(kTopSitesMigrationTestImage),
+                    JPEGCodec::FORMAT_RGB, &output,
+                    &outw, &outh);
+
+  JPEGCodec::Decode(kTopSitesMigrationTestImage,
+                    arraysize(kTopSitesMigrationTestImage),
                     JPEGCodec::FORMAT_RGBA, &output,
                     &outw, &outh);
 }
diff --git a/ui/ozone/common/gpu/ozone_gpu_message_params.h b/ui/ozone/common/gpu/ozone_gpu_message_params.h
index 5da9471..0d2e8e3 100644
--- a/ui/ozone/common/gpu/ozone_gpu_message_params.h
+++ b/ui/ozone/common/gpu/ozone_gpu_message_params.h
@@ -73,6 +73,15 @@
   bool is_overlay_candidate = true;
 };
 
+struct OverlayCheckReturn_Params {
+  OverlayCheckReturn_Params() = default;
+  OverlayCheckReturn_Params(const OverlayCheckReturn_Params& other) = default;
+  ~OverlayCheckReturn_Params() = default;
+
+  enum Status { PENDING, ABLE, NOT, LAST = NOT };
+  Status status = Status::PENDING;
+};
+
 }  // namespace ui
 
 #endif  // UI_OZONE_COMMON_GPU_OZONE_GPU_MESSAGE_PARAMS_H_
diff --git a/ui/ozone/common/gpu/ozone_gpu_messages.h b/ui/ozone/common/gpu/ozone_gpu_messages.h
index cdff15dc..b18c489 100644
--- a/ui/ozone/common/gpu/ozone_gpu_messages.h
+++ b/ui/ozone/common/gpu/ozone_gpu_messages.h
@@ -34,6 +34,9 @@
 
 IPC_ENUM_TRAITS_MAX_VALUE(gfx::OverlayTransform, gfx::OVERLAY_TRANSFORM_LAST)
 
+IPC_ENUM_TRAITS_MAX_VALUE(ui::OverlayCheckReturn_Params::Status,
+                          ui::OverlayCheckReturn_Params::Status::LAST)
+
 // clang-format off
 IPC_STRUCT_TRAITS_BEGIN(ui::DisplayMode_Params)
   IPC_STRUCT_TRAITS_MEMBER(size)
@@ -78,6 +81,10 @@
   IPC_STRUCT_TRAITS_MEMBER(is_overlay_candidate)
 IPC_STRUCT_TRAITS_END()
 
+IPC_STRUCT_TRAITS_BEGIN(ui::OverlayCheckReturn_Params)
+  IPC_STRUCT_TRAITS_MEMBER(status)
+IPC_STRUCT_TRAITS_END()
+
 // clang-format on
 
 //------------------------------------------------------------------------------
@@ -185,6 +192,7 @@
 
 // Response to OzoneGpuMsg_CheckOverlayCapabilities. Returns list of supported
 // params.
-IPC_MESSAGE_CONTROL2(OzoneHostMsg_OverlayCapabilitiesReceived,
+IPC_MESSAGE_CONTROL3(OzoneHostMsg_OverlayCapabilitiesReceived,
                      gfx::AcceleratedWidget /* widget */,
-                     std::vector<ui::OverlayCheck_Params> /* overlays */)
+                     std::vector<ui::OverlayCheck_Params> /* overlays */,
+                     std::vector<ui::OverlayCheckReturn_Params> /* returns */)
diff --git a/ui/ozone/platform/drm/cursor_proxy_mojo.cc b/ui/ozone/platform/drm/cursor_proxy_mojo.cc
index a0918fc..c48ddca 100644
--- a/ui/ozone/platform/drm/cursor_proxy_mojo.cc
+++ b/ui/ozone/platform/drm/cursor_proxy_mojo.cc
@@ -9,35 +9,45 @@
 
 namespace ui {
 
+// We assume that this is invoked only on the UI thread.
 CursorProxyMojo::CursorProxyMojo(service_manager::Connector* connector)
     : connector_(connector->Clone()) {
+  ui_thread_ref_ = base::PlatformThread::CurrentRef();
   connector->BindInterface(ui::mojom::kServiceName, &main_cursor_ptr_);
 }
 
-void CursorProxyMojo::InitializeOnEvdev() {
-  evdev_ref_ = base::PlatformThread::CurrentRef();
-  connector_->BindInterface(ui::mojom::kServiceName, &evdev_cursor_ptr_);
-}
-
 CursorProxyMojo::~CursorProxyMojo() {}
 
 void CursorProxyMojo::CursorSet(gfx::AcceleratedWidget widget,
                                 const std::vector<SkBitmap>& bitmaps,
                                 const gfx::Point& location,
                                 int frame_delay_ms) {
-  if (evdev_ref_ == base::PlatformThread::CurrentRef()) {
-    evdev_cursor_ptr_->SetCursor(widget, bitmaps, location, frame_delay_ms);
-  } else {
+  InitializeOnEvdevIfNecessary();
+  if (ui_thread_ref_ == base::PlatformThread::CurrentRef()) {
     main_cursor_ptr_->SetCursor(widget, bitmaps, location, frame_delay_ms);
+  } else {
+    evdev_cursor_ptr_->SetCursor(widget, bitmaps, location, frame_delay_ms);
   }
 }
 
 void CursorProxyMojo::Move(gfx::AcceleratedWidget widget,
                            const gfx::Point& location) {
-  if (evdev_ref_ == base::PlatformThread::CurrentRef()) {
-    evdev_cursor_ptr_->MoveCursor(widget, location);
-  } else {
+  InitializeOnEvdevIfNecessary();
+  if (ui_thread_ref_ == base::PlatformThread::CurrentRef()) {
     main_cursor_ptr_->MoveCursor(widget, location);
+  } else {
+    evdev_cursor_ptr_->MoveCursor(widget, location);
+  }
+}
+
+// Evdev runs this method on starting. But if a CursorProxyMojo is created long
+// after Evdev has started (e.g. if the Viz process crashes (and the
+// |CursorProxyMojo| self-destructs and then a new |CursorProxyMojo| is built
+// when the GpuThread/DrmThread pair are once again running), we need to run it
+// on cursor motions.
+void CursorProxyMojo::InitializeOnEvdevIfNecessary() {
+  if (ui_thread_ref_ != base::PlatformThread::CurrentRef()) {
+    connector_->BindInterface(ui::mojom::kServiceName, &evdev_cursor_ptr_);
   }
 }
 
diff --git a/ui/ozone/platform/drm/cursor_proxy_mojo.h b/ui/ozone/platform/drm/cursor_proxy_mojo.h
index 33577aee..e1bceff 100644
--- a/ui/ozone/platform/drm/cursor_proxy_mojo.h
+++ b/ui/ozone/platform/drm/cursor_proxy_mojo.h
@@ -33,7 +33,7 @@
                  const gfx::Point& point,
                  int frame_delay_ms) override;
   void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override;
-  void InitializeOnEvdev() override;
+  void InitializeOnEvdevIfNecessary() override;
 
   std::unique_ptr<service_manager::Connector> connector_;
 
@@ -41,7 +41,8 @@
   ui::ozone::mojom::DeviceCursorPtr main_cursor_ptr_;
   ui::ozone::mojom::DeviceCursorPtr evdev_cursor_ptr_;
 
-  base::PlatformThreadRef evdev_ref_;
+  base::PlatformThreadRef ui_thread_ref_;
+  DISALLOW_COPY_AND_ASSIGN(CursorProxyMojo);
 };
 
 }  // namespace ui
diff --git a/ui/ozone/platform/drm/gpu/drm_overlay_validator.cc b/ui/ozone/platform/drm/gpu/drm_overlay_validator.cc
index bfe65bbb..d93439c 100644
--- a/ui/ozone/platform/drm/gpu/drm_overlay_validator.cc
+++ b/ui/ozone/platform/drm/gpu/drm_overlay_validator.cc
@@ -104,17 +104,17 @@
 
 DrmOverlayValidator::~DrmOverlayValidator() {}
 
-std::vector<OverlayCheck_Params> DrmOverlayValidator::TestPageFlip(
+std::vector<OverlayCheckReturn_Params> DrmOverlayValidator::TestPageFlip(
     const std::vector<OverlayCheck_Params>& params,
     const OverlayPlaneList& last_used_planes) {
-  std::vector<OverlayCheck_Params> validated_params = params;
+  std::vector<OverlayCheckReturn_Params> returns(params.size());
   HardwareDisplayController* controller = window_->GetController();
   if (!controller) {
-    // Nothing much we can do here.
-    for (auto& overlay : validated_params)
-      overlay.is_overlay_candidate = false;
+    // The controller is not yet installed.
+    for (auto& param : returns)
+      param.status = OverlayCheckReturn_Params::Status::NOT;
 
-    return validated_params;
+    return returns;
   }
 
   OverlayPlaneList test_list;
@@ -124,32 +124,35 @@
   for (const auto& plane : last_used_planes)
     reusable_buffers.push_back(plane.buffer);
 
-  for (auto& overlay : validated_params) {
-    if (!overlay.is_overlay_candidate)
+  for (size_t i = 0; i < params.size(); ++i) {
+    if (!params[i].is_overlay_candidate) {
+      returns[i].status = OverlayCheckReturn_Params::Status::NOT;
       continue;
+    }
 
     gfx::Size scaled_buffer_size = GetScaledSize(
-        overlay.buffer_size, overlay.display_rect, overlay.crop_rect);
+        params[i].buffer_size, params[i].display_rect, params[i].crop_rect);
 
     uint32_t original_format =
-        overlay.plane_z_order
-            ? GetFourCCFormatFromBufferFormat(overlay.format)
-            : GetFourCCFormatForOpaqueFramebuffer(overlay.format);
+        params[i].plane_z_order
+            ? GetFourCCFormatFromBufferFormat(params[i].format)
+            : GetFourCCFormatForOpaqueFramebuffer(params[i].format);
     scoped_refptr<ScanoutBuffer> buffer =
-        GetBufferForPageFlipTest(drm, overlay.buffer_size, original_format,
+        GetBufferForPageFlipTest(drm, params[i].buffer_size, original_format,
                                  buffer_generator_, &reusable_buffers);
 
-    OverlayPlane plane(buffer, overlay.plane_z_order, overlay.transform,
-                       overlay.display_rect, overlay.crop_rect);
+    OverlayPlane plane(buffer, params[i].plane_z_order, params[i].transform,
+                       params[i].display_rect, params[i].crop_rect);
     test_list.push_back(plane);
 
     if (buffer && controller->TestPageFlip(test_list)) {
-      overlay.is_overlay_candidate = true;
+      returns[i].status = OverlayCheckReturn_Params::Status::ABLE;
 
       // If size scaling is needed, find an optimal format.
-      if (overlay.plane_z_order && scaled_buffer_size != overlay.buffer_size) {
+      if (params[i].plane_z_order &&
+          scaled_buffer_size != params[i].buffer_size) {
         uint32_t optimal_format = FindOptimalBufferFormat(
-            original_format, overlay.plane_z_order, overlay.display_rect,
+            original_format, params[i].plane_z_order, params[i].display_rect,
             window_->bounds(), controller);
 
         if (original_format != optimal_format) {
@@ -160,9 +163,9 @@
                                        buffer_generator_, &reusable_buffers);
           DCHECK(optimal_buffer);
 
-          OverlayPlane optimal_plane(optimal_buffer, overlay.plane_z_order,
-                                     overlay.transform, overlay.display_rect,
-                                     overlay.crop_rect);
+          OverlayPlane optimal_plane(
+              optimal_buffer, params[i].plane_z_order, params[i].transform,
+              params[i].display_rect, params[i].crop_rect);
           test_list.push_back(optimal_plane);
 
           // If test failed here, it means even though optimal_format is
@@ -183,14 +186,14 @@
       // hardware resources and they might be already in use by other planes.
       // For example this plane has requested scaling capabilities and all
       // available scalars are already in use by other planes.
-      overlay.is_overlay_candidate = false;
+      returns[i].status = OverlayCheckReturn_Params::Status::NOT;
       test_list.pop_back();
     }
   }
 
   UpdateOverlayHintsCache(test_list);
 
-  return validated_params;
+  return returns;
 }
 
 OverlayPlaneList DrmOverlayValidator::PrepareBuffersForPageFlip(
diff --git a/ui/ozone/platform/drm/gpu/drm_overlay_validator.h b/ui/ozone/platform/drm/gpu/drm_overlay_validator.h
index 82793dd..aba3185 100644
--- a/ui/ozone/platform/drm/gpu/drm_overlay_validator.h
+++ b/ui/ozone/platform/drm/gpu/drm_overlay_validator.h
@@ -13,6 +13,7 @@
 class DrmWindow;
 class ScanoutBufferGenerator;
 struct OverlayCheck_Params;
+struct OverlayCheckReturn_Params;
 
 class DrmOverlayValidator {
  public:
@@ -23,7 +24,7 @@
   // Tests if configurations |params| are compatible with |window_| and finds
   // which of these configurations can be promoted to Overlay composition
   // without failing the page flip. It expects |params| to be sorted by z_order.
-  std::vector<OverlayCheck_Params> TestPageFlip(
+  std::vector<OverlayCheckReturn_Params> TestPageFlip(
       const std::vector<OverlayCheck_Params>& params,
       const OverlayPlaneList& last_used_planes);
 
diff --git a/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc b/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc
index 3bffad6..3bb43de 100644
--- a/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc
@@ -157,18 +157,19 @@
   // present.
   ui::HardwareDisplayController* controller = window_->GetController();
   window_->SetController(nullptr);
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
-  EXPECT_FALSE(validated_params.front().is_overlay_candidate);
-  EXPECT_FALSE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.front().status, ui::OverlayCheckReturn_Params::Status::NOT);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::NOT);
   window_->SetController(controller);
 }
 
 TEST_F(DrmOverlayValidatorTest, DontPromoteMoreLayersThanAvailablePlanes) {
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
-  EXPECT_TRUE(validated_params.front().is_overlay_candidate);
-  EXPECT_FALSE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.front().status,
+            ui::OverlayCheckReturn_Params::Status::ABLE);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::NOT);
 }
 
 TEST_F(DrmOverlayValidatorTest, DontCollapseOverlayToPrimaryInFullScreen) {
@@ -177,12 +178,13 @@
   overlay_params_.back().display_rect = primary_rect_;
   plane_list_.back().display_bounds = primary_rect_;
 
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
   // Second candidate should be marked as Invalid as we have only one plane
   // per CRTC.
-  EXPECT_TRUE(validated_params.front().is_overlay_candidate);
-  EXPECT_FALSE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.front().status,
+            ui::OverlayCheckReturn_Params::Status::ABLE);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::NOT);
 }
 
 TEST_F(DrmOverlayValidatorTest, ClearCacheOnReset) {
@@ -314,11 +316,11 @@
   plane_manager_->SetPlaneProperties(planes_info);
   overlay_validator_->ClearCache();
 
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
 
-  for (const auto& param : validated_params)
-    EXPECT_TRUE(param.is_overlay_candidate);
+  for (const auto& param : returns)
+    EXPECT_EQ(param.status, ui::OverlayCheckReturn_Params::Status::ABLE);
 
   EXPECT_EQ(3, plane_manager_->plane_count());
 
@@ -349,11 +351,11 @@
   plane_manager_->SetPlaneProperties(planes_info);
   overlay_validator_->ClearCache();
 
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
 
-  for (const auto& param : validated_params)
-    EXPECT_TRUE(param.is_overlay_candidate);
+  for (const auto& param : returns)
+    EXPECT_EQ(param.status, ui::OverlayCheckReturn_Params::Status::ABLE);
 
   EXPECT_EQ(5, plane_manager_->plane_count());
 
@@ -380,15 +382,15 @@
   plane_manager_->SetPlaneProperties(planes_info);
   overlay_validator_->ClearCache();
 
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
   ui::OverlayPlaneList plane_list =
       overlay_validator_->PrepareBuffersForPageFlip(plane_list_);
   EXPECT_EQ(DRM_FORMAT_XRGB8888,
             plane_list.back().buffer->GetFramebufferPixelFormat());
   EXPECT_EQ(3, plane_manager_->plane_count());
-  for (const auto& param : validated_params)
-    EXPECT_TRUE(param.is_overlay_candidate);
+  for (const auto& param : returns)
+    EXPECT_EQ(param.status, ui::OverlayCheckReturn_Params::Status::ABLE);
 }
 
 TEST_F(DrmOverlayValidatorTest, RejectYUVBuffersIfNotSupported) {
@@ -411,10 +413,11 @@
   std::vector<ui::OverlayCheck_Params> validated_params = overlay_params_;
   validated_params.back().format = gfx::BufferFormat::UYVY_422;
   plane_manager_->ResetPlaneCount();
-  validated_params = overlay_validator_->TestPageFlip(validated_params,
-                                                      ui::OverlayPlaneList());
+  std::vector<ui::OverlayCheckReturn_Params> returns =
+      overlay_validator_->TestPageFlip(validated_params,
+                                       ui::OverlayPlaneList());
 
-  EXPECT_FALSE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::NOT);
 }
 
 TEST_F(DrmOverlayValidatorTest,
@@ -454,10 +457,11 @@
   std::vector<ui::OverlayCheck_Params> validated_params = overlay_params_;
   validated_params.back().format = gfx::BufferFormat::UYVY_422;
   plane_manager_->ResetPlaneCount();
-  validated_params = overlay_validator_->TestPageFlip(validated_params,
-                                                      ui::OverlayPlaneList());
+  std::vector<ui::OverlayCheckReturn_Params> returns =
+      overlay_validator_->TestPageFlip(validated_params,
+                                       ui::OverlayPlaneList());
 
-  EXPECT_TRUE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::ABLE);
 
   // Both controllers have Overlay which support DRM_FORMAT_UYVY, and scaling is
   // needed, hence this should be picked as the optimal format.
@@ -474,9 +478,9 @@
   plane_manager_->SetPlaneProperties(planes_info);
   overlay_validator_->ClearCache();
 
-  validated_params = overlay_validator_->TestPageFlip(validated_params,
-                                                      ui::OverlayPlaneList());
-  EXPECT_FALSE(validated_params.back().is_overlay_candidate);
+  returns = overlay_validator_->TestPageFlip(validated_params,
+                                             ui::OverlayPlaneList());
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::NOT);
 
   // Check case where we dont have support for packed formats in primary
   // display.
@@ -485,9 +489,9 @@
   plane_manager_->SetPlaneProperties(planes_info);
   overlay_validator_->ClearCache();
 
-  validated_params = overlay_validator_->TestPageFlip(validated_params,
-                                                      ui::OverlayPlaneList());
-  EXPECT_FALSE(validated_params.back().is_overlay_candidate);
+  returns = overlay_validator_->TestPageFlip(validated_params,
+                                             ui::OverlayPlaneList());
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::NOT);
   controller->RemoveCrtc(drm_, kSecondaryCrtc);
 }
 
@@ -523,10 +527,10 @@
   overlay_validator_->ClearCache();
 
   plane_manager_->ResetPlaneCount();
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
 
-  EXPECT_TRUE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::ABLE);
   ui::OverlayPlaneList plane_list =
       overlay_validator_->PrepareBuffersForPageFlip(plane_list_);
   EXPECT_EQ(DRM_FORMAT_XRGB8888,
@@ -537,9 +541,9 @@
   plane_manager_->SetPlaneProperties(planes_info);
   overlay_validator_->ClearCache();
 
-  validated_params =
+  returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
-  EXPECT_TRUE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::ABLE);
 
   plane_list = overlay_validator_->PrepareBuffersForPageFlip(plane_list_);
   EXPECT_EQ(DRM_FORMAT_XRGB8888,
@@ -552,9 +556,9 @@
   plane_manager_->SetPlaneProperties(planes_info);
   overlay_validator_->ClearCache();
 
-  validated_params =
+  returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
-  EXPECT_TRUE(validated_params.back().is_overlay_candidate);
+  EXPECT_EQ(returns.back().status, ui::OverlayCheckReturn_Params::Status::ABLE);
 
   plane_list = overlay_validator_->PrepareBuffersForPageFlip(plane_list_);
   EXPECT_EQ(DRM_FORMAT_XRGB8888,
@@ -636,7 +640,7 @@
   // In that case we should reject the overlay candidate.
   buffer_generator_->set_allocation_failure(true);
 
-  std::vector<ui::OverlayCheck_Params> validated_params =
+  std::vector<ui::OverlayCheckReturn_Params> returns =
       overlay_validator_->TestPageFlip(overlay_params_, ui::OverlayPlaneList());
-  EXPECT_FALSE(validated_params.front().is_overlay_candidate);
+  EXPECT_EQ(returns.front().status, ui::OverlayCheckReturn_Params::Status::NOT);
 }
diff --git a/ui/ozone/platform/drm/gpu/drm_thread.cc b/ui/ozone/platform/drm/gpu/drm_thread.cc
index 8eaaeee..8df0ba71 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread.cc
+++ b/ui/ozone/platform/drm/gpu/drm_thread.cc
@@ -220,10 +220,12 @@
     gfx::AcceleratedWidget widget,
     const std::vector<OverlayCheck_Params>& overlays,
     base::OnceCallback<void(gfx::AcceleratedWidget,
-                            const std::vector<OverlayCheck_Params>&)>
+                            const std::vector<OverlayCheck_Params>&,
+                            const std::vector<OverlayCheckReturn_Params>&)>
         callback) {
   std::move(callback).Run(
-      widget, screen_manager_->GetWindow(widget)->TestPageFlip(overlays));
+      widget, overlays,
+      screen_manager_->GetWindow(widget)->TestPageFlip(overlays));
 }
 
 void DrmThread::RefreshNativeDisplays(
diff --git a/ui/ozone/platform/drm/gpu/drm_thread.h b/ui/ozone/platform/drm/gpu/drm_thread.h
index 9b44e64..0af954b 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread.h
+++ b/ui/ozone/platform/drm/gpu/drm_thread.h
@@ -94,7 +94,8 @@
       gfx::AcceleratedWidget widget,
       const std::vector<OverlayCheck_Params>& overlays,
       base::OnceCallback<void(gfx::AcceleratedWidget,
-                              const std::vector<OverlayCheck_Params>&)>
+                              const std::vector<OverlayCheck_Params>&,
+                              const std::vector<OverlayCheckReturn_Params>&)>
           callback);
   void RefreshNativeDisplays(
       base::OnceCallback<void(const std::vector<DisplaySnapshot_Params>&)>
diff --git a/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.cc b/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.cc
index 2977ba36..d01d239 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.cc
+++ b/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.cc
@@ -239,8 +239,10 @@
 
 void DrmThreadMessageProxy::OnCheckOverlayCapabilitiesCallback(
     gfx::AcceleratedWidget widget,
-    const std::vector<OverlayCheck_Params>& overlays) const {
-  sender_->Send(new OzoneHostMsg_OverlayCapabilitiesReceived(widget, overlays));
+    const std::vector<OverlayCheck_Params>& overlays,
+    const std::vector<OverlayCheckReturn_Params>& returns) const {
+  sender_->Send(
+      new OzoneHostMsg_OverlayCapabilitiesReceived(widget, overlays, returns));
 }
 
 void DrmThreadMessageProxy::OnRefreshNativeDisplaysCallback(
diff --git a/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.h b/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.h
index 3ce4a3f..efbacc6 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.h
+++ b/ui/ozone/platform/drm/gpu/drm_thread_message_proxy.h
@@ -30,6 +30,7 @@
 struct DisplayMode_Params;
 struct DisplaySnapshot_Params;
 struct OverlayCheck_Params;
+struct OverlayCheckReturn_Params;
 
 class DrmThreadMessageProxy : public IPC::MessageFilter,
                               public InterThreadMessagingProxy {
@@ -80,7 +81,8 @@
 
   void OnCheckOverlayCapabilitiesCallback(
       gfx::AcceleratedWidget widget,
-      const std::vector<OverlayCheck_Params>& overlays) const;
+      const std::vector<OverlayCheck_Params>& overlays,
+      const std::vector<OverlayCheckReturn_Params>& returns) const;
   void OnRefreshNativeDisplaysCallback(
       const std::vector<DisplaySnapshot_Params>& displays) const;
   void OnConfigureNativeDisplayCallback(int64_t display_id, bool success) const;
diff --git a/ui/ozone/platform/drm/gpu/drm_window.cc b/ui/ozone/platform/drm/gpu/drm_window.cc
index 55bd1a05..2ec7689 100644
--- a/ui/ozone/platform/drm/gpu/drm_window.cc
+++ b/ui/ozone/platform/drm/gpu/drm_window.cc
@@ -147,7 +147,7 @@
   controller_->SchedulePageFlip(last_submitted_planes_, std::move(callback));
 }
 
-std::vector<OverlayCheck_Params> DrmWindow::TestPageFlip(
+std::vector<OverlayCheckReturn_Params> DrmWindow::TestPageFlip(
     const std::vector<OverlayCheck_Params>& overlay_params) {
   return overlay_validator_->TestPageFlip(overlay_params,
                                           last_submitted_planes_);
diff --git a/ui/ozone/platform/drm/gpu/drm_window.h b/ui/ozone/platform/drm/gpu/drm_window.h
index af30aaf6..f8af6d5b 100644
--- a/ui/ozone/platform/drm/gpu/drm_window.h
+++ b/ui/ozone/platform/drm/gpu/drm_window.h
@@ -32,6 +32,7 @@
 class DrmOverlayValidator;
 class HardwareDisplayController;
 struct OverlayCheck_Params;
+struct OverlayCheckReturn_Params;
 class ScanoutBufferGenerator;
 class ScreenManager;
 
@@ -86,7 +87,7 @@
 
   void SchedulePageFlip(const std::vector<OverlayPlane>& planes,
                         SwapCompletionOnceCallback callback);
-  std::vector<OverlayCheck_Params> TestPageFlip(
+  std::vector<OverlayCheckReturn_Params> TestPageFlip(
       const std::vector<OverlayCheck_Params>& overlay_params);
 
   // Returns the last buffer associated with this window.
diff --git a/ui/ozone/platform/drm/host/drm_cursor.cc b/ui/ozone/platform/drm/host/drm_cursor.cc
index 0dc9ae5..6226139 100644
--- a/ui/ozone/platform/drm/host/drm_cursor.cc
+++ b/ui/ozone/platform/drm/host/drm_cursor.cc
@@ -27,7 +27,7 @@
                  const gfx::Point& point,
                  int frame_delay_ms) override {}
   void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override {}
-  void InitializeOnEvdev() override {}
+  void InitializeOnEvdevIfNecessary() override {}
 
  private:
   DISALLOW_COPY_AND_ASSIGN(NullProxy);
@@ -44,11 +44,11 @@
 
 DrmCursor::~DrmCursor() {}
 
-void DrmCursor::SetDrmCursorProxy(DrmCursorProxy* proxy) {
+void DrmCursor::SetDrmCursorProxy(std::unique_ptr<DrmCursorProxy> proxy) {
   TRACE_EVENT0("drmcursor", "DrmCursor::SetDrmCursorProxy");
   DCHECK(thread_checker_.CalledOnValidThread());
   base::AutoLock lock(lock_);
-  proxy_.reset(proxy);
+  proxy_ = std::move(proxy);
 }
 
 void DrmCursor::ResetDrmCursorProxy() {
@@ -216,7 +216,7 @@
 
 void DrmCursor::InitializeOnEvdev() {
   DCHECK(evdev_thread_checker_.CalledOnValidThread());
-  proxy_->InitializeOnEvdev();
+  proxy_->InitializeOnEvdevIfNecessary();
 }
 
 void DrmCursor::SetCursorLocationLocked(const gfx::PointF& location) {
diff --git a/ui/ozone/platform/drm/host/drm_cursor.h b/ui/ozone/platform/drm/host/drm_cursor.h
index 092f8ce..a31f27f2 100644
--- a/ui/ozone/platform/drm/host/drm_cursor.h
+++ b/ui/ozone/platform/drm/host/drm_cursor.h
@@ -34,7 +34,7 @@
   virtual void Move(gfx::AcceleratedWidget window, const gfx::Point& point) = 0;
 
   // Initialize EvdevThread-specific state.
-  virtual void InitializeOnEvdev() = 0;
+  virtual void InitializeOnEvdevIfNecessary() = 0;
 };
 
 // DrmCursor manages all cursor state and semantics.
@@ -43,9 +43,10 @@
   explicit DrmCursor(DrmWindowHostManager* window_manager);
   ~DrmCursor() override;
 
-  // Sets or resets the DrmProxy |proxy|. If |proxy| is set, the DrmCursor uses
-  // it to communicate to the GPU process or thread.
-  void SetDrmCursorProxy(DrmCursorProxy* proxy);
+  // Sets or the DrmProxy |proxy|. If |proxy| is set, the DrmCursor uses
+  // it to communicate to the GPU process or thread. Returns the previous
+  // value.
+  void SetDrmCursorProxy(std::unique_ptr<DrmCursorProxy> proxy);
   void ResetDrmCursorProxy();
 
   // Change the cursor over the specifed window.
diff --git a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
index 01920a8..ccbcbeb 100644
--- a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
+++ b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
@@ -33,7 +33,7 @@
                  const gfx::Point& point,
                  int frame_delay_ms) override;
   void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override;
-  void InitializeOnEvdev() override;
+  void InitializeOnEvdevIfNecessary() override;
 
  private:
   bool IsConnected();
@@ -66,7 +66,7 @@
   Send(new OzoneGpuMsg_CursorMove(window, point));
 }
 
-void CursorIPC::InitializeOnEvdev() {}
+void CursorIPC::InitializeOnEvdevIfNecessary() {}
 
 void CursorIPC::Send(IPC::Message* message) {
   if (IsConnected() &&
@@ -180,7 +180,8 @@
   // and notify it after all other observers/handlers are notified such that the
   // (windowing) state on the GPU can be initialized before the cursor is
   // allowed to IPC messages (which are targeted to a specific window).
-  cursor_->SetDrmCursorProxy(new CursorIPC(send_runner_, send_callback_));
+  cursor_->SetDrmCursorProxy(
+      base::MakeUnique<CursorIPC>(send_runner_, send_callback_));
 }
 
 bool DrmGpuPlatformSupportHost::OnMessageReceivedForDrmDisplayHostManager(
@@ -292,8 +293,9 @@
 
 void DrmGpuPlatformSupportHost::OnOverlayResult(
     gfx::AcceleratedWidget widget,
-    const std::vector<OverlayCheck_Params>& params) {
-  overlay_manager_->GpuSentOverlayResult(widget, params);
+    const std::vector<OverlayCheck_Params>& params,
+    const std::vector<OverlayCheckReturn_Params>& returns) {
+  overlay_manager_->GpuSentOverlayResult(widget, params, returns);
 }
 
 bool DrmGpuPlatformSupportHost::GpuCheckOverlayCapabilities(
diff --git a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h
index c3778ab..5611b96 100644
--- a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h
+++ b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.h
@@ -110,7 +110,8 @@
 
   bool OnMessageReceivedForDrmOverlayManager(const IPC::Message& message);
   void OnOverlayResult(gfx::AcceleratedWidget widget,
-                       const std::vector<OverlayCheck_Params>& params);
+                       const std::vector<OverlayCheck_Params>& params,
+                       const std::vector<OverlayCheckReturn_Params>& returns);
 
   int host_id_ = -1;
   bool channel_established_ = false;
diff --git a/ui/ozone/platform/drm/host/drm_overlay_manager.cc b/ui/ozone/platform/drm/host/drm_overlay_manager.cc
index c6795d3..b45a6645 100644
--- a/ui/ozone/platform/drm/host/drm_overlay_manager.cc
+++ b/ui/ozone/platform/drm/host/drm_overlay_manager.cc
@@ -60,45 +60,53 @@
       candidate.buffer_size = gfx::ToNearestRect(candidate.display_rect).size();
 
     overlay_params.push_back(OverlayCheck_Params(candidate));
-  }
 
-  const auto& iter = cache_.Get(overlay_params);
-  // We are still waiting on results for this candidate list from GPU.
-  if (iter != cache_.end() && iter->second)
-    return;
+    if (!CanHandleCandidate(candidate, widget)) {
+      DCHECK(candidate.plane_z_order != 0);
+      overlay_params.back().is_overlay_candidate = false;
+    }
+  }
 
   size_t size = candidates->size();
+  const auto& iter = cache_.Get(overlay_params);
+  if (iter != cache_.end()) {
+    // We are still waiting on results for this candidate list from GPU.
+    if (iter->second.back().status ==
+        OverlayCheckReturn_Params::Status::PENDING)
+      return;
 
-  if (iter == cache_.end()) {
-    // We can skip GPU side validation in case all candidates are invalid.
-    bool needs_gpu_validation = false;
+    const std::vector<OverlayCheckReturn_Params>& returns = iter->second;
+    DCHECK(size == returns.size());
     for (size_t i = 0; i < size; i++) {
-      if (!overlay_params.at(i).is_overlay_candidate)
-        continue;
-
-      const OverlaySurfaceCandidate& candidate = candidates->at(i);
-      if (!CanHandleCandidate(candidate, widget)) {
-        DCHECK(candidate.plane_z_order != 0);
-        overlay_params.at(i).is_overlay_candidate = false;
-        continue;
-      }
-
-      needs_gpu_validation = true;
-    }
-
-    cache_.Put(overlay_params, needs_gpu_validation);
-
-    if (needs_gpu_validation)
-      SendOverlayValidationRequest(overlay_params, widget);
-  } else {
-    const std::vector<OverlayCheck_Params>& validated_params = iter->first;
-    DCHECK(size == validated_params.size());
-
-    for (size_t i = 0; i < size; i++) {
+      DCHECK(returns[i].status == OverlayCheckReturn_Params::Status::ABLE ||
+             returns[i].status == OverlayCheckReturn_Params::Status::NOT);
       candidates->at(i).overlay_handled =
-          validated_params.at(i).is_overlay_candidate;
+          returns[i].status == OverlayCheckReturn_Params::Status::ABLE ? true
+                                                                       : false;
+    }
+    return;
+  }
+
+  // We can skip GPU side validation in case all candidates are invalid.
+  bool needs_gpu_validation = false;
+  for (size_t i = 0; i < size; i++) {
+    if (!overlay_params.at(i).is_overlay_candidate)
+      continue;
+
+    needs_gpu_validation = true;
+  }
+
+  std::vector<OverlayCheckReturn_Params> returns(overlay_params.size());
+  if (needs_gpu_validation) {
+    for (auto param : returns) {
+      param.status = OverlayCheckReturn_Params::Status::NOT;
     }
   }
+
+  cache_.Put(overlay_params, returns);
+
+  if (needs_gpu_validation)
+    SendOverlayValidationRequest(overlay_params, widget);
 }
 
 void DrmOverlayManager::ResetCache() {
@@ -116,8 +124,9 @@
 
 void DrmOverlayManager::GpuSentOverlayResult(
     gfx::AcceleratedWidget widget,
-    const std::vector<OverlayCheck_Params>& params) {
-  cache_.Put(params, false);
+    const std::vector<OverlayCheck_Params>& params,
+    const std::vector<OverlayCheckReturn_Params>& returns) {
+  cache_.Put(params, returns);
 }
 
 bool DrmOverlayManager::CanHandleCandidate(
diff --git a/ui/ozone/platform/drm/host/drm_overlay_manager.h b/ui/ozone/platform/drm/host/drm_overlay_manager.h
index 8afad3bb..89f8b41 100644
--- a/ui/ozone/platform/drm/host/drm_overlay_manager.h
+++ b/ui/ozone/platform/drm/host/drm_overlay_manager.h
@@ -32,8 +32,10 @@
 
   // Communication-free implementations of actions performed in response to
   // messages from the GPU thread.
-  void GpuSentOverlayResult(gfx::AcceleratedWidget widget,
-                            const std::vector<OverlayCheck_Params>& params);
+  void GpuSentOverlayResult(
+      gfx::AcceleratedWidget widget,
+      const std::vector<OverlayCheck_Params>& params,
+      const std::vector<OverlayCheckReturn_Params>& returns);
 
   // Service method for DrmOverlayCandidatesHost
   void CheckOverlaySupport(
@@ -53,7 +55,9 @@
 
   // List of all OverlayCheck_Params which have been validated in GPU side.
   // Value is set to true if we are waiting for validation results from GPU.
-  base::MRUCache<std::vector<OverlayCheck_Params>, bool> cache_;
+  base::MRUCache<std::vector<OverlayCheck_Params>,
+                 std::vector<OverlayCheckReturn_Params>>
+      cache_;
 
   DISALLOW_COPY_AND_ASSIGN(DrmOverlayManager);
 };
diff --git a/ui/ozone/platform/drm/mus_thread_proxy.cc b/ui/ozone/platform/drm/mus_thread_proxy.cc
index a28e900..daff5ff 100644
--- a/ui/ozone/platform/drm/mus_thread_proxy.cc
+++ b/ui/ozone/platform/drm/mus_thread_proxy.cc
@@ -9,6 +9,7 @@
 #include "base/task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "ui/ozone/platform/drm/common/drm_util.h"
+#include "ui/ozone/platform/drm/cursor_proxy_mojo.h"
 #include "ui/ozone/platform/drm/gpu/drm_thread.h"
 #include "ui/ozone/platform/drm/gpu/proxy_helpers.h"
 #include "ui/ozone/platform/drm/host/drm_display_host_manager.h"
@@ -16,6 +17,26 @@
 
 namespace ui {
 
+namespace {
+
+// Forwarding proxy to handle ownership semantics.
+class CursorProxyThread : public DrmCursorProxy {
+ public:
+  explicit CursorProxyThread(MusThreadProxy* mus_thread_proxy);
+  ~CursorProxyThread() override;
+
+ private:
+  // DrmCursorProxy.
+  void CursorSet(gfx::AcceleratedWidget window,
+                 const std::vector<SkBitmap>& bitmaps,
+                 const gfx::Point& point,
+                 int frame_delay_ms) override;
+  void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override;
+  void InitializeOnEvdevIfNecessary() override;
+  MusThreadProxy* const mus_thread_proxy_;  // Not owned.
+  DISALLOW_COPY_AND_ASSIGN(CursorProxyThread);
+};
+
 CursorProxyThread::CursorProxyThread(MusThreadProxy* mus_thread_proxy)
     : mus_thread_proxy_(mus_thread_proxy) {}
 CursorProxyThread::~CursorProxyThread() {}
@@ -30,17 +51,20 @@
                              const gfx::Point& point) {
   mus_thread_proxy_->Move(window, point);
 }
-void CursorProxyThread::InitializeOnEvdev() {
-  mus_thread_proxy_->InitializeOnEvdev();
+void CursorProxyThread::InitializeOnEvdevIfNecessary() {
+  mus_thread_proxy_->InitializeOnEvdevIfNecessary();
 }
 
-MusThreadProxy::MusThreadProxy()
+}  // namespace
+
+MusThreadProxy::MusThreadProxy(DrmCursor* cursor,
+                               service_manager::Connector* connector)
     : ws_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       drm_thread_(nullptr),
+      cursor_(cursor),
+      connector_(connector),
       weak_ptr_factory_(this) {}
 
-void MusThreadProxy::InitializeOnEvdev() {}
-
 MusThreadProxy::~MusThreadProxy() {
   DCHECK(on_window_server_thread_.CalledOnValidThread());
   for (GpuThreadObserver& observer : gpu_thread_observers_)
@@ -59,6 +83,7 @@
   overlay_manager_ = overlay_manager;
 }
 
+// Runs on Gpu thread.
 void MusThreadProxy::StartDrmThread() {
   DCHECK(drm_thread_);
   drm_thread_->Start();
@@ -81,6 +106,21 @@
     observer.OnGpuProcessLaunched();
     observer.OnGpuThreadReady();
   }
+
+  // The cursor is special since it will process input events on the IO thread
+  // and can by-pass the UI thread. This means that we need to special case it
+  // and notify it after all other observers/handlers are notified.
+  if (connector_ == nullptr) {
+    // CursorProxyThread does not need to use delegate because the non-mojo
+    // MusThreadProxy is only used in tests that do not operate the cursor.
+    // Future refactoring will unify the mojo and in-process modes.
+    cursor_->SetDrmCursorProxy(base::MakeUnique<CursorProxyThread>(this));
+  } else {
+    cursor_->SetDrmCursorProxy(base::MakeUnique<CursorProxyMojo>(connector_));
+  }
+
+  // TODO(rjkroege): Call ResetDrmCursorProxy when the mojo connection to the
+  // DRM thread is broken.
 }
 
 void MusThreadProxy::AddGpuThreadObserver(GpuThreadObserver* observer) {
@@ -121,8 +161,9 @@
 }
 
 bool MusThreadProxy::GpuCreateWindow(gfx::AcceleratedWidget widget) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE, base::Bind(&DrmThread::CreateWindow,
                             base::Unretained(drm_thread_), widget));
@@ -130,8 +171,9 @@
 }
 
 bool MusThreadProxy::GpuDestroyWindow(gfx::AcceleratedWidget widget) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE, base::Bind(&DrmThread::DestroyWindow,
                             base::Unretained(drm_thread_), widget));
@@ -140,20 +182,23 @@
 
 bool MusThreadProxy::GpuWindowBoundsChanged(gfx::AcceleratedWidget widget,
                                             const gfx::Rect& bounds) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE, base::Bind(&DrmThread::SetWindowBounds,
                             base::Unretained(drm_thread_), widget, bounds));
   return true;
 }
 
+// Services needed for DrmCursorProxy.
 void MusThreadProxy::CursorSet(gfx::AcceleratedWidget widget,
                                const std::vector<SkBitmap>& bitmaps,
                                const gfx::Point& location,
                                int frame_delay_ms) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&DrmThread::SetCursor, base::Unretained(drm_thread_), widget,
@@ -163,12 +208,15 @@
 void MusThreadProxy::Move(gfx::AcceleratedWidget widget,
                           const gfx::Point& location) {
   // NOTE: Input events skip the main thread to avoid jank.
-  DCHECK(drm_thread_->IsRunning());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE, base::Bind(&DrmThread::MoveCursor,
                             base::Unretained(drm_thread_), widget, location));
 }
 
+void MusThreadProxy::InitializeOnEvdevIfNecessary() {}
+
 // Services needed for DrmOverlayManager.
 void MusThreadProxy::RegisterHandlerForDrmOverlayManager(
     DrmOverlayManager* handler) {
@@ -184,8 +232,9 @@
 bool MusThreadProxy::GpuCheckOverlayCapabilities(
     gfx::AcceleratedWidget widget,
     const std::vector<OverlayCheck_Params>& overlays) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   auto callback =
       base::BindOnce(&MusThreadProxy::GpuCheckOverlayCapabilitiesCallback,
                      weak_ptr_factory_.GetWeakPtr());
@@ -198,8 +247,9 @@
 }
 
 bool MusThreadProxy::GpuRefreshNativeDisplays() {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   auto callback =
       base::BindOnce(&MusThreadProxy::GpuRefreshNativeDisplaysCallback,
                      weak_ptr_factory_.GetWeakPtr());
@@ -214,8 +264,9 @@
 bool MusThreadProxy::GpuConfigureNativeDisplay(int64_t id,
                                                const DisplayMode_Params& pmode,
                                                const gfx::Point& origin) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
 
   auto mode = CreateDisplayModeFromParams(pmode);
   auto callback =
@@ -231,8 +282,9 @@
 }
 
 bool MusThreadProxy::GpuDisableNativeDisplay(int64_t id) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   auto callback =
       base::BindOnce(&MusThreadProxy::GpuDisableNativeDisplayCallback,
                      weak_ptr_factory_.GetWeakPtr());
@@ -245,8 +297,9 @@
 }
 
 bool MusThreadProxy::GpuTakeDisplayControl() {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   auto callback = base::BindOnce(&MusThreadProxy::GpuTakeDisplayControlCallback,
                                  weak_ptr_factory_.GetWeakPtr());
   auto safe_callback = CreateSafeOnceCallback(std::move(callback));
@@ -258,8 +311,9 @@
 }
 
 bool MusThreadProxy::GpuRelinquishDisplayControl() {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   auto callback =
       base::BindOnce(&MusThreadProxy::GpuRelinquishDisplayControlCallback,
                      weak_ptr_factory_.GetWeakPtr());
@@ -273,8 +327,9 @@
 
 bool MusThreadProxy::GpuAddGraphicsDevice(const base::FilePath& path,
                                           const base::FileDescriptor& fd) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE, base::Bind(&DrmThread::AddGraphicsDevice,
                             base::Unretained(drm_thread_), path, fd));
@@ -282,8 +337,9 @@
 }
 
 bool MusThreadProxy::GpuRemoveGraphicsDevice(const base::FilePath& path) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE, base::Bind(&DrmThread::RemoveGraphicsDevice,
                             base::Unretained(drm_thread_), path));
@@ -291,8 +347,9 @@
 }
 
 bool MusThreadProxy::GpuGetHDCPState(int64_t display_id) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   auto callback = base::BindOnce(&MusThreadProxy::GpuGetHDCPStateCallback,
                                  weak_ptr_factory_.GetWeakPtr());
   auto safe_callback = CreateSafeOnceCallback(std::move(callback));
@@ -306,7 +363,8 @@
 bool MusThreadProxy::GpuSetHDCPState(int64_t display_id,
                                      display::HDCPState state) {
   DCHECK(on_window_server_thread_.CalledOnValidThread());
-  DCHECK(drm_thread_->IsRunning());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   auto callback = base::BindOnce(&MusThreadProxy::GpuSetHDCPStateCallback,
                                  weak_ptr_factory_.GetWeakPtr());
   auto safe_callback = CreateSafeOnceCallback(std::move(callback));
@@ -322,8 +380,9 @@
     const std::vector<display::GammaRampRGBEntry>& degamma_lut,
     const std::vector<display::GammaRampRGBEntry>& gamma_lut,
     const std::vector<float>& correction_matrix) {
-  DCHECK(drm_thread_->IsRunning());
   DCHECK(on_window_server_thread_.CalledOnValidThread());
+  if (!drm_thread_ || !drm_thread_->IsRunning())
+    return false;
   drm_thread_->task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&DrmThread::SetColorCorrection, base::Unretained(drm_thread_),
@@ -333,9 +392,10 @@
 
 void MusThreadProxy::GpuCheckOverlayCapabilitiesCallback(
     gfx::AcceleratedWidget widget,
-    const std::vector<OverlayCheck_Params>& overlays) const {
+    const std::vector<OverlayCheck_Params>& overlays,
+    const std::vector<OverlayCheckReturn_Params>& returns) const {
   DCHECK(on_window_server_thread_.CalledOnValidThread());
-  overlay_manager_->GpuSentOverlayResult(widget, overlays);
+  overlay_manager_->GpuSentOverlayResult(widget, overlays, returns);
 }
 
 void MusThreadProxy::GpuConfigureNativeDisplayCallback(int64_t display_id,
diff --git a/ui/ozone/platform/drm/mus_thread_proxy.h b/ui/ozone/platform/drm/mus_thread_proxy.h
index 2926d26..0353e76 100644
--- a/ui/ozone/platform/drm/mus_thread_proxy.h
+++ b/ui/ozone/platform/drm/mus_thread_proxy.h
@@ -20,6 +20,10 @@
 class SingleThreadTaskRunner;
 }
 
+namespace service_manager {
+class Connector;
+}
+
 namespace ui {
 
 class DrmDisplayHostManager;
@@ -28,23 +32,6 @@
 class GpuThreadObserver;
 class MusThreadProxy;
 
-// Forwarding proxy to handle ownership semantics.
-class CursorProxyThread : public DrmCursorProxy {
- public:
-  explicit CursorProxyThread(MusThreadProxy* mus_thread_proxy);
-  ~CursorProxyThread() override;
-
- private:
-  // DrmCursorProxy.
-  void CursorSet(gfx::AcceleratedWidget window,
-                 const std::vector<SkBitmap>& bitmaps,
-                 const gfx::Point& point,
-                 int frame_delay_ms) override;
-  void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override;
-  void InitializeOnEvdev() override;
-  MusThreadProxy* const mus_thread_proxy_;  // Not owned.
-};
-
 // In Mus, the window server thread (analogous to Chrome's UI thread), GPU and
 // DRM threads coexist in a single Mus process. The |MusThreadProxy| connects
 // these threads together via cross-thread calls.
@@ -52,7 +39,7 @@
                        public InterThreadMessagingProxy,
                        public DrmCursorProxy {
  public:
-  MusThreadProxy();
+  MusThreadProxy(DrmCursor* cursor, service_manager::Connector* connector);
   ~MusThreadProxy() override;
 
   void StartDrmThread();
@@ -113,7 +100,7 @@
                  const gfx::Point& point,
                  int frame_delay_ms) override;
   void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override;
-  void InitializeOnEvdev() override;
+  void InitializeOnEvdevIfNecessary() override;
 
  private:
   void RunObservers();
@@ -121,7 +108,8 @@
 
   void GpuCheckOverlayCapabilitiesCallback(
       gfx::AcceleratedWidget widget,
-      const std::vector<OverlayCheck_Params>& overlays) const;
+      const std::vector<OverlayCheck_Params>& overlays,
+      const std::vector<OverlayCheckReturn_Params>& returns) const;
 
   void GpuConfigureNativeDisplayCallback(int64_t display_id,
                                          bool success) const;
@@ -145,6 +133,9 @@
 
   DrmDisplayHostManager* display_manager_;  // Not owned.
   DrmOverlayManager* overlay_manager_;      // Not owned.
+  DrmCursor* cursor_;                       // Not owned.
+
+  service_manager::Connector* connector_;
 
   base::ObserverList<GpuThreadObserver> gpu_thread_observers_;
 
diff --git a/ui/ozone/platform/drm/ozone_platform_gbm.cc b/ui/ozone/platform/drm/ozone_platform_gbm.cc
index 1ae295d..a324681b 100644
--- a/ui/ozone/platform/drm/ozone_platform_gbm.cc
+++ b/ui/ozone/platform/drm/ozone_platform_gbm.cc
@@ -124,8 +124,6 @@
       const gfx::Rect& bounds) override {
     GpuThreadAdapter* adapter = gpu_platform_support_host_.get();
     if (using_mojo_ || single_process_) {
-      DCHECK(drm_thread_proxy_)
-          << "drm_thread_proxy_ should exist (and be running) here.";
       adapter = mus_thread_proxy_.get();
     }
 
@@ -176,15 +174,13 @@
       gl_api_loader_.reset(new GlApiLoader());
 
     if (using_mojo_) {
-      DCHECK(args.connector);
-      mus_thread_proxy_.reset(new MusThreadProxy());
+      mus_thread_proxy_ =
+          base::MakeUnique<MusThreadProxy>(cursor_.get(), args.connector);
       adapter = mus_thread_proxy_.get();
-      cursor_->SetDrmCursorProxy(new CursorProxyMojo(args.connector));
     } else if (single_process_) {
-      mus_thread_proxy_.reset(new MusThreadProxy());
+      mus_thread_proxy_ =
+          base::MakeUnique<MusThreadProxy>(cursor_.get(), nullptr);
       adapter = mus_thread_proxy_.get();
-      cursor_->SetDrmCursorProxy(
-          new CursorProxyThread(mus_thread_proxy_.get()));
     } else {
       gpu_platform_support_host_.reset(
           new DrmGpuPlatformSupportHost(cursor_.get()));