diff --git a/DEPS b/DEPS
index 0d75b7d2..f13a8c6 100644
--- a/DEPS
+++ b/DEPS
@@ -196,7 +196,7 @@
     Var('chromium_git') + '/external/bidichecker/lib.git' + '@' + '97f2aa645b74c28c57eca56992235c79850fa9e0',
 
   'src/third_party/webgl/src':
-    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'c91689d6df5536fefaa07a459c80c210bd580a1b',
+    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '73b300f24942adf7013de30ccdc6a2cc88105e72',
 
   'src/third_party/webdriver/pylib':
     Var('chromium_git') + '/external/selenium/py.git' + '@' + '5fd78261a75fe08d27ca4835fb6c5ce4b42275bd',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 60d2237..1b1de9f 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -402,6 +402,10 @@
     "system/network/vpn_list_view.h",
     "system/networking_config_delegate.cc",
     "system/networking_config_delegate.h",
+    "system/night_light/night_light_controller.cc",
+    "system/night_light/night_light_controller.h",
+    "system/night_light/tray_night_light.cc",
+    "system/night_light/tray_night_light.h",
     "system/overview/overview_button_tray.cc",
     "system/overview/overview_button_tray.h",
     "system/palette/common_palette_tool.cc",
@@ -1193,6 +1197,8 @@
     "system/network/sms_observer_unittest.cc",
     "system/network/tray_network_unittest.cc",
     "system/network/vpn_list_unittest.cc",
+    "system/night_light/night_light_controller_unittest.cc",
+    "system/night_light/tray_night_light_unittest.cc",
     "system/overview/overview_button_tray_unittest.cc",
     "system/palette/mock_palette_tool_delegate.cc",
     "system/palette/mock_palette_tool_delegate.h",
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 34a9e15..b41d8ad 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -213,7 +213,9 @@
       <message name="IDS_ASH_STATUS_TRAY_MESSAGE_OUT_OF_USERS" desc="The error message when all the users are added into multi-profile session.">
         All available users have already been added to this session.
       </message>
-
+      <message name="IDS_ASH_STATUS_TRAY_NIGHT_LIGHT" desc="The label used for the button in the status tray to toggle the Night Light feature (which controls the color temperature of the screen) on or off.">
+        Toggle Night Light
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_CAST" desc="The label used as the header in the cast popup.">
         Cast screen
       </message>
diff --git a/ash/metrics/user_metrics_action.h b/ash/metrics/user_metrics_action.h
index 6fa1dd4..ea297ff 100644
--- a/ash/metrics/user_metrics_action.h
+++ b/ash/metrics/user_metrics_action.h
@@ -124,6 +124,7 @@
   UMA_TOUCHSCREEN_TAP_DOWN,
   UMA_TRAY_HELP,
   UMA_TRAY_LOCK_SCREEN,
+  UMA_TRAY_NIGHT_LIGHT,
   UMA_TRAY_OVERVIEW,
   UMA_TRAY_SETTINGS,
   UMA_TRAY_SHUT_DOWN,
diff --git a/ash/metrics/user_metrics_recorder.cc b/ash/metrics/user_metrics_recorder.cc
index a6ff35ea..a28b656b 100644
--- a/ash/metrics/user_metrics_recorder.cc
+++ b/ash/metrics/user_metrics_recorder.cc
@@ -547,6 +547,9 @@
     case UMA_TRAY_LOCK_SCREEN:
       RecordAction(UserMetricsAction("Tray_LockScreen"));
       break;
+    case UMA_TRAY_NIGHT_LIGHT:
+      RecordAction(UserMetricsAction("Tray_NightLight"));
+      break;
     case UMA_TRAY_OVERVIEW:
       RecordAction(UserMetricsAction("Tray_Overview"));
       break;
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 622ffc4a..1e57c91da 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -176,6 +176,10 @@
     "system_menu_update.icon",
     "system_menu_new_user.1x.icon",
     "system_menu_new_user.icon",
+    "system_menu_night_light_off.1x.icon",
+    "system_menu_night_light_off.icon",
+    "system_menu_night_light_on.1x.icon",
+    "system_menu_night_light_on.icon",
     "system_menu_usb.1x.icon",
     "system_menu_usb.icon",
     "system_menu_videocam.1x.icon",
@@ -204,6 +208,8 @@
     "system_tray_caps_lock.icon",
     "system_tray_cast.1x.icon",
     "system_tray_cast.icon",
+    "system_tray_night_light.1x.icon",
+    "system_tray_night_light.icon",
     "system_tray_recording.1x.icon",
     "system_tray_recording.icon",
     "system_tray_rotation_lock_auto.1x.icon",
diff --git a/ash/resources/vector_icons/system_menu_night_light_off.1x.icon b/ash/resources/vector_icons/system_menu_night_light_off.1x.icon
new file mode 100644
index 0000000..e7f4aa3
--- /dev/null
+++ b/ash/resources/vector_icons/system_menu_night_light_off.1x.icon
@@ -0,0 +1,34 @@
+// 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 17.15f, 13.59f,
+LINE_TO, 9.44f, 5.88f,
+R_CUBIC_TO, 0.7f, -0.72f, 1.57f, -1.27f, 2.56f, -1.59f,
+R_CUBIC_TO, -0.59f, -0.19f, -1.23f, -0.29f, -1.88f, -0.29f,
+R_CUBIC_TO, -0.76f, 0, -1.49f, 0.14f, -2.17f, 0.39f,
+LINE_TO, 6.41f, 2.85f,
+CUBIC_TO, 7.49f, 2.31f, 8.71f, 2, 10, 2,
+R_CUBIC_TO, 4.42f, 0, 8, 3.58f, 8, 8,
+R_CUBIC_TO, 0, 1.29f, -0.31f, 2.51f, -0.85f, 3.59f,
+CLOSE,
+R_MOVE_TO, -1.78f, 2.34f,
+CUBIC_TO, 13.95f, 17.22f, 12.06f, 18, 10, 18,
+R_CUBIC_TO, -4.42f, 0, -8, -3.58f, -8, -8,
+R_CUBIC_TO, 0, -2.06f, 0.78f, -3.95f, 2.07f, -5.37f,
+R_LINE_TO, 1.43f, 1.43f,
+CUBIC_TO, 4.57f, 7.12f, 4, 8.49f, 4, 10,
+R_CUBIC_TO, 0, 3.31f, 2.74f, 6, 6.12f, 6,
+R_CUBIC_TO, 0.66f, 0, 1.29f, -0.1f, 1.88f, -0.29f,
+R_CUBIC_TO, -2.46f, -0.78f, -4.24f, -3.04f, -4.24f, -5.71f,
+R_CUBIC_TO, 0, -0.51f, 0.07f, -1.01f, 0.19f, -1.49f,
+R_LINE_TO, 7.41f, 7.42f,
+CLOSE,
+MOVE_TO, 2.5f, 3.06f,
+LINE_TO, 3.56f, 2,
+R_LINE_TO, 14.31f, 14.31f,
+R_LINE_TO, -1.06f, 1.06f,
+LINE_TO, 2.5f, 3.06f,
+CLOSE,
+END
\ No newline at end of file
diff --git a/ash/resources/vector_icons/system_menu_night_light_off.icon b/ash/resources/vector_icons/system_menu_night_light_off.icon
new file mode 100644
index 0000000..1e7bc9f
--- /dev/null
+++ b/ash/resources/vector_icons/system_menu_night_light_off.icon
@@ -0,0 +1,34 @@
+// 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.
+
+CANVAS_DIMENSIONS, 40,
+MOVE_TO, 34.3f, 27.18f,
+LINE_TO, 18.38f, 11.26f,
+CUBIC_TO, 19.89f, 9.6f, 21.82f, 8.33f, 24, 7.63f,
+CUBIC_TO, 22.74f, 7.22f, 21.4f, 7, 20, 7,
+R_CUBIC_TO, -1.74f, 0, -3.4f, 0.34f, -4.92f, 0.96f,
+LINE_TO, 12.82f, 5.7f,
+CUBIC_TO, 14.98f, 4.61f, 17.42f, 4, 20, 4,
+R_CUBIC_TO, 8.83f, 0, 16, 7.17f, 16, 16,
+R_CUBIC_TO, 0, 2.58f, -0.61f, 5.02f, -1.7f, 7.18f,
+CLOSE,
+R_MOVE_TO, -3.56f, 4.68f,
+CUBIC_TO, 27.9f, 34.43f, 24.13f, 36, 20, 36,
+R_CUBIC_TO, -8.83f, 0, -16, -7.17f, -16, -16,
+R_CUBIC_TO, 0, -4.13f, 1.57f, -7.9f, 4.14f, -10.74f,
+R_LINE_TO, 2.12f, 2.12f,
+CUBIC_TO, 8.23f, 13.68f, 7, 16.7f, 7, 20,
+R_CUBIC_TO, 0, 7.18f, 5.82f, 13, 13, 13,
+R_CUBIC_TO, 1.4f, 0, 2.74f, -0.22f, 4, -0.63f,
+R_CUBIC_TO, -5.22f, -1.69f, -9, -6.59f, -9, -12.37f,
+R_CUBIC_TO, 0, -1.18f, 0.16f, -2.33f, 0.46f, -3.42f,
+LINE_TO, 30.74f, 31.86f,
+CLOSE,
+MOVE_TO, 5, 6.12f,
+LINE_TO, 7.12f, 4,
+R_LINE_TO, 28.63f, 28.63f,
+R_LINE_TO, -2.12f, 2.12f,
+LINE_TO, 5, 6.12f,
+CLOSE,
+END
\ No newline at end of file
diff --git a/ash/resources/vector_icons/system_menu_night_light_on.1x.icon b/ash/resources/vector_icons/system_menu_night_light_on.1x.icon
new file mode 100644
index 0000000..64623ec
--- /dev/null
+++ b/ash/resources/vector_icons/system_menu_night_light_on.1x.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 10, 2,
+R_CUBIC_TO, 4.42f, 0, 8, 3.58f, 8, 8,
+R_CUBIC_TO, 0, 4.42f, -3.58f, 8, -8, 8,
+R_CUBIC_TO, -4.42f, 0, -8, -3.58f, -8, -8,
+R_CUBIC_TO, 0, -4.42f, 3.58f, -8, 8, -8,
+CLOSE,
+R_MOVE_TO, -0.65f, 2,
+CUBIC_TO, 6.4f, 4, 4, 6.69f, 4, 10,
+R_CUBIC_TO, 0, 3.31f, 2.4f, 6, 5.35f, 6,
+R_CUBIC_TO, 0.57f, 0, 1.13f, -0.1f, 1.65f, -0.29f,
+CUBIC_TO, 8.85f, 14.93f, 7.29f, 12.67f, 7.29f, 10,
+R_CUBIC_TO, 0, -2.67f, 1.56f, -4.93f, 3.71f, -5.71f,
+CUBIC_TO, 10.48f, 4.1f, 9.93f, 4, 9.35f, 4,
+CLOSE,
+END
\ No newline at end of file
diff --git a/ash/resources/vector_icons/system_menu_night_light_on.icon b/ash/resources/vector_icons/system_menu_night_light_on.icon
new file mode 100644
index 0000000..df0d3c8
--- /dev/null
+++ b/ash/resources/vector_icons/system_menu_night_light_on.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 40,
+MOVE_TO, 16, 0,
+CUBIC_TO, 24.83f, 0, 32, 7.17f, 32, 16,
+CUBIC_TO, 32, 24.83f, 24.83f, 32, 16, 32,
+CUBIC_TO, 7.17f, 32, 0, 24.83f, 0, 16,
+CUBIC_TO, 0, 7.17f, 7.17f, 0, 16, 0,
+CLOSE,
+MOVE_TO, 20, 3.63f,
+CUBIC_TO, 18.74f, 3.22f, 17.4f, 3, 16, 3,
+CUBIC_TO, 8.82f, 3, 3, 8.82f, 3, 16,
+CUBIC_TO, 3, 23.18f, 8.82f, 29, 16, 29,
+CUBIC_TO, 17.4f, 29, 18.74f, 28.78f, 20, 28.37f,
+CUBIC_TO, 14.78f, 26.69f, 11, 21.78f, 11, 16,
+CUBIC_TO, 11, 10.22f, 14.78f, 5.31f, 20, 3.63f,
+LINE_TO, 20, 3.63f,
+CLOSE,
+END
\ No newline at end of file
diff --git a/ash/resources/vector_icons/system_tray_night_light.1x.icon b/ash/resources/vector_icons/system_tray_night_light.1x.icon
new file mode 100644
index 0000000..17b0c5c
--- /dev/null
+++ b/ash/resources/vector_icons/system_tray_night_light.1x.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 16,
+MOVE_TO, 7, 0,
+CUBIC_TO, 10.86f, 0, 14, 3.14f, 14, 7,
+CUBIC_TO, 14, 10.86f, 10.86f, 14, 7, 14,
+CUBIC_TO, 3.14f, 14, 0, 10.86f, 0, 7,
+CUBIC_TO, 0, 3.14f, 3.14f, 0, 7, 0,
+CLOSE,
+MOVE_TO, 9, 1.29f,
+CUBIC_TO, 8.41f, 1.1f, 7.77f, 1, 7.12f, 1,
+CUBIC_TO, 3.74f, 1, 1, 3.69f, 1, 7,
+CUBIC_TO, 1, 10.31f, 3.74f, 13, 7.12f, 13,
+CUBIC_TO, 7.77f, 13, 8.41f, 12.9f, 9, 12.71f,
+CUBIC_TO, 6.54f, 11.93f, 4.76f, 9.67f, 4.76f, 7,
+CUBIC_TO, 4.76f, 4.33f, 6.54f, 2.07f, 9, 1.29f,
+LINE_TO, 9, 1.29f,
+CLOSE,
+END
\ No newline at end of file
diff --git a/ash/resources/vector_icons/system_tray_night_light.icon b/ash/resources/vector_icons/system_tray_night_light.icon
new file mode 100644
index 0000000..863ad1f
--- /dev/null
+++ b/ash/resources/vector_icons/system_tray_night_light.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 32,
+MOVE_TO, 13, 0,
+CUBIC_TO, 20.18f, 0, 26, 5.82f, 26, 13,
+CUBIC_TO, 26, 20.18f, 20.18f, 26, 13, 26,
+CUBIC_TO, 5.82f, 26, 0, 20.18f, 0, 13,
+CUBIC_TO, 0, 5.82f, 5.82f, 0, 13, 0,
+CLOSE,
+MOVE_TO, 16, 2.53f,
+CUBIC_TO, 14.96f, 2.19f, 13.86f, 2, 12.71f, 2,
+CUBIC_TO, 6.79f, 2, 2, 6.92f, 2, 13,
+CUBIC_TO, 2, 19.08f, 6.79f, 24, 12.71f, 24,
+CUBIC_TO, 13.86f, 24, 14.96f, 23.81f, 16, 23.47f,
+CUBIC_TO, 11.7f, 22.04f, 8.59f, 17.89f, 8.59f, 13,
+CUBIC_TO, 8.59f, 8.11f, 11.7f, 3.96f, 16, 2.53f,
+LINE_TO, 16, 2.53f,
+CLOSE,
+END
\ No newline at end of file
diff --git a/ash/shell.cc b/ash/shell.cc
index 3988f49..525229c 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -74,6 +74,7 @@
 #include "ash/system/locale/locale_notification_controller.h"
 #include "ash/system/network/sms_observer.h"
 #include "ash/system/network/vpn_list.h"
+#include "ash/system/night_light/night_light_controller.h"
 #include "ash/system/power/power_event_observer.h"
 #include "ash/system/power/power_status.h"
 #include "ash/system/power/video_activity_notifier.h"
@@ -319,6 +320,11 @@
               switches::kUseIMEService));
 }
 
+// static
+void Shell::RegisterPrefs(PrefRegistrySimple* registry) {
+  NightLightController::RegisterPrefs(registry);
+}
+
 views::NonClientFrameView* Shell::CreateDefaultNonClientFrameView(
     views::Widget* widget) {
   // Use translucent-style window frames for dialogs.
@@ -562,6 +568,8 @@
       media_controller_(base::MakeUnique<MediaController>()),
       new_window_controller_(base::MakeUnique<NewWindowController>()),
       session_controller_(base::MakeUnique<SessionController>()),
+      night_light_controller_(
+          base::MakeUnique<NightLightController>(session_controller_.get())),
       shelf_controller_(base::MakeUnique<ShelfController>()),
       shell_delegate_(std::move(shell_delegate)),
       shutdown_controller_(base::MakeUnique<ShutdownController>()),
diff --git a/ash/shell.h b/ash/shell.h
index 94fc96da..53363db 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -25,6 +25,7 @@
 #include "ui/wm/core/cursor_manager.h"
 #include "ui/wm/public/activation_change_observer.h"
 
+class PrefRegistrySimple;
 class PrefService;
 
 namespace aura {
@@ -121,6 +122,7 @@
 class MouseCursorEventFilter;
 class MruWindowTracker;
 class NewWindowController;
+class NightLightController;
 class OverlayEventFilter;
 class PaletteDelegate;
 class PartialMagnificationController;
@@ -261,6 +263,9 @@
   static Config GetAshConfig();
   static bool ShouldUseIMEService();
 
+  // Registers all ash related prefs to the given |registry|.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
   // Creates a default views::NonClientFrameView for use by windows in the
   // Ash environment.
   views::NonClientFrameView* CreateDefaultNonClientFrameView(
@@ -327,6 +332,9 @@
   NewWindowController* new_window_controller() {
     return new_window_controller_.get();
   }
+  NightLightController* night_light_controller() {
+    return night_light_controller_.get();
+  }
   SessionController* session_controller() { return session_controller_.get(); }
   ShelfController* shelf_controller() { return shelf_controller_.get(); }
   ShelfModel* shelf_model();
@@ -686,6 +694,7 @@
   std::unique_ptr<PaletteDelegate> palette_delegate_;
   std::unique_ptr<ResizeShadowController> resize_shadow_controller_;
   std::unique_ptr<SessionController> session_controller_;
+  std::unique_ptr<NightLightController> night_light_controller_;
   std::unique_ptr<ShelfController> shelf_controller_;
   std::unique_ptr<ShelfWindowWatcher> shelf_window_watcher_;
   std::unique_ptr<ShellDelegate> shell_delegate_;
diff --git a/ash/system/night_light/night_light_controller.cc b/ash/system/night_light/night_light_controller.cc
new file mode 100644
index 0000000..bf1cae4
--- /dev/null
+++ b/ash/system/night_light/night_light_controller.cc
@@ -0,0 +1,142 @@
+// 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 "ash/system/night_light/night_light_controller.h"
+
+#include "ash/session/session_controller.h"
+#include "ash/shell.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+
+namespace ash {
+
+namespace {
+
+// The key of the dictionary value in the user's pref service that contains all
+// the NightLight settings.
+constexpr char kNightLightPrefsKey[] = "prefs.night_light_prefs";
+
+// Keys to the various NightLight settings inside its dictionary value.
+constexpr char kStatusKey[] = "night_light_status";
+constexpr char kColorTemperatureKey[] = "night_light_color_temperature";
+
+// The duration of the temperature change animation when the change is a result
+// of a manual user setting.
+// TODO(afakhry): Add automatic schedule animation duration when you implement
+// that part. It should be large enough (20 seconds as agreed) to give the user
+// a nice smooth transition.
+constexpr int kManualToggleAnimationDurationSec = 2;
+
+}  // namespace
+
+NightLightController::NightLightController(
+    SessionController* session_controller)
+    : session_controller_(session_controller) {
+  session_controller_->AddObserver(this);
+}
+
+NightLightController::~NightLightController() {
+  session_controller_->RemoveObserver(this);
+}
+
+// static
+void NightLightController::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(kNightLightPrefsKey);
+}
+
+void NightLightController::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void NightLightController::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void NightLightController::Toggle() {
+  SetEnabled(!enabled_);
+}
+
+void NightLightController::SetEnabled(bool enabled) {
+  if (enabled_ == enabled)
+    return;
+
+  enabled_ = enabled;
+  Refresh();
+  NotifyStatusChanged();
+  PersistUserPrefs();
+}
+
+void NightLightController::SetColorTemperature(float temperature) {
+  // TODO(afakhry): Spport changing the temperature when you implement the
+  // settings part of this feature. Right now we'll keep it fixed at the value
+  // |color_temperature_| whenever NightLight is turned on.
+
+  for (aura::Window* root_window : Shell::GetAllRootWindows()) {
+    ui::Layer* layer = root_window->layer();
+
+    ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
+    settings.SetTransitionDuration(
+        base::TimeDelta::FromSeconds(kManualToggleAnimationDurationSec));
+    settings.SetPreemptionStrategy(
+        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+
+    layer->SetLayerTemperature(temperature);
+  }
+}
+
+void NightLightController::OnActiveUserSessionChanged(
+    const AccountId& account_id) {
+  // Initial login and user switching in multi profiles.
+  InitFromUserPrefs();
+}
+
+void NightLightController::Refresh() {
+  SetColorTemperature(enabled_ ? color_temperature_ : 0.0f);
+}
+
+void NightLightController::InitFromUserPrefs() {
+  auto* pref_service = Shell::Get()->GetActiveUserPrefService();
+  if (!pref_service) {
+    // The pref_service can be NULL in ash_unittests.
+    return;
+  }
+
+  const base::DictionaryValue* night_light_prefs =
+      pref_service->GetDictionary(kNightLightPrefsKey);
+  bool enabled = false;
+  night_light_prefs->GetBoolean(kStatusKey, &enabled);
+  enabled_ = enabled;
+
+  double color_temperature = 0.5;
+  night_light_prefs->GetDouble(kColorTemperatureKey, &color_temperature);
+  color_temperature_ = static_cast<float>(color_temperature);
+
+  Refresh();
+  NotifyStatusChanged();
+}
+
+void NightLightController::PersistUserPrefs() {
+  auto* pref_service = ash::Shell::Get()->GetActiveUserPrefService();
+  if (!pref_service) {
+    // The pref_service can be NULL in ash_unittests.
+    return;
+  }
+  DictionaryPrefUpdate pref_updater(pref_service, kNightLightPrefsKey);
+
+  base::DictionaryValue* dictionary = pref_updater.Get();
+  dictionary->SetBoolean(kStatusKey, enabled_);
+  dictionary->SetDouble(kColorTemperatureKey,
+                        static_cast<double>(color_temperature_));
+}
+
+void NightLightController::NotifyStatusChanged() {
+  for (auto& observer : observers_)
+    observer.OnNightLightEnabledChanged(enabled_);
+}
+
+}  // namespace ash
diff --git a/ash/system/night_light/night_light_controller.h b/ash/system/night_light/night_light_controller.h
new file mode 100644
index 0000000..0b0e5c1b
--- /dev/null
+++ b/ash/system/night_light/night_light_controller.h
@@ -0,0 +1,79 @@
+// 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 ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_CONTROLLER_H_
+#define ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/session/session_observer.h"
+#include "base/observer_list.h"
+
+class PrefRegistrySimple;
+
+namespace ash {
+
+class SessionController;
+
+// Controls the NightLight feature that adjusts the color temperature of the
+// screen.
+class ASH_EXPORT NightLightController : public SessionObserver {
+ public:
+  class Observer {
+   public:
+    // Emitted when the NightLight status is changed.
+    virtual void OnNightLightEnabledChanged(bool enabled) = 0;
+
+   protected:
+    virtual ~Observer() {}
+  };
+
+  explicit NightLightController(SessionController* session_controller);
+  ~NightLightController() override;
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  float color_temperature() const { return color_temperature_; }
+  bool enabled() const { return enabled_; }
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  void Toggle();
+
+  void SetEnabled(bool enabled);
+
+  // Set the screen color temperature. |temperature| should be a value from
+  // 0.0f (least warm) to 1.0f (most warm).
+  void SetColorTemperature(float temperature);
+
+  // ash::SessionObserver:
+  void OnActiveUserSessionChanged(const AccountId& account_id) override;
+
+ private:
+  void Refresh();
+
+  void InitFromUserPrefs();
+
+  void PersistUserPrefs();
+
+  void NotifyStatusChanged();
+
+  // The observed session controller instance from which we know when to
+  // initialize the NightLight settings from the user preferences.
+  SessionController* const session_controller_;
+
+  // The applied color temperature value when NightLight is turned ON. It's a
+  // value from 0.0f (least warm, default display color) to 1.0f (most warm).
+  float color_temperature_ = 0.5f;
+
+  bool enabled_ = false;
+
+  base::ObserverList<Observer> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(NightLightController);
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_CONTROLLER_H_
diff --git a/ash/system/night_light/night_light_controller_unittest.cc b/ash/system/night_light/night_light_controller_unittest.cc
new file mode 100644
index 0000000..9f760448f
--- /dev/null
+++ b/ash/system/night_light/night_light_controller_unittest.cc
@@ -0,0 +1,137 @@
+// 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 "ash/system/night_light/night_light_controller.h"
+
+#include "ash/public/cpp/config.h"
+#include "ash/public/cpp/session_types.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/ash_test_helper.h"
+#include "ash/test/test_session_controller_client.h"
+#include "ash/test/test_shell_delegate.h"
+#include "base/macros.h"
+#include "components/prefs/testing_pref_service.h"
+#include "ui/compositor/layer.h"
+
+namespace ash {
+
+namespace {
+
+constexpr char user_1_email[] = "user1@nightlight";
+constexpr char user_2_email[] = "user2@nightlight";
+
+NightLightController* GetController() {
+  return Shell::Get()->night_light_controller();
+}
+
+// Tests that the color temperatures of all root layers are equal to the given
+// |expected_temperature| and returns true if so, or false otherwise.
+bool TestLayersTemperature(float expected_temperature) {
+  for (aura::Window* root_window : ash::Shell::GetAllRootWindows()) {
+    ui::Layer* layer = root_window->layer();
+    if (expected_temperature != layer->layer_temperature())
+      return false;
+  }
+
+  return true;
+}
+
+class TestObserver : public NightLightController::Observer {
+ public:
+  TestObserver() { GetController()->AddObserver(this); }
+  ~TestObserver() override { GetController()->RemoveObserver(this); }
+
+  // ash::NightLightController::Observer:
+  void OnNightLightEnabledChanged(bool enabled) override { status_ = enabled; }
+
+  bool status() const { return status_; }
+
+ private:
+  bool status_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+class NightLightTest : public test::AshTestBase {
+ public:
+  NightLightTest() = default;
+  ~NightLightTest() override = default;
+
+  void CreateTestUserSessions() {
+    GetSessionControllerClient()->Reset();
+    GetSessionControllerClient()->AddUserSession(user_1_email);
+    GetSessionControllerClient()->AddUserSession(user_2_email);
+  }
+
+  void SwitchActiveUser(const std::string& email) {
+    GetSessionControllerClient()->SwitchActiveUser(
+        AccountId::FromUserEmail(email));
+  }
+
+  void InjectTestPrefService(PrefService* pref_service) {
+    ash_test_helper()->test_shell_delegate()->set_active_user_pref_service(
+        pref_service);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NightLightTest);
+};
+
+// Tests toggling NightLight on / off and makes sure the observer is updated and
+// the layer temperatures are modified.
+TEST_F(NightLightTest, TestToggle) {
+  UpdateDisplay("800x600,800x600");
+
+  TestObserver observer;
+  NightLightController* controller = GetController();
+  controller->SetEnabled(false);
+  ASSERT_FALSE(controller->enabled());
+  EXPECT_TRUE(TestLayersTemperature(0.0f));
+  controller->Toggle();
+  EXPECT_TRUE(controller->enabled());
+  EXPECT_TRUE(observer.status());
+  EXPECT_TRUE(TestLayersTemperature(GetController()->color_temperature()));
+  controller->Toggle();
+  EXPECT_FALSE(controller->enabled());
+  EXPECT_FALSE(observer.status());
+  EXPECT_TRUE(TestLayersTemperature(0.0f));
+}
+
+// Tests that switching users retrieves NightLight settings for the active
+// user's prefs.
+TEST_F(NightLightTest, TestUserSwitchAndSettingsPersistence) {
+  if (Shell::GetAshConfig() == Config::MASH) {
+    // User switching doesn't work on mash.
+    return;
+  }
+
+  CreateTestUserSessions();
+  TestingPrefServiceSimple user_1_pref_service;
+  TestingPrefServiceSimple user_2_pref_service;
+  NightLightController::RegisterPrefs(user_1_pref_service.registry());
+  NightLightController::RegisterPrefs(user_2_pref_service.registry());
+
+  // Simulate user 1 login.
+  InjectTestPrefService(&user_1_pref_service);
+  SwitchActiveUser(user_1_email);
+  NightLightController* controller = GetController();
+  controller->SetEnabled(true);
+  EXPECT_TRUE(GetController()->enabled());
+  EXPECT_TRUE(TestLayersTemperature(GetController()->color_temperature()));
+
+  // Switch to user 2, and expect NightLight to be disabled.
+  InjectTestPrefService(&user_2_pref_service);
+  SwitchActiveUser(user_2_email);
+  EXPECT_FALSE(controller->enabled());
+
+  // Switch back to user 1, to find NightLight is still enabled.
+  InjectTestPrefService(&user_1_pref_service);
+  SwitchActiveUser(user_1_email);
+  EXPECT_TRUE(controller->enabled());
+}
+
+}  // namespace
+
+}  // namespace ash
diff --git a/ash/system/night_light/tray_night_light.cc b/ash/system/night_light/tray_night_light.cc
new file mode 100644
index 0000000..bbd64b3a
--- /dev/null
+++ b/ash/system/night_light/tray_night_light.cc
@@ -0,0 +1,31 @@
+// 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 "ash/system/night_light/tray_night_light.h"
+
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/shell.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+TrayNightLight::TrayNightLight(SystemTray* system_tray)
+    : TrayImageItem(system_tray, kSystemTrayNightLightIcon, UMA_NIGHT_LIGHT) {
+  Shell::Get()->night_light_controller()->AddObserver(this);
+}
+
+TrayNightLight::~TrayNightLight() {
+  Shell::Get()->night_light_controller()->RemoveObserver(this);
+}
+
+void TrayNightLight::OnNightLightEnabledChanged(bool enabled) {
+  if (tray_view())
+    tray_view()->SetVisible(enabled);
+}
+
+bool TrayNightLight::GetInitialVisibility() {
+  return Shell::Get()->night_light_controller()->enabled();
+}
+
+}  // namespace ash
diff --git a/ash/system/night_light/tray_night_light.h b/ash/system/night_light/tray_night_light.h
new file mode 100644
index 0000000..5a38d3f9
--- /dev/null
+++ b/ash/system/night_light/tray_night_light.h
@@ -0,0 +1,34 @@
+// 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 ASH_SYSTEM_NIGHT_LIGHT_TRAY_NIGHT_LIGHT_H_
+#define ASH_SYSTEM_NIGHT_LIGHT_TRAY_NIGHT_LIGHT_H_
+
+#include "ash/system/night_light/night_light_controller.h"
+#include "ash/system/tray/tray_image_item.h"
+#include "base/macros.h"
+
+namespace ash {
+
+// Shows the NightLight icon in the system tray whenever NightLight is enabled
+// and active.
+class TrayNightLight : public TrayImageItem,
+                       public NightLightController::Observer {
+ public:
+  explicit TrayNightLight(SystemTray* system_tray);
+  ~TrayNightLight() override;
+
+  // ash::NightLightController::Observer:
+  void OnNightLightEnabledChanged(bool enabled) override;
+
+  // ash::TrayImageItem:
+  bool GetInitialVisibility() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TrayNightLight);
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_NIGHT_LIGHT_TRAY_NIGHT_LIGHT_H_
diff --git a/ash/system/night_light/tray_night_light_unittest.cc b/ash/system/night_light/tray_night_light_unittest.cc
new file mode 100644
index 0000000..a99b68b
--- /dev/null
+++ b/ash/system/night_light/tray_night_light_unittest.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 "ash/system/night_light/tray_night_light.h"
+
+#include "ash/shell.h"
+#include "ash/system/night_light/night_light_controller.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/test/ash_test_base.h"
+
+namespace ash {
+
+namespace {
+
+using TrayNightLightTest = test::AshTestBase;
+
+// Tests that when NightLight is active, its tray icon in the System Tray is
+// visible.
+TEST_F(TrayNightLightTest, TestNightLightTrayVisibility) {
+  SystemTray* tray = GetPrimarySystemTray();
+  TrayNightLight* tray_night_light = tray->tray_night_light();
+  NightLightController* controller = Shell::Get()->night_light_controller();
+  controller->SetEnabled(false);
+  ASSERT_FALSE(controller->enabled());
+  EXPECT_FALSE(tray_night_light->tray_view()->visible());
+  controller->SetEnabled(true);
+  EXPECT_TRUE(tray_night_light->tray_view()->visible());
+}
+
+}  // namespace
+
+}  // namespace ash
diff --git a/ash/system/tiles/tiles_default_view.cc b/ash/system/tiles/tiles_default_view.cc
index d5c51a7..11fd29ee6 100644
--- a/ash/system/tiles/tiles_default_view.cc
+++ b/ash/system/tiles/tiles_default_view.cc
@@ -11,6 +11,7 @@
 #include "ash/shell_port.h"
 #include "ash/shutdown_controller.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/system/night_light/night_light_controller.h"
 #include "ash/system/tray/system_menu_button.h"
 #include "ash/system/tray/system_tray.h"
 #include "ash/system/tray/system_tray_controller.h"
@@ -41,6 +42,7 @@
     : owner_(owner),
       settings_button_(nullptr),
       help_button_(nullptr),
+      night_light_button_(nullptr),
       lock_button_(nullptr),
       power_button_(nullptr) {
   DCHECK(owner_);
@@ -60,13 +62,12 @@
   // Show the buttons in this row as disabled if the user is at the login
   // screen, lock screen, or in a secondary account flow. The exception is
   // |power_button_| which is always shown as enabled.
-  const bool disable_buttons = !TrayPopupUtils::CanOpenWebUISettings();
+  const bool can_show_web_ui = TrayPopupUtils::CanOpenWebUISettings();
 
   settings_button_ = new SystemMenuButton(
       this, TrayPopupInkDropStyle::HOST_CENTERED, kSystemMenuSettingsIcon,
       IDS_ASH_STATUS_TRAY_SETTINGS);
-  if (disable_buttons)
-    settings_button_->SetEnabled(false);
+  settings_button_->SetEnabled(can_show_web_ui);
   AddChildView(settings_button_);
   AddChildView(TrayPopupUtils::CreateVerticalSeparator());
 
@@ -80,16 +81,25 @@
     // flipping must be disabled. (crbug.com/475237)
     help_button_->EnableCanvasFlippingForRTLUI(false);
   }
-  if (disable_buttons)
-    help_button_->SetEnabled(false);
+  help_button_->SetEnabled(can_show_web_ui);
   AddChildView(help_button_);
   AddChildView(TrayPopupUtils::CreateVerticalSeparator());
 
+  night_light_button_ =
+      new SystemMenuButton(this, TrayPopupInkDropStyle::HOST_CENTERED,
+                           Shell::Get()->night_light_controller()->enabled()
+                               ? kSystemMenuNightLightOnIcon
+                               : kSystemMenuNightLightOffIcon,
+                           IDS_ASH_STATUS_TRAY_NIGHT_LIGHT);
+  night_light_button_->SetEnabled(can_show_web_ui);
+  AddChildView(night_light_button_);
+  AddChildView(TrayPopupUtils::CreateVerticalSeparator());
+
   lock_button_ =
       new SystemMenuButton(this, TrayPopupInkDropStyle::HOST_CENTERED,
                            kSystemMenuLockIcon, IDS_ASH_STATUS_TRAY_LOCK);
-  if (disable_buttons || !Shell::Get()->session_controller()->CanLockScreen())
-    lock_button_->SetEnabled(false);
+  lock_button_->SetEnabled(can_show_web_ui &&
+                           Shell::Get()->session_controller()->CanLockScreen());
 
   AddChildView(lock_button_);
   AddChildView(TrayPopupUtils::CreateVerticalSeparator());
@@ -114,6 +124,9 @@
   } else if (sender == help_button_) {
     ShellPort::Get()->RecordUserMetricsAction(UMA_TRAY_HELP);
     Shell::Get()->system_tray_controller()->ShowHelp();
+  } else if (sender == night_light_button_) {
+    ShellPort::Get()->RecordUserMetricsAction(UMA_TRAY_NIGHT_LIGHT);
+    Shell::Get()->night_light_controller()->Toggle();
   } else if (sender == lock_button_) {
     ShellPort::Get()->RecordUserMetricsAction(UMA_TRAY_LOCK_SCREEN);
     chromeos::DBusThreadManager::Get()
diff --git a/ash/system/tiles/tiles_default_view.h b/ash/system/tiles/tiles_default_view.h
index d6fe3a8a8..d3b1ff24 100644
--- a/ash/system/tiles/tiles_default_view.h
+++ b/ash/system/tiles/tiles_default_view.h
@@ -48,6 +48,7 @@
   // which case the corresponding pointer will be null.
   views::CustomButton* settings_button_;
   views::CustomButton* help_button_;
+  views::CustomButton* night_light_button_;
   views::CustomButton* lock_button_;
   views::CustomButton* power_button_;
 
diff --git a/ash/system/tiles/tray_tiles_unittest.cc b/ash/system/tiles/tray_tiles_unittest.cc
index 30fa094..48d5d1c6 100644
--- a/ash/system/tiles/tray_tiles_unittest.cc
+++ b/ash/system/tiles/tray_tiles_unittest.cc
@@ -39,6 +39,10 @@
     return tray_tiles()->default_view_->help_button_;
   }
 
+  views::CustomButton* GetNightLightButton() {
+    return tray_tiles()->default_view_->night_light_button_;
+  }
+
   views::CustomButton* GetLockButton() {
     return tray_tiles()->default_view_->lock_button_;
   }
@@ -61,6 +65,7 @@
       tray_tiles()->CreateDefaultViewForTesting());
   EXPECT_EQ(Button::STATE_DISABLED, GetSettingsButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetHelpButton()->state());
+  EXPECT_EQ(Button::STATE_DISABLED, GetNightLightButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetLockButton()->state());
   EXPECT_EQ(Button::STATE_NORMAL, GetPowerButton()->state());
 }
@@ -72,6 +77,7 @@
       tray_tiles()->CreateDefaultViewForTesting());
   EXPECT_EQ(Button::STATE_NORMAL, GetSettingsButton()->state());
   EXPECT_EQ(Button::STATE_NORMAL, GetHelpButton()->state());
+  EXPECT_EQ(Button::STATE_NORMAL, GetNightLightButton()->state());
   EXPECT_EQ(Button::STATE_NORMAL, GetLockButton()->state());
   EXPECT_EQ(Button::STATE_NORMAL, GetPowerButton()->state());
 }
@@ -83,6 +89,7 @@
       tray_tiles()->CreateDefaultViewForTesting());
   EXPECT_EQ(Button::STATE_DISABLED, GetSettingsButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetHelpButton()->state());
+  EXPECT_EQ(Button::STATE_DISABLED, GetNightLightButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetLockButton()->state());
   EXPECT_EQ(Button::STATE_NORMAL, GetPowerButton()->state());
 }
@@ -94,6 +101,7 @@
       tray_tiles()->CreateDefaultViewForTesting());
   EXPECT_EQ(Button::STATE_DISABLED, GetSettingsButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetHelpButton()->state());
+  EXPECT_EQ(Button::STATE_DISABLED, GetNightLightButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetLockButton()->state());
   EXPECT_EQ(Button::STATE_NORMAL, GetPowerButton()->state());
 }
@@ -109,6 +117,7 @@
       tray_tiles()->CreateDefaultViewForTesting());
   EXPECT_EQ(Button::STATE_DISABLED, GetSettingsButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetHelpButton()->state());
+  EXPECT_EQ(Button::STATE_DISABLED, GetNightLightButton()->state());
   EXPECT_EQ(Button::STATE_DISABLED, GetLockButton()->state());
   EXPECT_EQ(Button::STATE_NORMAL, GetPowerButton()->state());
 }
diff --git a/ash/system/tray/system_tray.cc b/ash/system/tray/system_tray.cc
index f16a8991..80c2694 100644
--- a/ash/system/tray/system_tray.cc
+++ b/ash/system/tray/system_tray.cc
@@ -27,6 +27,7 @@
 #include "ash/system/media_security/multi_profile_media_tray_item.h"
 #include "ash/system/network/tray_network.h"
 #include "ash/system/network/tray_vpn.h"
+#include "ash/system/night_light/tray_night_light.h"
 #include "ash/system/power/power_status.h"
 #include "ash/system/power/tray_power.h"
 #include "ash/system/screen_security/screen_capture_tray_item.h"
@@ -273,6 +274,8 @@
   AddTrayItem(base::WrapUnique(tray_audio_));
   AddTrayItem(base::MakeUnique<TrayBrightness>(this));
   AddTrayItem(base::MakeUnique<TrayCapsLock>(this));
+  tray_night_light_ = new TrayNightLight(this);
+  AddTrayItem(base::WrapUnique(tray_night_light_));
   // TODO(jamescook): Remove this when mus has support for display management
   // and we have a DisplayManager equivalent. See http://crbug.com/548429
   std::unique_ptr<SystemTrayItem> tray_rotation_lock =
diff --git a/ash/system/tray/system_tray.h b/ash/system/tray/system_tray.h
index 5e5244d..bffa9e7 100644
--- a/ash/system/tray/system_tray.h
+++ b/ash/system/tray/system_tray.h
@@ -29,6 +29,7 @@
 class TrayCast;
 class TrayEnterprise;
 class TrayNetwork;
+class TrayNightLight;
 class TraySupervisedUser;
 class TraySystemInfo;
 class TrayTiles;
@@ -49,6 +50,8 @@
 
   TrayUpdate* tray_update() { return tray_update_; }
 
+  TrayNightLight* tray_night_light() { return tray_night_light_; }
+
   // Calls TrayBackgroundView::Initialize(), creates the tray items, and
   // adds them to SystemTrayNotifier.
   void InitializeTrayItems(SystemTrayDelegate* delegate,
@@ -234,6 +237,7 @@
   TraySupervisedUser* tray_supervised_user_ = nullptr;
   TraySystemInfo* tray_system_info_ = nullptr;
   TrayUpdate* tray_update_ = nullptr;
+  TrayNightLight* tray_night_light_ = nullptr;
 
   // A reference to the Screen share and capture item.
   ScreenTrayItem* screen_capture_tray_item_ = nullptr;  // not owned
diff --git a/ash/system/tray/system_tray_item.h b/ash/system/tray/system_tray_item.h
index 9b3f5cc4..41f12b19 100644
--- a/ash/system/tray/system_tray_item.h
+++ b/ash/system/tray/system_tray_item.h
@@ -61,7 +61,8 @@
     UMA_TRACING = 23,
     UMA_USER = 24,
     UMA_VPN = 25,
-    UMA_COUNT = 26,
+    UMA_NIGHT_LIGHT = 26,
+    UMA_COUNT = 27,
   };
 
   SystemTrayItem(SystemTray* system_tray, UmaType type);
diff --git a/base/task_scheduler/post_task.cc b/base/task_scheduler/post_task.cc
index 6da11bd8..ec4bfb5 100644
--- a/base/task_scheduler/post_task.cc
+++ b/base/task_scheduler/post_task.cc
@@ -34,10 +34,10 @@
 // explicitly in |traits|, the returned TaskTraits have the current
 // TaskPriority.
 TaskTraits GetTaskTraitsWithExplicitPriority(const TaskTraits& traits) {
-  return traits.priority_set_explicitly()
-             ? traits
-             : TaskTraits(traits).WithPriority(
-                   internal::GetTaskPriorityForCurrentThread());
+  if (traits.priority_set_explicitly())
+    return traits;
+  return TaskTraits::Override(traits,
+                              {internal::GetTaskPriorityForCurrentThread()});
 }
 
 }  // namespace
diff --git a/base/task_scheduler/task.cc b/base/task_scheduler/task.cc
index fc513e3..3cf26be 100644
--- a/base/task_scheduler/task.cc
+++ b/base/task_scheduler/task.cc
@@ -24,11 +24,12 @@
           false),  // Not nestable.
       // Prevent a delayed BLOCK_SHUTDOWN task from blocking shutdown before
       // being scheduled by changing its shutdown behavior to SKIP_ON_SHUTDOWN.
-      traits(!delay.is_zero() && traits.shutdown_behavior() ==
-                                     TaskShutdownBehavior::BLOCK_SHUTDOWN
-                 ? TaskTraits(traits).WithShutdownBehavior(
-                       TaskShutdownBehavior::SKIP_ON_SHUTDOWN)
-                 : traits),
+      traits(
+          (!delay.is_zero() &&
+           traits.shutdown_behavior() == TaskShutdownBehavior::BLOCK_SHUTDOWN)
+              ? TaskTraits::Override(traits,
+                                     {TaskShutdownBehavior::SKIP_ON_SHUTDOWN})
+              : traits),
       delay(delay) {}
 
 Task::~Task() = default;
diff --git a/base/task_scheduler/task_traits.h b/base/task_scheduler/task_traits.h
index cd1d7ccf..a403a96 100644
--- a/base/task_scheduler/task_traits.h
+++ b/base/task_scheduler/task_traits.h
@@ -157,6 +157,9 @@
             internal::EnumArgGetter<base::TaskPriority,
                                     base::TaskPriority::USER_VISIBLE>(),
             args...)),
+        shutdown_behavior_set_explicitly_(
+            internal::HasArgOfType<base::TaskShutdownBehavior,
+                                   ArgTypes...>::value),
         shutdown_behavior_(internal::GetValueFromArgList(
             internal::EnumArgGetter<
                 base::TaskShutdownBehavior,
@@ -172,6 +175,14 @@
   constexpr TaskTraits(const TaskTraits& other) = default;
   TaskTraits& operator=(const TaskTraits& other) = default;
 
+  // Returns TaskTraits constructed by combining |left| and |right|. If a trait
+  // is specified in both |left| and |right|, the returned TaskTraits will have
+  // the value from |right|.
+  static constexpr TaskTraits Override(const TaskTraits& left,
+                                       const TaskTraits& right) {
+    return TaskTraits(left, right);
+  }
+
   // Deprecated.  Prefer constexpr construction to builder paradigm as
   // documented above.
   // TODO(fdoray): Remove these methods. https://crbug.com/713683
@@ -188,6 +199,11 @@
   // Returns the priority of tasks with these traits.
   constexpr TaskPriority priority() const { return priority_; }
 
+  // Returns true if the shutdown behavior was set explicitly.
+  constexpr bool shutdown_behavior_set_explicitly() const {
+    return shutdown_behavior_set_explicitly_;
+  }
+
   // Returns the shutdown behavior of tasks with these traits.
   constexpr TaskShutdownBehavior shutdown_behavior() const {
     return shutdown_behavior_;
@@ -202,10 +218,26 @@
   }
 
  private:
+  constexpr TaskTraits(const TaskTraits& left, const TaskTraits& right)
+      : priority_set_explicitly_(left.priority_set_explicitly_ ||
+                                 right.priority_set_explicitly_),
+        priority_(right.priority_set_explicitly_ ? right.priority_
+                                                 : left.priority_),
+        shutdown_behavior_set_explicitly_(
+            left.shutdown_behavior_set_explicitly_ ||
+            right.shutdown_behavior_set_explicitly_),
+        shutdown_behavior_(right.shutdown_behavior_set_explicitly_
+                               ? right.shutdown_behavior_
+                               : left.shutdown_behavior_),
+        may_block_(left.may_block_ || right.may_block_),
+        with_base_sync_primitives_(left.with_base_sync_primitives_ ||
+                                   right.with_base_sync_primitives_) {}
+
   // TODO(fdoray): Make these const after refactoring away deprecated builder
   // pattern.
   bool priority_set_explicitly_;
   TaskPriority priority_;
+  bool shutdown_behavior_set_explicitly_;
   TaskShutdownBehavior shutdown_behavior_;
   bool may_block_;
   bool with_base_sync_primitives_;
diff --git a/base/task_scheduler/task_traits_unittest.cc b/base/task_scheduler/task_traits_unittest.cc
index ed9065f7..2a350484 100644
--- a/base/task_scheduler/task_traits_unittest.cc
+++ b/base/task_scheduler/task_traits_unittest.cc
@@ -78,4 +78,98 @@
             traits_copy.with_base_sync_primitives());
 }
 
+TEST(TaskSchedulerTaskTraitsTest, OverridePriority) {
+  constexpr TaskTraits left = {TaskPriority::BACKGROUND};
+  constexpr TaskTraits right = {TaskPriority::USER_BLOCKING};
+  constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+  EXPECT_TRUE(overridden.priority_set_explicitly());
+  EXPECT_EQ(TaskPriority::USER_BLOCKING, overridden.priority());
+  EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+  EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+            overridden.shutdown_behavior());
+  EXPECT_FALSE(overridden.may_block());
+  EXPECT_FALSE(overridden.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideShutdownBehavior) {
+  constexpr TaskTraits left = {TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+  constexpr TaskTraits right = {TaskShutdownBehavior::BLOCK_SHUTDOWN};
+  constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+  EXPECT_FALSE(overridden.priority_set_explicitly());
+  EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+  EXPECT_TRUE(overridden.shutdown_behavior_set_explicitly());
+  EXPECT_EQ(TaskShutdownBehavior::BLOCK_SHUTDOWN,
+            overridden.shutdown_behavior());
+  EXPECT_FALSE(overridden.may_block());
+  EXPECT_FALSE(overridden.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideMayBlock) {
+  {
+    constexpr TaskTraits left = {MayBlock()};
+    constexpr TaskTraits right = {};
+    constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+    EXPECT_FALSE(overridden.priority_set_explicitly());
+    EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+    EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+    EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+              overridden.shutdown_behavior());
+    EXPECT_TRUE(overridden.may_block());
+    EXPECT_FALSE(overridden.with_base_sync_primitives());
+  }
+  {
+    constexpr TaskTraits left = {};
+    constexpr TaskTraits right = {MayBlock()};
+    constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+    EXPECT_FALSE(overridden.priority_set_explicitly());
+    EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+    EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+    EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+              overridden.shutdown_behavior());
+    EXPECT_TRUE(overridden.may_block());
+    EXPECT_FALSE(overridden.with_base_sync_primitives());
+  }
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideWithBaseSyncPrimitives) {
+  {
+    constexpr TaskTraits left = {WithBaseSyncPrimitives()};
+    constexpr TaskTraits right = {};
+    constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+    EXPECT_FALSE(overridden.priority_set_explicitly());
+    EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+    EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+    EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+              overridden.shutdown_behavior());
+    EXPECT_FALSE(overridden.may_block());
+    EXPECT_TRUE(overridden.with_base_sync_primitives());
+  }
+  {
+    constexpr TaskTraits left = {};
+    constexpr TaskTraits right = {WithBaseSyncPrimitives()};
+    constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+    EXPECT_FALSE(overridden.priority_set_explicitly());
+    EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+    EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+    EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+              overridden.shutdown_behavior());
+    EXPECT_FALSE(overridden.may_block());
+    EXPECT_TRUE(overridden.with_base_sync_primitives());
+  }
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideMultipleTraits) {
+  constexpr TaskTraits left = {MayBlock(), TaskPriority::BACKGROUND,
+                               TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+  constexpr TaskTraits right = {WithBaseSyncPrimitives(),
+                                TaskPriority::USER_BLOCKING};
+  constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+  EXPECT_TRUE(overridden.priority_set_explicitly());
+  EXPECT_EQ(right.priority(), overridden.priority());
+  EXPECT_TRUE(overridden.shutdown_behavior_set_explicitly());
+  EXPECT_EQ(left.shutdown_behavior(), overridden.shutdown_behavior());
+  EXPECT_TRUE(overridden.may_block());
+  EXPECT_TRUE(overridden.with_base_sync_primitives());
+}
+
 }  // namespace base
diff --git a/build/android/lint/suppressions.xml b/build/android/lint/suppressions.xml
index e864b66b..5b06714 100644
--- a/build/android/lint/suppressions.xml
+++ b/build/android/lint/suppressions.xml
@@ -440,8 +440,6 @@
   <issue id="UseCompoundDrawables">
     <!-- Upscaling 24dp to 48dp doesn't work as expected with a TextView compound drawable. -->
     <ignore regexp="chrome/android/java/res/layout/photo_picker_bitmap_view.xml"/>
-    <!-- TODO(crbug.com/669629): Remove this when the Chromecast dependency on old layout code is removed. -->
-    <ignore regexp="chromecast/internal/android/prebuilt/settings/res/layout-v17/setup_activity_progress.xml"/>
   </issue>
   <issue id="UselessParent">
     <ignore regexp="android_webview/tools/system_webview_shell/apk/res/layout/activity_webview_browser.xml"/>
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 183654c..90fbb3cc 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -99,7 +99,9 @@
   # branches)
   optimize_for_fuzzing = false
 
-  # Optimize symbol files for maximizing goma cache hit rate.
+  # Optimize symbol files for maximizing goma cache hit rate. This isn't
+  # on by default when goma is enabled because setting this to true may make
+  # it harder to debug binaries.
   strip_absolute_paths_from_debug_symbols = false
 }
 
diff --git a/cc/debug/debug_colors.cc b/cc/debug/debug_colors.cc
index db0fbd7..89e3d0bc 100644
--- a/cc/debug/debug_colors.cc
+++ b/cc/debug/debug_colors.cc
@@ -78,7 +78,7 @@
   return SkColorSetARGB(100, 80, 200, 200);
 }
 int DebugColors::HighResTileBorderWidth(float device_scale_factor) {
-  return Scale(1, device_scale_factor);
+  return Scale(3, device_scale_factor);
 }
 
 // Low-res tile borders are purple.
diff --git a/cc/paint/discardable_image_map_unittest.cc b/cc/paint/discardable_image_map_unittest.cc
index d9ab8fd1..015e539 100644
--- a/cc/paint/discardable_image_map_unittest.cc
+++ b/cc/paint/discardable_image_map_unittest.cc
@@ -39,9 +39,8 @@
   SkSize scale;
 };
 
-sk_sp<PaintRecord> CreateRecording(
-    const sk_sp<const SkImage>& discardable_image,
-    const gfx::Rect& visible_rect) {
+sk_sp<PaintRecord> CreateRecording(const sk_sp<SkImage>& discardable_image,
+                                   const gfx::Rect& visible_rect) {
   PaintRecorder recorder;
   PaintCanvas* canvas =
       recorder.beginRecording(visible_rect.width(), visible_rect.height());
@@ -651,7 +650,7 @@
 TEST_F(DiscardableImageMapTest, ClipsImageRects) {
   gfx::Rect visible_rect(500, 500);
 
-  sk_sp<const SkImage> discardable_image =
+  sk_sp<SkImage> discardable_image =
       CreateDiscardableImage(gfx::Size(500, 500));
   sk_sp<PaintRecord> record = CreateRecording(discardable_image, visible_rect);
 
diff --git a/cc/paint/paint_image.cc b/cc/paint/paint_image.cc
index d4e01f7..846b3bee 100644
--- a/cc/paint/paint_image.cc
+++ b/cc/paint/paint_image.cc
@@ -7,14 +7,12 @@
 namespace cc {
 
 PaintImage::PaintImage() = default;
-PaintImage::PaintImage(sk_sp<const SkImage> sk_image,
+PaintImage::PaintImage(sk_sp<SkImage> sk_image,
                        AnimationType animation_type,
                        CompletionState completion_state)
     : sk_image_(std::move(sk_image)),
       animation_type_(animation_type),
-      completion_state_(completion_state) {
-  DCHECK(sk_image_);
-}
+      completion_state_(completion_state) {}
 PaintImage::PaintImage(const PaintImage& other) = default;
 PaintImage::PaintImage(PaintImage&& other) = default;
 PaintImage::~PaintImage() = default;
diff --git a/cc/paint/paint_image.h b/cc/paint/paint_image.h
index df0f82ee..a79bcf02 100644
--- a/cc/paint/paint_image.h
+++ b/cc/paint/paint_image.h
@@ -21,7 +21,7 @@
   enum class CompletionState { UNKNOWN, DONE, PARTIALLY_DONE };
 
   PaintImage();
-  explicit PaintImage(sk_sp<const SkImage> sk_image,
+  explicit PaintImage(sk_sp<SkImage> sk_image,
                       AnimationType animation_type = AnimationType::STATIC,
                       CompletionState completion_state = CompletionState::DONE);
   PaintImage(const PaintImage& other);
@@ -32,13 +32,14 @@
   PaintImage& operator=(PaintImage&& other);
 
   bool operator==(const PaintImage& other);
+  explicit operator bool() const { return sk_image_; }
 
-  const sk_sp<const SkImage>& sk_image() const { return sk_image_; }
+  const sk_sp<SkImage>& sk_image() const { return sk_image_; }
   AnimationType animation_type() const { return animation_type_; }
   CompletionState completion_state() const { return completion_state_; }
 
  private:
-  sk_sp<const SkImage> sk_image_;
+  sk_sp<SkImage> sk_image_;
   AnimationType animation_type_ = AnimationType::UNKNOWN;
   CompletionState completion_state_ = CompletionState::UNKNOWN;
 };
diff --git a/cc/test/fake_content_layer_client.cc b/cc/test/fake_content_layer_client.cc
index dd67f0e..1881a978 100644
--- a/cc/test/fake_content_layer_client.cc
+++ b/cc/test/fake_content_layer_client.cc
@@ -16,12 +16,12 @@
 
 namespace cc {
 
-FakeContentLayerClient::ImageData::ImageData(sk_sp<const SkImage> img,
+FakeContentLayerClient::ImageData::ImageData(sk_sp<SkImage> img,
                                              const gfx::Point& point,
                                              const PaintFlags& flags)
     : image(std::move(img)), point(point), flags(flags) {}
 
-FakeContentLayerClient::ImageData::ImageData(sk_sp<const SkImage> img,
+FakeContentLayerClient::ImageData::ImageData(sk_sp<SkImage> img,
                                              const gfx::Transform& transform,
                                              const PaintFlags& flags)
     : image(std::move(img)), transform(transform), flags(flags) {}
diff --git a/cc/test/fake_content_layer_client.h b/cc/test/fake_content_layer_client.h
index d35f8b7..b644087 100644
--- a/cc/test/fake_content_layer_client.h
+++ b/cc/test/fake_content_layer_client.h
@@ -24,15 +24,15 @@
 class FakeContentLayerClient : public ContentLayerClient {
  public:
   struct ImageData {
-    ImageData(sk_sp<const SkImage> image,
+    ImageData(sk_sp<SkImage> image,
               const gfx::Point& point,
               const PaintFlags& flags);
-    ImageData(sk_sp<const SkImage> image,
+    ImageData(sk_sp<SkImage> image,
               const gfx::Transform& transform,
               const PaintFlags& flags);
     ImageData(const ImageData& other);
     ~ImageData();
-    sk_sp<const SkImage> image;
+    sk_sp<SkImage> image;
     gfx::Point point;
     gfx::Transform transform;
     PaintFlags flags;
@@ -59,14 +59,14 @@
     draw_rects_.push_back(std::make_pair(rect, flags));
   }
 
-  void add_draw_image(sk_sp<const SkImage> image,
+  void add_draw_image(sk_sp<SkImage> image,
                       const gfx::Point& point,
                       const PaintFlags& flags) {
     ImageData data(std::move(image), point, flags);
     draw_images_.push_back(data);
   }
 
-  void add_draw_image_with_transform(sk_sp<const SkImage> image,
+  void add_draw_image_with_transform(sk_sp<SkImage> image,
                                      const gfx::Transform& transform,
                                      const PaintFlags& flags) {
     ImageData data(std::move(image), transform, flags);
diff --git a/cc/test/fake_recording_source.h b/cc/test/fake_recording_source.h
index 57142534..90711128 100644
--- a/cc/test/fake_recording_source.h
+++ b/cc/test/fake_recording_source.h
@@ -99,17 +99,17 @@
     client_.add_draw_rectf(rect, flags);
   }
 
-  void add_draw_image(sk_sp<const SkImage> image, const gfx::Point& point) {
+  void add_draw_image(sk_sp<SkImage> image, const gfx::Point& point) {
     client_.add_draw_image(std::move(image), point, default_flags_);
   }
 
-  void add_draw_image_with_transform(sk_sp<const SkImage> image,
+  void add_draw_image_with_transform(sk_sp<SkImage> image,
                                      const gfx::Transform& transform) {
     client_.add_draw_image_with_transform(std::move(image), transform,
                                           default_flags_);
   }
 
-  void add_draw_image_with_flags(sk_sp<const SkImage> image,
+  void add_draw_image_with_flags(sk_sp<SkImage> image,
                                  const gfx::Point& point,
                                  const PaintFlags& flags) {
     client_.add_draw_image(std::move(image), point, flags);
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 342678d..df7b1cf 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -374,6 +374,7 @@
     <dimen name="photo_picker_label_gap">10dp</dimen>
     <dimen name="photo_picker_tile_min_size">100dp</dimen>
     <dimen name="photo_picker_tile_gap">4dp</dimen>
+    <dimen name="photo_picker_grainy_thumbnail_size">12dp</dimen>
 
     <!-- Account chooser dialog dimensions -->
     <dimen name="account_chooser_dialog_margin">24dp</dimen>
@@ -471,7 +472,7 @@
     <!-- Context Menu Dimensions -->
     <dimen name="context_menu_header_image_min_size">56dp</dimen>
     <dimen name="context_menu_header_image_max_size">56dp</dimen>
-    
+
     <!-- Divider Dimensions -->
     <dimen name="divider_height">1dp</dimen>
 
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 771ca11f..3a91d289 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -117,6 +117,7 @@
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.browser.vr_shell.VrShellDelegate;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetMetrics;
 import org.chromium.chrome.browser.widget.emptybackground.EmptyBackgroundViewWrapper;
 import org.chromium.chrome.browser.widget.findinpage.FindToolbarManager;
 import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble;
@@ -992,6 +993,8 @@
             if (getBottomSheet() != null) {
                 // Either a new tab is opening, a tab is being clobbered, or a tab is being brought
                 // to the front. In all scenarios, the bottom sheet should be closed.
+                getBottomSheet().getBottomSheetMetrics().setSheetCloseReason(
+                        BottomSheetMetrics.CLOSED_BY_NAVIGATION);
                 getBottomSheet().setSheetState(BottomSheet.SHEET_STATE_PEEK, true);
             }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/ReaderModeInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/ReaderModeInfoBar.java
new file mode 100644
index 0000000..23e115f2
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/ReaderModeInfoBar.java
@@ -0,0 +1,84 @@
+// 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.infobar;
+
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.TextView;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.dom_distiller.ReaderModeManager;
+import org.chromium.chrome.browser.tab.Tab;
+
+/**
+ * This is the InfoBar implementation of the Reader Mode UI. This is used in place of the
+ * {@link OverlayPanel} implementation when Chrome Home is enabled.
+ */
+public class ReaderModeInfoBar extends InfoBar {
+    /** A handle to the {@link ReaderModeManager} to trigger page navigations. */
+    private static ReaderModeManager sManager;
+
+    /**
+     * Default constructor.
+     */
+    private ReaderModeInfoBar() {
+        super(R.drawable.infobar_mobile_friendly, null, null);
+    }
+
+    @Override
+    protected boolean usesCompactLayout() {
+        return true;
+    }
+
+    @Override
+    protected void createCompactLayoutContent(InfoBarCompactLayout layout) {
+        TextView prompt = new TextView(getContext());
+        prompt.setText(R.string.reader_view_text);
+        prompt.setSingleLine();
+        prompt.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+                getContext().getResources().getDimension(R.dimen.infobar_text_size));
+        prompt.setTextColor(
+                ApiCompatibilityUtils.getColor(layout.getResources(), R.color.default_text_color));
+        prompt.setGravity(Gravity.CENTER_VERTICAL);
+
+        prompt.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // TODO(mdjones): Trigger navigation from manager.
+            }
+        });
+
+        layout.addContent(prompt, 1f);
+    }
+
+    public void onCloseButtonClicked() {
+        super.onCloseButtonClicked();
+
+        // TODO(mdjones): Notifiy the manager that the infobar was closed.
+    }
+
+    /**
+     * Create and show the Reader Mode {@link InfoBar}.
+     * @param tab The tab that the {@link InfoBar} should be shown in.
+     * @param manager The {@link ReaderModeManager} for this instance of Chrome.
+     */
+    public static void showReaderModeInfoBar(Tab tab, ReaderModeManager manager) {
+        sManager = manager;
+        nativeCreate(tab);
+    }
+
+    /**
+     * @return An instance of the {@link ReaderModeInfoBar}.
+     */
+    @CalledByNative
+    private static ReaderModeInfoBar create() {
+        return new ReaderModeInfoBar();
+    }
+
+    private static native void nativeCreate(Tab tab);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkUma.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkUma.java
index 1d4204c61..f93b4396 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkUma.java
@@ -90,6 +90,16 @@
                 "WebApk.Install.GooglePlayInstallResult", result, GOOGLE_PLAY_INSTALL_RESULT_MAX);
     }
 
+    /**
+     * Records whether updating a WebAPK from Google Play succeeded. If not, records the reason
+     * that the update failed.
+     */
+    public static void recordGooglePlayUpdateResult(int result) {
+        assert result >= 0 && result < GOOGLE_PLAY_INSTALL_RESULT_MAX;
+        RecordHistogram.recordEnumeratedHistogram(
+                "WebApk.Update.GooglePlayUpdateResult", result, GOOGLE_PLAY_INSTALL_RESULT_MAX);
+    }
+
     /** Records the duration of a WebAPK session (from launch/foreground to background). */
     public static void recordWebApkSessionDuration(long duration) {
         RecordHistogram.recordLongTimesHistogram(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ChromeHomeNewTabPageBase.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ChromeHomeNewTabPageBase.java
index 0b4018db..214fb2ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ChromeHomeNewTabPageBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ChromeHomeNewTabPageBase.java
@@ -96,6 +96,8 @@
                 // If the NTP is loading, the sheet state will be set to SHEET_STATE_HALF.
                 if (TextUtils.equals(tab.getUrl(), getUrl())) return;
 
+                mBottomSheet.getBottomSheetMetrics().setSheetCloseReason(
+                        BottomSheetMetrics.CLOSED_BY_NAVIGATION);
                 mBottomSheet.setSheetState(BottomSheet.SHEET_STATE_PEEK, true);
             }
         };
@@ -166,6 +168,8 @@
         mCloseButton.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
+                mBottomSheet.getBottomSheetMetrics().setSheetCloseReason(
+                        BottomSheetMetrics.CLOSED_BY_NTP_CLOSE_BUTTON);
                 mBottomSheet.setSheetState(BottomSheet.SHEET_STATE_PEEK, true);
                 if (mShowOverviewOnClose && getLayoutManager() != null) {
                     getLayoutManager().showOverview(false);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 7909168..4396fb2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -41,6 +41,7 @@
 import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
 import org.chromium.chrome.browser.suggestions.SuggestionsEventReporter;
 import org.chromium.chrome.browser.suggestions.SuggestionsEventReporterBridge;
+import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
 import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegateImpl;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegateImpl;
@@ -343,7 +344,7 @@
 
             @Override
             public void onHidden(Tab tab) {
-                if (mIsLoaded) recordNTPInteractionTime();
+                if (mIsLoaded) recordNTPHidden();
             }
 
             @Override
@@ -496,11 +497,14 @@
     private void recordNTPShown() {
         mLastShownTimeNs = System.nanoTime();
         RecordUserAction.record("MobileNTPShown");
+        SuggestionsMetrics.recordSurfaceVisible();
     }
 
-    private void recordNTPInteractionTime() {
+    /** Records UMA for the NTP being hidden and the time spent on it. */
+    private void recordNTPHidden() {
         RecordHistogram.recordMediumTimesHistogram(
                 "NewTabPage.TimeSpent", System.nanoTime() - mLastShownTimeNs, TimeUnit.NANOSECONDS);
+        SuggestionsMetrics.recordSurfaceHidden();
     }
 
     /**
@@ -548,7 +552,7 @@
         assert !mIsDestroyed;
         assert !ViewCompat
                 .isAttachedToWindow(getView()) : "Destroy called before removed from window";
-        if (mIsLoaded && !mTab.isHidden()) recordNTPInteractionTime();
+        if (mIsLoaded && !mTab.isHidden()) recordNTPHidden();
 
         if (mSnippetsBridge != null) {
             mSnippetsBridge.onDestroy();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java
index 5b48f40..781c1cf5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.base.PageTransition;
@@ -363,7 +364,7 @@
                 assert !NewTabPage.isNTPUrl(url);
                 return;
             }
-            if (NewTabPage.isNTPUrl(url)) {
+            if (NewTabPage.isNTPUrl(url) && !FeatureUtilities.isChromeHomeEnabled()) {
                 RecordUserAction.record("MobileNTP.Snippets.VisitEndBackInNTP");
             }
             endRecording(tab);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
index 8738ba3..d188446 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.ntp.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
 import org.chromium.chrome.browser.suggestions.ContentSuggestionsAdditionalAction;
+import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.suggestions.SuggestionsRanker;
 import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
@@ -65,6 +66,7 @@
 
         switch (mCategoryInfo.getAdditionalAction()) {
             case ContentSuggestionsAdditionalAction.VIEW_ALL:
+                SuggestionsMetrics.recordActionViewAll();
                 mCategoryInfo.performViewAllAction(uiDelegate.getNavigationDelegate());
                 return;
             case ContentSuggestionsAdditionalAction.FETCH:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusCardViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusCardViewHolder.java
index 8d88eef..b36f4c8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusCardViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusCardViewHolder.java
@@ -13,6 +13,7 @@
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ntp.ContextMenuManager;
+import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
 import org.chromium.chrome.browser.widget.displaystyle.UiConfig;
 
@@ -75,6 +76,7 @@
 
                 @Override
                 public void onClick(View v) {
+                    SuggestionsMetrics.recordCardActionTapped();
                     item.performAction(v.getContext());
                 }
             });
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
index 0181087..3b3f9c51 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
@@ -40,6 +40,7 @@
 import org.chromium.chrome.browser.ntp.cards.ImpressionTracker;
 import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder;
 import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
+import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
 import org.chromium.chrome.browser.widget.TintedImageView;
@@ -147,6 +148,7 @@
 
     @Override
     public void onCardTapped() {
+        SuggestionsMetrics.recordCardTapped();
         int windowDisposition = WindowOpenDisposition.CURRENT_TAB;
         mUiDelegate.getEventReporter().onSuggestionOpened(
                 mArticle, windowDisposition, mUiDelegate.getSuggestionsRanker());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java
index c1dff32..cd995bab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java
@@ -171,7 +171,7 @@
     @Override
     public void onUrlUpdated(Tab tab) {
         Log.d(TAG, "onUrlUpdated");
-        if (!isOfflinePage(tab)) {
+        if (!OfflinePageUtils.isOfflinePage(tab)) {
             stopObservingTab(tab);
         } else {
             if (isObservingTab(tab)) {
@@ -184,7 +184,7 @@
     }
 
     void startObservingTab(Tab tab) {
-        if (!isOfflinePage(tab)) return;
+        if (!OfflinePageUtils.isOfflinePage(tab)) return;
 
         mCurrentTab = tab;
 
@@ -240,15 +240,16 @@
     // Methods from ConnectionTypeObserver.
     @Override
     public void onConnectionTypeChanged(int connectionType) {
-        Log.d(TAG, "Got connectivity event, connectionType: " + connectionType + ", is connected: "
-                        + isConnected() + ", controller: " + mSnackbarController);
+        Log.d(TAG,
+                "Got connectivity event, connectionType: " + connectionType + ", is connected: "
+                        + OfflinePageUtils.isConnected() + ", controller: " + mSnackbarController);
         maybeShowReloadSnackbar(mCurrentTab, true);
 
         // Since we are loosing the connection, next time we connect, we still want to show a
         // snackbar. This works in event that onConnectionTypeChanged happens, while Chrome is not
         // visible. Making it visible after that would not trigger the snackbar, even though
         // connection state changed. See http://crbug.com/651410
-        if (!isConnected()) {
+        if (!OfflinePageUtils.isConnected()) {
             for (TabState tabState : mObservedTabs.values()) {
                 tabState.wasSnackbarSeen = false;
             }
@@ -275,26 +276,11 @@
         return mIsObservingNetworkChanges;
     }
 
-    @VisibleForTesting
-    boolean isConnected() {
-        return OfflinePageUtils.isConnected();
-    }
-
-    @VisibleForTesting
-    boolean isShowingOfflinePreview(Tab tab) {
-        return OfflinePageUtils.isShowingOfflinePreview(tab);
-    }
-
-    @VisibleForTesting
-    boolean isOfflinePage(Tab tab) {
-        return OfflinePageUtils.isOfflinePage(tab);
-    }
-
     void maybeShowReloadSnackbar(Tab tab, boolean isNetworkEvent) {
         // Exclude Offline Previews, as there is a seperate UI for previews.
-        if (tab == null || tab.isFrozen() || tab.isHidden() || !isOfflinePage(tab)
-                || isShowingOfflinePreview(tab) || !isConnected() || !isLoadedTab(tab)
-                || (wasSnackbarSeen(tab) && !isNetworkEvent)) {
+        if (tab == null || tab.isFrozen() || tab.isHidden() || !OfflinePageUtils.isOfflinePage(tab)
+                || OfflinePageUtils.isShowingOfflinePreview(tab) || !OfflinePageUtils.isConnected()
+                || !isLoadedTab(tab) || (wasSnackbarSeen(tab) && !isNetworkEvent)) {
             // Conditions to show a snackbar are not met.
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
index fb0f227..54e0c0e2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
@@ -76,7 +76,8 @@
     // Used instead of the constant so tests can override the value.
     private static int sSnackbarDurationMs = DEFAULT_SNACKBAR_DURATION_MS;
 
-    private static OfflinePageUtils sInstance;
+    /** Instance carrying actual implementation of utility methods. */
+    private static Internal sInstance;
 
     private static File sOfflineSharingDirectory;
 
@@ -88,6 +89,88 @@
     private static Map<ChromeActivity, RecentTabTracker> sTabModelObservers = new HashMap<>();
 
     /**
+     * Interface for implementation of offline page utilities, that can be implemented for testing.
+     * We are using an internal interface, so that instance methods can have the same names as
+     * static methods.
+     */
+    @VisibleForTesting
+    interface Internal {
+        /** Returns offline page bridge for specified profile. */
+        OfflinePageBridge getOfflinePageBridge(Profile profile);
+
+        /** Returns whether the network is connected. */
+        boolean isConnected();
+
+        /**
+         * Checks if an offline page is shown for the tab.
+         * @param tab The tab to be reloaded.
+         * @return True if the offline page is opened.
+         */
+        boolean isOfflinePage(Tab tab);
+
+        /**
+         * Returns whether the tab is showing offline preview.
+         * @param tab The current tab.
+         */
+        boolean isShowingOfflinePreview(Tab tab);
+
+        /**
+         * Shows the "reload" snackbar for the given tab.
+         * @param context The application context.
+         * @param snackbarManager Class that shows the snackbar.
+         * @param snackbarController Class to control the snackbar.
+         * @param tabId Id of a tab that the snackbar is related to.
+         */
+        void showReloadSnackbar(Context context, SnackbarManager snackbarManager,
+                final SnackbarController snackbarController, int tabId);
+    }
+
+    private static class OfflinePageUtilsImpl implements Internal {
+        @Override
+        public OfflinePageBridge getOfflinePageBridge(Profile profile) {
+            return OfflinePageBridge.getForProfile(profile);
+        }
+
+        @Override
+        public boolean isConnected() {
+            return NetworkChangeNotifier.isOnline();
+        }
+
+        @Override
+        public boolean isOfflinePage(Tab tab) {
+            WebContents webContents = tab.getWebContents();
+            if (webContents == null) return false;
+            OfflinePageBridge offlinePageBridge =
+                    getInstance().getOfflinePageBridge(tab.getProfile());
+            if (offlinePageBridge == null) return false;
+            return offlinePageBridge.isOfflinePage(webContents);
+        }
+
+        @Override
+        public boolean isShowingOfflinePreview(Tab tab) {
+            OfflinePageBridge offlinePageBridge = getOfflinePageBridge(tab.getProfile());
+            if (offlinePageBridge == null) return false;
+            return offlinePageBridge.isShowingOfflinePreview(tab.getWebContents());
+        }
+
+        @Override
+        public void showReloadSnackbar(Context context, SnackbarManager snackbarManager,
+                final SnackbarController snackbarController, int tabId) {
+            if (tabId == Tab.INVALID_TAB_ID) return;
+
+            Log.d(TAG, "showReloadSnackbar called with controller " + snackbarController);
+            Snackbar snackbar =
+                    Snackbar.make(context.getString(R.string.offline_pages_viewing_offline_page),
+                                    snackbarController, Snackbar.TYPE_ACTION,
+                                    Snackbar.UMA_OFFLINE_PAGE_RELOAD)
+                            .setSingleLine(false)
+                            .setAction(context.getString(R.string.reload), tabId);
+            snackbar.setDuration(sSnackbarDurationMs);
+            snackbarManager.showSnackbar(snackbar);
+        }
+    }
+
+    /**
      * Contains values from the histogram enum OfflinePagesTabRestoreType used for reporting the
      * OfflinePages.TabRestore metric.
      */
@@ -107,9 +190,9 @@
         public static final int COUNT = 10;
     }
 
-    private static OfflinePageUtils getInstance() {
+    private static Internal getInstance() {
         if (sInstance == null) {
-            sInstance = new OfflinePageUtils();
+            sInstance = new OfflinePageUtilsImpl();
         }
         return sInstance;
     }
@@ -128,11 +211,9 @@
         return Environment.getDataDirectory().getTotalSpace();
     }
 
-    /**
-     * Returns true if the network is connected.
-     */
+    /** Returns whether the network is connected. */
     public static boolean isConnected() {
-        return NetworkChangeNotifier.isOnline();
+        return getInstance().isConnected();
     }
 
     /*
@@ -203,25 +284,21 @@
         OfflinePageTabObserver.addObserverForTab(tab);
     }
 
+    protected void showReloadSnackbarInternal(Context context, SnackbarManager snackbarManager,
+            final SnackbarController snackbarController, int tabId) {}
+
     /**
      * Shows the "reload" snackbar for the given tab.
-     * @param activity The activity owning the tab.
-     * @param snackbarController Class to show the snackbar.
+     * @param context The application context.
+     * @param snackbarManager Class that shows the snackbar.
+     * @param snackbarController Class to control the snackbar.
+     * @param tabId Id of a tab that the snackbar is related to.
      */
     public static void showReloadSnackbar(Context context, SnackbarManager snackbarManager,
             final SnackbarController snackbarController, int tabId) {
-        if (tabId == Tab.INVALID_TAB_ID) return;
-
-        Log.d(TAG, "showReloadSnackbar called with controller " + snackbarController);
-        Snackbar snackbar =
-                Snackbar.make(context.getString(R.string.offline_pages_viewing_offline_page),
-                        snackbarController, Snackbar.TYPE_ACTION, Snackbar.UMA_OFFLINE_PAGE_RELOAD)
-                        .setSingleLine(false).setAction(context.getString(R.string.reload), tabId);
-        snackbar.setDuration(sSnackbarDurationMs);
-        snackbarManager.showSnackbar(snackbar);
+        getInstance().showReloadSnackbar(context, snackbarManager, snackbarController, tabId);
     }
 
-
     /**
      * Records UMA data when the Offline Pages Background Load service awakens.
      * @param context android context
@@ -558,9 +635,7 @@
      * @param tab The current tab.
      */
     public static boolean isShowingOfflinePreview(Tab tab) {
-        OfflinePageBridge offlinePageBridge = getInstance().getOfflinePageBridge(tab.getProfile());
-        if (offlinePageBridge == null) return false;
-        return offlinePageBridge.isShowingOfflinePreview(tab.getWebContents());
+        return getInstance().isShowingOfflinePreview(tab);
     }
 
     /**
@@ -569,11 +644,7 @@
      * @return True if the offline page is opened.
      */
     public static boolean isOfflinePage(Tab tab) {
-        WebContents webContents = tab.getWebContents();
-        if (webContents == null) return false;
-        OfflinePageBridge offlinePageBridge = getInstance().getOfflinePageBridge(tab.getProfile());
-        if (offlinePageBridge == null) return false;
-        return offlinePageBridge.isOfflinePage(webContents);
+        return getInstance().isOfflinePage(tab);
     }
 
     /**
@@ -617,10 +688,6 @@
         tab.loadUrl(params);
     }
 
-    protected OfflinePageBridge getOfflinePageBridge(Profile profile) {
-        return OfflinePageBridge.getForProfile(profile);
-    }
-
     /**
      * Tracks tab creation and closure for the Recent Tabs feature.  UI needs to stop showing
      * recent offline pages as soon as the tab is closed.  The TabModel is used to get profile
@@ -792,7 +859,7 @@
     }
 
     @VisibleForTesting
-    static void setInstanceForTesting(OfflinePageUtils instance) {
+    static void setInstanceForTesting(Internal instance) {
         sInstance = instance;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index 6045de0..ed2cbb94 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -944,7 +944,13 @@
         // (since we apply spans when the URL is not focused, we only optimize this when the
         // URL is being edited).
         if (!TextUtils.equals(getEditableText(), text)) {
-            super.setText(text, type);
+            // Certain OEM implementations of setText trigger disk reads. crbug.com/633298
+            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+            try {
+                super.setText(text, type);
+            } finally {
+                StrictMode.setThreadPolicy(oldPolicy);
+            }
         }
 
         // Verify the autocomplete is still valid after the text change.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/BitmapScalerTask.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/BitmapScalerTask.java
new file mode 100644
index 0000000..2fe1519
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/BitmapScalerTask.java
@@ -0,0 +1,56 @@
+// 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.photo_picker;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.util.LruCache;
+
+import org.chromium.base.ThreadUtils;
+
+/**
+ * A worker task to scale bitmaps in the background.
+ */
+class BitmapScalerTask extends AsyncTask<Bitmap, Void, Bitmap> {
+    private final LruCache<String, Bitmap> mCache;
+    private final String mFilePath;
+    private final int mSize;
+
+    /**
+     * A BitmapScalerTask constructor.
+     */
+    public BitmapScalerTask(LruCache<String, Bitmap> cache, String filePath, int size) {
+        mCache = cache;
+        mFilePath = filePath;
+        mSize = size;
+    }
+
+    /**
+     * Scales the image provided. Called on a non-UI thread.
+     * @param params Ignored, do not use.
+     * @return A sorted list of images (by last-modified first).
+     */
+    @Override
+    protected Bitmap doInBackground(Bitmap... bitmaps) {
+        assert !ThreadUtils.runningOnUiThread();
+
+        if (isCancelled()) return null;
+
+        return BitmapUtils.scale(bitmaps[0], mSize, false);
+    }
+
+    /**
+     * Communicates the results back to the client. Called on the UI thread.
+     * @param result The resulting scaled bitmap.
+     */
+    @Override
+    protected void onPostExecute(Bitmap result) {
+        if (isCancelled()) {
+            return;
+        }
+
+        mCache.put(mFilePath, result);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/BitmapUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/BitmapUtils.java
index ddad8aa..f1e7620 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/BitmapUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/BitmapUtils.java
@@ -107,4 +107,20 @@
         if (height > size) y = (height - size) / 2;
         return Bitmap.createBitmap(bitmap, x, y, size, size);
     }
+
+    /**
+     * Scales a |bitmap| to a certain size.
+     * @param bitmap The bitmap to scale.
+     * @param scaleMaxSize What to scale it to.
+     * @param filter True if the source should be filtered.
+     * @return The resulting scaled bitmap.
+     */
+    public static Bitmap scale(Bitmap bitmap, float scaleMaxSize, boolean filter) {
+        float ratio = Math.min((float) scaleMaxSize / bitmap.getWidth(),
+                (float) scaleMaxSize / bitmap.getHeight());
+        int height = Math.round(ratio * bitmap.getHeight());
+        int width = Math.round(ratio * bitmap.getWidth());
+
+        return Bitmap.createScaledBitmap(bitmap, width, height, filter);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmapViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmapViewHolder.java
index b4aa77b..4dd0886 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmapViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmapViewHolder.java
@@ -4,10 +4,14 @@
 
 package org.chromium.chrome.browser.photo_picker;
 
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.os.AsyncTask;
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.text.TextUtils;
 
+import org.chromium.chrome.R;
+
 import java.util.List;
 
 /**
@@ -41,6 +45,17 @@
             return;
         }
 
+        if (mCategoryView.getHighResBitmaps().get(filePath) == null) {
+            mCategoryView.getHighResBitmaps().put(filePath, bitmap);
+        }
+
+        if (mCategoryView.getLowResBitmaps().get(filePath) == null) {
+            Resources resources = mItemView.getContext().getResources();
+            new BitmapScalerTask(mCategoryView.getLowResBitmaps(), filePath,
+                    resources.getDimensionPixelSize(R.dimen.photo_picker_grainy_thumbnail_size))
+                    .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, bitmap);
+        }
+
         if (!TextUtils.equals(mBitmapDetails.getFilePath(), filePath)) {
             return;
         }
@@ -67,12 +82,24 @@
             return;
         }
 
-        // TODO(finnur): Use cached image, if available.
-
-        mItemView.initialize(mBitmapDetails, null, true);
+        String filePath = mBitmapDetails.getFilePath();
+        Bitmap original = mCategoryView.getHighResBitmaps().get(filePath);
+        if (original != null) {
+            mItemView.initialize(mBitmapDetails, original, false);
+            return;
+        }
 
         int size = mCategoryView.getImageSize();
-        mCategoryView.getDecoderServiceHost().decodeImage(mBitmapDetails.getFilePath(), size, this);
+        Bitmap placeholder = mCategoryView.getLowResBitmaps().get(filePath);
+        if (placeholder != null) {
+            // For performance stats see http://crbug.com/719919.
+            placeholder = BitmapUtils.scale(placeholder, size, false);
+            mItemView.initialize(mBitmapDetails, placeholder, true);
+        } else {
+            mItemView.initialize(mBitmapDetails, null, true);
+        }
+
+        mCategoryView.getDecoderServiceHost().decodeImage(filePath, size, this);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java
index 62e4d58..84c2dda 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java
@@ -6,10 +6,12 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
+import android.util.LruCache;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
@@ -30,6 +32,8 @@
 public class PickerCategoryView extends RelativeLayout
         implements FileEnumWorkerTask.FilesEnumeratedCallback, RecyclerView.RecyclerListener,
                    DecoderServiceHost.ServiceReadyCallback, OnMenuItemClickListener {
+    private static final int KILOBYTE = 1024;
+
     // The dialog that owns us.
     private PhotoPickerDialog mDialog;
 
@@ -60,6 +64,14 @@
     // The {@link SelectionDelegate} keeping track of which images are selected.
     private SelectionDelegate<PickerBitmap> mSelectionDelegate;
 
+    // A low-resolution cache for images. Helpful for cache misses from the high-resolution cache
+    // to avoid showing gray squares (we show pixelated versions instead until image can be loaded
+    // off disk, which is much less jarring).
+    private LruCache<String, Bitmap> mLowResBitmaps;
+
+    // A high-resolution cache for images.
+    private LruCache<String, Bitmap> mHighResBitmaps;
+
     /**
      * The number of columns to show. Note: mColumns and mPadding (see below) should both be even
      * numbers or both odd, not a mix (the column padding will not be of uniform thickness if they
@@ -118,7 +130,11 @@
         mRecyclerView.setLayoutManager(mLayoutManager);
         mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(mColumns, mPadding));
 
-        // TODO(finnur): Implement caching.
+        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / KILOBYTE);
+        final int cacheSizeLarge = maxMemory / 2; // 1/2 of the available memory.
+        final int cacheSizeSmall = maxMemory / 8; // 1/8th of the available memory.
+        mLowResBitmaps = new LruCache<String, Bitmap>(cacheSizeSmall);
+        mHighResBitmaps = new LruCache<String, Bitmap>(cacheSizeLarge);
     }
 
     /**
@@ -222,6 +238,14 @@
         return mDecoderServiceHost;
     }
 
+    public LruCache<String, Bitmap> getLowResBitmaps() {
+        return mLowResBitmaps;
+    }
+
+    public LruCache<String, Bitmap> getHighResBitmaps() {
+        return mHighResBitmaps;
+    }
+
     public boolean isMultiSelectAllowed() {
         return mMultiSelectionAllowed;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/rlz/RlzPingHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/rlz/RlzPingHandler.java
new file mode 100644
index 0000000..4ed88a7
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/rlz/RlzPingHandler.java
@@ -0,0 +1,49 @@
+// 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.rlz;
+
+import android.text.TextUtils;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.chrome.browser.identity.SettingsSecureBasedIdentificationGenerator;
+import org.chromium.chrome.browser.profiles.Profile;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A handler for revenue related pings that needs customized brand and event codes.
+ */
+@JNINamespace("chrome::android")
+public class RlzPingHandler {
+    private static final String ID_SALT = "RLZSalt";
+
+    private RlzPingHandler() {}
+
+    /**
+     * Generates a network ping with multiple events and a custom brand code. The application id is
+     * always "chrome" and the language uses the default system language. The machine id is
+     * a 50 character long generated string through
+     * {@link SettingsSecureBasedIdentificationGenerator}.
+     * @param brand The custom brand to be used for the ping.
+     * @param events The list of events that should be sent with the ping.
+     * @param callback A callback to be notified of the validity of the response received.
+     */
+    public static void startPing(
+            String brand, List<String> events, final Callback<Boolean> callback) {
+        String id =
+                new SettingsSecureBasedIdentificationGenerator(ContextUtils.getApplicationContext())
+                        .getUniqueId(ID_SALT);
+        id = id + id.substring(0, 50 - id.length() - 1);
+
+        nativeStartPing(Profile.getLastUsedProfile().getOriginalProfile(), brand,
+                Locale.getDefault().getLanguage(), TextUtils.join(",", events), id, callback);
+    }
+
+    private static native void nativeStartPing(Profile profile, String brand, String language,
+            String events, String id, Callback<Boolean> callback);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java
index 9fd7a66cd5..6c10e4a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java
@@ -35,9 +35,6 @@
 
 /**
  * Provides content to be displayed inside of the Home tab of bottom sheet.
- *
- * TODO(dgn): If the bottom sheet view is not recreated across tab changes, it will have to be
- * notified of it, at least when it is pulled up on the new tab.
  */
 public class SuggestionsBottomSheetContent implements BottomSheet.BottomSheetContent {
     private static SuggestionsSource sSuggestionsSourceForTesting;
@@ -92,17 +89,19 @@
             @Override
             public void onSheetOpened() {
                 mRecyclerView.scrollToPosition(0);
-
-                // TODO(https://crbug.com/689962) Ensure this call does not discard all suggestions
-                // every time the sheet is opened.
-                adapter.refreshSuggestions();
-                mSuggestionsUiDelegate.getEventReporter().onSurfaceOpened();
+                prepareSuggestionsForReveal(adapter);
             }
+
+            @Override
+            public void onSheetClosed() {
+                SuggestionsMetrics.recordSurfaceHidden();
+            }
+
         };
         mBottomSheet = activity.getBottomSheet();
         mBottomSheet.addObserver(mBottomSheetObserver);
-        adapter.refreshSuggestions();
-        mSuggestionsUiDelegate.getEventReporter().onSurfaceOpened();
+
+        if (mBottomSheet.isSheetOpen()) prepareSuggestionsForReveal(adapter);
 
         mShadowView = (FadingShadowView) mView.findViewById(R.id.shadow);
         mShadowView.init(
@@ -164,6 +163,13 @@
         return BottomSheetContentController.TYPE_SUGGESTIONS;
     }
 
+    /** Called when the UI is revlealed, prepares the list of suggestions. */
+    private void prepareSuggestionsForReveal(NewTabPageAdapter adapter) {
+        adapter.refreshSuggestions();
+        mSuggestionsUiDelegate.getEventReporter().onSurfaceOpened();
+        SuggestionsMetrics.recordSurfaceVisible();
+    }
+
     public static void setSuggestionsSourceForTesting(SuggestionsSource suggestionsSource) {
         sSuggestionsSourceForTesting = suggestionsSource;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
new file mode 100644
index 0000000..fa64959c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
@@ -0,0 +1,46 @@
+// 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.suggestions;
+
+import org.chromium.base.metrics.RecordUserAction;
+
+/**
+ * Exposes methods to report suggestions related events, for UMA or Fetch scheduling purposes.
+ */
+public abstract class SuggestionsMetrics {
+    private SuggestionsMetrics() {}
+
+    // UI Element interactions
+
+    public static void recordSurfaceVisible() {
+        RecordUserAction.record("Suggestions.SurfaceVisible");
+    }
+
+    public static void recordSurfaceHidden() {
+        RecordUserAction.record("Suggestions.SurfaceHidden");
+    }
+
+    public static void recordTileTapped() {
+        RecordUserAction.record("Suggestions.Tile.Tapped");
+    }
+
+    public static void recordCardTapped() {
+        RecordUserAction.record("Suggestions.Card.Tapped");
+    }
+
+    public static void recordCardActionTapped() {
+        RecordUserAction.record("Suggestions.Card.ActionTapped");
+    }
+
+    public static void recordCardSwipedAway() {
+        RecordUserAction.record("Suggestions.Card.SwipedAway");
+    }
+
+    // Effect/Purpose of the interactions. Most are recorded in |content_suggestions_metrics.h|
+
+    public static void recordActionViewAll() {
+        RecordUserAction.record("Suggestions.Category.ViewAll");
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
index 2f521566..6fee021f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
@@ -329,6 +329,7 @@
         @Override
         public void onSwiped(ViewHolder viewHolder, int direction) {
             onItemDismissStarted(viewHolder);
+            SuggestionsMetrics.recordCardSwipedAway();
             dismissItemInternal(viewHolder);
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java
index 460bb3b..c3d0f644 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroup.java
@@ -25,7 +25,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
 import org.chromium.base.VisibleForTesting;
-import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback;
@@ -441,6 +440,7 @@
             Tile tile = getTile(mUrl);
             if (tile == null) return;
 
+            SuggestionsMetrics.recordTileTapped();
             mTileGroupDelegate.openMostVisitedItem(WindowOpenDisposition.CURRENT_TAB, tile);
         }
 
@@ -487,8 +487,6 @@
         @Override
         public void onResult(String restoredUrl) {
             mPendingInsertionUrl = restoredUrl;
-
-            RecordUserAction.record("Suggestions.Tile.RemovalUndone");
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroupDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroupDelegateImpl.java
index da233f30..cd3903aa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroupDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGroupDelegateImpl.java
@@ -153,7 +153,6 @@
     }
 
     private void recordOpenedTile(Tile tile) {
-        // TODO(mvanouwerkerk): Fix metrics to distinguish NTP from Home.
         NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_MOST_VISITED_TILE);
         RecordUserAction.record("MobileNTPMostVisited");
         NewTabPageUma.recordExplicitUserNavigation(
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 437c735..3cc5f37 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
@@ -340,6 +340,9 @@
             @SheetState
             int targetState = getTargetSheetState(
                     getSheetOffsetFromBottom() + getFlingDistance(-velocityY), -velocityY);
+            if (targetState == SHEET_STATE_PEEK) {
+                mMetrics.setSheetCloseReason(BottomSheetMetrics.CLOSED_BY_SWIPE);
+            }
             setSheetState(targetState, true);
             mIsScrolling = false;
 
@@ -435,6 +438,9 @@
                 @SheetState
                 int targetState = getTargetSheetState(getSheetOffsetFromBottom(), currentVelocity);
 
+                if (targetState == SHEET_STATE_PEEK) {
+                    mMetrics.setSheetCloseReason(BottomSheetMetrics.CLOSED_BY_SWIPE);
+                }
                 setSheetState(targetState, true);
             }
         }
@@ -584,6 +590,7 @@
         }
 
         // In all non-native cases, minimize the sheet.
+        mMetrics.setSheetCloseReason(BottomSheetMetrics.CLOSED_BY_NAVIGATION);
         setSheetState(SHEET_STATE_PEEK, true);
 
         assert mTabModelSelector != null;
@@ -1119,6 +1126,7 @@
 
     @Override
     public void onFadingViewClick() {
+        mMetrics.setSheetCloseReason(BottomSheetMetrics.CLOSED_BY_TAP_SCRIM);
         setSheetState(SHEET_STATE_PEEK, true);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java
index e5136e0..8cbbd17 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java
@@ -32,12 +32,29 @@
     public static final int OPENED_BY_EXPAND_BUTTON = 3;
     private static final int OPENED_BY_BOUNDARY = 4;
 
+    /** The different ways that the bottom sheet can be closed. */
+    @IntDef({CLOSED_BY_NONE, CLOSED_BY_SWIPE, CLOSED_BY_NTP_CLOSE_BUTTON, CLOSED_BY_TAP_SCRIM,
+            CLOSED_BY_NAVIGATION})
+    public @interface SheetCloseReason {}
+    private static final int CLOSED_BY_NONE = -1;
+    public static final int CLOSED_BY_SWIPE = 0;
+    public static final int CLOSED_BY_NTP_CLOSE_BUTTON = 1;
+    public static final int CLOSED_BY_TAP_SCRIM = 2;
+    public static final int CLOSED_BY_NAVIGATION = 3;
+
     /** Whether the sheet is currently open. */
     private boolean mIsSheetOpen;
 
     /** The last {@link BottomSheetContent} that was displayed. */
     private BottomSheetContent mLastContent;
 
+    /**
+     * The current reason the sheet might become closed. This may change before the sheet actually
+     * reaches the closed state.
+     */
+    @SheetCloseReason
+    private int mSheetCloseReason;
+
     /** When this class was created. Used as a proxy for when the app was started. */
     private long mCreationTime;
 
@@ -54,7 +71,6 @@
     @Override
     public void onSheetOpened() {
         mIsSheetOpen = true;
-        RecordUserAction.record("Android.ChromeHome.Opened");
 
         boolean isFirstOpen = mLastOpenTime == 0;
         mLastOpenTime = System.currentTimeMillis();
@@ -72,7 +88,8 @@
     @Override
     public void onSheetClosed() {
         mIsSheetOpen = false;
-        RecordUserAction.record("Android.ChromeHome.Closed");
+        recordSheetCloseReason(mSheetCloseReason);
+        mSheetCloseReason = CLOSED_BY_NONE;
 
         mLastCloseTime = System.currentTimeMillis();
         RecordHistogram.recordMediumTimesHistogram("Android.ChromeHome.DurationOpen",
@@ -115,11 +132,62 @@
     }
 
     /**
+     * Set the reason the bottom sheet is currently closing. This value is not recorded until after
+     * the sheet is actually closed.
+     * @param reason The {@link SheetCloseReason} that the sheet is closing.
+     */
+    public void setSheetCloseReason(@SheetCloseReason int reason) {
+        mSheetCloseReason = reason;
+    }
+
+    /**
      * Records the reason the sheet was opened.
      * @param reason The {@link SheetOpenReason} that caused the bottom sheet to open.
      */
     public void recordSheetOpenReason(@SheetOpenReason int reason) {
+        switch (reason) {
+            case OPENED_BY_SWIPE:
+                RecordUserAction.record("Android.ChromeHome.OpenedBySwipe");
+                break;
+            case OPENED_BY_OMNIBOX_FOCUS:
+                RecordUserAction.record("Android.ChromeHome.OpenedByOmnibox");
+                break;
+            case OPENED_BY_NEW_TAB_CREATION:
+                RecordUserAction.record("Android.ChromeHome.OpenedByNTP");
+                break;
+            case OPENED_BY_EXPAND_BUTTON:
+                RecordUserAction.record("Android.ChromeHome.OpenedByExpandButton");
+                break;
+            default:
+                assert false;
+        }
         RecordHistogram.recordEnumeratedHistogram(
                 "Android.ChromeHome.OpenReason", reason, OPENED_BY_BOUNDARY);
     }
+
+    /**
+     * Records the reason the sheet was closed.
+     * @param reason The {@link SheetCloseReason} that cause the bottom sheet to close.
+     */
+    public void recordSheetCloseReason(@SheetCloseReason int reason) {
+        switch (reason) {
+            case CLOSED_BY_SWIPE:
+                RecordUserAction.record("Android.ChromeHome.ClosedBySwipe");
+                break;
+            case CLOSED_BY_NTP_CLOSE_BUTTON:
+                RecordUserAction.record("Android.ChromeHome.ClosedByNTPCloseButton");
+                break;
+            case CLOSED_BY_TAP_SCRIM:
+                RecordUserAction.record("Android.ChromeHome.ClosedByTapScrim");
+                break;
+            case CLOSED_BY_NAVIGATION:
+                RecordUserAction.record("Android.ChromeHome.ClosedByNavigation");
+                break;
+            case CLOSED_BY_NONE:
+                RecordUserAction.record("Android.ChromeHome.Closed");
+                break;
+            default:
+                assert false;
+        }
+    }
 }
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 0392559..d1d7637a 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -462,6 +462,7 @@
   "java/src/org/chromium/chrome/browser/infobar/IPHInfoBarSupport.java",
   "java/src/org/chromium/chrome/browser/infobar/PermissionInfoBar.java",
   "java/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfoBarDelegate.java",
+  "java/src/org/chromium/chrome/browser/infobar/ReaderModeInfoBar.java",
   "java/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBar.java",
   "java/src/org/chromium/chrome/browser/infobar/SimpleConfirmInfoBarBuilder.java",
   "java/src/org/chromium/chrome/browser/infobar/SubresourceFilterInfoBar.java",
@@ -788,6 +789,7 @@
   "java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java",
   "java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java",
   "java/src/org/chromium/chrome/browser/physicalweb/BitmapHttpRequest.java",
+  "java/src/org/chromium/chrome/browser/photo_picker/BitmapScalerTask.java",
   "java/src/org/chromium/chrome/browser/photo_picker/BitmapUtils.java",
   "java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java",
   "java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java",
@@ -948,6 +950,7 @@
   "java/src/org/chromium/chrome/browser/push_messaging/PushMessagingServiceObserver.java",
   "java/src/org/chromium/chrome/browser/rappor/RapporServiceBridge.java",
   "java/src/org/chromium/chrome/browser/rlz/RevenueStats.java",
+  "java/src/org/chromium/chrome/browser/rlz/RlzPingHandler.java",
   "java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchActivityFadingBackgroundView.java",
@@ -1018,6 +1021,7 @@
   "java/src/org/chromium/chrome/browser/suggestions/SuggestionsUiDelegateImpl.java",
   "java/src/org/chromium/chrome/browser/suggestions/SuggestionsEventReporter.java",
   "java/src/org/chromium/chrome/browser/suggestions/SuggestionsEventReporterBridge.java",
+  "java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java",
   "java/src/org/chromium/chrome/browser/suggestions/SuggestionsRanker.java",
   "java/src/org/chromium/chrome/browser/superviseduser/SupervisedUserContentProvider.java",
   "java/src/org/chromium/chrome/browser/sync/GmsCoreSyncListener.java",
@@ -1713,7 +1717,7 @@
   "junit/src/org/chromium/chrome/browser/offlinepages/ClientIdTest.java",
   "junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridgeUnitTest.java",
   "junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserverTest.java",
-  "junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java",
   "junit/src/org/chromium/chrome/browser/offlinepages/ShadowDeviceConditions.java",
   "junit/src/org/chromium/chrome/browser/offlinepages/ShadowGcmNetworkManager.java",
   "junit/src/org/chromium/chrome/browser/offlinepages/StubBackgroundSchedulerProcessor.java",
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index 14fe46a7..1d421c8 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -29,6 +29,7 @@
 import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsUnitTestUtils.bindViewHolders;
 import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestionsTestUtils.createDummySuggestions;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -38,6 +39,7 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.Callback;
+import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.DisableHistogramsRule;
 import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder.UpdateLayoutParamsCallback;
@@ -81,6 +83,7 @@
 
     @Before
     public void setUp() {
+        RecordUserAction.setDisabledForTests(true);
         MockitoAnnotations.initMocks(this);
         mBridge = new FakeOfflinePageBridge();
 
@@ -88,6 +91,11 @@
         CardsVariationParameters.setTestVariationParams(new HashMap<String, String>());
     }
 
+    @After
+    public void tearDown() {
+        RecordUserAction.setDisabledForTests(false);
+    }
+
     @Test
     @Feature({"Ntp"})
     public void testDismissSibling() {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserverTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserverTest.java
index a901d25..40a7c419 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserverTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserverTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -14,6 +15,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,6 +51,8 @@
     @Mock private SnackbarManager mSnackbarManager;
     @Mock private SnackbarController mSnackbarController;
     @Mock private Tab mTab;
+    @Mock
+    private OfflinePageUtils.Internal mOfflinePageUtils;
 
     private OfflinePageTabObserver createObserver() {
         OfflinePageTabObserver observer = spy(new OfflinePageTabObserver(
@@ -56,13 +61,7 @@
         // directly mock out.
         doNothing().when(observer).startObservingNetworkChanges();
         doNothing().when(observer).stopObservingNetworkChanges();
-        // TODO(fgorski): This call has to be mocked out until we update OfflinePageUtils.
-        // It also goes to NetworkChangeNotifier from there.
-        doReturn(false).when(observer).isConnected();
-        doReturn(false).when(observer).isShowingOfflinePreview(any(Tab.class));
-        // TODO(fgorski): This call has to be mocked out until we update OfflinePageUtils.
-        doNothing().when(observer).showReloadSnackbar(any(Tab.class));
-        doReturn(true).when(observer).isOfflinePage(any(Tab.class));
+
         // Assert tab model observer was created.
         assertTrue(observer.getTabModelObserver() != null);
         return observer;
@@ -81,6 +80,16 @@
 
         // Setting up mock snackbar manager.
         doNothing().when(mSnackbarManager).dismissSnackbars(eq(mSnackbarController));
+
+        // Setting up offline page utils.
+        OfflinePageUtils.setInstanceForTesting(mOfflinePageUtils);
+        doReturn(false).when(mOfflinePageUtils).isConnected();
+        doReturn(false).when(mOfflinePageUtils).isShowingOfflinePreview(any(Tab.class));
+        doReturn(true).when(mOfflinePageUtils).isOfflinePage(any(Tab.class));
+        doNothing()
+                .when(mOfflinePageUtils)
+                .showReloadSnackbar(any(Context.class), any(SnackbarManager.class),
+                        any(SnackbarController.class), anyInt());
     }
 
     private void showTab(OfflinePageTabObserver observer) {
@@ -104,14 +113,14 @@
     }
 
     private void connect(OfflinePageTabObserver observer, boolean notify) {
-        doReturn(true).when(observer).isConnected();
+        doReturn(true).when(mOfflinePageUtils).isConnected();
         if (notify) {
             observer.onConnectionTypeChanged(0);
         }
     }
 
     private void disconnect(OfflinePageTabObserver observer, boolean notify) {
-        doReturn(false).when(observer).isConnected();
+        doReturn(false).when(mOfflinePageUtils).isConnected();
         if (notify) {
             observer.onConnectionTypeChanged(0);
         }
@@ -130,14 +139,14 @@
     public void testStartObservingTab() {
         OfflinePageTabObserver observer = createObserver();
 
-        doReturn(false).when(observer).isOfflinePage(any(Tab.class));
+        doReturn(false).when(mOfflinePageUtils).isOfflinePage(any(Tab.class));
         observer.startObservingTab(mTab);
 
         assertFalse(observer.isObservingNetworkChanges());
         assertFalse(observer.isObservingTab(mTab));
         verify(observer, times(0)).showReloadSnackbar(any(Tab.class));
 
-        doReturn(true).when(observer).isOfflinePage(any(Tab.class));
+        doReturn(true).when(mOfflinePageUtils).isOfflinePage(any(Tab.class));
         observer.startObservingTab(mTab);
 
         assertTrue(observer.isObservingNetworkChanges());
@@ -171,7 +180,7 @@
         OfflinePageTabObserver observer = createObserver();
 
         observer.startObservingTab(mTab);
-        doReturn(true).when(observer).isConnected();
+        doReturn(true).when(mOfflinePageUtils).isConnected();
         observer.onPageLoadFinished(mTab);
 
         verify(observer, times(1)).showReloadSnackbar(any(Tab.class));
@@ -267,7 +276,7 @@
         hideTab(null);
 
         observer.startObservingTab(mTab);
-        doReturn(true).when(observer).isShowingOfflinePreview(mTab);
+        doReturn(true).when(mOfflinePageUtils).isShowingOfflinePreview(mTab);
         observer.onPageLoadFinished(mTab);
 
         verify(observer, times(0)).showReloadSnackbar(any(Tab.class));
@@ -400,7 +409,7 @@
         verify(mSnackbarManager, times(1)).dismissSnackbars(eq(mSnackbarController));
 
         // URL updated and tab no longer shows offline page.
-        doReturn(false).when(observer).isOfflinePage(any(Tab.class));
+        doReturn(false).when(mOfflinePageUtils).isOfflinePage(any(Tab.class));
         observer.onUrlUpdated(mTab);
 
         assertFalse(observer.isObservingTab(mTab));
@@ -432,7 +441,7 @@
         observer.onPageLoadFinished(mTab);
 
         // URL updated and tab no longer shows offline page.
-        doReturn(false).when(observer).isOfflinePage(any(Tab.class));
+        doReturn(false).when(mOfflinePageUtils).isOfflinePage(any(Tab.class));
         observer.onUrlUpdated(mTab);
 
         assertFalse(observer.isObservingTab(mTab));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
similarity index 92%
rename from chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsUnitTest.java
rename to chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
index 11a221c7..96a0747 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
@@ -16,14 +16,6 @@
 
 import android.os.Environment;
 
-import org.chromium.base.BaseChromiumApplication;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.bookmarks.BookmarkId;
-import org.chromium.components.bookmarks.BookmarkType;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.testing.local.LocalRobolectricTestRunner;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,22 +26,34 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.multidex.ShadowMultiDex;
 
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.bookmarks.BookmarkId;
+import org.chromium.components.bookmarks.BookmarkType;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
 import java.io.File;
 
 /**
  * Unit tests for OfflinePageUtils.
  */
 @RunWith(LocalRobolectricTestRunner.class)
-@Config(manifest = Config.NONE,
-        application = BaseChromiumApplication.class,
-        shadows = { OfflinePageUtilsUnitTest.WrappedEnvironment.class, ShadowMultiDex.class })
-public class OfflinePageUtilsUnitTest {
-
-    @Mock private File mMockDataDirectory;
-    @Mock private Tab mTab;
-    @Mock private WebContents mWebContents;
-    @Mock private OfflinePageBridge mOfflinePageBridge;
-    @Mock private OfflinePageUtils mOfflinePageUtils;
+@Config(manifest = Config.NONE, application = BaseChromiumApplication.class,
+        shadows = {OfflinePageUtilsTest.WrappedEnvironment.class, ShadowMultiDex.class})
+public class OfflinePageUtilsTest {
+    @Mock
+    private File mMockDataDirectory;
+    @Mock
+    private Tab mTab;
+    @Mock
+    private WebContents mWebContents;
+    @Mock
+    private OfflinePageBridge mOfflinePageBridge;
+    @Mock
+    private OfflinePageUtils.Internal mOfflinePageUtils;
 
     @Before
     public void setUp() throws Exception {
@@ -100,13 +104,13 @@
         assertEquals("cs.chromium.org",
                 OfflinePageUtils.stripSchemeFromOnlineUrl("http://cs.chromium.org"));
         // If there is no scheme, nothing changes.
-        assertEquals("cs.chromium.org",
-                OfflinePageUtils.stripSchemeFromOnlineUrl("cs.chromium.org"));
+        assertEquals(
+                "cs.chromium.org", OfflinePageUtils.stripSchemeFromOnlineUrl("cs.chromium.org"));
         // Path is not touched/changed.
         String urlWithPath = "code.google.com/p/chromium/codesearch#search"
                 + "/&q=offlinepageutils&sq=package:chromium&type=cs";
-        assertEquals(urlWithPath,
-                OfflinePageUtils.stripSchemeFromOnlineUrl("https://" + urlWithPath));
+        assertEquals(
+                urlWithPath, OfflinePageUtils.stripSchemeFromOnlineUrl("https://" + urlWithPath));
         // Beginning and ending spaces get trimmed.
         assertEquals("cs.chromium.org",
                 OfflinePageUtils.stripSchemeFromOnlineUrl("  https://cs.chromium.org  "));
diff --git a/chrome/app/nibs/BUILD.gn b/chrome/app/nibs/BUILD.gn
index 6b0caef..1a06558 100644
--- a/chrome/app/nibs/BUILD.gn
+++ b/chrome/app/nibs/BUILD.gn
@@ -31,7 +31,6 @@
   "ExtensionInstallPromptWebstoreData.xib",
   "ExtensionInstalledBubble.xib",
   "FirstRunBubble.xib",
-  "FirstRunDialog.xib",
   "HttpAuthLoginSheet.xib",
   "HungRendererDialog.xib",
   "MainMenu.xib",
diff --git a/chrome/app/nibs/FirstRunDialog.xib b/chrome/app/nibs/FirstRunDialog.xib
deleted file mode 100644
index a223b06b..0000000
--- a/chrome/app/nibs/FirstRunDialog.xib
+++ /dev/null
@@ -1,140 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13F1077" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
-    <dependencies>
-        <deployment version="1090" identifier="macosx"/>
-        <development version="5100" identifier="xcode"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
-    </dependencies>
-    <objects>
-        <customObject id="-2" userLabel="File's Owner" customClass="FirstRunDialogController">
-            <connections>
-                <outlet property="objectsToSize_" destination="106" id="117"/>
-                <outlet property="setAsDefaultCheckbox_" destination="11" id="131"/>
-                <outlet property="statsCheckbox_" destination="30" id="124"/>
-                <outlet property="window" destination="1" id="65"/>
-            </connections>
-        </customObject>
-        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
-        <customObject id="-3" userLabel="Application"/>
-        <window title="^IDS_FIRSTRUN_DLG_MAC_WINDOW_TITLE$IDS_PRODUCT_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="1">
-            <windowStyleMask key="styleMask" titled="YES"/>
-            <windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
-            <rect key="contentRect" x="196" y="290" width="480" height="209"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028"/>
-            <view key="contentView" id="2">
-                <rect key="frame" x="0.0" y="0.0" width="480" height="209"/>
-                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                <subviews>
-                    <button id="11">
-                        <rect key="frame" x="45" y="126" width="528" height="18"/>
-                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                        <buttonCell key="cell" type="check" title="^IDS_FIRSTRUN_DLG_MAC_SET_DEFAULT_BROWSER_LABEL" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="12">
-                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
-                            <font key="font" metaFont="system"/>
-                        </buttonCell>
-                        <connections>
-                            <binding destination="-2" name="value" keyPath="makeDefaultBrowser" id="68"/>
-                        </connections>
-                    </button>
-                    <box autoresizesSubviews="NO" title="Box" boxType="custom" borderType="none" id="21">
-                        <rect key="frame" x="0.0" y="158" width="480" height="55"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
-                        <view key="contentView">
-                            <rect key="frame" x="0.0" y="0.0" width="480" height="55"/>
-                            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                            <subviews>
-                                <textField verticalHuggingPriority="750" id="3">
-                                    <rect key="frame" x="13" y="25" width="390" height="17"/>
-                                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                                    <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="^IDS_FIRSTRUN_DLG_MAC_COMPLETE_INSTALLATION_LABEL$IDS_PRODUCT_NAME" id="4">
-                                        <font key="font" metaFont="system"/>
-                                        <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
-                                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
-                                    </textFieldCell>
-                                </textField>
-                                <imageView id="9">
-                                    <rect key="frame" x="408" y="-25" width="96" height="96"/>
-                                    <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
-                                    <imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="NSApplicationIcon" id="10"/>
-                                </imageView>
-                            </subviews>
-                        </view>
-                        <color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
-                        <color key="fillColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
-                    </box>
-                    <box autoresizesSubviews="NO" verticalHuggingPriority="750" title="Box" boxType="separator" titlePosition="noTitle" id="22">
-                        <rect key="frame" x="0.0" y="155" width="480" height="5"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
-                        <color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
-                        <color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
-                        <font key="titleFont" metaFont="system"/>
-                    </box>
-                    <box autoresizesSubviews="NO" verticalHuggingPriority="750" title="Box" boxType="separator" titlePosition="noTitle" id="29">
-                        <rect key="frame" x="0.0" y="55" width="480" height="5"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
-                        <color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
-                        <color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
-                        <font key="titleFont" metaFont="system"/>
-                    </box>
-                    <button id="30">
-                        <rect key="frame" x="45" y="101" width="389" height="19"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
-                        <buttonCell key="cell" type="check" title="^IDS_FIRSTRUN_DLG_MAC_OPTIONS_SEND_USAGE_STATS_LABEL$IDS_PRODUCT_NAME" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="31">
-                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
-                            <font key="font" metaFont="system"/>
-                        </buttonCell>
-                        <connections>
-                            <binding destination="-2" name="value" keyPath="statsEnabled" id="69"/>
-                        </connections>
-                    </button>
-                    <customView id="97" customClass="GTMWidthBasedTweaker">
-                        <rect key="frame" x="147" y="-2" width="334" height="52"/>
-                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
-                        <subviews>
-                            <button verticalHuggingPriority="750" id="27">
-                                <rect key="frame" x="14" y="12" width="306" height="32"/>
-                                <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
-                                <buttonCell key="cell" type="push" title="^IDS_FIRSTRUN_DLG_MAC_START_CHROME_BUTTON$IDS_PRODUCT_NAME" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="28">
-                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                                    <font key="font" metaFont="system"/>
-                                    <string key="keyEquivalent" base64-UTF8="YES">
-DQ
-</string>
-                                </buttonCell>
-                                <connections>
-                                    <action selector="ok:" target="-2" id="72"/>
-                                </connections>
-                            </button>
-                        </subviews>
-                    </customView>
-                    <button id="36">
-                        <rect key="frame" x="60" y="76" width="359" height="19"/>
-                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                        <buttonCell key="cell" type="roundRect" title="^IDS_LEARN_MORE" bezelStyle="roundedRect" alignment="center" state="on" imageScaling="proportionallyDown" inset="2" id="37" customClass="HyperlinkButtonCell">
-                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                        </buttonCell>
-                        <connections>
-                            <action selector="learnMore:" target="-2" id="73"/>
-                        </connections>
-                    </button>
-                </subviews>
-            </view>
-        </window>
-        <customObject id="91" customClass="ChromeUILocalizer">
-            <connections>
-                <outlet property="owner_" destination="-2" id="118"/>
-            </connections>
-        </customObject>
-        <customObject id="106" userLabel="Objects to Size" customClass="GTMIBArray">
-            <connections>
-                <outlet property="object1_" destination="97" id="107"/>
-                <outlet property="object2_" destination="36" id="108"/>
-                <outlet property="object3_" destination="11" id="119"/>
-                <outlet property="object5_" destination="3" id="130"/>
-            </connections>
-        </customObject>
-    </objects>
-    <resources>
-        <image name="NSApplicationIcon" width="128" height="128"/>
-    </resources>
-</document>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 9092afd..85b6d5f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -564,6 +564,10 @@
     "media/media_access_handler.h",
     "media/media_device_id_salt.cc",
     "media/media_device_id_salt.h",
+    "media/media_engagement_service.cc",
+    "media/media_engagement_service.h",
+    "media/media_engagement_service_factory.cc",
+    "media/media_engagement_service_factory.h",
     "media/media_url_constants.cc",
     "media/media_url_constants.h",
     "media/midi_permission_context.cc",
@@ -2962,6 +2966,8 @@
       "android/resource_mapper.h",
       "android/rlz/revenue_stats.cc",
       "android/rlz/revenue_stats.h",
+      "android/rlz/rlz_ping_handler.cc",
+      "android/rlz/rlz_ping_handler.h",
       "android/search_geolocation/search_geolocation_disclosure_infobar_delegate.cc",
       "android/search_geolocation/search_geolocation_disclosure_infobar_delegate.h",
       "android/search_geolocation/search_geolocation_disclosure_tab_helper.cc",
@@ -3183,6 +3189,7 @@
       "//components/safe_browsing_db",
       "//components/toolbar",
       "//components/web_contents_delegate_android",
+      "//rlz:rlz_utils",
       "//sandbox:sandbox_features",
       "//third_party/android_opengl/etc1",
       "//third_party/android_tools:cpu_features",
@@ -4144,6 +4151,7 @@
       "../android/java/src/org/chromium/chrome/browser/infobar/InstantAppsInfoBarDelegate.java",
       "../android/java/src/org/chromium/chrome/browser/infobar/PermissionInfoBar.java",
       "../android/java/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfoBarDelegate.java",
+      "../android/java/src/org/chromium/chrome/browser/infobar/ReaderModeInfoBar.java",
       "../android/java/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBar.java",
       "../android/java/src/org/chromium/chrome/browser/infobar/SimpleConfirmInfoBarBuilder.java",
       "../android/java/src/org/chromium/chrome/browser/infobar/SubresourceFilterExperimentalInfoBar.java",
@@ -4221,6 +4229,7 @@
       "../android/java/src/org/chromium/chrome/browser/push_messaging/PushMessagingServiceObserver.java",
       "../android/java/src/org/chromium/chrome/browser/rappor/RapporServiceBridge.java",
       "../android/java/src/org/chromium/chrome/browser/rlz/RevenueStats.java",
+      "../android/java/src/org/chromium/chrome/browser/rlz/RlzPingHandler.java",
       "../android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java",
       "../android/java/src/org/chromium/chrome/browser/sessions/SessionTabHelper.java",
       "../android/java/src/org/chromium/chrome/browser/signin/AccountManagementScreenHelper.java",
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index 8d4a2ab1..0d87796b 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -95,6 +95,7 @@
 #include "chrome/browser/android/rappor/rappor_service_bridge.h"
 #include "chrome/browser/android/recently_closed_tabs_bridge.h"
 #include "chrome/browser/android/rlz/revenue_stats.h"
+#include "chrome/browser/android/rlz/rlz_ping_handler.h"
 #include "chrome/browser/android/search_geolocation/search_geolocation_disclosure_tab_helper.h"
 #include "chrome/browser/android/service_tab_launcher.h"
 #include "chrome/browser/android/sessions/session_tab_helper_android.h"
@@ -159,6 +160,7 @@
 #include "chrome/browser/ui/android/infobars/grouped_permission_infobar.h"
 #include "chrome/browser/ui/android/infobars/infobar_android.h"
 #include "chrome/browser/ui/android/infobars/infobar_container_android.h"
+#include "chrome/browser/ui/android/infobars/reader_mode_infobar.h"
 #include "chrome/browser/ui/android/infobars/simple_confirm_infobar_builder.h"
 #include "chrome/browser/ui/android/infobars/translate_compact_infobar.h"
 #include "chrome/browser/ui/android/infobars/translate_infobar.h"
@@ -388,6 +390,7 @@
     {"RapporServiceBridge", rappor::RegisterRapporServiceBridge},
     {"RecentlyClosedBridge", RecentlyClosedTabsBridge::Register},
     {"RecordCastAction", remote_media::RegisterRecordCastAction},
+    {"ReaderModeInfoBar", RegisterReaderModeInfoBar},
     {"ReaderModeSceneLayer", RegisterReaderModeSceneLayer},
     {"RemoteMediaPlayerBridge",
      remote_media::RemoteMediaPlayerBridge::RegisterRemoteMediaPlayerBridge},
@@ -395,6 +398,7 @@
     {"ResourcePrefetchPredictor",
      predictors::RegisterResourcePrefetchPredictor},
     {"RevenueStats", chrome::android::RegisterRevenueStats},
+    {"RlzPingHandler", chrome::android::RegisterRlzPingHandler},
     {"SafeBrowsing", safe_browsing::android::RegisterBrowserJNI},
     {"SceneLayer", RegisterSceneLayer},
     {"ScreenshotTask", chrome::android::RegisterScreenshotTask},
diff --git a/chrome/browser/android/ntp/content_suggestions_notifier_service.cc b/chrome/browser/android/ntp/content_suggestions_notifier_service.cc
index ad4f121..b970049a 100644
--- a/chrome/browser/android/ntp/content_suggestions_notifier_service.cc
+++ b/chrome/browser/android/ntp/content_suggestions_notifier_service.cc
@@ -204,7 +204,7 @@
   void AppStatusChanged(base::android::ApplicationState state) {
     if (variations::GetVariationParamByFeatureAsBool(
             kNotificationsFeature, kNotificationsKeepWhenFrontmostParam,
-            false)) {
+            true)) {
       return;
     }
     if (!ShouldNotifyInState(state)) {
diff --git a/chrome/browser/android/rlz/rlz_ping_handler.cc b/chrome/browser/android/rlz/rlz_ping_handler.cc
new file mode 100644
index 0000000..53723c44
--- /dev/null
+++ b/chrome/browser/android/rlz/rlz_ping_handler.cc
@@ -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.
+
+#include "chrome/browser/android/rlz/rlz_ping_handler.h"
+
+#include "base/android/callback_android.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "jni/RlzPingHandler_jni.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "rlz/lib/lib_values.h"
+#include "rlz/lib/net_response_check.h"
+#include "url/gurl.h"
+
+using base::android::ConvertJavaStringToUTF16;
+using base::android::JavaParamRef;
+
+constexpr int kMaxRetries = 10;
+
+namespace chrome {
+namespace android {
+
+RlzPingHandler::RlzPingHandler(jobject jprofile) {
+  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  DCHECK(profile);
+  request_context_ = profile->GetRequestContext();
+}
+
+RlzPingHandler::~RlzPingHandler() = default;
+
+void RlzPingHandler::Ping(
+    const base::android::JavaParamRef<jstring>& j_brand,
+    const base::android::JavaParamRef<jstring>& j_language,
+    const base::android::JavaParamRef<jstring>& j_events,
+    const base::android::JavaParamRef<jstring>& j_id,
+    const base::android::JavaParamRef<jobject>& j_callback) {
+  if (!j_brand || !j_language || !j_events || !j_id || !j_callback) {
+    base::android::RunCallbackAndroid(j_callback, false);
+    delete this;
+    return;
+  }
+
+  JNIEnv* env = base::android::AttachCurrentThread();
+
+  j_callback_.Reset(env, j_callback);
+  std::string brand = ConvertJavaStringToUTF8(env, j_brand);
+  std::string language = ConvertJavaStringToUTF8(env, j_language);
+  std::string events = ConvertJavaStringToUTF8(env, j_events);
+  std::string id = ConvertJavaStringToUTF8(env, j_id);
+
+  DCHECK_EQ(brand.length(), 4u);
+  DCHECK_EQ(language.length(), 2u);
+  DCHECK_EQ(id.length(), 50u);
+
+  GURL request_url(base::StringPrintf(
+      "https://%s%s?", rlz_lib::kFinancialServer, rlz_lib::kFinancialPingPath));
+  request_url = net::AppendQueryParameter(
+      request_url, rlz_lib::kProductSignatureCgiVariable, "chrome");
+  request_url = net::AppendQueryParameter(
+      request_url, rlz_lib::kProductBrandCgiVariable, brand);
+  request_url = net::AppendQueryParameter(
+      request_url, rlz_lib::kProductLanguageCgiVariable, language);
+  request_url = net::AppendQueryParameter(request_url,
+                                          rlz_lib::kEventsCgiVariable, events);
+  request_url = net::AppendQueryParameter(request_url,
+                                          rlz_lib::kMachineIdCgiVariable, id);
+
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("rlz", R"(
+          semantics {
+            sender: "RLZ Ping Handler"
+            description:
+            "Sends rlz pings for revenue related tracking to the designated web"
+            "end point."
+            trigger:
+            "Critical signals like first install, a promotion dialog being"
+            "shown, a user selection for a promotion may trigger a ping"
+            destination: WEBSITE
+          }
+          policy {
+            cookies_allowed: true
+            cookies_store: "user"
+            setting: "Not user controlled. But it uses a trusted web end point"
+                     "that doesn't use user data"
+            policy_exception_justification:
+              "Not implemented, considered not useful as no content is being "
+              "uploaded."
+          })");
+
+  url_fetcher_ = net::URLFetcher::Create(0, request_url, net::URLFetcher::GET,
+                                         this, traffic_annotation);
+  url_fetcher_->SetMaxRetriesOn5xx(kMaxRetries);
+  url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries);
+  url_fetcher_->SetAutomaticallyRetryOn5xx(true);
+  url_fetcher_->SetRequestContext(request_context_.get());
+  url_fetcher_->Start();
+}
+
+void RlzPingHandler::OnURLFetchComplete(const net::URLFetcher* source) {
+  DCHECK_EQ(source, url_fetcher_.get());
+
+  jboolean valid = false;
+  if (!source->GetStatus().is_success() ||
+      source->GetResponseCode() != net::HTTP_OK) {
+    LOG(WARNING) << base::StringPrintf("Rlz endpoint responded with code %d.",
+                                       source->GetResponseCode());
+  } else {
+    std::string response;
+    source->GetResponseAsString(&response);
+    int response_length = -1;
+    valid = rlz_lib::IsPingResponseValid(response.c_str(), &response_length);
+  }
+
+  // TODO(yusufo) : Investigate what else can be checked for validity that is
+  // specific to the ping
+  base::android::RunCallbackAndroid(j_callback_, valid);
+  delete this;
+}
+
+void StartPing(JNIEnv* env,
+               const JavaParamRef<jclass>& clazz,
+               const base::android::JavaParamRef<jobject>& j_profile,
+               const base::android::JavaParamRef<jstring>& j_brand,
+               const base::android::JavaParamRef<jstring>& j_language,
+               const base::android::JavaParamRef<jstring>& j_events,
+               const base::android::JavaParamRef<jstring>& j_id,
+               const base::android::JavaParamRef<jobject>& j_callback) {
+  RlzPingHandler* handler = new RlzPingHandler(j_profile);
+  handler->Ping(j_brand, j_language, j_events, j_id, j_callback);
+}
+
+// Register native methods
+bool RegisterRlzPingHandler(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+}  // namespace android
+}  // namespace chrome
diff --git a/chrome/browser/android/rlz/rlz_ping_handler.h b/chrome/browser/android/rlz/rlz_ping_handler.h
new file mode 100644
index 0000000..2abe720
--- /dev/null
+++ b/chrome/browser/android/rlz/rlz_ping_handler.h
@@ -0,0 +1,51 @@
+// 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 CHROME_BROWSER_ANDROID_RLZ_RLZ_PING_HANDLER_H_
+#define CHROME_BROWSER_ANDROID_RLZ_RLZ_PING_HANDLER_H_
+
+#include <jni.h>
+#include "base/android/scoped_java_ref.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace chrome {
+namespace android {
+
+// JNI bridge for   RlzPingHandler.java
+class RlzPingHandler : public net::URLFetcherDelegate {
+ public:
+  explicit RlzPingHandler(jobject jprofile);
+  ~RlzPingHandler() override;
+
+  // Makes a GET request to the designated web end point with the given
+  // parameters. |j_brand| is a 4 character priorly designated brand value.
+  // |j_language| is the 2 letter lower case language. |events| is a single
+  // string where multiple 4 character long events are concatenated with ,
+  // and |id| is a unique id for the device that is 50 characters long.
+  void Ping(const base::android::JavaParamRef<jstring>& j_brand,
+            const base::android::JavaParamRef<jstring>& j_language,
+            const base::android::JavaParamRef<jstring>& j_events,
+            const base::android::JavaParamRef<jstring>& j_id,
+            const base::android::JavaParamRef<jobject>& j_callback);
+
+ private:
+  // net::URLFetcherDelegate:
+  void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+  scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+  std::unique_ptr<net::URLFetcher> url_fetcher_;
+  base::android::ScopedJavaGlobalRef<jobject> j_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(RlzPingHandler);
+};
+
+bool RegisterRlzPingHandler(JNIEnv* env);
+
+}  // namespace android
+}  // namespace chrome
+
+#endif  // CHROME_BROWSER_ANDROID_RLZ_RLZ_PING_HANDLER_H_
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index c275577..dfb99af 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3226,8 +3226,8 @@
     content::WebContents* web_contents =
         content::WebContents::FromRenderFrameHost(render_frame_host);
     if (web_contents) {
-      registry->AddInterface(base::Bind(
-          payments::CreatePaymentRequestForWebContents, web_contents));
+      registry->AddInterface(base::Bind(payments::CreatePaymentRequest,
+                                        render_frame_host, web_contents));
     }
   }
 #endif
diff --git a/chrome/browser/devtools/devtools_embedder_message_dispatcher.h b/chrome/browser/devtools/devtools_embedder_message_dispatcher.h
index 681012c..db6a8f10 100644
--- a/chrome/browser/devtools/devtools_embedder_message_dispatcher.h
+++ b/chrome/browser/devtools/devtools_embedder_message_dispatcher.h
@@ -71,7 +71,9 @@
     virtual void SetDevicesDiscoveryConfig(
         bool discover_usb_devices,
         bool port_forwarding_enabled,
-        const std::string& port_forwarding_config) = 0;
+        const std::string& port_forwarding_config,
+        bool network_discovery_enabled,
+        const std::string& network_discovery_config) = 0;
     virtual void PerformActionOnRemotePage(const std::string& page_id,
                                            const std::string& action) = 0;
     virtual void OpenRemotePage(const std::string& browser_id,
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index 91c8e15..cbdff1523 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -100,6 +100,12 @@
 static const char kRemotePageActionActivate[] = "activate";
 static const char kRemotePageActionClose[] = "close";
 
+static const char kConfigDiscoverUsbDevices[] = "discoverUsbDevices";
+static const char kConfigPortForwardingEnabled[] = "portForwardingEnabled";
+static const char kConfigPortForwardingConfig[] = "portForwardingConfig";
+static const char kConfigNetworkDiscoveryEnabled[] = "networkDiscoveryEnabled";
+static const char kConfigNetworkDiscoveryConfig[] = "networkDiscoveryConfig";
+
 // This constant should be in sync with
 // the constant at shell_devtools_frontend.cc.
 const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4;
@@ -918,30 +924,63 @@
 void DevToolsUIBindings::SetDevicesDiscoveryConfig(
     bool discover_usb_devices,
     bool port_forwarding_enabled,
-    const std::string& port_forwarding_config) {
-  base::DictionaryValue* config_dict = nullptr;
-  std::unique_ptr<base::Value> parsed_config =
+    const std::string& port_forwarding_config,
+    bool network_discovery_enabled,
+    const std::string& network_discovery_config) {
+  base::DictionaryValue* port_forwarding_dict = nullptr;
+  std::unique_ptr<base::Value> parsed_port_forwarding =
       base::JSONReader::Read(port_forwarding_config);
-  if (!parsed_config || !parsed_config->GetAsDictionary(&config_dict))
+  if (!parsed_port_forwarding ||
+      !parsed_port_forwarding->GetAsDictionary(&port_forwarding_dict)) {
+    return;
+  }
+
+  base::ListValue* network_list = nullptr;
+  std::unique_ptr<base::Value> parsed_network =
+      base::JSONReader::Read(network_discovery_config);
+  if (!parsed_network || !parsed_network->GetAsList(&network_list))
     return;
 
   profile_->GetPrefs()->SetBoolean(
       prefs::kDevToolsDiscoverUsbDevicesEnabled, discover_usb_devices);
   profile_->GetPrefs()->SetBoolean(
       prefs::kDevToolsPortForwardingEnabled, port_forwarding_enabled);
-  profile_->GetPrefs()->Set(
-      prefs::kDevToolsPortForwardingConfig, *config_dict);
+  profile_->GetPrefs()->Set(prefs::kDevToolsPortForwardingConfig,
+                            *port_forwarding_dict);
+  profile_->GetPrefs()->SetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled,
+                                   network_discovery_enabled);
+  profile_->GetPrefs()->Set(prefs::kDevToolsTCPDiscoveryConfig, *network_list);
 }
 
 void DevToolsUIBindings::DevicesDiscoveryConfigUpdated() {
-  CallClientFunction(
-      "DevToolsAPI.devicesDiscoveryConfigChanged",
-      profile_->GetPrefs()->FindPreference(
-          prefs::kDevToolsDiscoverUsbDevicesEnabled)->GetValue(),
-      profile_->GetPrefs()->FindPreference(
-          prefs::kDevToolsPortForwardingEnabled)->GetValue(),
-      profile_->GetPrefs()->FindPreference(
-          prefs::kDevToolsPortForwardingConfig)->GetValue());
+  base::DictionaryValue config;
+  config.Set(kConfigDiscoverUsbDevices,
+             profile_->GetPrefs()
+                 ->FindPreference(prefs::kDevToolsDiscoverUsbDevicesEnabled)
+                 ->GetValue()
+                 ->CreateDeepCopy());
+  config.Set(kConfigPortForwardingEnabled,
+             profile_->GetPrefs()
+                 ->FindPreference(prefs::kDevToolsPortForwardingEnabled)
+                 ->GetValue()
+                 ->CreateDeepCopy());
+  config.Set(kConfigPortForwardingConfig,
+             profile_->GetPrefs()
+                 ->FindPreference(prefs::kDevToolsPortForwardingConfig)
+                 ->GetValue()
+                 ->CreateDeepCopy());
+  config.Set(kConfigNetworkDiscoveryEnabled,
+             profile_->GetPrefs()
+                 ->FindPreference(prefs::kDevToolsDiscoverTCPTargetsEnabled)
+                 ->GetValue()
+                 ->CreateDeepCopy());
+  config.Set(kConfigNetworkDiscoveryConfig,
+             profile_->GetPrefs()
+                 ->FindPreference(prefs::kDevToolsTCPDiscoveryConfig)
+                 ->GetValue()
+                 ->CreateDeepCopy());
+  CallClientFunction("DevToolsAPI.devicesDiscoveryConfigChanged", &config,
+                     nullptr, nullptr);
 }
 
 void DevToolsUIBindings::SendPortForwardingStatus(const base::Value& status) {
@@ -968,6 +1007,14 @@
     pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig,
         base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated,
                    base::Unretained(this)));
+    pref_change_registrar_.Add(
+        prefs::kDevToolsDiscoverTCPTargetsEnabled,
+        base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated,
+                   base::Unretained(this)));
+    pref_change_registrar_.Add(
+        prefs::kDevToolsTCPDiscoveryConfig,
+        base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated,
+                   base::Unretained(this)));
     port_status_serializer_.reset(new PortForwardingStatusSerializer(
         base::Bind(&DevToolsUIBindings::SendPortForwardingStatus,
                    base::Unretained(this)),
diff --git a/chrome/browser/devtools/devtools_ui_bindings.h b/chrome/browser/devtools/devtools_ui_bindings.h
index a278572..46e94ed 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.h
+++ b/chrome/browser/devtools/devtools_ui_bindings.h
@@ -132,7 +132,9 @@
   void SetDevicesDiscoveryConfig(
       bool discover_usb_devices,
       bool port_forwarding_enabled,
-      const std::string& port_forwarding_config) override;
+      const std::string& port_forwarding_config,
+      bool network_discovery_enabled,
+      const std::string& network_discovery_config) override;
   void SetDevicesUpdatesEnabled(bool enabled) override;
   void PerformActionOnRemotePage(const std::string& page_id,
                                  const std::string& action) override;
diff --git a/chrome/browser/media/OWNERS b/chrome/browser/media/OWNERS
index 4258926..a3d42e0 100644
--- a/chrome/browser/media/OWNERS
+++ b/chrome/browser/media/OWNERS
@@ -6,4 +6,7 @@
 per-file cast_*=hubbe@chromium.org
 per-file cast_*=miu@chromium.org
 
+# For Media Engagement
+per-file media_engagement*=mlamouri@chromium.org
+
 # COMPONENT: Blink>Media
diff --git a/chrome/browser/media/media_engagement_service.cc b/chrome/browser/media/media_engagement_service.cc
new file mode 100644
index 0000000..2eb7ffb
--- /dev/null
+++ b/chrome/browser/media/media_engagement_service.cc
@@ -0,0 +1,62 @@
+// 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 "chrome/browser/media/media_engagement_service.h"
+
+#include "chrome/browser/media/media_engagement_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "media/base/media_switches.h"
+
+class MediaEngagementService::ContentsObserver
+    : public content::WebContentsObserver {
+ public:
+  ContentsObserver(content::WebContents* web_contents,
+                   MediaEngagementService* service)
+      : WebContentsObserver(web_contents), service_(service) {}
+
+  ~ContentsObserver() override = default;
+
+  // WebContentsObserver implementation.
+  void WebContentsDestroyed() override {
+    service_->contents_observers_.erase(this);
+    delete this;
+  }
+
+ private:
+  // |this| is owned by |service_|.
+  MediaEngagementService* service_;
+
+  DISALLOW_COPY_AND_ASSIGN(ContentsObserver);
+};
+
+// static
+bool MediaEngagementService::IsEnabled() {
+  return base::FeatureList::IsEnabled(media::kMediaEngagement);
+}
+
+// static
+MediaEngagementService* MediaEngagementService::Get(Profile* profile) {
+  DCHECK(IsEnabled());
+  return MediaEngagementServiceFactory::GetForProfile(profile);
+}
+
+// static
+void MediaEngagementService::CreateWebContentsObserver(
+    content::WebContents* web_contents) {
+  DCHECK(IsEnabled());
+  MediaEngagementService* service =
+      Get(Profile::FromBrowserContext(web_contents->GetBrowserContext()));
+  if (!service)
+    return;
+  service->contents_observers_.insert(
+      new ContentsObserver(web_contents, service));
+}
+
+MediaEngagementService::MediaEngagementService(Profile* profile) {
+  DCHECK(IsEnabled());
+}
+
+MediaEngagementService::~MediaEngagementService() = default;
diff --git a/chrome/browser/media/media_engagement_service.h b/chrome/browser/media/media_engagement_service.h
new file mode 100644
index 0000000..3701c02
--- /dev/null
+++ b/chrome/browser/media/media_engagement_service.h
@@ -0,0 +1,42 @@
+// 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 CHROME_BROWSER_MEDIA_MEDIA_ENGAGEMENT_SERVICE_H_
+#define CHROME_BROWSER_MEDIA_MEDIA_ENGAGEMENT_SERVICE_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class Profile;
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+class MediaEngagementService : public KeyedService {
+ public:
+  // Returns the instance attached to the given |profile|.
+  static MediaEngagementService* Get(Profile* profile);
+
+  // Returns whether the feature is enabled.
+  static bool IsEnabled();
+
+  // Observe the given |web_contents| by creating an internal
+  // WebContentsObserver.
+  static void CreateWebContentsObserver(content::WebContents* web_contents);
+
+  explicit MediaEngagementService(Profile* profile);
+  ~MediaEngagementService() override;
+
+ private:
+  class ContentsObserver;
+
+  std::set<ContentsObserver*> contents_observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaEngagementService);
+};
+
+#endif  // CHROME_BROWSER_MEDIA_MEDIA_ENGAGEMENT_SERVICE_H_
diff --git a/chrome/browser/media/media_engagement_service_factory.cc b/chrome/browser/media/media_engagement_service_factory.cc
new file mode 100644
index 0000000..8bab790
--- /dev/null
+++ b/chrome/browser/media/media_engagement_service_factory.cc
@@ -0,0 +1,39 @@
+// 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 "chrome/browser/media/media_engagement_service_factory.h"
+
+#include "chrome/browser/media/media_engagement_service.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+// static
+MediaEngagementService* MediaEngagementServiceFactory::GetForProfile(
+    Profile* profile) {
+  return static_cast<MediaEngagementService*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+// static
+MediaEngagementServiceFactory* MediaEngagementServiceFactory::GetInstance() {
+  return base::Singleton<MediaEngagementServiceFactory>::get();
+}
+
+MediaEngagementServiceFactory::MediaEngagementServiceFactory()
+    : BrowserContextKeyedServiceFactory(
+          "MediaEngagementServiceFactory",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+MediaEngagementServiceFactory::~MediaEngagementServiceFactory() {}
+
+KeyedService* MediaEngagementServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new MediaEngagementService(Profile::FromBrowserContext(context));
+}
+
+content::BrowserContext* MediaEngagementServiceFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
+}
diff --git a/chrome/browser/media/media_engagement_service_factory.h b/chrome/browser/media/media_engagement_service_factory.h
new file mode 100644
index 0000000..1eee510
--- /dev/null
+++ b/chrome/browser/media/media_engagement_service_factory.h
@@ -0,0 +1,35 @@
+// 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 CHROME_BROWSER_MEDIA_MEDIA_ENGAGEMENT_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_MEDIA_MEDIA_ENGAGEMENT_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class MediaEngagementService;
+class Profile;
+
+class MediaEngagementServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static MediaEngagementService* GetForProfile(Profile* profile);
+  static MediaEngagementServiceFactory* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<MediaEngagementServiceFactory>;
+
+  MediaEngagementServiceFactory();
+  ~MediaEngagementServiceFactory() override;
+
+  // BrowserContextKeyedBaseFactory methods:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* profile) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaEngagementServiceFactory);
+};
+
+#endif  // CHROME_BROWSER_MEDIA_MEDIA_ENGAGEMENT_SERVICE_FACTORY_H_
diff --git a/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc b/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
index 037db01..42467a5a 100644
--- a/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
+++ b/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
@@ -16,8 +16,9 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/lock.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
-#include "content/public/test/test_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 "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
@@ -161,15 +162,13 @@
   EXPECT_EQ(expected_list_size, model->GetSourceCount());
 }
 
-ACTION_P(QuitMessageLoop, message_loop) {
-  message_loop->task_runner()->PostTask(
-      FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+ACTION_P2(QuitRunLoop, task_runner, run_loop) {
+  task_runner->PostTask(FROM_HERE, run_loop->QuitWhenIdleClosure());
 }
 
 class NativeDesktopMediaListTest : public views::ViewsTestBase {
  public:
-  NativeDesktopMediaListTest()
-      : ui_thread_(content::BrowserThread::UI, message_loop()) {}
+  NativeDesktopMediaListTest() = default;
 
   void TearDown() override {
     for (size_t i = 0; i < desktop_widgets_.size(); i++)
@@ -306,6 +305,8 @@
       aura_window_first_index--;
     }
 
+    base::RunLoop run_loop;
+
     {
       testing::InSequence dummy;
       size_t source_count = screen ? window_count + 1 : window_count;
@@ -318,10 +319,11 @@
       }
       EXPECT_CALL(observer_,
                   OnSourceThumbnailChanged(model_.get(), source_count - 1))
-          .WillOnce(QuitMessageLoop(message_loop()));
+          .WillOnce(
+              QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
     }
     model_->StartUpdating(&observer_);
-    base::RunLoop().Run();
+    run_loop.Run();
 
     if (screen) {
       EXPECT_EQ(model_->GetSource(0).id.type, DesktopMediaID::TYPE_SCREEN);
@@ -347,6 +349,8 @@
   }
 
  protected:
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+
   // Must be listed before |model_|, so it's destroyed last.
   MockObserver observer_;
 
@@ -358,8 +362,6 @@
   std::map<DesktopMediaID::Id, DesktopMediaID::Id> native_aura_id_map_;
   std::unique_ptr<NativeDesktopMediaList> model_;
 
-  content::TestBrowserThread ui_thread_;
-
   DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaListTest);
 };
 
@@ -380,15 +382,18 @@
 TEST_F(NativeDesktopMediaListTest, AddNativeWindow) {
   AddWindowsAndVerify(true, kDefaultWindowCount, kDefaultAuraCount, false);
 
+  base::RunLoop run_loop;
+
   const int index = kDefaultWindowCount + 1;
   EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
-      .WillOnce(DoAll(CheckListSize(model_.get(), index + 1),
-                      QuitMessageLoop(message_loop())));
+      .WillOnce(
+          DoAll(CheckListSize(model_.get(), index + 1),
+                QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
 
   AddNativeWindow(index);
   window_capturer_->SetWindowList(window_list_);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 
   EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
   EXPECT_EQ(model_->GetSource(index).id.id, index);
@@ -398,15 +403,18 @@
 TEST_F(NativeDesktopMediaListTest, AddAuraWindow) {
   AddWindowsAndVerify(true, kDefaultWindowCount, kDefaultAuraCount, false);
 
+  base::RunLoop run_loop;
+
   const int index = kDefaultWindowCount + 1;
   EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
-      .WillOnce(DoAll(CheckListSize(model_.get(), index + 1),
-                      QuitMessageLoop(message_loop())));
+      .WillOnce(
+          DoAll(CheckListSize(model_.get(), index + 1),
+                QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
 
   AddAuraWindow();
   window_capturer_->SetWindowList(window_list_);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 
   int native_id = window_list_.back().id;
   EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
@@ -419,61 +427,72 @@
 TEST_F(NativeDesktopMediaListTest, RemoveNativeWindow) {
   AddWindowsAndVerify(true, kDefaultWindowCount, kDefaultAuraCount, false);
 
+  base::RunLoop run_loop;
+
   EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 1))
-      .WillOnce(DoAll(CheckListSize(model_.get(), kDefaultWindowCount),
-                      QuitMessageLoop(message_loop())));
+      .WillOnce(
+          DoAll(CheckListSize(model_.get(), kDefaultWindowCount),
+                QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
 
   window_list_.erase(window_list_.begin());
   window_capturer_->SetWindowList(window_list_);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 }
 
 #if defined(ENABLE_AURA_WINDOW_TESTS)
 TEST_F(NativeDesktopMediaListTest, RemoveAuraWindow) {
   AddWindowsAndVerify(true, kDefaultWindowCount, kDefaultAuraCount, false);
 
+  base::RunLoop run_loop;
+
   int aura_window_start_index = kDefaultWindowCount - kDefaultAuraCount + 1;
   EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), aura_window_start_index))
-      .WillOnce(DoAll(CheckListSize(model_.get(), kDefaultWindowCount),
-                      QuitMessageLoop(message_loop())));
+      .WillOnce(
+          DoAll(CheckListSize(model_.get(), kDefaultWindowCount),
+                QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
 
   RemoveAuraWindow(0);
   window_capturer_->SetWindowList(window_list_);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 }
 #endif  // defined(ENABLE_AURA_WINDOW_TESTS)
 
 TEST_F(NativeDesktopMediaListTest, RemoveAllWindows) {
   AddWindowsAndVerify(true, kDefaultWindowCount, kDefaultAuraCount, false);
 
+  base::RunLoop run_loop;
+
   testing::InSequence seq;
   for (int i = 0; i < kDefaultWindowCount - 1; i++) {
     EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 1))
         .WillOnce(CheckListSize(model_.get(), kDefaultWindowCount - i));
   }
   EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 1))
-      .WillOnce(DoAll(CheckListSize(model_.get(), 1),
-                      QuitMessageLoop(message_loop())));
+      .WillOnce(
+          DoAll(CheckListSize(model_.get(), 1),
+                QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
 
   window_list_.clear();
   window_capturer_->SetWindowList(window_list_);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 }
 
 TEST_F(NativeDesktopMediaListTest, UpdateTitle) {
   AddWindowsAndVerify(true, kDefaultWindowCount, kDefaultAuraCount, false);
 
+  base::RunLoop run_loop;
+
   EXPECT_CALL(observer_, OnSourceNameChanged(model_.get(), 1))
-      .WillOnce(QuitMessageLoop(message_loop()));
+      .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
 
   const std::string kTestTitle = "New Title";
   window_list_[0].title = kTestTitle;
   window_capturer_->SetWindowList(window_list_);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 
   EXPECT_EQ(model_->GetSource(1).name, base::UTF8ToUTF16(kTestTitle));
 }
@@ -488,24 +507,29 @@
         .Times(testing::AnyNumber());
   }
 
+  base::RunLoop run_loop;
+
   EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), 1))
-      .WillOnce(QuitMessageLoop(message_loop()));
+      .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
 
   // Update frame for the window and verify that we get notification about it.
   window_capturer_->SetNextFrameValue(1, 10);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 }
 
 TEST_F(NativeDesktopMediaListTest, MoveWindow) {
   AddWindowsAndVerify(true, kDefaultWindowCount, kDefaultAuraCount, false);
 
+  base::RunLoop run_loop;
+
   EXPECT_CALL(observer_, OnSourceMoved(model_.get(), 2, 1))
-      .WillOnce(DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
-                      QuitMessageLoop(message_loop())));
+      .WillOnce(
+          DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
+                QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
 
   std::swap(window_list_[0], window_list_[1]);
   window_capturer_->SetWindowList(window_list_);
 
-  base::RunLoop().Run();
+  run_loop.Run();
 }
diff --git a/chrome/browser/memory/tab_manager_delegate_chromeos.cc b/chrome/browser/memory/tab_manager_delegate_chromeos.cc
index e39f69f..08c658b 100644
--- a/chrome/browser/memory/tab_manager_delegate_chromeos.cc
+++ b/chrome/browser/memory/tab_manager_delegate_chromeos.cc
@@ -94,8 +94,10 @@
 
 std::ostream& operator<<(std::ostream& os, const ProcessType& type) {
   switch (type) {
+    case ProcessType::FOCUSED_TAB:
+      return os << "FOCUSED_TAB";
     case ProcessType::FOCUSED_APP:
-      return os << "FOCUSED_APP/FOCUSED_TAB";
+      return os << "FOCUSED_APP";
     case ProcessType::VISIBLE_APP:
       return os << "VISIBLE_APP";
     case ProcessType::BACKGROUND_TAB:
@@ -144,7 +146,8 @@
     return TabManager::CompareTabStats(*tab(), *rhs.tab());
   // Impossible case. If app and tab are mixed in one process type, favor
   // apps.
-  NOTREACHED() << "Undefined comparison between apps and tabs";
+  NOTREACHED() << "Undefined comparison between apps and tabs: process_type="
+               << process_type();
   return app();
 }
 
diff --git a/chrome/browser/memory/tab_manager_delegate_chromeos.h b/chrome/browser/memory/tab_manager_delegate_chromeos.h
index 931767df..0c86a265 100644
--- a/chrome/browser/memory/tab_manager_delegate_chromeos.h
+++ b/chrome/browser/memory/tab_manager_delegate_chromeos.h
@@ -33,16 +33,18 @@
 // Possible types of Apps/Tabs processes. From most important to least
 // important.
 enum class ProcessType {
-  // There can be only one process having process type "FOCUSED_APP"
-  // or "FOCUSED_TAB" in the system at any give time (i.e., The focused window
-  // could be either a chrome window or an Android app. But not both.
-  FOCUSED_APP = 1,
-  FOCUSED_TAB = FOCUSED_APP,
+  // Conceptually, the system cannot have both FOCUSED_TAB and FOCUSED_APP at
+  // the same time, but because Chrome cannot retrieve FOCUSED_APP status
+  // synchronously, Chrome may still see both at the same time. When that
+  // happens, treat FOCUSED_TAB as the most important since the (synchronously
+  // retrieved) tab information is more reliable and up-to-date.
+  FOCUSED_TAB = 1,
+  FOCUSED_APP = 2,
 
-  VISIBLE_APP = 2,
-  BACKGROUND_TAB = 3,
-  BACKGROUND_APP = 4,
-  UNKNOWN_TYPE = 5,
+  VISIBLE_APP = 3,
+  BACKGROUND_TAB = 4,
+  BACKGROUND_APP = 5,
+  UNKNOWN_TYPE = 6,
 };
 
 // The Chrome OS TabManagerDelegate is responsible for keeping the
@@ -99,6 +101,8 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest, CandidatesSorted);
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest,
+                           CandidatesSortedWithFocusedAppAndTab);
+  FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest,
                            DoNotKillRecentlyKilledArcProcesses);
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest, IsRecentlyKilledArcProcess);
   FRIEND_TEST_ALL_PREFIXES(TabManagerDelegateTest, KillMultipleProcesses);
diff --git a/chrome/browser/memory/tab_manager_delegate_chromeos_unittest.cc b/chrome/browser/memory/tab_manager_delegate_chromeos_unittest.cc
index fd25dbbe..8d10119 100644
--- a/chrome/browser/memory/tab_manager_delegate_chromeos_unittest.cc
+++ b/chrome/browser/memory/tab_manager_delegate_chromeos_unittest.cc
@@ -109,6 +109,28 @@
   EXPECT_EQ("service", candidates[8].app()->process_name());
 }
 
+// Occasionally, Chrome sees both FOCUSED_TAB and FOCUSED_APP at the same time.
+// Test that Chrome treats the former as a more important process.
+TEST_F(TabManagerDelegateTest, CandidatesSortedWithFocusedAppAndTab) {
+  std::vector<arc::ArcProcess> arc_processes;
+  arc_processes.emplace_back(1, 10, "focused", arc::mojom::ProcessState::TOP,
+                             kIsFocused, 100);
+  TabStats tab1;
+  tab1.tab_contents_id = 100;
+  tab1.is_pinned = true;
+  tab1.is_selected = true;
+  const TabStatsList tab_list = {tab1};
+
+  const std::vector<TabManagerDelegate::Candidate> candidates =
+      TabManagerDelegate::GetSortedCandidates(tab_list, arc_processes);
+  ASSERT_EQ(2U, candidates.size());
+  // FOCUSED_TAB should be the first one.
+  ASSERT_TRUE(candidates[0].tab());
+  EXPECT_EQ(100, candidates[0].tab()->tab_contents_id);
+  ASSERT_TRUE(candidates[1].app());
+  EXPECT_EQ("focused", candidates[1].app()->process_name());
+}
+
 class MockTabManagerDelegate : public TabManagerDelegate {
  public:
   MockTabManagerDelegate()
diff --git a/chrome/browser/payments/payment_request_factory.cc b/chrome/browser/payments/payment_request_factory.cc
index ccf5964..e74a33b 100644
--- a/chrome/browser/payments/payment_request_factory.cc
+++ b/chrome/browser/payments/payment_request_factory.cc
@@ -4,25 +4,23 @@
 
 #include "chrome/browser/payments/payment_request_factory.h"
 
-#include <memory>
+#include <utility>
 
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/payments/chrome_payment_request_delegate.h"
 #include "components/payments/content/payment_request_web_contents_manager.h"
-#include "components/payments/core/payment_request_delegate.h"
-#include "content/public/browser/web_contents.h"
 
 namespace payments {
 
-void CreatePaymentRequestForWebContents(
-    content::WebContents* web_contents,
-    const service_manager::BindSourceInfo& source_info,
-    payments::mojom::PaymentRequestRequest request) {
+void CreatePaymentRequest(content::RenderFrameHost* render_frame_host,
+                          content::WebContents* web_contents,
+                          const service_manager::BindSourceInfo& source_info,
+                          mojom::PaymentRequestRequest request) {
   DCHECK(web_contents);
   PaymentRequestWebContentsManager::GetOrCreateForWebContents(web_contents)
       ->CreatePaymentRequest(
-          web_contents,
+          render_frame_host, web_contents,
           base::MakeUnique<ChromePaymentRequestDelegate>(web_contents),
           std::move(request),
           /*observer_for_testing=*/nullptr);
diff --git a/chrome/browser/payments/payment_request_factory.h b/chrome/browser/payments/payment_request_factory.h
index 8188568..f575a56 100644
--- a/chrome/browser/payments/payment_request_factory.h
+++ b/chrome/browser/payments/payment_request_factory.h
@@ -6,22 +6,23 @@
 #define CHROME_BROWSER_PAYMENTS_PAYMENT_REQUEST_FACTORY_H_
 
 #include "components/payments/mojom/payment_request.mojom.h"
-#include "mojo/public/cpp/bindings/binding.h"
 #include "services/service_manager/public/cpp/bind_source_info.h"
 
 namespace content {
+class RenderFrameHost;
 class WebContents;
 }
 
 namespace payments {
 
-// Will create a PaymentRequest attached to |web_contents|, based on the
-// contents of |request|. This is called everytime a new Mojo PaymentRequest is
-// created.
-void CreatePaymentRequestForWebContents(
-    content::WebContents* web_contents,
-    const service_manager::BindSourceInfo& source_info,
-    payments::mojom::PaymentRequestRequest request);
+// Will create a PaymentRequest based on the contents of |request|. The
+// |request| was initiated by the frame hosted by |render_frame_host|, which is
+// inside of |web_contents|. This function is called every time a new instance
+// of PaymentRequest is created in the renderer.
+void CreatePaymentRequest(content::RenderFrameHost* render_frame_host,
+                          content::WebContents* web_contents,
+                          const service_manager::BindSourceInfo& source_info,
+                          mojom::PaymentRequestRequest request);
 
 }  // namespace payments
 
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index d101a40a..500f488 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -261,6 +261,7 @@
 #endif
 
 #if defined(USE_ASH)
+#include "ash/shell.h"
 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
 #endif
 
@@ -644,6 +645,7 @@
 
 #if defined(USE_ASH)
   RegisterChromeLauncherUserPrefs(registry);
+  ash::Shell::RegisterPrefs(registry);
 #endif
 
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 3320105..42114fc8 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -28,6 +28,8 @@
 #include "chrome/browser/google/google_url_tracker_factory.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
+#include "chrome/browser/media/media_engagement_service.h"
+#include "chrome/browser/media/media_engagement_service_factory.h"
 #include "chrome/browser/media/router/media_router_factory.h"
 #include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
 #include "chrome/browser/net/nqe/ui_network_quality_estimator_service_factory.h"
@@ -269,6 +271,8 @@
       ->SetUIDelegateFactory(std::move(networking_private_ui_delegate_factory));
 #endif
 #endif
+  if (MediaEngagementService::IsEnabled())
+    MediaEngagementServiceFactory::GetInstance();
   media_router::MediaRouterFactory::GetInstance();
 #if !defined(OS_ANDROID)
   media_router::MediaRouterUIServiceFactory::GetInstance();
diff --git a/chrome/browser/supervised_user/supervised_user_service_factory.cc b/chrome/browser/supervised_user/supervised_user_service_factory.cc
index 1664652..09ceffd 100644
--- a/chrome/browser/supervised_user/supervised_user_service_factory.cc
+++ b/chrome/browser/supervised_user/supervised_user_service_factory.cc
@@ -25,6 +25,13 @@
 }
 
 // static
+SupervisedUserService* SupervisedUserServiceFactory::GetForProfileIfExists(
+    Profile* profile) {
+  return static_cast<SupervisedUserService*>(
+      GetInstance()->GetServiceForBrowserContext(profile, /*create=*/false));
+}
+
+// static
 SupervisedUserServiceFactory* SupervisedUserServiceFactory::GetInstance() {
   return base::Singleton<SupervisedUserServiceFactory>::get();
 }
@@ -43,8 +50,6 @@
       extensions::ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
 #endif
   DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
-
-  // TODO(skym, crbug.com/705545): Fix this circular dependency.
   DependsOn(ProfileSyncServiceFactory::GetInstance());
 }
 
diff --git a/chrome/browser/supervised_user/supervised_user_service_factory.h b/chrome/browser/supervised_user/supervised_user_service_factory.h
index 5106700..f850c2c 100644
--- a/chrome/browser/supervised_user/supervised_user_service_factory.h
+++ b/chrome/browser/supervised_user/supervised_user_service_factory.h
@@ -16,6 +16,8 @@
  public:
   static SupervisedUserService* GetForProfile(Profile* profile);
 
+  static SupervisedUserService* GetForProfileIfExists(Profile* profile);
+
   static SupervisedUserServiceFactory* GetInstance();
 
   // Used to create instances for testing.
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index 6a48990..c952021 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -447,10 +447,16 @@
       return SupervisedUserSharedSettingsServiceFactory::GetForBrowserContext(
           profile_)->AsWeakPtr();
 #endif  // !defined(OS_ANDROID)
-    case syncer::SUPERVISED_USER_WHITELISTS:
-      return SupervisedUserServiceFactory::GetForProfile(profile_)
-          ->GetWhitelistService()
-          ->AsWeakPtr();
+    case syncer::SUPERVISED_USER_WHITELISTS: {
+      // Unlike other types here, ProfileSyncServiceFactory does not declare a
+      // DependsOn the SupervisedUserServiceFactory (in order to avoid circular
+      // dependency), which means we cannot assume it is still alive.
+      SupervisedUserService* supervised_user_service =
+          SupervisedUserServiceFactory::GetForProfileIfExists(profile_);
+      if (supervised_user_service)
+        return supervised_user_service->GetWhitelistService()->AsWeakPtr();
+      return base::WeakPtr<syncer::SyncableService>();
+    }
 #endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
     case syncer::ARTICLES: {
       dom_distiller::DomDistillerService* service =
@@ -507,7 +513,7 @@
           ->AsWeakPtr();
 #endif  // defined(OS_CHROMEOS)
     case syncer::TYPED_URLS:
-      // TODO(gangwu):implement TypedURLSyncBridge and return real
+      // TODO(gangwu): Implement TypedURLSyncBridge and return real
       // TypedURLSyncBridge here.
       return base::WeakPtr<syncer::ModelTypeSyncBridge>();
     default:
diff --git a/chrome/browser/sync/profile_sync_service_factory.cc b/chrome/browser/sync/profile_sync_service_factory.cc
index 743d212..3fda480 100644
--- a/chrome/browser/sync/profile_sync_service_factory.cc
+++ b/chrome/browser/sync/profile_sync_service_factory.cc
@@ -141,8 +141,6 @@
   DependsOn(SigninManagerFactory::GetInstance());
   DependsOn(SpellcheckServiceFactory::GetInstance());
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
-  // TODO(skym, crbug.com/705545): Fix this circular dependency.
-  // DependsOn(SupervisedUserServiceFactory::GetInstance());
   DependsOn(SupervisedUserSettingsServiceFactory::GetInstance());
 #if !defined(OS_ANDROID)
   DependsOn(SupervisedUserSharedSettingsServiceFactory::GetInstance());
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e6c3445..ee693f8 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2270,6 +2270,8 @@
       "android/infobars/instant_apps_infobar.h",
       "android/infobars/permission_infobar.cc",
       "android/infobars/permission_infobar.h",
+      "android/infobars/reader_mode_infobar.cc",
+      "android/infobars/reader_mode_infobar.h",
       "android/infobars/search_geolocation_disclosure_infobar.cc",
       "android/infobars/search_geolocation_disclosure_infobar.h",
       "android/infobars/simple_confirm_infobar_builder.cc",
@@ -2412,6 +2414,8 @@
       "cocoa/download/download_util_mac.mm",
       "cocoa/first_run_dialog.h",
       "cocoa/first_run_dialog.mm",
+      "cocoa/first_run_dialog_controller.h",
+      "cocoa/first_run_dialog_controller.mm",
       "cocoa/handoff_active_url_observer.cc",
       "cocoa/handoff_active_url_observer.h",
       "cocoa/handoff_active_url_observer_bridge.h",
diff --git a/chrome/browser/ui/android/infobars/reader_mode_infobar.cc b/chrome/browser/ui/android/infobars/reader_mode_infobar.cc
new file mode 100644
index 0000000..e424ebd6
--- /dev/null
+++ b/chrome/browser/ui/android/infobars/reader_mode_infobar.cc
@@ -0,0 +1,63 @@
+// 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 "chrome/browser/ui/android/infobars/reader_mode_infobar.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/android/tab_android.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "components/infobars/core/infobar_delegate.h"
+#include "content/public/browser/web_contents.h"
+#include "jni/ReaderModeInfoBar_jni.h"
+#include "ui/android/window_android.h"
+
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+
+class ReaderModeInfoBarDelegate : public infobars::InfoBarDelegate {
+ public:
+  ~ReaderModeInfoBarDelegate() override {}
+
+  infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override {
+    return InfoBarDelegate::InfoBarIdentifier::READER_MODE_INFOBAR_ANDROID;
+  }
+
+  bool EqualsDelegate(infobars::InfoBarDelegate* delegate) const override {
+    return delegate->GetIdentifier() == GetIdentifier();
+  }
+};
+
+ReaderModeInfoBar::ReaderModeInfoBar(
+    std::unique_ptr<ReaderModeInfoBarDelegate> delegate)
+    : InfoBarAndroid(std::move(delegate)) {}
+
+ReaderModeInfoBar::~ReaderModeInfoBar() {}
+
+infobars::InfoBarDelegate* ReaderModeInfoBar::GetDelegate() {
+  return delegate();
+}
+
+ScopedJavaLocalRef<jobject> ReaderModeInfoBar::CreateRenderInfoBar(
+    JNIEnv* env) {
+  return Java_ReaderModeInfoBar_create(env);
+}
+
+void ReaderModeInfoBar::ProcessButton(int action) {}
+
+void Create(JNIEnv* env,
+            const JavaParamRef<jclass>& j_caller,
+            const JavaParamRef<jobject>& j_tab) {
+  InfoBarService* service = InfoBarService::FromWebContents(
+      TabAndroid::GetNativeTab(env, j_tab)->web_contents());
+
+  service->AddInfoBar(base::MakeUnique<ReaderModeInfoBar>(
+      base::MakeUnique<ReaderModeInfoBarDelegate>()));
+}
+
+bool RegisterReaderModeInfoBar(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
diff --git a/chrome/browser/ui/android/infobars/reader_mode_infobar.h b/chrome/browser/ui/android/infobars/reader_mode_infobar.h
new file mode 100644
index 0000000..22059e2
--- /dev/null
+++ b/chrome/browser/ui/android/infobars/reader_mode_infobar.h
@@ -0,0 +1,37 @@
+// 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 CHROME_BROWSER_UI_ANDROID_INFOBARS_READER_MODE_INFOBAR_H_
+#define CHROME_BROWSER_UI_ANDROID_INFOBARS_READER_MODE_INFOBAR_H_
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/macros.h"
+#include "chrome/browser/ui/android/infobars/infobar_android.h"
+#include "components/infobars/core/infobar_delegate.h"
+
+class ReaderModeInfoBarDelegate;
+
+class ReaderModeInfoBar : public InfoBarAndroid {
+ public:
+  explicit ReaderModeInfoBar(
+      std::unique_ptr<ReaderModeInfoBarDelegate> delegate);
+  ~ReaderModeInfoBar() override;
+
+ protected:
+  infobars::InfoBarDelegate* GetDelegate();
+
+  // InfoBarAndroid overrides.
+  void ProcessButton(int action) override;
+  base::android::ScopedJavaLocalRef<jobject> CreateRenderInfoBar(
+      JNIEnv* env) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReaderModeInfoBar);
+};
+
+// Register native methods.
+bool RegisterReaderModeInfoBar(JNIEnv* env);
+
+#endif  // CHROME_BROWSER_UI_ANDROID_INFOBARS_READER_MODE_INFOBAR_H_
diff --git a/chrome/browser/ui/ash/session_controller_client.cc b/chrome/browser/ui/ash/session_controller_client.cc
index 6d15a76..96b2ab5e 100644
--- a/chrome/browser/ui/ash/session_controller_client.cc
+++ b/chrome/browser/ui/ash/session_controller_client.cc
@@ -26,6 +26,7 @@
 #include "chrome/grit/theme_resources.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/session_manager_client.h"
+#include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
 #include "components/session_manager/core/session_manager.h"
 #include "content/public/browser/notification_service.h"
@@ -377,6 +378,18 @@
         ->AddObserver(this);
   }
 
+  base::Closure session_info_changed_closure =
+      base::Bind(&SessionControllerClient::SendSessionInfoIfChanged,
+                 weak_ptr_factory_.GetWeakPtr());
+  std::unique_ptr<PrefChangeRegistrar> pref_change_registrar =
+      base::MakeUnique<PrefChangeRegistrar>();
+  pref_change_registrar->Init(profile->GetPrefs());
+  pref_change_registrar->Add(prefs::kAllowScreenLock,
+                             session_info_changed_closure);
+  pref_change_registrar->Add(prefs::kEnableAutoScreenLock,
+                             session_info_changed_closure);
+  pref_change_registrars_.push_back(std::move(pref_change_registrar));
+
   // Needed because the user-to-profile mapping isn't available until later,
   // which is needed in UserToUserSession().
   base::ThreadTaskRunnerHandle::Get()->PostTask(
diff --git a/chrome/browser/ui/ash/session_controller_client.h b/chrome/browser/ui/ash/session_controller_client.h
index 9fffbe0..a7e4dc4 100644
--- a/chrome/browser/ui/ash/session_controller_client.h
+++ b/chrome/browser/ui/ash/session_controller_client.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_UI_ASH_SESSION_CONTROLLER_CLIENT_H_
 #define CHROME_BROWSER_UI_ASH_SESSION_CONTROLLER_CLIENT_H_
 
+#include <vector>
+
 #include "ash/public/interfaces/session_controller.mojom.h"
 #include "base/callback_forward.h"
 #include "base/gtest_prod_util.h"
@@ -18,6 +20,7 @@
 #include "mojo/public/cpp/bindings/binding.h"
 
 class Profile;
+class PrefChangeRegistrar;
 
 namespace ash {
 enum class AddUserSessionPolicy;
@@ -97,6 +100,7 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(SessionControllerClientTest, SendUserSession);
   FRIEND_TEST_ALL_PREFIXES(SessionControllerClientTest, SupervisedUser);
+  FRIEND_TEST_ALL_PREFIXES(SessionControllerClientTest, UserPrefsChange);
 
   // Called when the login profile is ready.
   void OnLoginUserProfilePrepared(Profile* profile);
@@ -131,6 +135,11 @@
 
   content::NotificationRegistrar registrar_;
 
+  // Pref change observers to update session info when a relevant user pref
+  // changes. There is one observer per user and they have no particular order,
+  // i.e. they don't much the user session order.
+  std::vector<std::unique_ptr<PrefChangeRegistrar>> pref_change_registrars_;
+
   // Used to suppress duplicate IPCs to ash.
   ash::mojom::SessionInfoPtr last_sent_session_info_;
   ash::mojom::UserSessionPtr last_sent_user_session_;
diff --git a/chrome/browser/ui/ash/session_controller_client_unittest.cc b/chrome/browser/ui/ash/session_controller_client_unittest.cc
index 660e1a8..3304917b 100644
--- a/chrome/browser/ui/ash/session_controller_client_unittest.cc
+++ b/chrome/browser/ui/ash/session_controller_client_unittest.cc
@@ -22,6 +22,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "components/prefs/pref_service.h"
 #include "components/session_manager/core/session_manager.h"
 #include "components/signin/core/account_id/account_id.h"
 #include "components/user_manager/user_manager.h"
@@ -66,6 +67,25 @@
     NotifyOnLogin();
   }
 
+  user_manager::UserList GetUnlockUsers() const override {
+    // Test case UserPrefsChange expects that the list of the unlock users
+    // depends on prefs::kAllowScreenLock.
+    user_manager::UserList unlock_users;
+    for (user_manager::User* user : users_) {
+      Profile* user_profile =
+          chromeos::ProfileHelper::Get()->GetProfileByUser(user);
+      // Skip if user has a profile and kAllowScreenLock is set to false.
+      if (user_profile &&
+          !user_profile->GetPrefs()->GetBoolean(prefs::kAllowScreenLock)) {
+        continue;
+      }
+
+      unlock_users.push_back(user);
+    }
+
+    return unlock_users;
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(TestChromeUserManager);
 };
@@ -172,28 +192,29 @@
   TestChromeUserManager* user_manager() { return user_manager_; }
 
   // Adds a regular user with a profile.
-  void InitForMultiProfile() {
+  TestingProfile* InitForMultiProfile() {
     const AccountId account_id(AccountId::FromUserEmail(kUser));
     const user_manager::User* user = user_manager()->AddUser(account_id);
 
     // Note that user profiles are created after user login in reality.
-    CreateTestingProfile(user);
+    return CreateTestingProfile(user);
   }
 
-  // Calls private methods to create a testing profile.
-  void CreateTestingProfile(const user_manager::User* user) {
+  // Calls private methods to create a testing profile. The created profile
+  // is owned by ProfileManager.
+  TestingProfile* CreateTestingProfile(const user_manager::User* user) {
     const AccountId& account_id = user->GetAccountId();
-    user_profile_ =
+    TestingProfile* profile =
         profile_manager_->CreateTestingProfile(account_id.GetUserEmail());
-    user_profile_->set_profile_name(account_id.GetUserEmail());
-    chromeos::ProfileHelper::Get()->SetUserToProfileMappingForTesting(
-        user, user_profile_);
+    profile->set_profile_name(account_id.GetUserEmail());
+    chromeos::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user,
+                                                                      profile);
+    return profile;
   }
 
   content::TestBrowserThreadBundle threads_;
   std::unique_ptr<policy::PolicyCertVerifier> cert_verifier_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
-  TestingProfile* user_profile_;
   session_manager::SessionManager session_manager_;
 
  private:
@@ -246,7 +267,7 @@
 
 // Make sure MultiProfile disabled by primary user policy.
 TEST_F(SessionControllerClientTest, MultiProfileDisallowedByUserPolicy) {
-  InitForMultiProfile();
+  TestingProfile* user_profile = InitForMultiProfile();
   EXPECT_EQ(ash::AddUserSessionPolicy::ALLOWED,
             SessionControllerClient::GetAddUserSessionPolicy());
   const AccountId account_id(AccountId::FromUserEmail(kUser));
@@ -258,7 +279,7 @@
   EXPECT_EQ(ash::AddUserSessionPolicy::ALLOWED,
             SessionControllerClient::GetAddUserSessionPolicy());
 
-  user_profile_->GetPrefs()->SetString(
+  user_profile->GetPrefs()->SetString(
       prefs::kMultiProfileUserBehavior,
       chromeos::MultiProfileUserController::kBehaviorNotAllowed);
   EXPECT_EQ(ash::AddUserSessionPolicy::ERROR_NOT_ALLOWED_PRIMARY_USER,
@@ -287,7 +308,7 @@
 // Make sure MultiProfile disabled by primary user certificates in memory.
 TEST_F(SessionControllerClientTest,
        MultiProfileDisallowedByPrimaryUserCertificatesInMemory) {
-  InitForMultiProfile();
+  TestingProfile* user_profile = InitForMultiProfile();
   user_manager()->AddUser(AccountId::FromUserEmail("bb@b.b"));
 
   const AccountId account_id(AccountId::FromUserEmail(kUser));
@@ -298,9 +319,9 @@
   g_policy_cert_verifier_for_factory = cert_verifier_.get();
   ASSERT_TRUE(
       policy::PolicyCertServiceFactory::GetInstance()->SetTestingFactoryAndUse(
-          user_profile_, CreateTestPolicyCertService));
+          user_profile, CreateTestPolicyCertService));
   policy::PolicyCertService* service =
-      policy::PolicyCertServiceFactory::GetForProfile(user_profile_);
+      policy::PolicyCertServiceFactory::GetForProfile(user_profile);
   ASSERT_TRUE(service);
 
   EXPECT_FALSE(service->has_policy_certificates());
@@ -352,13 +373,13 @@
 // Make sure adding users to multiprofiles disabled by primary user policy.
 TEST_F(SessionControllerClientTest,
        AddUserToMultiprofileDisallowedByPrimaryUserPolicy) {
-  InitForMultiProfile();
+  TestingProfile* user_profile = InitForMultiProfile();
 
   EXPECT_EQ(ash::AddUserSessionPolicy::ALLOWED,
             SessionControllerClient::GetAddUserSessionPolicy());
   const AccountId account_id(AccountId::FromUserEmail(kUser));
   user_manager()->LoginUser(account_id);
-  user_profile_->GetPrefs()->SetString(
+  user_profile->GetPrefs()->SetString(
       prefs::kMultiProfileUserBehavior,
       chromeos::MultiProfileUserController::kBehaviorNotAllowed);
   user_manager()->AddUser(AccountId::FromUserEmail("bb@b.b"));
@@ -429,17 +450,17 @@
             session_controller.last_user_session()->type);
 
   // Simulate profile creation after login.
-  CreateTestingProfile(user);
-  user_profile_->SetSupervisedUserId("child-id");
+  TestingProfile* user_profile = CreateTestingProfile(user);
+  user_profile->SetSupervisedUserId("child-id");
 
   // Simulate supervised user custodians.
-  PrefService* prefs = user_profile_->GetPrefs();
+  PrefService* prefs = user_profile->GetPrefs();
   prefs->SetString(prefs::kSupervisedUserCustodianEmail, "parent1@test.com");
   prefs->SetString(prefs::kSupervisedUserSecondCustodianEmail,
                    "parent2@test.com");
 
   // Simulate the notification that the profile is ready.
-  client.OnLoginUserProfilePrepared(user_profile_);
+  client.OnLoginUserProfilePrepared(user_profile);
   base::RunLoop().RunUntilIdle();  // For PostTask and mojo interface.
 
   // The custodians were sent over the mojo interface.
@@ -457,3 +478,44 @@
   EXPECT_EQ("parent3@test.com",
             session_controller.last_user_session()->custodian_email);
 }
+
+TEST_F(SessionControllerClientTest, UserPrefsChange) {
+  // Create an object to test and connect it to our test interface.
+  SessionControllerClient client;
+  TestSessionController session_controller;
+  client.session_controller_ = session_controller.CreateInterfacePtrAndBind();
+  client.Init();
+  SessionControllerClient::FlushForTesting();
+
+  // Simulate login.
+  const AccountId account_id(AccountId::FromUserEmail("user@test.com"));
+  const user_manager::User* user = user_manager()->AddUser(account_id);
+  session_manager_.CreateSession(
+      account_id, chromeos::ProfileHelper::GetUserIdHashByUserIdForTesting(
+                      "user@test.com"));
+  session_manager_.SetSessionState(SessionState::ACTIVE);
+  SessionControllerClient::FlushForTesting();
+
+  // Simulate the notification that the profile is ready.
+  TestingProfile* const user_profile = CreateTestingProfile(user);
+  client.OnLoginUserProfilePrepared(user_profile);
+
+  // Manipulate user prefs and verify SessionController is updated.
+  PrefService* const user_prefs = user_profile->GetPrefs();
+
+  user_prefs->SetBoolean(prefs::kAllowScreenLock, true);
+  SessionControllerClient::FlushForTesting();
+  EXPECT_TRUE(session_controller.last_session_info()->can_lock_screen);
+  user_prefs->SetBoolean(prefs::kAllowScreenLock, false);
+  SessionControllerClient::FlushForTesting();
+  EXPECT_FALSE(session_controller.last_session_info()->can_lock_screen);
+
+  user_prefs->SetBoolean(prefs::kEnableAutoScreenLock, true);
+  SessionControllerClient::FlushForTesting();
+  EXPECT_TRUE(
+      session_controller.last_session_info()->should_lock_screen_automatically);
+  user_prefs->SetBoolean(prefs::kEnableAutoScreenLock, false);
+  SessionControllerClient::FlushForTesting();
+  EXPECT_FALSE(
+      session_controller.last_session_info()->should_lock_screen_automatically);
+}
diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h
index e0d91ad..594a3f7b 100644
--- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h
+++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h
@@ -141,6 +141,7 @@
   SkColor InactiveFrameColor() const override;
   gfx::Insets GetFrameInsets() const override;
   bool CanHaveAlphaEnabled() const override;
+  void SetActivateOnPointer(bool activate_on_pointer) override;
 
   // These are used to simulate Mac-style hide/show. Since windows can be hidden
   // and shown using the app.window API, this sets is_hidden_with_app_ to
diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm
index c6a0fba..a9d9a06 100644
--- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm
+++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm
@@ -663,6 +663,10 @@
   return false;
 }
 
+void NativeAppWindowCocoa::SetActivateOnPointer(bool activate_on_pointer) {
+  NOTIMPLEMENTED();
+}
+
 gfx::NativeView NativeAppWindowCocoa::GetHostView() const {
   return WebContents()->GetNativeView();
 }
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view_cocoa.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view_cocoa.mm
index 3aaa475..b9e788cf1 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view_cocoa.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view_cocoa.mm
@@ -108,6 +108,11 @@
     [importBookmarksButton_ setFont:smallSystemFont];
     [importBookmarksButton_ sizeToFit];
 
+    // Hide by default so these don't flash if it takes a while for the bookmark
+    // model to load.
+    [noItemTextField_ setHidden:YES];
+    [importBookmarksButton_ setHidden:YES];
+
     [self addSubview:noItemTextField_];
     [self addSubview:importBookmarksButton_];
     [self registerForNotificationsAndDraggedTypes];
diff --git a/chrome/browser/ui/cocoa/first_run_dialog.h b/chrome/browser/ui/cocoa/first_run_dialog.h
index f576325..1f938da 100644
--- a/chrome/browser/ui/cocoa/first_run_dialog.h
+++ b/chrome/browser/ui/cocoa/first_run_dialog.h
@@ -10,27 +10,10 @@
 // Class that acts as a controller for the modal first run dialog.
 // The dialog asks the user's explicit permission for reporting stats to help
 // us improve Chromium.
-@interface FirstRunDialogController : NSWindowController {
- @private
-  // Bound to the value of the checkbox in FirstRunDialog.xib.
-  BOOL statsEnabled_;
-  BOOL makeDefaultBrowser_;
+@interface FirstRunDialogController : NSWindowController
 
-  IBOutlet NSArray* objectsToSize_;
-  IBOutlet NSButton* setAsDefaultCheckbox_;
-  IBOutlet NSButton* statsCheckbox_;
-  BOOL beenSized_;
-}
-
-// Called when the "Start Google Chrome" button is pressed.
-- (IBAction)ok:(id)sender;
-
-// Called when the "Learn More" button is pressed.
-- (IBAction)learnMore:(id)sender;
-
-// Properties for bindings.
-@property(assign, nonatomic) BOOL statsEnabled;
-@property(assign, nonatomic) BOOL makeDefaultBrowser;
+- (BOOL)isStatsReportingEnabled;
+- (BOOL)isMakeDefaultBrowserEnabled;
 
 @end
 
diff --git a/chrome/browser/ui/cocoa/first_run_dialog.mm b/chrome/browser/ui/cocoa/first_run_dialog.mm
index 1844bfc..a3de622 100644
--- a/chrome/browser/ui/cocoa/first_run_dialog.mm
+++ b/chrome/browser/ui/cocoa/first_run_dialog.mm
@@ -4,6 +4,7 @@
 
 #import "chrome/browser/ui/cocoa/first_run_dialog.h"
 
+#include "base/compiler_specific.h"
 #include "base/mac/bundle_locations.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/memory/ref_counted.h"
@@ -17,6 +18,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/shell_integration.h"
+#include "chrome/browser/ui/cocoa/first_run_dialog_controller.h"
 #include "chrome/common/url_constants.h"
 #include "components/search_engines/template_url_service.h"
 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
@@ -57,48 +59,32 @@
 
 // Show the first run UI.
 // Returns true if the first run dialog was shown.
-bool ShowFirstRun(Profile* profile) {
-  bool dialog_shown = false;
-#if defined(GOOGLE_CHROME_BUILD)
+bool ShowFirstRunModal(Profile* profile) {
   // The purpose of the dialog is to ask the user to enable stats and crash
   // reporting. This setting may be controlled through configuration management
   // in enterprise scenarios. If that is the case, skip the dialog entirely, as
   // it's not worth bothering the user for only the default browser question
   // (which is likely to be forced in enterprise deployments anyway).
-  if (!IsMetricsReportingPolicyManaged()) {
-    base::scoped_nsobject<FirstRunDialogController> dialog(
-        [[FirstRunDialogController alloc] init]);
+  if (IsMetricsReportingPolicyManaged())
+    return false;
+  base::scoped_nsobject<FirstRunDialogController> dialog(
+      [[FirstRunDialogController alloc] init]);
 
-    [dialog.get() showWindow:nil];
-    dialog_shown = true;
+  [dialog.get() showWindow:nil];
 
-    // If the dialog asked the user to opt-in for stats and crash reporting,
-    // record the decision and enable the crash reporter if appropriate.
-    bool consent_given = [dialog.get() statsEnabled];
-    ChangeMetricsReportingState(consent_given);
+  // If the dialog asked the user to opt-in for stats and crash reporting,
+  // record the decision and enable the crash reporter if appropriate.
+  bool consent_given = [dialog.get() isStatsReportingEnabled];
+  ChangeMetricsReportingState(consent_given);
 
-    // If selected set as default browser.
-    BOOL make_default_browser = [dialog.get() makeDefaultBrowser];
-    if (make_default_browser) {
-      bool success = shell_integration::SetAsDefaultBrowser();
-      DCHECK(success);
-    }
+  // If selected set as default browser.
+  BOOL make_default_browser = [dialog.get() isMakeDefaultBrowserEnabled];
+  if (make_default_browser) {
+    bool success = shell_integration::SetAsDefaultBrowser();
+    DCHECK(success);
   }
-#else  // GOOGLE_CHROME_BUILD
-  // We don't show the dialog in Chromium.
-#endif  // GOOGLE_CHROME_BUILD
 
-  // Set preference to show first run bubble and welcome page.
-  // Only display the bubble if there is a default search provider.
-  TemplateURLService* search_engines_model =
-      TemplateURLServiceFactory::GetForProfile(profile);
-  if (search_engines_model &&
-      search_engines_model->GetDefaultSearchProvider()) {
-    first_run::SetShowFirstRunBubblePref(first_run::FIRST_RUN_BUBBLE_SHOW);
-  }
-  first_run::SetShouldShowWelcomePage();
-
-  return dialog_shown;
+  return true;
 }
 
 // True when the stats checkbox should be checked by default. This is only
@@ -113,25 +99,50 @@
 namespace first_run {
 
 bool ShowFirstRunDialog(Profile* profile) {
-  return ShowFirstRun(profile);
+  bool dialog_shown = false;
+#if defined(GOOGLE_CHROME_BUILD)
+  dialog_shown = ShowFirstRunModal(profile);
+#else
+  (void)ShowFirstRunModal;  // Placate compiler.
+#endif
+  // Set preference to show first run bubble and welcome page.
+  // Only display the bubble if there is a default search provider.
+  TemplateURLService* search_engines_model =
+      TemplateURLServiceFactory::GetForProfile(profile);
+  if (search_engines_model &&
+      search_engines_model->GetDefaultSearchProvider()) {
+    first_run::SetShowFirstRunBubblePref(first_run::FIRST_RUN_BUBBLE_SHOW);
+  }
+  first_run::SetShouldShowWelcomePage();
+
+  return dialog_shown;
 }
 
 }  // namespace first_run
 
-@implementation FirstRunDialogController
+@implementation FirstRunDialogController {
+  base::scoped_nsobject<FirstRunDialogViewController> viewController_;
+}
 
-@synthesize statsEnabled = statsEnabled_;
-@synthesize makeDefaultBrowser = makeDefaultBrowser_;
+- (instancetype)init {
+  viewController_.reset([[FirstRunDialogViewController alloc]
+      initWithStatsCheckboxInitiallyChecked:StatsCheckboxDefault()
+              defaultBrowserCheckboxVisible:shell_integration::
+                                                CanSetAsDefaultBrowser()]);
 
-- (id)init {
-  NSString* nibpath =
-      [base::mac::FrameworkBundle() pathForResource:@"FirstRunDialog"
-                                             ofType:@"nib"];
-  if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
-    // Bound to the dialog checkboxes.
-    makeDefaultBrowser_ = shell_integration::CanSetAsDefaultBrowser();
-    statsEnabled_ = StatsCheckboxDefault();
-  }
+  // Create the content view controller (and the content view) *before* the
+  // window, so that we can find out what the content view's frame is supposed
+  // to be for use here.
+  base::scoped_nsobject<NSWindow> window([[NSWindow alloc]
+      initWithContentRect:[[viewController_ view] frame]
+                styleMask:NSTitledWindowMask
+                  backing:NSBackingStoreBuffered
+                    defer:YES]);
+  [window setContentView:[viewController_ view]];
+  [window setTitle:[viewController_ windowTitle]];
+
+  self = [super initWithWindow:window.get()];
+
   return self;
 }
 
@@ -153,100 +164,6 @@
 - (void)show {
   NSWindow* win = [self window];
 
-  if (!shell_integration::CanSetAsDefaultBrowser()) {
-    [setAsDefaultCheckbox_ setHidden:YES];
-  }
-
-  // Only support the sizing the window once.
-  DCHECK(!beenSized_) << "ShowWindow was called twice?";
-  if (!beenSized_) {
-    beenSized_ = YES;
-    DCHECK_GT([objectsToSize_ count], 0U);
-
-    // Size everything to fit, collecting the widest growth needed (XIB provides
-    // the min size, i.e.-never shrink, just grow).
-    CGFloat largestWidthChange = 0.0;
-    for (NSView* view in objectsToSize_) {
-      DCHECK_NE(statsCheckbox_, view) << "Stats checkbox shouldn't be in list";
-      if (![view isHidden]) {
-        NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:view];
-        DCHECK_EQ(delta.height, 0.0)
-            << "Didn't expect anything to change heights";
-        if (largestWidthChange < delta.width)
-          largestWidthChange = delta.width;
-      }
-    }
-
-    // Make the window wide enough to fit everything.
-    if (largestWidthChange > 0.0) {
-      NSView* contentView = [win contentView];
-      NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
-      windowFrame.size.width += largestWidthChange;
-      windowFrame = [contentView convertRect:windowFrame toView:nil];
-      [win setFrame:windowFrame display:NO];
-    }
-
-    // The stats checkbox gets some really long text, so it gets word wrapped
-    // and then sized.
-    DCHECK(statsCheckbox_);
-    CGFloat statsCheckboxHeightChange = 0.0;
-    [GTMUILocalizerAndLayoutTweaker wrapButtonTitleForWidth:statsCheckbox_];
-    statsCheckboxHeightChange =
-        [GTMUILocalizerAndLayoutTweaker sizeToFitView:statsCheckbox_].height;
-
-    // Walk bottom up shuffling for all the hidden views.
-    NSArray* subViews =
-        [[[win contentView] subviews] sortedArrayUsingComparator:^(id a, id b) {
-          CGFloat y1 = NSMinY([a frame]);
-          CGFloat y2 = NSMinY([b frame]);
-          if (y1 < y2)
-            return NSOrderedAscending;
-          else if (y1 > y2)
-            return NSOrderedDescending;
-          else
-            return NSOrderedSame;
-        }];
-    CGFloat moveDown = 0.0;
-    NSUInteger numSubViews = [subViews count];
-    for (NSUInteger idx = 0 ; idx < numSubViews ; ++idx) {
-      NSView* view = [subViews objectAtIndex:idx];
-
-      // If the view is hidden, collect the amount to move everything above it
-      // down, if it's not hidden, apply any shift down.
-      if ([view isHidden]) {
-        DCHECK_GT((numSubViews - 1), idx)
-            << "Don't support top view being hidden";
-        NSView* nextView = [subViews objectAtIndex:(idx + 1)];
-        CGFloat viewBottom = [view frame].origin.y;
-        CGFloat nextViewBottom = [nextView frame].origin.y;
-        moveDown += nextViewBottom - viewBottom;
-      } else {
-        if (moveDown != 0.0) {
-          NSPoint origin = [view frame].origin;
-          origin.y -= moveDown;
-          [view setFrameOrigin:origin];
-        }
-      }
-      // Special case, if this is the stats checkbox, everything above it needs
-      // to get moved up by the amount it changed height.
-      if (view == statsCheckbox_) {
-        moveDown -= statsCheckboxHeightChange;
-      }
-    }
-
-    // Resize the window for any height change from hidden views, etc.
-    if (moveDown != 0.0) {
-      NSView* contentView = [win contentView];
-      [contentView setAutoresizesSubviews:NO];
-      NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
-      windowFrame.size.height -= moveDown;
-      windowFrame = [contentView convertRect:windowFrame toView:nil];
-      [win setFrame:windowFrame display:NO];
-      [contentView setAutoresizesSubviews:YES];
-    }
-
-  }
-
   // Neat weirdness in the below code - the Application menu stays enabled
   // while the window is open but selecting items from it (e.g. Quit) has
   // no effect.  I'm guessing that this is an artifact of us being a
@@ -258,15 +175,12 @@
   [NSApp runModalForWindow:win];
 }
 
-- (IBAction)ok:(id)sender {
-  [[self window] close];
-  [NSApp stopModal];
+- (BOOL)isStatsReportingEnabled {
+  return [viewController_ isStatsReportingEnabled];
 }
 
-- (IBAction)learnMore:(id)sender {
-  NSString* urlStr = base::SysUTF8ToNSString(chrome::kLearnMoreReportingURL);
-  NSURL* learnMoreUrl = [NSURL URLWithString:urlStr];
-  [[NSWorkspace sharedWorkspace] openURL:learnMoreUrl];
+- (BOOL)isMakeDefaultBrowserEnabled {
+  return [viewController_ isMakeDefaultBrowserEnabled];
 }
 
 @end
diff --git a/chrome/browser/ui/cocoa/first_run_dialog_controller.h b/chrome/browser/ui/cocoa/first_run_dialog_controller.h
new file mode 100644
index 0000000..008f5108
--- /dev/null
+++ b/chrome/browser/ui/cocoa/first_run_dialog_controller.h
@@ -0,0 +1,24 @@
+// 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 CHROME_BROWSER_UI_COCOA_FIRST_RUN_DIALOG_CONTROLLER_H_
+#define CHROME_BROWSER_UI_COCOA_FIRST_RUN_DIALOG_CONTROLLER_H_
+
+#include <Cocoa/Cocoa.h>
+
+// FirstRunDialogViewController is the NSViewController for the first run
+// dialog's content view.
+@interface FirstRunDialogViewController : NSViewController
+
+- (instancetype)initWithStatsCheckboxInitiallyChecked:(BOOL)checked
+                        defaultBrowserCheckboxVisible:(BOOL)visible;
+
+- (NSString*)windowTitle;
+
+- (BOOL)isStatsReportingEnabled;
+- (BOOL)isMakeDefaultBrowserEnabled;
+
+@end
+
+#endif  // CHROME_BROWSER_UI_COCOA_FIRST_RUN_DIALOG_CONTROLLER_H_
diff --git a/chrome/browser/ui/cocoa/first_run_dialog_controller.mm b/chrome/browser/ui/cocoa/first_run_dialog_controller.mm
new file mode 100644
index 0000000..e5ecd3dd
--- /dev/null
+++ b/chrome/browser/ui/cocoa/first_run_dialog_controller.mm
@@ -0,0 +1,186 @@
+// 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 "chrome/browser/ui/cocoa/first_run_dialog_controller.h"
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/browser/ui/cocoa/key_equivalent_constants.h"
+#include "chrome/browser/ui/cocoa/l10n_util.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/cocoa/controls/button_utils.h"
+#include "ui/base/cocoa/controls/textfield_utils.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+namespace {
+
+// Return the internationalized message |message_id|, with the product name
+// substituted in for $1.
+NSString* NSStringWithProductName(int message_id) {
+  return l10n_util::GetNSStringF(message_id,
+                                 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+}
+
+void MoveViewsDown(NSArray* views, CGFloat distance) {
+  for (NSView* view : views) {
+    NSRect frame = view.frame;
+    frame.origin.y -= distance;
+    [view setFrame:frame];
+  }
+}
+
+}  // namespace
+
+@implementation FirstRunDialogViewController {
+  // These are owned by the NSView hierarchy:
+  NSButton* defaultBrowserCheckbox_;
+  NSButton* statsCheckbox_;
+
+  // This is owned by NSViewController:
+  NSView* view_;
+
+  BOOL statsCheckboxInitiallyChecked_;
+  BOOL defaultBrowserCheckboxVisible_;
+}
+
+- (instancetype)initWithStatsCheckboxInitiallyChecked:(BOOL)checked
+                        defaultBrowserCheckboxVisible:(BOOL)visible {
+  if ((self = [super init])) {
+    statsCheckboxInitiallyChecked_ = checked;
+    defaultBrowserCheckboxVisible_ = visible;
+  }
+  return self;
+}
+
+- (void)loadView {
+  // Frame constants in this method were taken directly from the now-deleted
+  // chrome/app/nibs/FirstRunDialog.xib.
+  NSBox* topBox =
+      [[[NSBox alloc] initWithFrame:NSMakeRect(0, 158, 480, 55)] autorelease];
+  [topBox setFillColor:[NSColor whiteColor]];
+  [topBox setBoxType:NSBoxCustom];
+  [topBox setBorderType:NSNoBorder];
+  [topBox setContentViewMargins:NSZeroSize];
+
+  NSTextField* completionLabel = [TextFieldUtils
+      labelWithString:NSStringWithProductName(
+                          IDS_FIRSTRUN_DLG_MAC_COMPLETE_INSTALLATION_LABEL)];
+  [completionLabel setFrame:NSMakeRect(13, 25, 390, 17)];
+
+  NSImageView* logoImage = [[[NSImageView alloc]
+      initWithFrame:NSMakeRect(408, -25, 96, 96)] autorelease];
+  [logoImage setImage:[NSImage imageNamed:NSImageNameApplicationIcon]];
+  [logoImage setRefusesFirstResponder:YES];
+  [logoImage setEditable:NO];
+
+  defaultBrowserCheckbox_ = [ButtonUtils
+      checkboxWithTitle:l10n_util::GetNSString(
+                            IDS_FIRSTRUN_DLG_MAC_SET_DEFAULT_BROWSER_LABEL)];
+  [defaultBrowserCheckbox_ setFrame:NSMakeRect(45, 126, 528, 18)];
+  if (!defaultBrowserCheckboxVisible_)
+    [defaultBrowserCheckbox_ setHidden:YES];
+
+  statsCheckbox_ = [ButtonUtils
+      checkboxWithTitle:
+          NSStringWithProductName(
+              IDS_FIRSTRUN_DLG_MAC_OPTIONS_SEND_USAGE_STATS_LABEL)];
+  [statsCheckbox_ setFrame:NSMakeRect(45, 101, 389, 19)];
+  if (statsCheckboxInitiallyChecked_)
+    [statsCheckbox_ setNextState];
+
+  NSButton* startChromeButton =
+      [ButtonUtils buttonWithTitle:NSStringWithProductName(
+                                       IDS_FIRSTRUN_DLG_MAC_START_CHROME_BUTTON)
+                            action:@selector(ok:)
+                            target:self];
+  [startChromeButton setFrame:NSMakeRect(161, 12, 306, 32)];
+  [startChromeButton setKeyEquivalent:kKeyEquivalentReturn];
+
+  NSButton* learnMoreLink =
+      [ButtonUtils linkWithTitle:l10n_util::GetNSString(IDS_LEARN_MORE)
+                          action:@selector(learnMore:)
+                          target:self];
+  [learnMoreLink setFrame:NSMakeRect(60, 76, 359, 19)];
+
+  NSBox* topSeparator =
+      [[[NSBox alloc] initWithFrame:NSMakeRect(0, 155, 480, 5)] autorelease];
+  [topSeparator setBoxType:NSBoxSeparator];
+
+  NSBox* bottomSeparator =
+      [[[NSBox alloc] initWithFrame:NSMakeRect(0, 55, 480, 5)] autorelease];
+  [bottomSeparator setBoxType:NSBoxSeparator];
+
+  [topBox addSubview:completionLabel];
+  [topBox addSubview:logoImage];
+
+  base::scoped_nsobject<NSView> content_view(
+      [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 480, 209)]);
+  self.view = content_view.get();
+  [self.view addSubview:topBox];
+  [self.view addSubview:topSeparator];
+  [self.view addSubview:defaultBrowserCheckbox_];
+  [self.view addSubview:statsCheckbox_];
+  [self.view addSubview:learnMoreLink];
+  [self.view addSubview:bottomSeparator];
+  [self.view addSubview:startChromeButton];
+
+  // Now that the content view is constructed, fix the layout: since this view
+  // isn't using autolayout, if the widths of some of the subviews change
+  // because of localization, they need to be resized and perhaps repositioned,
+  // which is done here by |VerticallyReflowGroup()|.
+  CGFloat oldWidth = NSWidth([startChromeButton frame]);
+  cocoa_l10n_util::VerticallyReflowGroup(
+      @[ defaultBrowserCheckbox_, statsCheckbox_, learnMoreLink ]);
+
+  // The "Start Chrome" button needs to be sized to fit the localized string
+  // inside it, but it should still be at the right-most edge of the dialog, so
+  // any width added or subtracted by |sizeToFit| is added to its x coord, which
+  // keeps its right edge where it was.
+  [startChromeButton sizeToFit];
+  NSRect frame = [startChromeButton frame];
+  frame.origin.x += oldWidth - NSWidth([startChromeButton frame]);
+  [startChromeButton setFrame:frame];
+
+  // Lastly, if the default browser checkbox is actually invisible, move the
+  // views above it downward so that there's not a big open space in the content
+  // view, and resize the content view itself so there isn't extra space.
+  if (!defaultBrowserCheckboxVisible_) {
+    CGFloat delta = NSHeight([defaultBrowserCheckbox_ frame]);
+    MoveViewsDown(@[ topBox, topSeparator ], delta);
+    NSRect frame = [self.view frame];
+    frame.size.height -= delta;
+    [self.view setAutoresizesSubviews:NO];
+    [self.view setFrame:frame];
+    [self.view setAutoresizesSubviews:YES];
+  }
+}
+
+- (NSString*)windowTitle {
+  return NSStringWithProductName(IDS_FIRSTRUN_DLG_MAC_WINDOW_TITLE);
+}
+
+- (BOOL)isStatsReportingEnabled {
+  return [statsCheckbox_ state] == NSOnState;
+}
+
+- (BOOL)isMakeDefaultBrowserEnabled {
+  return [defaultBrowserCheckbox_ state] == NSOnState;
+}
+
+- (void)ok:(id)sender {
+  [[[self view] window] close];
+  [NSApp stopModal];
+}
+
+- (void)learnMore:(id)sender {
+  NSString* urlStr = base::SysUTF8ToNSString(chrome::kLearnMoreReportingURL);
+  NSURL* learnMoreUrl = [NSURL URLWithString:urlStr];
+  [[NSWorkspace sharedWorkspace] openURL:learnMoreUrl];
+}
+
+@end
diff --git a/chrome/browser/ui/cocoa/first_run_dialog_controller_unittest.mm b/chrome/browser/ui/cocoa/first_run_dialog_controller_unittest.mm
new file mode 100644
index 0000000..c1cd6b1
--- /dev/null
+++ b/chrome/browser/ui/cocoa/first_run_dialog_controller_unittest.mm
@@ -0,0 +1,46 @@
+// 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 "chrome/browser/ui/cocoa/first_run_dialog_controller.h"
+#include "base/mac/scoped_nsobject.h"
+#include "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using FirstRunDialogControllerTest = CocoaTest;
+using TestController = base::scoped_nsobject<FirstRunDialogViewController>;
+
+TestController MakeTestController(BOOL stats, BOOL browser) {
+  return TestController([[FirstRunDialogViewController alloc]
+      initWithStatsCheckboxInitiallyChecked:stats
+              defaultBrowserCheckboxVisible:browser]);
+}
+
+NSView* FindBrowserButton(NSView* view) {
+  for (NSView* subview : [view subviews]) {
+    if (![subview isKindOfClass:[NSButton class]])
+      continue;
+    NSString* title = [(NSButton*)subview title];
+    if ([title rangeOfString:@"browser"].location != NSNotFound)
+      return subview;
+  }
+  return nil;
+}
+
+TEST(FirstRunDialogControllerTest, SetStatsDefault) {
+  TestController controller(MakeTestController(YES, YES));
+  [controller view];  // Make sure view is actually loaded.
+  EXPECT_TRUE([controller isStatsReportingEnabled]);
+}
+
+TEST(FirstRunDialogControllerTest, ShowBrowser) {
+  TestController controller(MakeTestController(YES, YES));
+  NSView* checkbox = FindBrowserButton([controller view]);
+  EXPECT_FALSE(checkbox.hidden);
+}
+
+TEST(FirstRunDialogControllerTest, HideBrowser) {
+  TestController controller(MakeTestController(YES, NO));
+  NSView* checkbox = FindBrowserButton([controller view]);
+  EXPECT_TRUE(checkbox.hidden);
+}
diff --git a/chrome/browser/ui/find_bar/find_bar_controller.h b/chrome/browser/ui/find_bar/find_bar_controller.h
index 25ecaed1..e912a06 100644
--- a/chrome/browser/ui/find_bar/find_bar_controller.h
+++ b/chrome/browser/ui/find_bar/find_bar_controller.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/strings/string16.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index a70d9b2..7cb45b44 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/history/history_tab_helper.h"
 #include "chrome/browser/history/top_sites_factory.h"
 #include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/media/media_engagement_service.h"
 #include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_observer.h"
 #include "chrome/browser/metrics/renderer_uptime_web_contents_observer.h"
 #include "chrome/browser/net/net_error_tab_helper.h"
@@ -298,4 +299,7 @@
 
   if (tracing::NavigationTracingObserver::IsEnabled())
     tracing::NavigationTracingObserver::CreateForWebContents(web_contents);
+
+  if (MediaEngagementService::IsEnabled())
+    MediaEngagementService::CreateWebContentsObserver(web_contents);
 }
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
index 3097a35..ef0430f 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
@@ -373,6 +373,12 @@
   }
 }
 
+void ChromeNativeAppWindowViewsAuraAsh::SetActivateOnPointer(
+    bool activate_on_pointer) {
+  widget()->GetNativeWindow()->SetProperty(aura::client::kActivateOnPointerKey,
+                                           activate_on_pointer);
+}
+
 void ChromeNativeAppWindowViewsAuraAsh::OnMenuClosed() {
   menu_runner_.reset();
   menu_model_adapter_.reset();
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h
index e3a0d77f..2a2ae09 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.h
@@ -64,6 +64,7 @@
   void SetFullscreen(int fullscreen_types) override;
   void UpdateDraggableRegions(
       const std::vector<extensions::DraggableRegion>& regions) override;
+  void SetActivateOnPointer(bool activate_on_pointer) override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(ShapedAppWindowTargeterTest,
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
index e556570..915f4897 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
@@ -13,6 +13,7 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/command_line.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
@@ -33,7 +34,9 @@
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
 #include "services/service_manager/public/cpp/bind_source_info.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -59,10 +62,18 @@
       is_valid_ssl_(true) {}
 PaymentRequestBrowserTestBase::~PaymentRequestBrowserTestBase() {}
 
+void PaymentRequestBrowserTestBase::SetUpCommandLine(
+    base::CommandLine* command_line) {
+  // HTTPS server only serves a valid cert for localhost, so this is needed to
+  // load pages from "a.com" without an interstitial.
+  command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
+}
+
 void PaymentRequestBrowserTestBase::SetUpOnMainThread() {
   // Setup the https server.
   https_server_ = base::MakeUnique<net::EmbeddedTestServer>(
       net::EmbeddedTestServer::TYPE_HTTPS);
+  host_resolver()->AddRule("a.com", "127.0.0.1");
   ASSERT_TRUE(https_server_->InitializeAndListen());
   https_server_->ServeFilesFromSourceDirectory("chrome/test/data/payments");
   https_server_->StartAcceptingConnections();
@@ -82,10 +93,12 @@
 }
 
 void PaymentRequestBrowserTestBase::NavigateTo(const std::string& file_path) {
-  ui_test_utils::NavigateToURL(browser(),
-                               file_path.find("data:") == 0U
-                                   ? GURL(file_path)
-                                   : https_server()->GetURL(file_path));
+  if (file_path.find("data:") == 0U) {
+    ui_test_utils::NavigateToURL(browser(), GURL(file_path));
+  } else {
+    ui_test_utils::NavigateToURL(browser(),
+                                 https_server()->GetURL("a.com", file_path));
+  }
 }
 
 void PaymentRequestBrowserTestBase::SetIncognito() {
@@ -414,8 +427,8 @@
           web_contents, this /* observer */, is_incognito_, is_valid_ssl_);
   delegate_ = delegate.get();
   PaymentRequestWebContentsManager::GetOrCreateForWebContents(web_contents)
-      ->CreatePaymentRequest(web_contents, std::move(delegate),
-                             std::move(request), this);
+      ->CreatePaymentRequest(web_contents->GetMainFrame(), web_contents,
+                             std::move(delegate), std::move(request), this);
 }
 
 void PaymentRequestBrowserTestBase::ClickOnDialogViewAndWait(
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.h b/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
index 8365bff..35abd42c 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.h
@@ -90,6 +90,7 @@
   explicit PaymentRequestBrowserTestBase(const std::string& test_file_path);
   ~PaymentRequestBrowserTestBase() override;
 
+  void SetUpCommandLine(base::CommandLine* command_line) override;
   void SetUpOnMainThread() override;
 
   void NavigateTo(const std::string& file_path);
diff --git a/chrome/browser/ui/views/payments/payment_request_can_make_payment_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_can_make_payment_browsertest.cc
index cb4e072..7575799 100644
--- a/chrome/browser/ui/views/payments/payment_request_can_make_payment_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_can_make_payment_browsertest.cc
@@ -96,4 +96,102 @@
   ExpectBodyContains({"true"});
 }
 
+class PaymentRequestCanMakePaymentQueryCCTest
+    : public PaymentRequestBrowserTestBase {
+ protected:
+  PaymentRequestCanMakePaymentQueryCCTest()
+      : PaymentRequestBrowserTestBase(
+            "/payment_request_can_make_payment_query_cc_test.html") {}
+
+  void CallCanMakePayment(bool visa) {
+    ResetEventObserver(DialogEvent::CAN_MAKE_PAYMENT_CALLED);
+    ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(),
+                                       visa ? "buy();" : "other_buy();"));
+    WaitForObservedEvent();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PaymentRequestCanMakePaymentQueryCCTest);
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryCCTest, QueryQuota) {
+  // Query "visa" payment method.
+  CallCanMakePayment(/*visa=*/true);
+
+  // User does not have a visa card.
+  ExpectBodyContains({"false"});
+
+  // Query "mastercard" payment method.
+  CallCanMakePayment(/*visa=*/false);
+
+  // Query quota exceeded.
+  ExpectBodyContains({"NotAllowedError"});
+
+  AddCreditCard(autofill::test::GetCreditCard());  // visa
+
+  // Query "visa" payment method.
+  CallCanMakePayment(/*visa=*/true);
+
+  // User now has a visa card. The query is cached, but the result is always
+  // fresh.
+  ExpectBodyContains({"true"});
+
+  // Query "mastercard" payment method.
+  CallCanMakePayment(/*visa=*/false);
+
+  // Query quota exceeded.
+  ExpectBodyContains({"NotAllowedError"});
+}
+
+class PaymentRequestCanMakePaymentQueryBasicCardTest
+    : public PaymentRequestBrowserTestBase {
+ protected:
+  PaymentRequestCanMakePaymentQueryBasicCardTest()
+      : PaymentRequestBrowserTestBase("/payment_request_basic_card_test.html") {
+  }
+
+  void CallCanMakePayment(bool visa) {
+    ResetEventObserver(DialogEvent::CAN_MAKE_PAYMENT_CALLED);
+    ASSERT_TRUE(content::ExecuteScript(
+        GetActiveWebContents(),
+        visa ? "checkBasicVisa();" : "checkBasicCard();"));
+    WaitForObservedEvent();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PaymentRequestCanMakePaymentQueryBasicCardTest);
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryBasicCardTest,
+                       QueryQuota) {
+  // Query "basic-card" payment method with "supportedNetworks": ["visa"] in the
+  // payment method specific data.
+  CallCanMakePayment(/*visa=*/true);
+
+  // User does not have a visa card.
+  ExpectBodyContains({"false"});
+
+  // Query "basic-card" payment method without "supportedNetworks" parameter.
+  CallCanMakePayment(/*visa=*/false);
+
+  // Query quota exceeded.
+  ExpectBodyContains({"NotAllowedError"});
+
+  AddCreditCard(autofill::test::GetCreditCard());  // visa
+
+  // Query "basic-card" payment method with "supportedNetworks": ["visa"] in the
+  // payment method specific data.
+  CallCanMakePayment(/*visa=*/true);
+
+  // User now has a visa card. The query is cached, but the result is always
+  // fresh.
+  ExpectBodyContains({"true"});
+
+  // Query "basic-card" payment method without "supportedNetworks" parameter.
+  CallCanMakePayment(/*visa=*/false);
+
+  // Query quota exceeded.
+  ExpectBodyContains({"NotAllowedError"});
+}
+
 }  // namespace payments
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view_unittest.cc b/chrome/browser/ui/views/toolbar/toolbar_action_view_unittest.cc
index eec286e9..c64c1c6 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
+
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
@@ -11,9 +13,8 @@
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
-#include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
 #include "chrome/test/base/testing_profile.h"
-#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_web_contents_factory.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/events/test/event_generator.h"
@@ -98,10 +99,7 @@
 
 class ToolbarActionViewUnitTest : public views::ViewsTestBase {
  public:
-  ToolbarActionViewUnitTest()
-      : widget_(nullptr),
-        ui_thread_(content::BrowserThread::UI, message_loop()),
-        scoped_task_scheduler_(base::MessageLoop::current()) {}
+  ToolbarActionViewUnitTest() : widget_(nullptr) {}
   ~ToolbarActionViewUnitTest() override {}
 
   void SetUp() override {
@@ -122,13 +120,12 @@
   views::Widget* widget() { return widget_; }
 
  private:
+  // Web contents need a UI thread and a TaskScheduler.
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+
   // The widget managed by this test.
   views::Widget* widget_;
 
-  // Web contents need a UI thread and a TaskScheduler.
-  content::TestBrowserThread ui_thread_;
-  base::test::ScopedTaskScheduler scoped_task_scheduler_;
-
   DISALLOW_COPY_AND_ASSIGN(ToolbarActionViewUnitTest);
 };
 
diff --git a/chrome/renderer/resources/extensions/media_router_bindings.js b/chrome/renderer/resources/extensions/media_router_bindings.js
index 4eac395..c6554c1 100644
--- a/chrome/renderer/resources/extensions/media_router_bindings.js
+++ b/chrome/renderer/resources/extensions/media_router_bindings.js
@@ -189,6 +189,7 @@
       default:
         throw new Error('Scheme must be http or https');
     }
+    mojoOrigin.suborigin = '';
     return new originMojom.Origin(mojoOrigin);
   }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 0cf7abb..e3964e4 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4450,6 +4450,7 @@
         "../browser/ui/cocoa/find_bar/find_bar_view_unittest.mm",
         "../browser/ui/cocoa/find_pasteboard_unittest.mm",
         "../browser/ui/cocoa/first_run_bubble_controller_unittest.mm",
+        "../browser/ui/cocoa/first_run_dialog_controller_unittest.mm",
         "../browser/ui/cocoa/floating_bar_backing_view_unittest.mm",
         "../browser/ui/cocoa/framed_browser_window_unittest.mm",
         "../browser/ui/cocoa/fullscreen/fullscreen_toolbar_controller_unittest.mm",
diff --git a/chrome/test/data/payments/can_make_payment_query_cc.js b/chrome/test/data/payments/can_make_payment_query_cc.js
index f56b8fd..a60e9dd 100644
--- a/chrome/test/data/payments/can_make_payment_query_cc.js
+++ b/chrome/test/data/payments/can_make_payment_query_cc.js
@@ -4,15 +4,12 @@
  * found in the LICENSE file.
  */
 
-/* global PaymentRequest:false */
-/* global print:false */
-
 /**
  * Checks for existence of a complete VISA credit card.
  */
 function buy() {  // eslint-disable-line no-unused-vars
   try {
-    var request = new PaymentRequest(
+    const request = new PaymentRequest(
         [{supportedMethods: ['visa']}],
         {total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}});
     request.canMakePayment()
@@ -30,9 +27,9 @@
 /**
  * Checks for existence of a complete MasterCard credit card.
  */
-function other_buy() {  // eslint-disable-line no-unused-vars
+function other_buy() {  // eslint-disable-line no-unused-vars, camelcase
   try {
-    var request = new PaymentRequest(
+    const request = new PaymentRequest(
         [{supportedMethods: ['mastercard']}],
         {total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}});
     request.canMakePayment()
diff --git a/components/autofill/content/common/autofill_types_struct_traits_unittest.cc b/components/autofill/content/common/autofill_types_struct_traits_unittest.cc
index b60bff0..dba2fa6 100644
--- a/components/autofill/content/common/autofill_types_struct_traits_unittest.cc
+++ b/components/autofill/content/common/autofill_types_struct_traits_unittest.cc
@@ -93,7 +93,7 @@
   form->icon_url = GURL("https://foo.com/icon.png");
   form->federation_origin =
       url::Origin::UnsafelyCreateOriginWithoutNormalization(
-          "http", "www.google.com", 80);
+          "http", "www.google.com", 80, "");
   form->skip_zero_click = false;
   form->layout = PasswordForm::Layout::LAYOUT_LOGIN_AND_SIGNUP;
   form->was_parsed_using_autofill_predictions = false;
diff --git a/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc b/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc
index df12ab1a..7049f31f 100644
--- a/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc
@@ -448,6 +448,13 @@
 }
 
 void AutocompleteSyncBridge::LoadMetadata() {
+  if (!web_data_backend_ || !web_data_backend_->GetDatabase() ||
+      !GetAutofillTable()) {
+    change_processor()->ReportError(FROM_HERE,
+                                    "Failed to load AutofillWebDatabase.");
+    return;
+  }
+
   auto batch = base::MakeUnique<syncer::MetadataBatch>();
   if (!GetAutofillTable()->GetAllSyncMetadata(syncer::AUTOFILL, batch.get())) {
     change_processor()->ReportError(
diff --git a/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc b/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc
index 668ff41..59506b1 100644
--- a/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc
+++ b/components/autofill/core/browser/webdata/autocomplete_sync_bridge_unittest.cc
@@ -141,10 +141,10 @@
   }
   ~AutocompleteSyncBridgeTest() override {}
 
-  void ResetBridge() {
+  void ResetBridge(bool expect_error = false) {
     bridge_.reset(new AutocompleteSyncBridge(
-        &backend_,
-        RecordingModelTypeChangeProcessor::FactoryForBridgeTest(&processor_)));
+        &backend_, RecordingModelTypeChangeProcessor::FactoryForBridgeTest(
+                       &processor_, expect_error)));
   }
 
   void SaveSpecificsToTable(
@@ -266,6 +266,8 @@
 
   AutofillTable* table() { return &table_; }
 
+  FakeAutofillBackend* backend() { return &backend_; }
+
  private:
   ScopedTempDir temp_dir_;
   base::test::ScopedTaskEnvironment scoped_task_environment_;
@@ -592,6 +594,12 @@
   EXPECT_EQ(1u, processor().metadata()->TakeAllMetadata().size());
 }
 
+TEST_F(AutocompleteSyncBridgeTest, LoadMetadataReportsErrorForMissingDB) {
+  backend()->SetWebDatabase(nullptr);
+  // The processor's destructor will verify that an error has occured.
+  ResetBridge(/*expect_error=*/true);
+}
+
 TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataEmpty) {
   VerifyMerge(std::vector<AutofillSpecifics>());
 
diff --git a/components/exo/wayland/server.cc b/components/exo/wayland/server.cc
index 9aa55fc..f9e941d 100644
--- a/components/exo/wayland/server.cc
+++ b/components/exo/wayland/server.cc
@@ -2149,7 +2149,8 @@
     // already activated on the client side, so do not notify about the
     // activation. It means that zcr_remote_shell_v1_send_activated is used
     // only to notify about activations originating in Aura.
-    if (gained_active && gained_active->GetProperty(kIgnoreWindowActivated)) {
+    if (gained_active && ShellSurface::GetMainSurface(gained_active) &&
+        gained_active->GetProperty(kIgnoreWindowActivated)) {
       gained_active->SetProperty(kIgnoreWindowActivated, false);
       return;
     }
diff --git a/components/infobars/core/infobar_delegate.h b/components/infobars/core/infobar_delegate.h
index c041844..48ad40203 100644
--- a/components/infobars/core/infobar_delegate.h
+++ b/components/infobars/core/infobar_delegate.h
@@ -145,6 +145,7 @@
     SEARCH_GEOLOCATION_DISCLOSURE_INFOBAR_DELEGATE = 72,
     AUTOMATION_INFOBAR_DELEGATE = 73,
     VR_SERVICES_UPGRADE_ANDROID = 74,
+    READER_MODE_INFOBAR_ANDROID = 75,
   };
 
   // Describes navigation events, used to decide whether infobars should be
diff --git a/components/navigation_metrics/OWNERS b/components/navigation_metrics/OWNERS
index e9db99ed..06f6dae 100644
--- a/components/navigation_metrics/OWNERS
+++ b/components/navigation_metrics/OWNERS
@@ -1,4 +1,6 @@
 cbentzel@chromium.org
 davidben@chromium.org
+elawrence@chromium.org
+estark@chromium.org
 
 # COMPONENT: Internals>Network
diff --git a/components/ntp_snippets/content_suggestions_metrics.cc b/components/ntp_snippets/content_suggestions_metrics.cc
index 5d2d4af..c6345f2 100644
--- a/components/ntp_snippets/content_suggestions_metrics.cc
+++ b/components/ntp_snippets/content_suggestions_metrics.cc
@@ -292,6 +292,8 @@
   if (category.IsKnownCategory(KnownCategories::ARTICLES)) {
     RecordContentSuggestionsUsage();
   }
+
+  base::RecordAction(base::UserMetricsAction("Suggestions.Content.Opened"));
 }
 
 void OnSuggestionMenuOpened(int global_position,
@@ -366,5 +368,17 @@
       "NewTabPage.ContentSuggestions.Preferences.RemoteSuggestions", enabled);
 }
 
+void RecordContentSuggestionDismissed() {
+  base::RecordAction(base::UserMetricsAction("Suggestions.Content.Dismissed"));
+}
+
+void RecordCategoryDismissed() {
+  base::RecordAction(base::UserMetricsAction("Suggestions.Category.Dismissed"));
+}
+
+void RecordFetchAction() {
+  base::RecordAction(base::UserMetricsAction("Suggestions.Category.Fetch"));
+}
+
 }  // namespace metrics
 }  // namespace ntp_snippets
diff --git a/components/ntp_snippets/content_suggestions_metrics.h b/components/ntp_snippets/content_suggestions_metrics.h
index a2f04e566..3bfff0ec7 100644
--- a/components/ntp_snippets/content_suggestions_metrics.h
+++ b/components/ntp_snippets/content_suggestions_metrics.h
@@ -60,6 +60,12 @@
 
 void RecordRemoteSuggestionsProviderState(bool enabled);
 
+void RecordContentSuggestionDismissed();
+
+void RecordCategoryDismissed();
+
+void RecordFetchAction();
+
 }  // namespace metrics
 }  // namespace ntp_snippets
 
diff --git a/components/ntp_snippets/content_suggestions_service.cc b/components/ntp_snippets/content_suggestions_service.cc
index c31a72e..565f420 100644
--- a/components/ntp_snippets/content_suggestions_service.cc
+++ b/components/ntp_snippets/content_suggestions_service.cc
@@ -20,6 +20,7 @@
 #include "components/favicon/core/large_icon_service.h"
 #include "components/favicon_base/fallback_icon_style.h"
 #include "components/favicon_base/favicon_types.h"
+#include "components/ntp_snippets/content_suggestions_metrics.h"
 #include "components/ntp_snippets/pref_names.h"
 #include "components/ntp_snippets/remote/remote_suggestions_provider.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -324,6 +325,9 @@
                  << " for unavailable category " << suggestion_id.category();
     return;
   }
+
+  metrics::RecordContentSuggestionDismissed();
+
   providers_by_category_[suggestion_id.category()]->DismissSuggestion(
       suggestion_id);
 
@@ -339,6 +343,8 @@
     return;
   }
 
+  metrics::RecordCategoryDismissed();
+
   ContentSuggestionsProvider* provider = providers_it->second;
   UnregisterCategory(category, provider);
 
@@ -382,6 +388,8 @@
     return;
   }
 
+  metrics::RecordFetchAction();
+
   providers_it->second->Fetch(category, known_suggestion_ids, callback);
 }
 
diff --git a/components/ntp_tiles/most_visited_sites.cc b/components/ntp_tiles/most_visited_sites.cc
index 6809aa4..b09aa4c8 100644
--- a/components/ntp_tiles/most_visited_sites.cc
+++ b/components/ntp_tiles/most_visited_sites.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/feature_list.h"
+#include "base/metrics/user_metrics.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/history/core/browser/top_sites.h"
 #include "components/ntp_tiles/constants.h"
@@ -134,6 +135,13 @@
 
 void MostVisitedSites::AddOrRemoveBlacklistedUrl(const GURL& url,
                                                  bool add_url) {
+  if (add_url) {
+    base::RecordAction(base::UserMetricsAction("Suggestions.Site.Removed"));
+  } else {
+    base::RecordAction(
+        base::UserMetricsAction("Suggestions.Site.RemovalUndone"));
+  }
+
   if (top_sites_) {
     // Always blacklist in the local TopSites.
     if (add_url)
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index a74987dd..7b6216a 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -4,6 +4,8 @@
 
 static_library("content") {
   sources = [
+    "can_make_payment_query_factory.cc",
+    "can_make_payment_query_factory.h",
     "payment_request.cc",
     "payment_request.h",
     "payment_request_dialog.h",
@@ -20,6 +22,7 @@
   deps = [
     ":utils",
     "//components/autofill/core/browser",
+    "//components/keyed_service/content",
     "//components/payments/core",
     "//components/payments/mojom",
     "//components/strings:components_strings_grit",
diff --git a/components/payments/content/DEPS b/components/payments/content/DEPS
index 548a164..2cf753e70 100644
--- a/components/payments/content/DEPS
+++ b/components/payments/content/DEPS
@@ -3,6 +3,7 @@
   "-components/payments/content/utility",
   "+components/autofill",
   "+components/data_use_measurement",
+  "+components/keyed_service/content",
   "+components/link_header_util",
   "+components/strings",
   "+content/public",
diff --git a/components/payments/content/can_make_payment_query_factory.cc b/components/payments/content/can_make_payment_query_factory.cc
new file mode 100644
index 0000000..2f881730
--- /dev/null
+++ b/components/payments/content/can_make_payment_query_factory.cc
@@ -0,0 +1,36 @@
+// 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/payments/content/can_make_payment_query_factory.h"
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/payments/core/can_make_payment_query.h"
+
+namespace payments {
+
+// static
+CanMakePaymentQueryFactory* CanMakePaymentQueryFactory::GetInstance() {
+  return base::Singleton<CanMakePaymentQueryFactory>::get();
+}
+
+CanMakePaymentQuery* CanMakePaymentQueryFactory::GetForContext(
+    content::BrowserContext* context) {
+  return static_cast<CanMakePaymentQuery*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+CanMakePaymentQueryFactory::CanMakePaymentQueryFactory()
+    : BrowserContextKeyedServiceFactory(
+          "CanMakePaymentQuery",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+CanMakePaymentQueryFactory::~CanMakePaymentQueryFactory() {}
+
+KeyedService* CanMakePaymentQueryFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new CanMakePaymentQuery;
+}
+
+}  // namespace payments
diff --git a/components/payments/content/can_make_payment_query_factory.h b/components/payments/content/can_make_payment_query_factory.h
new file mode 100644
index 0000000..3d67e03
--- /dev/null
+++ b/components/payments/content/can_make_payment_query_factory.h
@@ -0,0 +1,46 @@
+// 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_PAYMENTS_CONTENT_CAN_MAKE_PAYMENT_QUERY_FACTORY_H_
+#define COMPONENTS_PAYMENTS_CONTENT_CAN_MAKE_PAYMENT_QUERY_FACTORY_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace payments {
+
+class CanMakePaymentQuery;
+
+// Ensures that there's only one instance of CanMakePaymentQuery per browser
+// context.
+class CanMakePaymentQueryFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static CanMakePaymentQueryFactory* GetInstance();
+  CanMakePaymentQuery* GetForContext(content::BrowserContext* context);
+
+ private:
+  friend struct base::DefaultSingletonTraits<CanMakePaymentQueryFactory>;
+
+  CanMakePaymentQueryFactory();
+  ~CanMakePaymentQueryFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(CanMakePaymentQueryFactory);
+};
+
+}  // namespace payments
+
+#endif  // COMPONENTS_PAYMENTS_CONTENT_CAN_MAKE_PAYMENT_QUERY_FACTORY_H_
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index 138b6d1..69a13a702 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -8,15 +8,19 @@
 #include <utility>
 
 #include "base/memory/ptr_util.h"
+#include "components/payments/content/can_make_payment_query_factory.h"
 #include "components/payments/content/origin_security_checker.h"
 #include "components/payments/content/payment_details_validation.h"
 #include "components/payments/content/payment_request_web_contents_manager.h"
+#include "components/payments/core/can_make_payment_query.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 
 namespace payments {
 
 PaymentRequest::PaymentRequest(
+    content::RenderFrameHost* render_frame_host,
     content::WebContents* web_contents,
     std::unique_ptr<PaymentRequestDelegate> delegate,
     PaymentRequestWebContentsManager* manager,
@@ -26,6 +30,7 @@
       delegate_(std::move(delegate)),
       manager_(manager),
       binding_(this, std::move(request)),
+      frame_origin_(GURL(render_frame_host->GetLastCommittedURL()).GetOrigin()),
       observer_for_testing_(observer_for_testing),
       journey_logger_(delegate_->IsIncognito(),
                       web_contents_->GetLastCommittedURL(),
@@ -167,14 +172,30 @@
 }
 
 void PaymentRequest::CanMakePayment() {
-  // TODO(crbug.com/704676): Implement a quota policy for this method.
-  // PaymentRequest.canMakePayments() never returns false in incognito mode.
-  client_->OnCanMakePayment(
-      delegate_->IsIncognito() || state()->CanMakePayment()
-          ? mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT
-          : mojom::CanMakePaymentQueryResult::CANNOT_MAKE_PAYMENT);
-  journey_logger_.SetCanMakePaymentValue(delegate_->IsIncognito() ||
-                                         state()->CanMakePayment());
+  bool can_make_payment = state()->CanMakePayment();
+  if (delegate_->IsIncognito()) {
+    client_->OnCanMakePayment(
+        mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT);
+    journey_logger_.SetCanMakePaymentValue(true);
+  } else if (CanMakePaymentQueryFactory::GetInstance()
+                 ->GetForContext(web_contents_->GetBrowserContext())
+                 ->CanQuery(frame_origin_, spec()->stringified_method_data())) {
+    client_->OnCanMakePayment(
+        can_make_payment
+            ? mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT
+            : mojom::CanMakePaymentQueryResult::CANNOT_MAKE_PAYMENT);
+    journey_logger_.SetCanMakePaymentValue(can_make_payment);
+  } else if (OriginSecurityChecker::IsOriginLocalhostOrFile(frame_origin_)) {
+    client_->OnCanMakePayment(
+        can_make_payment
+            ? mojom::CanMakePaymentQueryResult::WARNING_CAN_MAKE_PAYMENT
+            : mojom::CanMakePaymentQueryResult::WARNING_CANNOT_MAKE_PAYMENT);
+    journey_logger_.SetCanMakePaymentValue(can_make_payment);
+  } else {
+    client_->OnCanMakePayment(
+        mojom::CanMakePaymentQueryResult::QUERY_QUOTA_EXCEEDED);
+  }
+
   if (observer_for_testing_)
     observer_for_testing_->OnCanMakePaymentCalled();
 }
diff --git a/components/payments/content/payment_request.h b/components/payments/content/payment_request.h
index 2514a8b..4613add 100644
--- a/components/payments/content/payment_request.h
+++ b/components/payments/content/payment_request.h
@@ -16,8 +16,10 @@
 #include "components/payments/mojom/payment_request.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
+#include "url/gurl.h"
 
 namespace content {
+class RenderFrameHost;
 class WebContents;
 }
 
@@ -45,7 +47,8 @@
     virtual ~ObserverForTest() {}
   };
 
-  PaymentRequest(content::WebContents* web_contents,
+  PaymentRequest(content::RenderFrameHost* render_frame_host,
+                 content::WebContents* web_contents,
                  std::unique_ptr<PaymentRequestDelegate> delegate,
                  PaymentRequestWebContentsManager* manager,
                  mojo::InterfaceRequest<mojom::PaymentRequest> request,
@@ -101,6 +104,10 @@
   std::unique_ptr<PaymentRequestSpec> spec_;
   std::unique_ptr<PaymentRequestState> state_;
 
+  // The RFC 6454 origin of the frame that has invoked PaymentRequest API. This
+  // can be either the main frame or an iframe.
+  const GURL frame_origin_;
+
   // May be null, must outlive this object.
   ObserverForTest* observer_for_testing_;
 
diff --git a/components/payments/content/payment_request_spec.cc b/components/payments/content/payment_request_spec.cc
index a5addb5c..2f7a7e4 100644
--- a/components/payments/content/payment_request_spec.cc
+++ b/components/payments/content/payment_request_spec.cc
@@ -143,6 +143,11 @@
   method_data_vector.reserve(method_data_mojom.size());
   for (const mojom::PaymentMethodDataPtr& method_data_entry :
        method_data_mojom) {
+    for (const std::string& method : method_data_entry->supported_methods) {
+      stringified_method_data_[method].insert(
+          method_data_entry->stringified_data);
+    }
+
     PaymentMethodData method_data;
     method_data.supported_methods = method_data_entry->supported_methods;
     // Transfer the supported basic card networks.
diff --git a/components/payments/content/payment_request_spec.h b/components/payments/content/payment_request_spec.h
index dba1e03..98e7d67 100644
--- a/components/payments/content/payment_request_spec.h
+++ b/components/payments/content/payment_request_spec.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_REQUEST_SPEC_H_
 #define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_REQUEST_SPEC_H_
 
+#include <map>
 #include <set>
 #include <string>
 #include <vector>
@@ -77,6 +78,10 @@
   const std::set<std::string>& supported_card_networks_set() const {
     return supported_card_networks_set_;
   }
+  const std::map<std::string, std::set<std::string>>& stringified_method_data()
+      const {
+    return stringified_method_data_;
+  }
   // Returns whether the |method_name| was specified as supported through the
   // "basic-card" payment method. If false, it means either the |method_name| is
   // not supported at all, or specified directly in supportedMethods.
@@ -152,6 +157,10 @@
   // |supported_card_networks_set_| to check merchant support.
   std::set<std::string> basic_card_specified_networks_;
 
+  // A mapping of the payment method names to the corresponding JSON-stringified
+  // payment method specific data.
+  std::map<std::string, std::set<std::string>> stringified_method_data_;
+
   // The |observer_for_testing_| will fire after all the |observers_| have been
   // notified.
   base::ObserverList<Observer> observers_;
diff --git a/components/payments/content/payment_request_web_contents_manager.cc b/components/payments/content/payment_request_web_contents_manager.cc
index 1c15003..1d186e1 100644
--- a/components/payments/content/payment_request_web_contents_manager.cc
+++ b/components/payments/content/payment_request_web_contents_manager.cc
@@ -28,13 +28,14 @@
 }
 
 void PaymentRequestWebContentsManager::CreatePaymentRequest(
+    content::RenderFrameHost* render_frame_host,
     content::WebContents* web_contents,
     std::unique_ptr<PaymentRequestDelegate> delegate,
     mojo::InterfaceRequest<payments::mojom::PaymentRequest> request,
     PaymentRequest::ObserverForTest* observer_for_testing) {
   auto new_request = base::MakeUnique<PaymentRequest>(
-      web_contents, std::move(delegate), this, std::move(request),
-      observer_for_testing);
+      render_frame_host, web_contents, std::move(delegate), this,
+      std::move(request), observer_for_testing);
   PaymentRequest* request_ptr = new_request.get();
   payment_requests_.insert(std::make_pair(request_ptr, std::move(new_request)));
 }
diff --git a/components/payments/content/payment_request_web_contents_manager.h b/components/payments/content/payment_request_web_contents_manager.h
index 1c000ec3..1d4a538 100644
--- a/components/payments/content/payment_request_web_contents_manager.h
+++ b/components/payments/content/payment_request_web_contents_manager.h
@@ -15,6 +15,7 @@
 #include "mojo/public/cpp/bindings/binding.h"
 
 namespace content {
+class RenderFrameHost;
 class WebContents;
 }
 
@@ -40,8 +41,10 @@
   static PaymentRequestWebContentsManager* GetOrCreateForWebContents(
       content::WebContents* web_contents);
 
-  // Creates the PaymentRequest that will interact with this |web_contents|.
+  // Creates the PaymentRequest that will interact with this |render_frame_host|
+  // and the associated |web_contents|.
   void CreatePaymentRequest(
+      content::RenderFrameHost* render_frame_host,
       content::WebContents* web_contents,
       std::unique_ptr<PaymentRequestDelegate> delegate,
       mojo::InterfaceRequest<payments::mojom::PaymentRequest> request,
diff --git a/components/payments/core/BUILD.gn b/components/payments/core/BUILD.gn
index 8d94990..5094bd22 100644
--- a/components/payments/core/BUILD.gn
+++ b/components/payments/core/BUILD.gn
@@ -11,6 +11,8 @@
     "autofill_payment_instrument.h",
     "basic_card_response.cc",
     "basic_card_response.h",
+    "can_make_payment_query.cc",
+    "can_make_payment_query.h",
     "currency_formatter.cc",
     "currency_formatter.h",
     "journey_logger.cc",
@@ -34,6 +36,7 @@
   deps = [
     "//base",
     "//components/autofill/core/browser",
+    "//components/keyed_service/core",
     "//components/strings:components_strings_grit",
     "//components/ukm",
     "//third_party/libphonenumber",
diff --git a/components/payments/core/DEPS b/components/payments/core/DEPS
index 9347542..9117b76 100644
--- a/components/payments/core/DEPS
+++ b/components/payments/core/DEPS
@@ -2,6 +2,7 @@
   "-components/payments/content",
   "-content",
   "+components/autofill/core",
+  "+components/keyed_service/core",
   "+components/metrics",
   "+components/strings",
   "+components/ukm",
diff --git a/components/payments/core/can_make_payment_query.cc b/components/payments/core/can_make_payment_query.cc
new file mode 100644
index 0000000..c4264be8
--- /dev/null
+++ b/components/payments/core/can_make_payment_query.cc
@@ -0,0 +1,44 @@
+// 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/payments/core/can_make_payment_query.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/time/time.h"
+
+namespace payments {
+
+CanMakePaymentQuery::CanMakePaymentQuery() {}
+
+CanMakePaymentQuery::~CanMakePaymentQuery() {}
+
+bool CanMakePaymentQuery::CanQuery(
+    const GURL& frame_origin,
+    const std::map<std::string, std::set<std::string>>& query) {
+  const auto& it = queries_.find(frame_origin);
+  if (it == queries_.end()) {
+    std::unique_ptr<base::OneShotTimer> timer =
+        base::MakeUnique<base::OneShotTimer>();
+    timer->Start(FROM_HERE, base::TimeDelta::FromMinutes(30),
+                 base::Bind(&CanMakePaymentQuery::ExpireQuotaForFrameOrigin,
+                            base::Unretained(this), frame_origin));
+    timers_.insert(std::make_pair(frame_origin, std::move(timer)));
+    queries_.insert(std::make_pair(frame_origin, query));
+    return true;
+  }
+
+  return it->second == query;
+}
+
+void CanMakePaymentQuery::ExpireQuotaForFrameOrigin(const GURL& frame_origin) {
+  timers_.erase(frame_origin);
+  queries_.erase(frame_origin);
+}
+
+}  // namespace payments
diff --git a/components/payments/core/can_make_payment_query.h b/components/payments/core/can_make_payment_query.h
new file mode 100644
index 0000000..69da576
--- /dev/null
+++ b/components/payments/core/can_make_payment_query.h
@@ -0,0 +1,50 @@
+// 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_PAYMENTS_CORE_CAN_MAKE_PAYMENT_QUERY_H_
+#define COMPONENTS_PAYMENTS_CORE_CAN_MAKE_PAYMENT_QUERY_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/timer/timer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
+
+namespace payments {
+
+// Keeps track of canMakePayment() queries per browser context.
+class CanMakePaymentQuery : public KeyedService {
+ public:
+  CanMakePaymentQuery();
+  ~CanMakePaymentQuery() override;
+
+  // Returns whether |frame_origin| can call canMakePayment() with |query|,
+  // which is a mapping of payment method names to the corresponding
+  // JSON-stringified payment method data. Remembers the frame-to-query mapping
+  // for 30 minutes to enforce the quota.
+  bool CanQuery(const GURL& frame_origin,
+                const std::map<std::string, std::set<std::string>>& query);
+
+ private:
+  void ExpireQuotaForFrameOrigin(const GURL& frame_origin);
+
+  // A mapping of frame origin to the timer that, when fired, allows the frame
+  // to invoke canMakePayment() with a different query.
+  std::map<GURL, std::unique_ptr<base::OneShotTimer>> timers_;
+
+  // A mapping of frame origin to its last query. Each query is a mapping of
+  // payment method names to the corresponding JSON-stringified payment method
+  // data.
+  std::map<GURL, std::map<std::string, std::set<std::string>>> queries_;
+
+  DISALLOW_COPY_AND_ASSIGN(CanMakePaymentQuery);
+};
+
+}  // namespace payments
+
+#endif  // COMPONENTS_PAYMENTS_CORE_CAN_MAKE_PAYMENT_QUERY_H_
diff --git a/components/sync/DEPS b/components/sync/DEPS
index 25bc59b..5ec8a7b 100644
--- a/components/sync/DEPS
+++ b/components/sync/DEPS
@@ -6,6 +6,7 @@
   # to test.
   "+base",
   "+build",
+  "+components/version_info",
   "+google_apis",
   "+testing",
 
diff --git a/components/sync/base/DEPS b/components/sync/base/DEPS
index 67e7d838..e17cf5f 100644
--- a/components/sync/base/DEPS
+++ b/components/sync/base/DEPS
@@ -6,7 +6,6 @@
   "+components/prefs",
   "+components/sync/protocol",
   "+components/sync_preferences",
-  "+components/version_info",
   "+crypto",
   "+google/cacheinvalidation",
   "+third_party/zlib",  # For UniquePosition compression
diff --git a/components/sync/device_info/DEPS b/components/sync/device_info/DEPS
index d0e8a8e..6cdc821 100644
--- a/components/sync/device_info/DEPS
+++ b/components/sync/device_info/DEPS
@@ -5,5 +5,4 @@
   "+components/sync/engine",
   "+components/sync/model",
   "+components/sync/protocol",
-  "+components/version_info",
 ]
diff --git a/components/sync/driver/DEPS b/components/sync/driver/DEPS
index ad82970..29e27de 100644
--- a/components/sync/driver/DEPS
+++ b/components/sync/driver/DEPS
@@ -15,9 +15,7 @@
   "+components/sync/syncable",
   "+components/sync/test",
   "+components/sync_preferences",
-  "+components/version_info",
   "+google/cacheinvalidation",
-  "+google_apis",
   "+net",
   "+policy",
 ]
diff --git a/components/sync/model/recording_model_type_change_processor.cc b/components/sync/model/recording_model_type_change_processor.cc
index e74e44f4..a20e299 100644
--- a/components/sync/model/recording_model_type_change_processor.cc
+++ b/components/sync/model/recording_model_type_change_processor.cc
@@ -16,10 +16,13 @@
 
 std::unique_ptr<ModelTypeChangeProcessor> CreateAndAssignProcessor(
     RecordingModelTypeChangeProcessor** processor_address,
+    bool expect_error,
     ModelType type,
     ModelTypeSyncBridge* bridge) {
   auto processor = base::MakeUnique<RecordingModelTypeChangeProcessor>();
   *processor_address = processor.get();
+  if (expect_error)
+    processor->ExpectError();
   // Not all compilers are smart enough to up cast during copy elision, so we
   // explicitly create a correctly typed unique_ptr.
   return base::WrapUnique(processor.release());
@@ -61,9 +64,10 @@
 // static
 ModelTypeSyncBridge::ChangeProcessorFactory
 RecordingModelTypeChangeProcessor::FactoryForBridgeTest(
-    RecordingModelTypeChangeProcessor** processor_address) {
+    RecordingModelTypeChangeProcessor** processor_address,
+    bool expect_error) {
   return base::Bind(&CreateAndAssignProcessor,
-                    base::Unretained(processor_address));
+                    base::Unretained(processor_address), expect_error);
 }
 
 }  //  namespace syncer
diff --git a/components/sync/model/recording_model_type_change_processor.h b/components/sync/model/recording_model_type_change_processor.h
index 59f87e9..861ec23 100644
--- a/components/sync/model/recording_model_type_change_processor.h
+++ b/components/sync/model/recording_model_type_change_processor.h
@@ -48,7 +48,8 @@
   // want to verify the RecordingModelTypeChangeProcessor was given data by the
   // bridge they are testing.
   static ModelTypeSyncBridge::ChangeProcessorFactory FactoryForBridgeTest(
-      RecordingModelTypeChangeProcessor** processor_address);
+      RecordingModelTypeChangeProcessor** processor_address,
+      bool expect_error = false);
 
  private:
   std::multimap<std::string, std::unique_ptr<EntityData>> put_multimap_;
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 333afca9..172ece03 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -136,7 +136,6 @@
     "//third_party/WebKit/public:features",
     "//third_party/WebKit/public:image_resources",
     "//third_party/WebKit/public:mojo_bindings",
-    "//third_party/WebKit/public:offscreen_canvas_mojo_bindings",
     "//third_party/WebKit/public:resources",
     "//third_party/angle:angle_common",
     "//third_party/icu",
@@ -173,6 +172,7 @@
   public_deps = [
     "//ipc",
     "//media/mojo/interfaces:remoting",
+    "//third_party/WebKit/public:offscreen_canvas_mojo_bindings",
     "//third_party/leveldatabase",
   ]
 
@@ -1236,8 +1236,6 @@
     "renderer_host/media/video_capture_provider.h",
     "renderer_host/native_web_keyboard_event_aura.cc",
     "renderer_host/native_web_keyboard_event_mac.mm",
-    "renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc",
-    "renderer_host/offscreen_canvas_compositor_frame_sink_manager.h",
     "renderer_host/offscreen_canvas_provider_impl.cc",
     "renderer_host/offscreen_canvas_provider_impl.h",
     "renderer_host/offscreen_canvas_surface_impl.cc",
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 42f550f0..2f7909aa9 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -2794,12 +2794,12 @@
     return true;
 
   // Standard URLs must match the reported origin.
-  if (url.IsStandard() && !origin.IsSameOriginWith(url::Origin(url)))
+  if (url.IsStandard() && !origin.IsSamePhysicalOriginWith(url::Origin(url)))
     return false;
 
   // A non-unique origin must be a valid URL, which allows us to safely do a
   // conversion to GURL.
-  GURL origin_url(origin.Serialize());
+  GURL origin_url = origin.GetPhysicalOrigin().GetURL();
 
   // Verify that the origin is allowed to commit in this process.
   // Note: This also handles non-standard cases for |url|, such as
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
deleted file mode 100644
index 4a87ca3..0000000
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
-
-#include "base/lazy_instance.h"
-#include "cc/surfaces/surface_manager.h"
-#include "content/browser/compositor/surface_utils.h"
-
-namespace content {
-
-namespace {
-base::LazyInstance<OffscreenCanvasCompositorFrameSinkManager>::Leaky g_manager =
-    LAZY_INSTANCE_INITIALIZER;
-}
-
-OffscreenCanvasCompositorFrameSinkManager::
-    OffscreenCanvasCompositorFrameSinkManager() {
-  GetSurfaceManager()->AddObserver(this);
-}
-
-OffscreenCanvasCompositorFrameSinkManager::
-    ~OffscreenCanvasCompositorFrameSinkManager() {
-  registered_surface_instances_.clear();
-  GetSurfaceManager()->RemoveObserver(this);
-}
-
-OffscreenCanvasCompositorFrameSinkManager*
-OffscreenCanvasCompositorFrameSinkManager::GetInstance() {
-  return g_manager.Pointer();
-}
-
-void OffscreenCanvasCompositorFrameSinkManager::OnSurfaceCreated(
-    const cc::SurfaceInfo& surface_info) {
-  auto surface_iter =
-      registered_surface_instances_.find(surface_info.id().frame_sink_id());
-  if (surface_iter == registered_surface_instances_.end())
-    return;
-  OffscreenCanvasSurfaceImpl* surface_impl = surface_iter->second;
-  surface_impl->OnSurfaceCreated(surface_info);
-}
-
-void OffscreenCanvasCompositorFrameSinkManager::
-    RegisterOffscreenCanvasSurfaceInstance(
-        const cc::FrameSinkId& frame_sink_id,
-        OffscreenCanvasSurfaceImpl* surface_instance) {
-  DCHECK(surface_instance);
-  DCHECK_EQ(registered_surface_instances_.count(frame_sink_id), 0u);
-  registered_surface_instances_[frame_sink_id] = surface_instance;
-}
-
-void OffscreenCanvasCompositorFrameSinkManager::
-    UnregisterOffscreenCanvasSurfaceInstance(
-        const cc::FrameSinkId& frame_sink_id) {
-  DCHECK_EQ(registered_surface_instances_.count(frame_sink_id), 1u);
-  registered_surface_instances_.erase(frame_sink_id);
-}
-
-OffscreenCanvasSurfaceImpl*
-OffscreenCanvasCompositorFrameSinkManager::GetSurfaceInstance(
-    const cc::FrameSinkId& frame_sink_id) {
-  auto search = registered_surface_instances_.find(frame_sink_id);
-  if (search != registered_surface_instances_.end()) {
-    return search->second;
-  }
-  return nullptr;
-}
-
-}  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h
deleted file mode 100644
index 2d3a2599..0000000
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_MANAGER_H_
-#define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_MANAGER_H_
-
-#include "base/memory/weak_ptr.h"
-#include "cc/surfaces/surface_id.h"
-#include "cc/surfaces/surface_observer.h"
-#include "content/browser/renderer_host/offscreen_canvas_surface_impl.h"
-
-namespace content {
-
-class CONTENT_EXPORT OffscreenCanvasCompositorFrameSinkManager
-    : public cc::SurfaceObserver {
- public:
-  OffscreenCanvasCompositorFrameSinkManager();
-  virtual ~OffscreenCanvasCompositorFrameSinkManager();
-
-  static OffscreenCanvasCompositorFrameSinkManager* GetInstance();
-
-  void RegisterOffscreenCanvasSurfaceInstance(
-      const cc::FrameSinkId& frame_sink_id,
-      OffscreenCanvasSurfaceImpl* offscreen_canvas_surface);
-  void UnregisterOffscreenCanvasSurfaceInstance(
-      const cc::FrameSinkId& frame_sink_id);
-  OffscreenCanvasSurfaceImpl* GetSurfaceInstance(
-      const cc::FrameSinkId& frame_sink_id);
-
- private:
-  friend class OffscreenCanvasCompositorFrameSinkManagerTest;
-
-  // cc::SurfaceObserver implementation.
-  void OnSurfaceCreated(const cc::SurfaceInfo& surface_info) override;
-  void OnSurfaceDamaged(const cc::SurfaceId&, bool* changed) override {}
-
-  // When an OffscreenCanvasSurfaceImpl instance is destructed, it will
-  // unregister the corresponding entry from this map.
-  std::unordered_map<cc::FrameSinkId,
-                     OffscreenCanvasSurfaceImpl*,
-                     cc::FrameSinkIdHash>
-      registered_surface_instances_;
-  DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasCompositorFrameSinkManager);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_MANAGER_H_
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc
deleted file mode 100644
index 8733de6c..0000000
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 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.
-
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
-
-#include "base/message_loop/message_loop.h"
-#include "cc/surfaces/local_surface_id_allocator.h"
-#include "content/browser/compositor/test/no_transport_image_transport_factory.h"
-#include "content/browser/renderer_host/offscreen_canvas_surface_impl.h"
-#include "content/public/test/test_browser_thread.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
-
-#if defined(OS_ANDROID)
-#include "base/memory/ptr_util.h"
-#else
-#include "content/browser/compositor/image_transport_factory.h"
-#endif
-
-namespace content {
-
-class OffscreenCanvasCompositorFrameSinkManagerTest : public testing::Test {
- public:
-  int getNumSurfaceImplInstances() {
-    return OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-        ->registered_surface_instances_.size();
-  }
-
-  void OnSurfaceCreated(const cc::SurfaceId& surface_id) {
-    OffscreenCanvasCompositorFrameSinkManager::GetInstance()->OnSurfaceCreated(
-        cc::SurfaceInfo(surface_id, 1.0f, gfx::Size(10, 10)));
-  }
-
- protected:
-  void SetUp() override;
-  void TearDown() override;
-
- private:
-  std::unique_ptr<TestBrowserThread> ui_thread_;
-  base::MessageLoopForUI message_loop_;
-};
-
-void OffscreenCanvasCompositorFrameSinkManagerTest::SetUp() {
-#if !defined(OS_ANDROID)
-  ImageTransportFactory::InitializeForUnitTests(
-      std::unique_ptr<ImageTransportFactory>(
-          new NoTransportImageTransportFactory));
-#endif
-  ui_thread_.reset(new TestBrowserThread(BrowserThread::UI, &message_loop_));
-}
-
-void OffscreenCanvasCompositorFrameSinkManagerTest::TearDown() {
-#if !defined(OS_ANDROID)
-  ImageTransportFactory::Terminate();
-#endif
-}
-
-// This test mimics the workflow of OffscreenCanvas.commit() on renderer
-// process.
-TEST_F(OffscreenCanvasCompositorFrameSinkManagerTest,
-       SingleHTMLCanvasElementTransferToOffscreen) {
-  blink::mojom::OffscreenCanvasSurfaceClientPtr client;
-  cc::FrameSinkId frame_sink_id(3, 3);
-  cc::LocalSurfaceIdAllocator local_surface_id_allocator;
-  cc::LocalSurfaceId current_local_surface_id(
-      local_surface_id_allocator.GenerateId());
-
-  auto surface_impl = base::WrapUnique(new OffscreenCanvasSurfaceImpl(
-      cc::FrameSinkId(), frame_sink_id, std::move(client)));
-  EXPECT_EQ(1, this->getNumSurfaceImplInstances());
-  EXPECT_EQ(surface_impl.get(),
-            OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-                ->GetSurfaceInstance(frame_sink_id));
-
-  this->OnSurfaceCreated(
-      cc::SurfaceId(frame_sink_id, current_local_surface_id));
-  EXPECT_EQ(current_local_surface_id, surface_impl->current_local_surface_id());
-
-  surface_impl = nullptr;
-  EXPECT_EQ(0, this->getNumSurfaceImplInstances());
-}
-
-TEST_F(OffscreenCanvasCompositorFrameSinkManagerTest,
-       MultiHTMLCanvasElementTransferToOffscreen) {
-  blink::mojom::OffscreenCanvasSurfaceClientPtr client_a;
-  cc::FrameSinkId dummy_parent_frame_sink_id(0, 0);
-  cc::FrameSinkId frame_sink_id_a(3, 3);
-  auto surface_impl_a = base::WrapUnique(new OffscreenCanvasSurfaceImpl(
-      dummy_parent_frame_sink_id, frame_sink_id_a, std::move(client_a)));
-
-  blink::mojom::OffscreenCanvasSurfaceClientPtr client_b;
-  cc::FrameSinkId frame_sink_id_b(4, 4);
-
-  auto surface_impl_b = base::WrapUnique(new OffscreenCanvasSurfaceImpl(
-      dummy_parent_frame_sink_id, frame_sink_id_b, std::move(client_b)));
-
-  EXPECT_EQ(2, this->getNumSurfaceImplInstances());
-  EXPECT_EQ(surface_impl_a.get(),
-            OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-                ->GetSurfaceInstance(frame_sink_id_a));
-  EXPECT_EQ(surface_impl_b.get(),
-            OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-                ->GetSurfaceInstance(frame_sink_id_b));
-
-  surface_impl_a = nullptr;
-  EXPECT_EQ(1, this->getNumSurfaceImplInstances());
-  surface_impl_b = nullptr;
-  EXPECT_EQ(0, this->getNumSurfaceImplInstances());
-}
-
-}  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_provider_impl.cc b/content/browser/renderer_host/offscreen_canvas_provider_impl.cc
index 7e9567a..c16e1aa 100644
--- a/content/browser/renderer_host/offscreen_canvas_provider_impl.cc
+++ b/content/browser/renderer_host/offscreen_canvas_provider_impl.cc
@@ -4,8 +4,7 @@
 
 #include "content/browser/renderer_host/offscreen_canvas_provider_impl.h"
 
-#include "content/browser/compositor/surface_utils.h"
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
+#include "base/bind.h"
 #include "content/browser/renderer_host/offscreen_canvas_surface_impl.h"
 
 namespace content {
@@ -36,8 +35,13 @@
     return;
   }
 
-  OffscreenCanvasSurfaceImpl::Create(parent_frame_sink_id, frame_sink_id,
-                                     std::move(client), std::move(request));
+  auto destroy_callback = base::BindOnce(
+      &OffscreenCanvasProviderImpl::DestroyOffscreenCanvasSurface,
+      base::Unretained(this), frame_sink_id);
+
+  canvas_map_[frame_sink_id] = base::MakeUnique<OffscreenCanvasSurfaceImpl>(
+      parent_frame_sink_id, frame_sink_id, std::move(client),
+      std::move(request), std::move(destroy_callback));
 }
 
 void OffscreenCanvasProviderImpl::CreateCompositorFrameSink(
@@ -50,16 +54,19 @@
     return;
   }
 
-  // TODO(kylechar): Add test for bad |frame_sink_id|.
-  auto* manager = OffscreenCanvasCompositorFrameSinkManager::GetInstance();
-  auto* surface_impl = manager->GetSurfaceInstance(frame_sink_id);
-  if (!surface_impl) {
+  auto iter = canvas_map_.find(frame_sink_id);
+  if (iter == canvas_map_.end()) {
     DLOG(ERROR) << "No OffscreenCanvasSurfaceImpl for " << frame_sink_id;
     return;
   }
 
-  surface_impl->CreateCompositorFrameSink(std::move(client),
+  iter->second->CreateCompositorFrameSink(std::move(client),
                                           std::move(request));
 }
 
+void OffscreenCanvasProviderImpl::DestroyOffscreenCanvasSurface(
+    cc::FrameSinkId frame_sink_id) {
+  canvas_map_.erase(frame_sink_id);
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_provider_impl.h b/content/browser/renderer_host/offscreen_canvas_provider_impl.h
index 554e121..54f5070 100644
--- a/content/browser/renderer_host/offscreen_canvas_provider_impl.h
+++ b/content/browser/renderer_host/offscreen_canvas_provider_impl.h
@@ -5,14 +5,20 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_PROVIDER_IMPL_H_
 #define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_PROVIDER_IMPL_H_
 
+#include <memory>
+
+#include "base/containers/flat_map.h"
 #include "cc/surfaces/frame_sink_id.h"
+#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
 
 namespace content {
 
+class OffscreenCanvasSurfaceImpl;
+
 // Creates OffscreenCanvasSurfaces and MojoCompositorFrameSinks for a renderer.
-class OffscreenCanvasProviderImpl
+class CONTENT_EXPORT OffscreenCanvasProviderImpl
     : public blink::mojom::OffscreenCanvasProvider {
  public:
   explicit OffscreenCanvasProviderImpl(uint32_t renderer_client_id);
@@ -32,11 +38,20 @@
       cc::mojom::MojoCompositorFrameSinkRequest request) override;
 
  private:
+  friend class OffscreenCanvasProviderImplTest;
+
+  // Destroys the |canvas_map_| entry for |frame_sink_id|. Provided as a
+  // callback to each OffscreenCanvasSurfaceImpl so they can destroy themselves.
+  void DestroyOffscreenCanvasSurface(cc::FrameSinkId frame_sink_id);
+
   // FrameSinkIds for offscreen canvas must use the renderer client id.
   const uint32_t renderer_client_id_;
 
   mojo::BindingSet<blink::mojom::OffscreenCanvasProvider> bindings_;
 
+  base::flat_map<cc::FrameSinkId, std::unique_ptr<OffscreenCanvasSurfaceImpl>>
+      canvas_map_;
+
   DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasProviderImpl);
 };
 
diff --git a/content/browser/renderer_host/offscreen_canvas_provider_impl_unittest.cc b/content/browser/renderer_host/offscreen_canvas_provider_impl_unittest.cc
new file mode 100644
index 0000000..06e1f5a
--- /dev/null
+++ b/content/browser/renderer_host/offscreen_canvas_provider_impl_unittest.cc
@@ -0,0 +1,347 @@
+// 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 "content/browser/renderer_host/offscreen_canvas_provider_impl.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "cc/ipc/mojo_compositor_frame_sink.mojom.h"
+#include "cc/output/compositor_frame.h"
+#include "content/browser/compositor/test/no_transport_image_transport_factory.h"
+#include "content/browser/renderer_host/offscreen_canvas_surface_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
+
+#if !defined(OS_ANDROID)
+#include "content/browser/compositor/image_transport_factory.h"
+#endif
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+
+namespace content {
+namespace {
+
+constexpr uint32_t kRendererClientId = 3;
+constexpr cc::FrameSinkId kFrameSinkParent(kRendererClientId, 1);
+constexpr cc::FrameSinkId kFrameSinkA(kRendererClientId, 3);
+constexpr cc::FrameSinkId kFrameSinkB(kRendererClientId, 4);
+
+// Stub OffscreenCanvasSurfaceClient that stores the latest SurfaceInfo.
+class StubOffscreenCanvasSurfaceClient
+    : public blink::mojom::OffscreenCanvasSurfaceClient {
+ public:
+  StubOffscreenCanvasSurfaceClient() : binding_(this) {}
+  ~StubOffscreenCanvasSurfaceClient() override {}
+
+  blink::mojom::OffscreenCanvasSurfaceClientPtr GetInterfacePtr() {
+    return binding_.CreateInterfacePtrAndBind();
+  }
+
+  const cc::SurfaceInfo& GetLastSurfaceInfo() const {
+    return last_surface_info_;
+  }
+
+ private:
+  // blink::mojom::OffscreenCanvasSurfaceClient:
+  void OnSurfaceCreated(const cc::SurfaceInfo& surface_info) override {
+    last_surface_info_ = surface_info;
+  }
+
+  mojo::Binding<blink::mojom::OffscreenCanvasSurfaceClient> binding_;
+  cc::SurfaceInfo last_surface_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(StubOffscreenCanvasSurfaceClient);
+};
+
+// Stub MojoCompositorFrameSinkClient that does nothing.
+class StubCompositorFrameSinkClient
+    : public cc::mojom::MojoCompositorFrameSinkClient {
+ public:
+  StubCompositorFrameSinkClient() : binding_(this) {}
+  ~StubCompositorFrameSinkClient() override {}
+
+  cc::mojom::MojoCompositorFrameSinkClientPtr GetInterfacePtr() {
+    return binding_.CreateInterfacePtrAndBind();
+  }
+
+ private:
+  // cc::mojom::MojoCompositorFrameSinkClient:
+  void DidReceiveCompositorFrameAck(
+      const cc::ReturnedResourceArray& resources) override {}
+  void OnBeginFrame(const cc::BeginFrameArgs& begin_frame_args) override {}
+  void ReclaimResources(const cc::ReturnedResourceArray& resources) override {}
+
+  mojo::Binding<cc::mojom::MojoCompositorFrameSinkClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(StubCompositorFrameSinkClient);
+};
+
+// Create a CompositorFrame suitable to send over IPC.
+cc::CompositorFrame MakeCompositorFrame() {
+  cc::CompositorFrame frame;
+  frame.metadata.begin_frame_ack.source_id =
+      cc::BeginFrameArgs::kManualSourceId;
+  frame.metadata.begin_frame_ack.sequence_number =
+      cc::BeginFrameArgs::kStartingFrameNumber;
+  frame.metadata.device_scale_factor = 1.0f;
+
+  auto render_pass = cc::RenderPass::Create();
+  render_pass->id = 1;
+  render_pass->output_rect = gfx::Rect(100, 100);
+  frame.render_pass_list.push_back(std::move(render_pass));
+
+  return frame;
+}
+
+// Creates a closure that sets |error_variable| true when run.
+base::Closure ConnectionErrorClosure(bool* error_variable) {
+  return base::Bind([](bool* error_variable) { *error_variable = true; },
+                    error_variable);
+}
+
+}  // namespace
+
+class OffscreenCanvasProviderImplTest : public testing::Test {
+ public:
+  OffscreenCanvasProviderImpl* provider() { return provider_.get(); }
+
+  // Gets the OffscreenCanvasSurfaceImpl for |frame_sink_id| or null if it
+  // it doesn't exist.
+  OffscreenCanvasSurfaceImpl* GetOffscreenCanvasSurface(
+      const cc::FrameSinkId& frame_sink_id) {
+    auto iter = provider_->canvas_map_.find(frame_sink_id);
+    if (iter == provider_->canvas_map_.end())
+      return nullptr;
+    return iter->second.get();
+  }
+
+  // Gets list of FrameSinkId for all offscreen canvases.
+  std::vector<cc::FrameSinkId> GetAllCanvases() {
+    std::vector<cc::FrameSinkId> frame_sink_ids;
+    for (auto& map_entry : provider_->canvas_map_)
+      frame_sink_ids.push_back(map_entry.second->frame_sink_id());
+    std::sort(frame_sink_ids.begin(), frame_sink_ids.end());
+    return frame_sink_ids;
+  }
+
+  void DeleteOffscreenCanvasProviderImpl() { provider_.reset(); }
+
+  void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
+
+ protected:
+  void SetUp() override {
+#if !defined(OS_ANDROID)
+    ImageTransportFactory::InitializeForUnitTests(
+        std::unique_ptr<ImageTransportFactory>(
+            new NoTransportImageTransportFactory));
+    ImageTransportFactory::GetInstance()
+        ->GetFrameSinkManagerHost()
+        ->ConnectToFrameSinkManager();
+#endif
+    provider_ =
+        base::MakeUnique<OffscreenCanvasProviderImpl>(kRendererClientId);
+  }
+  void TearDown() override {
+    provider_.reset();
+#if !defined(OS_ANDROID)
+    ImageTransportFactory::Terminate();
+#endif
+  }
+
+ private:
+  base::MessageLoop message_loop_;
+  std::unique_ptr<OffscreenCanvasProviderImpl> provider_;
+};
+
+// Mimics the workflow of OffscreenCanvas.commit() on renderer process.
+TEST_F(OffscreenCanvasProviderImplTest,
+       SingleHTMLCanvasElementTransferToOffscreen) {
+  // Mimic connection from the renderer main thread to browser.
+  StubOffscreenCanvasSurfaceClient surface_client;
+  blink::mojom::OffscreenCanvasSurfacePtr surface;
+  provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA,
+                                           surface_client.GetInterfacePtr(),
+                                           mojo::MakeRequest(&surface));
+
+  OffscreenCanvasSurfaceImpl* surface_impl =
+      GetOffscreenCanvasSurface(kFrameSinkA);
+
+  // There should be a single OffscreenCanvasSurfaceImpl and it should have the
+  // provided FrameSinkId and parent FrameSinkId.
+  EXPECT_EQ(kFrameSinkA, surface_impl->frame_sink_id());
+  EXPECT_EQ(kFrameSinkParent, surface_impl->parent_frame_sink_id());
+  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA));
+
+  // Mimic connection from the renderer main or worker thread to browser.
+  cc::mojom::MojoCompositorFrameSinkPtr compositor_frame_sink;
+  StubCompositorFrameSinkClient compositor_frame_sink_client;
+  provider()->CreateCompositorFrameSink(
+      kFrameSinkA, compositor_frame_sink_client.GetInterfacePtr(),
+      mojo::MakeRequest(&compositor_frame_sink));
+
+  // Renderer submits a CompositorFrame with |local_id|.
+  const cc::LocalSurfaceId local_id(1, base::UnguessableToken::Create());
+  compositor_frame_sink->SubmitCompositorFrame(local_id, MakeCompositorFrame());
+
+  RunUntilIdle();
+
+  // OffscreenCanvasSurfaceImpl in browser should have LocalSurfaceId that was
+  // submitted with the CompositorFrame.
+  EXPECT_EQ(local_id, surface_impl->local_surface_id());
+
+  // OffscreenCanvasSurfaceClient in the renderer should get the new SurfaceId
+  // including the |local_id|.
+  const auto& surface_info = surface_client.GetLastSurfaceInfo();
+  EXPECT_EQ(kFrameSinkA, surface_info.id().frame_sink_id());
+  EXPECT_EQ(local_id, surface_info.id().local_surface_id());
+}
+
+// Check that renderer closing the mojom::OffscreenCanvasSurface connection
+// destroys the OffscreenCanvasSurfaceImpl in browser.
+TEST_F(OffscreenCanvasProviderImplTest, ClientClosesConnection) {
+  StubOffscreenCanvasSurfaceClient surface_client;
+  blink::mojom::OffscreenCanvasSurfacePtr surface;
+  provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA,
+                                           surface_client.GetInterfacePtr(),
+                                           mojo::MakeRequest(&surface));
+
+  RunUntilIdle();
+
+  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA));
+
+  // Mimic closing the connection from the renderer.
+  surface.reset();
+
+  RunUntilIdle();
+
+  // The renderer closing the connection should destroy the
+  // OffscreenCanvasSurfaceImpl.
+  EXPECT_THAT(GetAllCanvases(), IsEmpty());
+}
+
+// Check that destroying OffscreenCanvasProviderImpl closes connection to
+// renderer.
+TEST_F(OffscreenCanvasProviderImplTest, ProviderClosesConnections) {
+  StubOffscreenCanvasSurfaceClient surface_client;
+  blink::mojom::OffscreenCanvasSurfacePtr surface;
+  provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA,
+                                           surface_client.GetInterfacePtr(),
+                                           mojo::MakeRequest(&surface));
+
+  // Observe connection errors on |surface|.
+  bool connection_error = false;
+  surface.set_connection_error_handler(
+      ConnectionErrorClosure(&connection_error));
+
+  RunUntilIdle();
+
+  // There should be a OffscreenCanvasSurfaceImpl and |surface| should be bound.
+  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA));
+  EXPECT_TRUE(surface.is_bound());
+  EXPECT_FALSE(connection_error);
+
+  // Delete OffscreenCanvasProviderImpl before client disconnects.
+  DeleteOffscreenCanvasProviderImpl();
+
+  RunUntilIdle();
+
+  // This should destroy the OffscreenCanvasSurfaceImpl and close the connection
+  // to |surface| triggering a connection error.
+  EXPECT_TRUE(connection_error);
+}
+
+// Check that connecting MojoCompositorFrameSink without first making a
+// OffscreenCanvasSurface connection fails.
+TEST_F(OffscreenCanvasProviderImplTest, ClientConnectionWrongOrder) {
+  // Mimic connection from the renderer main or worker thread.
+  cc::mojom::MojoCompositorFrameSinkPtr compositor_frame_sink;
+  StubCompositorFrameSinkClient compositor_frame_sink_client;
+  // Try to connect MojoCompositorFrameSink without first making
+  // OffscreenCanvasSurface connection. This should fail.
+  provider()->CreateCompositorFrameSink(
+      kFrameSinkA, compositor_frame_sink_client.GetInterfacePtr(),
+      mojo::MakeRequest(&compositor_frame_sink));
+
+  // Observe connection errors on |compositor_frame_sink|.
+  bool connection_error = false;
+  compositor_frame_sink.set_connection_error_handler(
+      ConnectionErrorClosure(&connection_error));
+
+  RunUntilIdle();
+
+  // The connection for |compositor_frame_sink| will have failed and triggered a
+  // connection error.
+  EXPECT_TRUE(connection_error);
+}
+
+// Check that trying to create an OffscreenCanvasSurfaceImpl with a client id
+// that doesn't match the renderer fails.
+TEST_F(OffscreenCanvasProviderImplTest, InvalidClientId) {
+  const cc::FrameSinkId invalid_frame_sink_id(4, 3);
+  EXPECT_NE(kRendererClientId, invalid_frame_sink_id.client_id());
+
+  StubOffscreenCanvasSurfaceClient surface_client;
+  blink::mojom::OffscreenCanvasSurfacePtr surface;
+  provider()->CreateOffscreenCanvasSurface(
+      kFrameSinkParent, invalid_frame_sink_id, surface_client.GetInterfacePtr(),
+      mojo::MakeRequest(&surface));
+
+  // Observe connection errors on |surface|.
+  bool connection_error = false;
+  surface.set_connection_error_handler(
+      ConnectionErrorClosure(&connection_error));
+
+  RunUntilIdle();
+
+  // No OffscreenCanvasSurfaceImpl should have been created.
+  EXPECT_THAT(GetAllCanvases(), IsEmpty());
+
+  // The connection for |surface| will have failed and triggered a connection
+  // error.
+  EXPECT_TRUE(connection_error);
+}
+
+// Mimic renderer with two offscreen canvases.
+TEST_F(OffscreenCanvasProviderImplTest,
+       MultiHTMLCanvasElementTransferToOffscreen) {
+  StubOffscreenCanvasSurfaceClient surface_client_a;
+  blink::mojom::OffscreenCanvasSurfacePtr surface_a;
+  provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA,
+                                           surface_client_a.GetInterfacePtr(),
+                                           mojo::MakeRequest(&surface_a));
+
+  StubOffscreenCanvasSurfaceClient surface_client_b;
+  blink::mojom::OffscreenCanvasSurfacePtr surface_b;
+  provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkB,
+                                           surface_client_b.GetInterfacePtr(),
+                                           mojo::MakeRequest(&surface_b));
+
+  RunUntilIdle();
+
+  // There should be two OffscreenCanvasSurfaceImpls created.
+  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA, kFrameSinkB));
+
+  // Mimic closing first connection from the renderer.
+  surface_a.reset();
+
+  RunUntilIdle();
+
+  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkB));
+
+  // Mimic closing second connection from the renderer.
+  surface_b.reset();
+
+  RunUntilIdle();
+
+  EXPECT_THAT(GetAllCanvases(), IsEmpty());
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
index 4145493..8058e25f 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
+++ b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
@@ -7,24 +7,28 @@
 #include <memory>
 #include <utility>
 
-#include "base/bind_helpers.h"
 #include "base/memory/ptr_util.h"
 #include "cc/surfaces/surface_manager.h"
 #include "content/browser/compositor/frame_sink_manager_host.h"
 #include "content/browser/compositor/surface_utils.h"
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
 
 namespace content {
 
 OffscreenCanvasSurfaceImpl::OffscreenCanvasSurfaceImpl(
     const cc::FrameSinkId& parent_frame_sink_id,
     const cc::FrameSinkId& frame_sink_id,
-    blink::mojom::OffscreenCanvasSurfaceClientPtr client)
+    blink::mojom::OffscreenCanvasSurfaceClientPtr client,
+    blink::mojom::OffscreenCanvasSurfaceRequest request,
+    DestroyCallback destroy_callback)
     : client_(std::move(client)),
+      binding_(this, std::move(request)),
+      destroy_callback_(std::move(destroy_callback)),
       frame_sink_id_(frame_sink_id),
       parent_frame_sink_id_(parent_frame_sink_id) {
-  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-      ->RegisterOffscreenCanvasSurfaceInstance(frame_sink_id_, this);
+  binding_.set_connection_error_handler(
+      base::Bind(&OffscreenCanvasSurfaceImpl::OnSurfaceConnectionClosed,
+                 base::Unretained(this)));
+  GetFrameSinkManagerHost()->AddObserver(this);
 }
 
 OffscreenCanvasSurfaceImpl::~OffscreenCanvasSurfaceImpl() {
@@ -32,22 +36,7 @@
     GetFrameSinkManagerHost()->UnregisterFrameSinkHierarchy(
         parent_frame_sink_id_, frame_sink_id_);
   }
-  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-      ->UnregisterOffscreenCanvasSurfaceInstance(frame_sink_id_);
-}
-
-// static
-void OffscreenCanvasSurfaceImpl::Create(
-    const cc::FrameSinkId& parent_frame_sink_id,
-    const cc::FrameSinkId& frame_sink_id,
-    blink::mojom::OffscreenCanvasSurfaceClientPtr client,
-    blink::mojom::OffscreenCanvasSurfaceRequest request) {
-  std::unique_ptr<OffscreenCanvasSurfaceImpl> impl =
-      base::MakeUnique<OffscreenCanvasSurfaceImpl>(
-          parent_frame_sink_id, frame_sink_id, std::move(client));
-  OffscreenCanvasSurfaceImpl* surface_service = impl.get();
-  surface_service->binding_ =
-      mojo::MakeStrongBinding(std::move(impl), std::move(request));
+  GetFrameSinkManagerHost()->RemoveObserver(this);
 }
 
 void OffscreenCanvasSurfaceImpl::CreateCompositorFrameSink(
@@ -69,13 +58,12 @@
 
 void OffscreenCanvasSurfaceImpl::OnSurfaceCreated(
     const cc::SurfaceInfo& surface_info) {
-  DCHECK_EQ(surface_info.id().frame_sink_id(), frame_sink_id_);
-  if (!current_local_surface_id_.is_valid() ||
-      surface_info.id().local_surface_id() != current_local_surface_id_) {
-    current_local_surface_id_ = surface_info.id().local_surface_id();
-    if (client_)
-      client_->OnSurfaceCreated(surface_info);
-  }
+  if (surface_info.id().frame_sink_id() != frame_sink_id_)
+    return;
+
+  local_surface_id_ = surface_info.id().local_surface_id();
+  if (client_)
+    client_->OnSurfaceCreated(surface_info);
 }
 
 void OffscreenCanvasSurfaceImpl::Require(const cc::SurfaceId& surface_id,
@@ -87,4 +75,8 @@
   GetSurfaceManager()->SatisfySequence(sequence);
 }
 
+void OffscreenCanvasSurfaceImpl::OnSurfaceConnectionClosed() {
+  std::move(destroy_callback_).Run();
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_impl.h b/content/browser/renderer_host/offscreen_canvas_surface_impl.h
index 14d4b0a..ff566c5 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_impl.h
+++ b/content/browser/renderer_host/offscreen_canvas_surface_impl.h
@@ -5,8 +5,13 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_IMPL_H_
 #define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_IMPL_H_
 
-#include "cc/surfaces/surface_id.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "cc/surfaces/frame_sink_id.h"
+#include "cc/surfaces/surface_info.h"
+#include "content/browser/compositor/frame_sink_observer.h"
+#include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
 
 namespace content {
@@ -14,18 +19,28 @@
 // The browser owned object for an offscreen canvas connection. Holds
 // connections to both the renderer and frame sink manager.
 class CONTENT_EXPORT OffscreenCanvasSurfaceImpl
-    : public blink::mojom::OffscreenCanvasSurface {
+    : public blink::mojom::OffscreenCanvasSurface,
+      public NON_EXPORTED_BASE(FrameSinkObserver) {
  public:
+  using DestroyCallback = base::OnceCallback<void()>;
+
   OffscreenCanvasSurfaceImpl(
       const cc::FrameSinkId& parent_frame_sink_id,
       const cc::FrameSinkId& frame_sink_id,
-      blink::mojom::OffscreenCanvasSurfaceClientPtr client);
+      blink::mojom::OffscreenCanvasSurfaceClientPtr client,
+      blink::mojom::OffscreenCanvasSurfaceRequest request,
+      DestroyCallback destroy_callback);
   ~OffscreenCanvasSurfaceImpl() override;
 
-  static void Create(const cc::FrameSinkId& parent_frame_sink_id,
-                     const cc::FrameSinkId& frame_sink_id,
-                     blink::mojom::OffscreenCanvasSurfaceClientPtr client,
-                     blink::mojom::OffscreenCanvasSurfaceRequest request);
+  const cc::FrameSinkId& frame_sink_id() const { return frame_sink_id_; }
+
+  const cc::FrameSinkId& parent_frame_sink_id() const {
+    return parent_frame_sink_id_;
+  }
+
+  const cc::LocalSurfaceId& local_surface_id() const {
+    return local_surface_id_;
+  }
 
   // Creates a MojoCompositorFrameSink connection to FrameSinkManager for an
   // offscreen canvas client. The corresponding private interface will be owned
@@ -35,35 +50,33 @@
       cc::mojom::MojoCompositorFrameSinkClientPtr client,
       cc::mojom::MojoCompositorFrameSinkRequest request);
 
-  void OnSurfaceCreated(const cc::SurfaceInfo& surface_info);
+  // FrameSinkObserver implementation.
+  void OnSurfaceCreated(const cc::SurfaceInfo& surface_info) override;
 
   // blink::mojom::OffscreenCanvasSurface implementation.
   void Require(const cc::SurfaceId& surface_id,
                const cc::SurfaceSequence& sequence) override;
   void Satisfy(const cc::SurfaceSequence& sequence) override;
 
-  const cc::FrameSinkId& frame_sink_id() const { return frame_sink_id_; }
-
-  const cc::FrameSinkId& parent_frame_sink_id() const {
-    return parent_frame_sink_id_;
-  }
-
-  const cc::LocalSurfaceId& current_local_surface_id() const {
-    return current_local_surface_id_;
-  }
-
  private:
+  // Registered as a callback for when |binding_| is closed. Will call
+  // |destroy_callback_|.
+  void OnSurfaceConnectionClosed();
+
   blink::mojom::OffscreenCanvasSurfaceClientPtr client_;
-  mojo::StrongBindingPtr<blink::mojom::OffscreenCanvasSurface> binding_;
+  mojo::Binding<blink::mojom::OffscreenCanvasSurface> binding_;
 
   // Private connection for the CompositorFrameSink. The CompositorFrameSink
   // will not be destroyed until both private and offscreen canvas client
   // connections are closed.
   cc::mojom::MojoCompositorFrameSinkPrivatePtr compositor_frame_sink_private_;
 
+  // To be called if |binding_| is closed.
+  DestroyCallback destroy_callback_;
+
   // Surface-related state
   const cc::FrameSinkId frame_sink_id_;
-  cc::LocalSurfaceId current_local_surface_id_;
+  cc::LocalSurfaceId local_surface_id_;
   const cc::FrameSinkId parent_frame_sink_id_;
 
   bool has_created_compositor_frame_sink_ = false;
diff --git a/content/child/blink_platform_impl_unittest.cc b/content/child/blink_platform_impl_unittest.cc
index 47b3659..ca84ee2f 100644
--- a/content/child/blink_platform_impl_unittest.cc
+++ b/content/child/blink_platform_impl_unittest.cc
@@ -20,7 +20,7 @@
   url::Origin checked_origin =
       url::Origin::UnsafelyCreateOriginWithoutNormalization(
           origin.Protocol().Utf8(), origin.Host().Utf8(),
-          origin.EffectivePort());
+          origin.EffectivePort(), origin.Suborigin().Utf8());
   url::Origin non_checked_origin =
       url::Origin::CreateFromNormalizedTupleWithSuborigin(
           origin.Protocol().Utf8(), origin.Host().Utf8(),
diff --git a/content/public/common/common_param_traits.cc b/content/public/common/common_param_traits.cc
index 3fcfdda..286e6621 100644
--- a/content/public/common/common_param_traits.cc
+++ b/content/public/common/common_param_traits.cc
@@ -21,6 +21,7 @@
   GetParamSize(s, p.scheme());
   GetParamSize(s, p.host());
   GetParamSize(s, p.port());
+  GetParamSize(s, p.suborigin());
 }
 
 void ParamTraits<url::Origin>::Write(base::Pickle* m, const url::Origin& p) {
@@ -28,6 +29,7 @@
   WriteParam(m, p.scheme());
   WriteParam(m, p.host());
   WriteParam(m, p.port());
+  WriteParam(m, p.suborigin());
 }
 
 bool ParamTraits<url::Origin>::Read(const base::Pickle* m,
@@ -37,15 +39,17 @@
   std::string scheme;
   std::string host;
   uint16_t port;
+  std::string suborigin;
   if (!ReadParam(m, iter, &unique) || !ReadParam(m, iter, &scheme) ||
-      !ReadParam(m, iter, &host) || !ReadParam(m, iter, &port)) {
+      !ReadParam(m, iter, &host) || !ReadParam(m, iter, &port) ||
+      !ReadParam(m, iter, &suborigin)) {
     *p = url::Origin();
     return false;
   }
 
   *p = unique ? url::Origin()
               : url::Origin::UnsafelyCreateOriginWithoutNormalization(
-                    scheme, host, port);
+                    scheme, host, port, suborigin);
 
   // If a unique origin was created, but the unique flag wasn't set, then
   // the values provided to 'UnsafelyCreateOriginWithoutNormalization' were
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index c72402f..1122904 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1240,7 +1240,7 @@
     "../browser/renderer_host/media/video_capture_controller_unittest.cc",
     "../browser/renderer_host/media/video_capture_manager_unittest.cc",
     "../browser/renderer_host/media/video_capture_unittest.cc",
-    "../browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc",
+    "../browser/renderer_host/offscreen_canvas_provider_impl_unittest.cc",
     "../browser/renderer_host/render_process_host_unittest.cc",
     "../browser/renderer_host/render_view_host_unittest.cc",
     "../browser/renderer_host/render_widget_host_unittest.cc",
@@ -1512,7 +1512,6 @@
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/WebKit/public:blink",
-    "//third_party/WebKit/public:offscreen_canvas_mojo_bindings",
     "//third_party/icu",
     "//third_party/leveldatabase",
     "//third_party/re2",
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
index d3585f8..1e495a9 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
+++ b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
@@ -1,3 +1,3 @@
 # AUTOGENERATED FILE - DO NOT EDIT
 # SEE roll_webgl_conformance.py
-Current webgl revision c91689d6df5536fefaa07a459c80c210bd580a1b
+Current webgl revision 73b300f24942adf7013de30ccdc6a2cc88105e72
diff --git a/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc b/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc
index 74f6c9d3..779e9a2c 100644
--- a/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc
+++ b/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc
@@ -31,6 +31,8 @@
 namespace SetAlwaysOnTop = app_current_window_internal::SetAlwaysOnTop;
 namespace SetVisibleOnAllWorkspaces =
     app_current_window_internal::SetVisibleOnAllWorkspaces;
+namespace SetActivateOnPointer =
+    app_current_window_internal::SetActivateOnPointer;
 
 using app_current_window_internal::Bounds;
 using app_current_window_internal::Region;
@@ -377,4 +379,13 @@
   return RespondNow(NoArguments());
 }
 
+ExtensionFunction::ResponseAction
+AppCurrentWindowInternalSetActivateOnPointerFunction::Run() {
+  std::unique_ptr<SetActivateOnPointer::Params> params(
+      SetActivateOnPointer::Params::Create(*args_));
+  CHECK(params.get());
+  window()->GetBaseWindow()->SetActivateOnPointer(params->activate_on_pointer);
+  return RespondNow(NoArguments());
+}
+
 }  // namespace extensions
diff --git a/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h b/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h
index 66f265a4..72ddd534 100644
--- a/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h
+++ b/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h
@@ -190,6 +190,17 @@
   ResponseAction Run() override;
 };
 
+class AppCurrentWindowInternalSetActivateOnPointerFunction
+    : public AppCurrentWindowInternalExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.setActivateOnPointer",
+                             APP_CURRENTWINDOWINTERNAL_SETACTIVATEONPOINTER)
+
+ protected:
+  ~AppCurrentWindowInternalSetActivateOnPointerFunction() override {}
+  ResponseAction Run() override;
+};
+
 }  // namespace extensions
 
 #endif  // EXTENSIONS_BROWSER_API_APP_CURRENT_WINDOW_INTERNAL_APP_CURRENT_WINDOW_INTERNAL_API_H_
diff --git a/extensions/browser/app_window/native_app_window.h b/extensions/browser/app_window/native_app_window.h
index a6510c88..719180bb 100644
--- a/extensions/browser/app_window/native_app_window.h
+++ b/extensions/browser/app_window/native_app_window.h
@@ -94,6 +94,9 @@
   // when compositing.
   virtual bool CanHaveAlphaEnabled() const = 0;
 
+  // Sets whether the window should be activated on pointer event.
+  virtual void SetActivateOnPointer(bool activate_on_pointer) = 0;
+
   ~NativeAppWindow() override {}
 };
 
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 0faac6a..8be8786 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1233,6 +1233,7 @@
   WEBRTC_AUDIO_PRIVATE_SET_AUDIO_EXPERIMENTS,
   AUTOTESTPRIVATE_GETPLAYSTORESTATE,
   AUTOTESTPRIVATE_SETPLAYSTOREENABLED,
+  APP_CURRENTWINDOWINTERNAL_SETACTIVATEONPOINTER,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/common/api/app_current_window_internal.idl b/extensions/common/api/app_current_window_internal.idl
index 344ef1c..13937b3 100644
--- a/extensions/common/api/app_current_window_internal.idl
+++ b/extensions/common/api/app_current_window_internal.idl
@@ -51,6 +51,7 @@
     static void setShape(Region region);
     static void setAlwaysOnTop(boolean always_on_top);
     static void setVisibleOnAllWorkspaces(boolean always_visible);
+    static void setActivateOnPointer(boolean activate_on_pointer);
   };
 
   interface Events {
diff --git a/extensions/components/native_app_window/native_app_window_views.cc b/extensions/components/native_app_window/native_app_window_views.cc
index f4abafac..e2818e20 100644
--- a/extensions/components/native_app_window/native_app_window_views.cc
+++ b/extensions/components/native_app_window/native_app_window_views.cc
@@ -439,4 +439,6 @@
   widget_->SetVisibleOnAllWorkspaces(always_visible);
 }
 
+void NativeAppWindowViews::SetActivateOnPointer(bool activate_on_pointer) {}
+
 }  // namespace native_app_window
diff --git a/extensions/components/native_app_window/native_app_window_views.h b/extensions/components/native_app_window/native_app_window_views.h
index f7cb963..b5213a0 100644
--- a/extensions/components/native_app_window/native_app_window_views.h
+++ b/extensions/components/native_app_window/native_app_window_views.h
@@ -147,6 +147,7 @@
                                  const gfx::Size& max_size) override;
   bool CanHaveAlphaEnabled() const override;
   void SetVisibleOnAllWorkspaces(bool always_visible) override;
+  void SetActivateOnPointer(bool activate_on_pointer) override;
 
   // web_modal::WebContentsModalDialogHost implementation.
   gfx::NativeView GetHostView() const override;
diff --git a/extensions/shell/browser/shell_native_app_window.cc b/extensions/shell/browser/shell_native_app_window.cc
index cee27aab..8d9044c 100644
--- a/extensions/shell/browser/shell_native_app_window.cc
+++ b/extensions/shell/browser/shell_native_app_window.cc
@@ -195,4 +195,8 @@
   return false;
 }
 
+void ShellNativeAppWindow::SetActivateOnPointer(bool activate_on_pointer) {
+  NOTIMPLEMENTED();
+}
+
 }  // namespace extensions
diff --git a/extensions/shell/browser/shell_native_app_window.h b/extensions/shell/browser/shell_native_app_window.h
index f3ff7be..389d082a 100644
--- a/extensions/shell/browser/shell_native_app_window.h
+++ b/extensions/shell/browser/shell_native_app_window.h
@@ -68,6 +68,7 @@
                                  const gfx::Size& max_size) override;
   void SetVisibleOnAllWorkspaces(bool always_visible) override;
   bool CanHaveAlphaEnabled() const override;
+  void SetActivateOnPointer(bool activate_on_pointer) override;
 
  private:
   AppWindow* app_window_;
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc
index b4fa9fc..aeaf971 100644
--- a/gpu/command_buffer/service/texture_manager.cc
+++ b/gpu/command_buffer/service/texture_manager.cc
@@ -2455,6 +2455,21 @@
         error_state, function_name, args.target, "target");
     return false;
   }
+  if (feature_info_->IsWebGL1OrES2Context()) {
+    switch (args.format) {
+      case GL_DEPTH_COMPONENT:
+      case GL_DEPTH_STENCIL:
+        if (args.target != GL_TEXTURE_2D) {
+          ERRORSTATE_SET_GL_ERROR(
+              error_state, GL_INVALID_OPERATION, function_name,
+              "invalid target for depth/stencil textures");
+          return false;
+        }
+        break;
+      default:
+        break;
+    }
+  }
   if (!ValidateTextureParameters(
       error_state, function_name, true, args.format, args.type,
       args.internal_format, args.level)) {
diff --git a/ios/chrome/browser/ui/authentication/BUILD.gn b/ios/chrome/browser/ui/authentication/BUILD.gn
index 0de74588..950d2be 100644
--- a/ios/chrome/browser/ui/authentication/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/BUILD.gn
@@ -136,6 +136,7 @@
   ]
   deps = [
     "//base",
+    "//components/signin/core/browser",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/ui",
     "//ios/chrome/browser/ui/collection_view/cells",
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view.h b/ios/chrome/browser/ui/authentication/signin_promo_view.h
index 1935e8a..cd08711 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view.h
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view.h
@@ -7,6 +7,8 @@
 
 #import <UIKit/UIKit.h>
 
+#include "components/signin/core/browser/signin_metrics.h"
+
 @class MDCFlatButton;
 
 typedef NS_ENUM(NSInteger, SigninPromoViewMode) {
@@ -36,9 +38,6 @@
 @property(nonatomic, readonly) UILabel* textLabel;
 @property(nonatomic, readonly) MDCFlatButton* primaryButton;
 @property(nonatomic, readonly) MDCFlatButton* secondaryButton;
-// If set to YES, ShowSigninCommand is sent when primary or secondary buttons
-// are tapped.
-@property(nonatomic, getter=doesSendChromeCommand) BOOL sendChromeCommand;
 
 // Horizontal padding used for |textLabel|, |primaryButton| and
 // |secondaryButton|. Used to compute the preferred max layout width of
@@ -52,6 +51,13 @@
 // cropped first). Must only be called in the "Warm State" mode.
 - (void)setProfileImage:(UIImage*)image;
 
+// Enables SigninPromoView to send ShowSigninCommand when primary or secondary
+// buttons are tapped, and sets the metric access point. By default, command is
+// disabled.
+// This method should be called only once.
+- (void)enableChromeCommandWithAccessPoint:
+    (signin_metrics::AccessPoint)accessPoint;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_SIGN_PROMO_VIEW_H_
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view.mm b/ios/chrome/browser/ui/authentication/signin_promo_view.mm
index bfa3bfa..3e38e1fd 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view.mm
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view.mm
@@ -5,6 +5,8 @@
 #import "ios/chrome/browser/ui/authentication/signin_promo_view.h"
 
 #include "base/logging.h"
+#include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
 #import "ios/chrome/browser/ui/commands/show_signin_command.h"
@@ -34,6 +36,8 @@
 @implementation SigninPromoView {
   NSArray<NSLayoutConstraint*>* _coldStateConstraints;
   NSArray<NSLayoutConstraint*>* _warmStateConstraints;
+  BOOL _shouldSendChromeCommand;
+  signin_metrics::AccessPoint _accessPoint;
 }
 
 @synthesize mode = _mode;
@@ -41,12 +45,12 @@
 @synthesize textLabel = _textLabel;
 @synthesize primaryButton = _primaryButton;
 @synthesize secondaryButton = _secondaryButton;
-@synthesize sendChromeCommand = _sendChromeCommand;
 
 - (instancetype)initWithFrame:(CGRect)frame {
   self = [super initWithFrame:frame];
   if (self) {
     self.isAccessibilityElement = YES;
+    _accessPoint = signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN;
 
     // Adding subviews.
     self.clipsToBounds = YES;
@@ -205,23 +209,29 @@
   return kHorizontalPadding;
 }
 
+- (void)enableChromeCommandWithAccessPoint:
+    (signin_metrics::AccessPoint)accessPoint {
+  DCHECK(!_shouldSendChromeCommand);
+  _shouldSendChromeCommand = YES;
+  _accessPoint = accessPoint;
+}
+
 - (void)onPrimaryButtonAction:(id)unused {
-  if (!_sendChromeCommand) {
+  if (!_shouldSendChromeCommand) {
     return;
   }
+  [self recordSigninUserActionForAccessPoint];
   ShowSigninCommand* command = nil;
   switch (_mode) {
     case SigninPromoViewModeColdState:
       command = [[ShowSigninCommand alloc]
           initWithOperation:AUTHENTICATION_OPERATION_SIGNIN
-                accessPoint:signin_metrics::AccessPoint::
-                                ACCESS_POINT_RECENT_TABS];
+                accessPoint:_accessPoint];
       break;
     case SigninPromoViewModeWarmState:
       command = [[ShowSigninCommand alloc]
           initWithOperation:AUTHENTICATION_OPERATION_SIGNIN_PROMO_CONTINUE_AS
-                accessPoint:signin_metrics::AccessPoint::
-                                ACCESS_POINT_RECENT_TABS];
+                accessPoint:_accessPoint];
       break;
   }
   DCHECK(command);
@@ -229,15 +239,32 @@
 }
 
 - (void)onSecondaryButtonAction:(id)unused {
-  if (!_sendChromeCommand) {
+  if (!_shouldSendChromeCommand) {
     return;
   }
+  [self recordSigninUserActionForAccessPoint];
   ShowSigninCommand* command = [[ShowSigninCommand alloc]
       initWithOperation:AUTHENTICATION_OPERATION_SIGNIN
-            accessPoint:signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS];
+            accessPoint:_accessPoint];
   [self chromeExecuteCommand:command];
 }
 
+- (void)recordSigninUserActionForAccessPoint {
+  switch (_accessPoint) {
+    case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER:
+      base::RecordAction(
+          base::UserMetricsAction("Signin_Signin_FromBookmarkManager"));
+      break;
+    case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
+      base::RecordAction(
+          base::UserMetricsAction("Signin_Signin_FromRecentTabs"));
+      break;
+    default:
+      NOTREACHED() << "Unexpected value for access point " << (int)_accessPoint;
+      break;
+  }
+}
+
 #pragma mark - NSObject(Accessibility)
 
 - (NSArray<UIAccessibilityCustomAction*>*)accessibilityCustomActions {
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_folder_collection_view.mm b/ios/chrome/browser/ui/bookmarks/bookmark_folder_collection_view.mm
index 40d298ac..29fe56a 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_folder_collection_view.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_folder_collection_view.mm
@@ -411,7 +411,9 @@
           dequeueReusableCellWithReuseIdentifier:[BookmarkSigninPromoCell
                                                      reuseIdentifier]
                                     forIndexPath:indexPath];
-      signinPromoCell.signinPromoView.sendChromeCommand = YES;
+      [signinPromoCell.signinPromoView
+          enableChromeCommandWithAccessPoint:signin_metrics::AccessPoint::
+                                                 ACCESS_POINT_BOOKMARK_MANAGER];
       [[_signinPromoViewMediator createConfigurator]
           configureSigninPromoView:signinPromoCell.signinPromoView];
       __weak BookmarkFolderCollectionView* weakSelf = self;
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
index 185abc0d..846b29fc3 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
@@ -828,7 +828,9 @@
       contentViewTopMargin = kSigninPromoViewTopMargin;
       SigninPromoView* signinPromoView =
           [[SigninPromoView alloc] initWithFrame:CGRectZero];
-      signinPromoView.sendChromeCommand = YES;
+      [signinPromoView
+          enableChromeCommandWithAccessPoint:signin_metrics::AccessPoint::
+                                                 ACCESS_POINT_RECENT_TABS];
       signinPromoView.textLabel.text =
           l10n_util::GetNSString(IDS_IOS_SIGNIN_PROMO_RECENT_TABS);
       signinPromoView.textLabel.preferredMaxLayoutWidth =
diff --git a/ios/web/web_state/navigation_context_impl.h b/ios/web/web_state/navigation_context_impl.h
index 5756cedf..de9a9a7 100644
--- a/ios/web/web_state/navigation_context_impl.h
+++ b/ios/web/web_state/navigation_context_impl.h
@@ -18,6 +18,12 @@
 class NavigationContextImpl : public NavigationContext {
  public:
   // Creates navigation context for sucessful navigation to a different page.
+  // Response headers will ne null.
+  static std::unique_ptr<NavigationContextImpl> CreateNavigationContext(
+      WebState* web_state,
+      const GURL& url);
+
+  // Creates navigation context for sucessful navigation to a different page.
   static std::unique_ptr<NavigationContextImpl> CreateNavigationContext(
       WebState* web_state,
       const GURL& url,
@@ -47,6 +53,12 @@
   net::HttpResponseHeaders* GetResponseHeaders() const override;
   ~NavigationContextImpl() override;
 
+  // Setters for navigation context data members.
+  void SetIsSameDocument(bool is_same_document);
+  void SetIsErrorPage(bool is_error_page);
+  void SetResponseHeaders(
+      const scoped_refptr<net::HttpResponseHeaders>& response_headers);
+
  private:
   NavigationContextImpl(
       WebState* web_state,
diff --git a/ios/web/web_state/navigation_context_impl.mm b/ios/web/web_state/navigation_context_impl.mm
index 4d6406f..39df2fa 100644
--- a/ios/web/web_state/navigation_context_impl.mm
+++ b/ios/web/web_state/navigation_context_impl.mm
@@ -13,6 +13,16 @@
 
 // static
 std::unique_ptr<NavigationContextImpl>
+NavigationContextImpl::CreateNavigationContext(WebState* web_state,
+                                               const GURL& url) {
+  std::unique_ptr<NavigationContextImpl> result(new NavigationContextImpl(
+      web_state, url, false /* is_same_document */, false /* is_error_page */,
+      nullptr /* response_headers */));
+  return result;
+}
+
+// static
+std::unique_ptr<NavigationContextImpl>
 NavigationContextImpl::CreateNavigationContext(
     WebState* web_state,
     const GURL& url,
@@ -76,6 +86,19 @@
   return response_headers_.get();
 }
 
+void NavigationContextImpl::SetIsSameDocument(bool is_same_document) {
+  is_same_document_ = is_same_document;
+}
+
+void NavigationContextImpl::SetIsErrorPage(bool is_error_page) {
+  is_error_page_ = is_error_page;
+}
+
+void NavigationContextImpl::SetResponseHeaders(
+    const scoped_refptr<net::HttpResponseHeaders>& response_headers) {
+  response_headers_ = response_headers;
+}
+
 NavigationContextImpl::NavigationContextImpl(
     WebState* web_state,
     const GURL& url,
diff --git a/ios/web/web_state/navigation_context_impl_unittest.mm b/ios/web/web_state/navigation_context_impl_unittest.mm
index 65af1d16..9ffedf9a 100644
--- a/ios/web/web_state/navigation_context_impl_unittest.mm
+++ b/ios/web/web_state/navigation_context_impl_unittest.mm
@@ -30,8 +30,8 @@
   scoped_refptr<net::HttpResponseHeaders> response_headers_;
 };
 
-// Tests CreateNavigationContext factory method.
-TEST_F(NavigationContextImplTest, NavigationContext) {
+// Tests legacy CreateNavigationContext factory method.
+TEST_F(NavigationContextImplTest, LegacyNavigationContext) {
   std::unique_ptr<NavigationContext> context =
       NavigationContextImpl::CreateNavigationContext(&web_state_, url_,
                                                      response_headers_);
@@ -44,6 +44,19 @@
   EXPECT_EQ(response_headers_.get(), context->GetResponseHeaders());
 }
 
+// Tests CreateNavigationContext factory method.
+TEST_F(NavigationContextImplTest, NavigationContext) {
+  std::unique_ptr<NavigationContext> context =
+      NavigationContextImpl::CreateNavigationContext(&web_state_, url_);
+  ASSERT_TRUE(context);
+
+  EXPECT_EQ(&web_state_, context->GetWebState());
+  EXPECT_EQ(url_, context->GetUrl());
+  EXPECT_FALSE(context->IsSameDocument());
+  EXPECT_FALSE(context->IsErrorPage());
+  EXPECT_FALSE(context->GetResponseHeaders());
+}
+
 // Tests CreateSameDocumentNavigationContext factory method.
 TEST_F(NavigationContextImplTest, SameDocumentNavigationContext) {
   std::unique_ptr<NavigationContext> context =
@@ -72,4 +85,33 @@
   EXPECT_EQ(response_headers_.get(), context->GetResponseHeaders());
 }
 
+// Tests NavigationContextImpl Setters.
+TEST_F(NavigationContextImplTest, Setters) {
+  std::unique_ptr<NavigationContextImpl> context =
+      NavigationContextImpl::CreateNavigationContext(&web_state_, url_);
+  ASSERT_TRUE(context);
+
+  ASSERT_FALSE(context->IsSameDocument());
+  ASSERT_FALSE(context->IsErrorPage());
+  ASSERT_NE(response_headers_.get(), context->GetResponseHeaders());
+
+  // SetSameDocument
+  context->SetIsSameDocument(true);
+  EXPECT_TRUE(context->IsSameDocument());
+  EXPECT_FALSE(context->IsErrorPage());
+  EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
+
+  // SetErrorPage
+  context->SetIsErrorPage(true);
+  EXPECT_TRUE(context->IsSameDocument());
+  EXPECT_TRUE(context->IsErrorPage());
+  EXPECT_NE(response_headers_.get(), context->GetResponseHeaders());
+
+  // SetResponseHeaders
+  context->SetResponseHeaders(response_headers_);
+  EXPECT_TRUE(context->IsSameDocument());
+  EXPECT_TRUE(context->IsErrorPage());
+  EXPECT_EQ(response_headers_.get(), context->GetResponseHeaders());
+}
+
 }  // namespace web
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index b6de64a2..8fcd483 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -222,6 +222,11 @@
 const base::Feature kExternalClearKeyForTesting{
     "external-clear-key-for-testing", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables Media Engagement Index recording in order to bypass autoplay
+// policies.
+const base::Feature kMediaEngagement{"media-engagement",
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
 #if defined(OS_ANDROID)
 // Lock the screen orientation when a video goes fullscreen.
 const base::Feature kVideoFullscreenOrientationLock{
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index fbb8454..0d20303 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -107,6 +107,7 @@
 MEDIA_EXPORT extern const base::Feature kUseNewMediaCache;
 MEDIA_EXPORT extern const base::Feature kVideoBlitColorAccuracy;
 MEDIA_EXPORT extern const base::Feature kVideoColorManagement;
+MEDIA_EXPORT extern const base::Feature kMediaEngagement;
 
 #if defined(OS_ANDROID)
 MEDIA_EXPORT extern const base::Feature kAndroidMediaPlayerRenderer;
diff --git a/media/renderers/skcanvas_video_renderer.cc b/media/renderers/skcanvas_video_renderer.cc
index 3c7cfb9..f8e8faa 100644
--- a/media/renderers/skcanvas_video_renderer.cc
+++ b/media/renderers/skcanvas_video_renderer.cc
@@ -432,7 +432,7 @@
   // sw image into the SkPicture. The long term solution is for Skia to provide
   // a SkPicture filter that makes a picture safe for multiple CPU raster
   // threads. (skbug.com/4321).
-  sk_sp<const SkImage> image;
+  sk_sp<SkImage> image;
   if (canvas->imageInfo().colorType() == kUnknown_SkColorType)
     image = last_image_->makeNonTextureImage();
   else
diff --git a/net/quic/chromium/bidirectional_stream_quic_impl.cc b/net/quic/chromium/bidirectional_stream_quic_impl.cc
index 6309e84..1afd2b60 100644
--- a/net/quic/chromium/bidirectional_stream_quic_impl.cc
+++ b/net/quic/chromium/bidirectional_stream_quic_impl.cc
@@ -340,7 +340,7 @@
   closed_stream_received_bytes_ = stream_->stream_bytes_read();
   closed_stream_sent_bytes_ = stream_->stream_bytes_written();
   closed_is_first_stream_ = stream_->IsFirstStream();
-  stream_->SetDelegate(nullptr);
+  stream_->ClearDelegate();
   stream_ = nullptr;
 }
 
diff --git a/net/quic/chromium/bidirectional_stream_quic_impl.h b/net/quic/chromium/bidirectional_stream_quic_impl.h
index e9f58509..e8f5cfe 100644
--- a/net/quic/chromium/bidirectional_stream_quic_impl.h
+++ b/net/quic/chromium/bidirectional_stream_quic_impl.h
@@ -78,7 +78,7 @@
   void ResetStream();
 
   const std::unique_ptr<QuicChromiumClientSession::Handle> session_;
-  QuicChromiumClientStream* stream_;  // Non-owning.
+  std::unique_ptr<QuicChromiumClientStream::Handle> stream_;
 
   const BidirectionalStreamRequestInfo* request_info_;
   BidirectionalStreamImpl::Delegate* delegate_;
diff --git a/net/quic/chromium/quic_chromium_client_session.cc b/net/quic/chromium/quic_chromium_client_session.cc
index 6d46a32..4c70e14 100644
--- a/net/quic/chromium/quic_chromium_client_session.cc
+++ b/net/quic/chromium/quic_chromium_client_session.cc
@@ -301,13 +301,14 @@
   return stream_request_->StartRequest(callback);
 }
 
-QuicChromiumClientStream* QuicChromiumClientSession::Handle::ReleaseStream(
+std::unique_ptr<QuicChromiumClientStream::Handle>
+QuicChromiumClientSession::Handle::ReleaseStream(
     QuicChromiumClientStream::Delegate* delegate) {
   DCHECK(stream_request_);
 
-  auto* stream = stream_request_->ReleaseStream(delegate);
+  auto handle = stream_request_->ReleaseStream(delegate);
   stream_request_.reset();
-  return stream;
+  return handle;
 }
 
 int QuicChromiumClientSession::Handle::WaitForHandshakeConfirmation(
@@ -376,14 +377,13 @@
   return rv;
 }
 
-QuicChromiumClientStream*
+std::unique_ptr<QuicChromiumClientStream::Handle>
 QuicChromiumClientSession::StreamRequest::ReleaseStream(
     QuicChromiumClientStream::Delegate* delegate) {
   DCHECK(stream_);
   QuicChromiumClientStream* stream = stream_;
   stream_ = nullptr;
-  stream->SetDelegate(delegate);
-  return stream;
+  return stream->CreateHandle(delegate);
 }
 
 void QuicChromiumClientSession::StreamRequest::OnRequestCompleteSuccess(
diff --git a/net/quic/chromium/quic_chromium_client_session.h b/net/quic/chromium/quic_chromium_client_session.h
index 35a05fa..93f26b09 100644
--- a/net/quic/chromium/quic_chromium_client_session.h
+++ b/net/quic/chromium/quic_chromium_client_session.h
@@ -90,8 +90,8 @@
     int RequestStream(bool requires_confirmation,
                       const CompletionCallback& callback);
 
-    // Releases |stream_| to the caller and sets |delegate| on it.
-    QuicChromiumClientStream* ReleaseStream(
+    // Releases |stream_| to the caller and sets |delegate| on the handle.
+    std::unique_ptr<QuicChromiumClientStream::Handle> ReleaseStream(
         QuicChromiumClientStream::Delegate* delegate);
 
     // Sends Rst for the stream, and makes sure that future calls to
@@ -194,7 +194,7 @@
     int StartRequest(const CompletionCallback& callback);
 
     // Releases |stream_| to the caller and sets |delegate| on it.
-    QuicChromiumClientStream* ReleaseStream(
+    std::unique_ptr<QuicChromiumClientStream::Handle> ReleaseStream(
         QuicChromiumClientStream::Delegate* delegate);
 
    private:
diff --git a/net/quic/chromium/quic_chromium_client_stream.cc b/net/quic/chromium/quic_chromium_client_stream.cc
index 15f46431..5eb505a3 100644
--- a/net/quic/chromium/quic_chromium_client_stream.cc
+++ b/net/quic/chromium/quic_chromium_client_stream.cc
@@ -21,13 +21,226 @@
 
 namespace net {
 
+QuicChromiumClientStream::Handle::Handle(QuicChromiumClientStream* stream,
+                                         Delegate* delegate)
+    : stream_(stream), delegate_(delegate) {
+  SaveState();
+}
+
+QuicChromiumClientStream::Handle::~Handle() {
+  if (stream_) {
+    stream_->ClearHandle();
+    // TODO(rch): If stream_ is still valid, it should probably be Reset()
+    // so that it does not leak.
+    // stream_->Reset(QUIC_STREAM_CANCELLED);
+  }
+}
+
+void QuicChromiumClientStream::Handle::ClearDelegate() {
+  delegate_ = nullptr;
+}
+
+void QuicChromiumClientStream::Handle::OnInitialHeadersAvailable(
+    const SpdyHeaderBlock& headers,
+    size_t frame_len) {
+  delegate_->OnInitialHeadersAvailable(headers, frame_len);
+}
+
+void QuicChromiumClientStream::Handle::OnTrailingHeadersAvailable(
+    const SpdyHeaderBlock& headers,
+    size_t frame_len) {
+  delegate_->OnTrailingHeadersAvailable(headers, frame_len);
+}
+
+void QuicChromiumClientStream::Handle::OnDataAvailable() {
+  delegate_->OnDataAvailable();
+}
+
+void QuicChromiumClientStream::Handle::OnClose() {
+  if (stream_)
+    SaveState();
+  stream_ = nullptr;
+  if (delegate_) {
+    auto* delegate = delegate_;
+    delegate_ = nullptr;
+    delegate->OnClose();
+  }
+}
+
+void QuicChromiumClientStream::Handle::OnError(int error) {
+  if (stream_)
+    SaveState();
+  stream_ = nullptr;
+  if (delegate_) {
+    auto* delegate = delegate_;
+    delegate_ = nullptr;
+    delegate->OnError(error);
+  }
+}
+
+size_t QuicChromiumClientStream::Handle::WriteHeaders(
+    SpdyHeaderBlock header_block,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface>
+        ack_notifier_delegate) {
+  if (!stream_)
+    return 0;
+  return stream_->WriteHeaders(std::move(header_block), fin,
+                               ack_notifier_delegate);
+}
+
+int QuicChromiumClientStream::Handle::WriteStreamData(
+    base::StringPiece data,
+    bool fin,
+    const CompletionCallback& callback) {
+  if (!stream_)
+    return ERR_CONNECTION_CLOSED;
+  return stream_->WriteStreamData(data, fin, callback);
+}
+
+int QuicChromiumClientStream::Handle::WritevStreamData(
+    const std::vector<scoped_refptr<IOBuffer>>& buffers,
+    const std::vector<int>& lengths,
+    bool fin,
+    const CompletionCallback& callback) {
+  if (!stream_)
+    return ERR_CONNECTION_CLOSED;
+  return stream_->WritevStreamData(buffers, lengths, fin, callback);
+}
+
+int QuicChromiumClientStream::Handle::Read(IOBuffer* buf, int buf_len) {
+  if (!stream_)
+    return ERR_CONNECTION_CLOSED;
+  return stream_->Read(buf, buf_len);
+}
+
+void QuicChromiumClientStream::Handle::OnFinRead() {
+  if (stream_)
+    stream_->OnFinRead();
+}
+
+void QuicChromiumClientStream::Handle::DisableConnectionMigration() {
+  if (stream_)
+    stream_->DisableConnectionMigration();
+}
+
+void QuicChromiumClientStream::Handle::SetPriority(SpdyPriority priority) {
+  if (stream_)
+    stream_->SetPriority(priority);
+}
+
+void QuicChromiumClientStream::Handle::Reset(
+    QuicRstStreamErrorCode error_code) {
+  if (stream_)
+    stream_->Reset(error_code);
+}
+
+QuicStreamId QuicChromiumClientStream::Handle::id() const {
+  if (!stream_)
+    return id_;
+  return stream_->id();
+}
+
+QuicErrorCode QuicChromiumClientStream::Handle::connection_error() const {
+  if (!stream_)
+    return connection_error_;
+  return stream_->connection_error();
+}
+
+QuicRstStreamErrorCode QuicChromiumClientStream::Handle::stream_error() const {
+  if (!stream_)
+    return stream_error_;
+  return stream_->stream_error();
+}
+
+bool QuicChromiumClientStream::Handle::fin_sent() const {
+  if (!stream_)
+    return fin_sent_;
+  return stream_->fin_sent();
+}
+
+bool QuicChromiumClientStream::Handle::fin_received() const {
+  if (!stream_)
+    return fin_received_;
+  return stream_->fin_received();
+}
+
+uint64_t QuicChromiumClientStream::Handle::stream_bytes_read() const {
+  if (!stream_)
+    return stream_bytes_read_;
+  return stream_->stream_bytes_read();
+}
+
+uint64_t QuicChromiumClientStream::Handle::stream_bytes_written() const {
+  if (!stream_)
+    return stream_bytes_written_;
+  return stream_->stream_bytes_written();
+}
+
+size_t QuicChromiumClientStream::Handle::NumBytesConsumed() const {
+  if (!stream_)
+    return num_bytes_consumed_;
+  return stream_->sequencer()->NumBytesConsumed();
+}
+
+bool QuicChromiumClientStream::Handle::IsDoneReading() const {
+  if (!stream_)
+    return is_done_reading_;
+  return stream_->IsDoneReading();
+}
+
+bool QuicChromiumClientStream::Handle::IsFirstStream() const {
+  if (!stream_)
+    return is_first_stream_;
+  return stream_->IsFirstStream();
+}
+
+void QuicChromiumClientStream::Handle::OnPromiseHeaderList(
+    QuicStreamId promised_id,
+    size_t frame_len,
+    const QuicHeaderList& header_list) {
+  stream_->OnPromiseHeaderList(promised_id, frame_len, header_list);
+}
+
+SpdyPriority QuicChromiumClientStream::Handle::priority() const {
+  if (!stream_)
+    return priority_;
+  return stream_->priority();
+}
+
+bool QuicChromiumClientStream::Handle::can_migrate() {
+  if (!stream_)
+    return false;
+  return stream_->can_migrate();
+}
+
+QuicChromiumClientStream::Delegate*
+QuicChromiumClientStream::Handle::GetDelegate() {
+  return delegate_;
+}
+
+void QuicChromiumClientStream::Handle::SaveState() {
+  DCHECK(stream_);
+  fin_sent_ = stream_->fin_sent();
+  fin_received_ = stream_->fin_received();
+  num_bytes_consumed_ = stream_->sequencer()->NumBytesConsumed();
+  id_ = stream_->id();
+  connection_error_ = stream_->connection_error();
+  stream_error_ = stream_->stream_error();
+  is_done_reading_ = stream_->IsDoneReading();
+  is_first_stream_ = stream_->IsFirstStream();
+  stream_bytes_read_ = stream_->stream_bytes_read();
+  stream_bytes_written_ = stream_->stream_bytes_written();
+  priority_ = stream_->priority();
+}
+
 QuicChromiumClientStream::QuicChromiumClientStream(
     QuicStreamId id,
     QuicClientSessionBase* session,
     const NetLogWithSource& net_log)
     : QuicSpdyStream(id, session),
       net_log_(net_log),
-      delegate_(nullptr),
+      handle_(nullptr),
       headers_delivered_(false),
       initial_headers_sent_(false),
       session_(session),
@@ -36,8 +249,8 @@
       weak_factory_(this) {}
 
 QuicChromiumClientStream::~QuicChromiumClientStream() {
-  if (delegate_)
-    delegate_->OnClose();
+  if (handle_)
+    handle_->OnClose();
 }
 
 void QuicChromiumClientStream::OnInitialHeadersComplete(
@@ -58,14 +271,14 @@
   ConsumeHeaderList();
   session_->OnInitialHeadersComplete(id(), header_block);
 
-  if (delegate_) {
-    // The delegate will receive the headers via a posted task.
-    NotifyDelegateOfInitialHeadersAvailableLater(std::move(header_block),
-                                                 frame_len);
+  if (handle_) {
+    // The handle will receive the headers via a posted task.
+    NotifyHandleOfInitialHeadersAvailableLater(std::move(header_block),
+                                               frame_len);
     return;
   }
 
-  // Buffer the headers and deliver them when the delegate arrives.
+  // Buffer the headers and deliver them when the handle arrives.
   initial_headers_ = std::move(header_block);
   initial_headers_frame_len_ = frame_len;
 }
@@ -75,8 +288,8 @@
     size_t frame_len,
     const QuicHeaderList& header_list) {
   QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list);
-  NotifyDelegateOfTrailingHeadersAvailableLater(received_trailers().Clone(),
-                                                frame_len);
+  NotifyHandleOfTrailingHeadersAvailableLater(received_trailers().Clone(),
+                                              frame_len);
 }
 
 void QuicChromiumClientStream::OnPromiseHeaderList(
@@ -109,16 +322,16 @@
     return;
   }
 
-  // The delegate will read the data via a posted task, and
+  // The handle will read the data via a posted task, and
   // will be able to, potentially, read all data which has queued up.
-  if (delegate_)
-    NotifyDelegateOfDataAvailableLater();
+  if (handle_)
+    NotifyHandleOfDataAvailableLater();
 }
 
 void QuicChromiumClientStream::OnClose() {
-  if (delegate_) {
-    delegate_->OnClose();
-    delegate_ = nullptr;
+  if (handle_) {
+    handle_->OnClose();
+    handle_ = nullptr;
   }
   QuicStream::OnClose();
 }
@@ -192,25 +405,32 @@
   return ERR_IO_PENDING;
 }
 
-void QuicChromiumClientStream::SetDelegate(
+std::unique_ptr<QuicChromiumClientStream::Handle>
+QuicChromiumClientStream::CreateHandle(
     QuicChromiumClientStream::Delegate* delegate) {
-  DCHECK(!(delegate_ && delegate));
-  delegate_ = delegate;
-  if (delegate == nullptr)
-    return;
+  DCHECK(!handle_);
+  auto handle = std::unique_ptr<QuicChromiumClientStream::Handle>(
+      new QuicChromiumClientStream::Handle(this, delegate));
+  handle_ = handle.get();
 
   // Should this perhaps be via PostTask to make reasoning simpler?
   if (!initial_headers_.empty()) {
-    delegate_->OnInitialHeadersAvailable(std::move(initial_headers_),
-                                         initial_headers_frame_len_);
+    handle_->OnInitialHeadersAvailable(std::move(initial_headers_),
+                                       initial_headers_frame_len_);
   }
+
+  return handle;
+}
+
+void QuicChromiumClientStream::ClearHandle() {
+  handle_ = nullptr;
 }
 
 void QuicChromiumClientStream::OnError(int error) {
-  if (delegate_) {
-    QuicChromiumClientStream::Delegate* delegate = delegate_;
-    delegate_ = nullptr;
-    delegate->OnError(error);
+  if (handle_) {
+    QuicChromiumClientStream::Handle* handle = handle_;
+    handle_ = nullptr;
+    handle->OnError(error);
   }
 }
 
@@ -230,22 +450,22 @@
   return bytes_read;
 }
 
-void QuicChromiumClientStream::NotifyDelegateOfInitialHeadersAvailableLater(
+void QuicChromiumClientStream::NotifyHandleOfInitialHeadersAvailableLater(
     SpdyHeaderBlock headers,
     size_t frame_len) {
-  DCHECK(delegate_);
+  DCHECK(handle_);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::Bind(
-          &QuicChromiumClientStream::NotifyDelegateOfInitialHeadersAvailable,
+          &QuicChromiumClientStream::NotifyHandleOfInitialHeadersAvailable,
           weak_factory_.GetWeakPtr(), base::Passed(std::move(headers)),
           frame_len));
 }
 
-void QuicChromiumClientStream::NotifyDelegateOfInitialHeadersAvailable(
+void QuicChromiumClientStream::NotifyHandleOfInitialHeadersAvailable(
     SpdyHeaderBlock headers,
     size_t frame_len) {
-  if (!delegate_)
+  if (!handle_)
     return;
 
   DCHECK(!headers_delivered_);
@@ -254,50 +474,50 @@
       NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_READ_RESPONSE_HEADERS,
       base::Bind(&SpdyHeaderBlockNetLogCallback, &headers));
 
-  delegate_->OnInitialHeadersAvailable(headers, frame_len);
+  handle_->OnInitialHeadersAvailable(headers, frame_len);
 }
 
-void QuicChromiumClientStream::NotifyDelegateOfTrailingHeadersAvailableLater(
+void QuicChromiumClientStream::NotifyHandleOfTrailingHeadersAvailableLater(
     SpdyHeaderBlock headers,
     size_t frame_len) {
-  DCHECK(delegate_);
+  DCHECK(handle_);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::Bind(
-          &QuicChromiumClientStream::NotifyDelegateOfTrailingHeadersAvailable,
+          &QuicChromiumClientStream::NotifyHandleOfTrailingHeadersAvailable,
           weak_factory_.GetWeakPtr(), base::Passed(std::move(headers)),
           frame_len));
 }
 
-void QuicChromiumClientStream::NotifyDelegateOfTrailingHeadersAvailable(
+void QuicChromiumClientStream::NotifyHandleOfTrailingHeadersAvailable(
     SpdyHeaderBlock headers,
     size_t frame_len) {
-  if (!delegate_)
+  if (!handle_)
     return;
 
   DCHECK(headers_delivered_);
   // Only mark trailers consumed when we are about to notify delegate.
   MarkTrailersConsumed();
   // Post an async task to notify delegate of the FIN flag.
-  NotifyDelegateOfDataAvailableLater();
+  NotifyHandleOfDataAvailableLater();
   net_log_.AddEvent(
       NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_READ_RESPONSE_TRAILERS,
       base::Bind(&SpdyHeaderBlockNetLogCallback, &headers));
 
-  delegate_->OnTrailingHeadersAvailable(headers, frame_len);
+  handle_->OnTrailingHeadersAvailable(headers, frame_len);
 }
 
-void QuicChromiumClientStream::NotifyDelegateOfDataAvailableLater() {
-  DCHECK(delegate_);
+void QuicChromiumClientStream::NotifyHandleOfDataAvailableLater() {
+  DCHECK(handle_);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::Bind(&QuicChromiumClientStream::NotifyDelegateOfDataAvailable,
+      base::Bind(&QuicChromiumClientStream::NotifyHandleOfDataAvailable,
                  weak_factory_.GetWeakPtr()));
 }
 
-void QuicChromiumClientStream::NotifyDelegateOfDataAvailable() {
-  if (delegate_)
-    delegate_->OnDataAvailable();
+void QuicChromiumClientStream::NotifyHandleOfDataAvailable() {
+  if (handle_)
+    handle_->OnDataAvailable();
 }
 
 void QuicChromiumClientStream::DisableConnectionMigration() {
diff --git a/net/quic/chromium/quic_chromium_client_stream.h b/net/quic/chromium/quic_chromium_client_stream.h
index d65f2e5..e8d05e9 100644
--- a/net/quic/chromium/quic_chromium_client_stream.h
+++ b/net/quic/chromium/quic_chromium_client_stream.h
@@ -32,6 +32,8 @@
 // are owned by the QuicClientSession which created them.
 class NET_EXPORT_PRIVATE QuicChromiumClientStream : public QuicSpdyStream {
  public:
+  // TODO(rch): Remove this class completely in favor of async methods
+  // on the Handle.
   // Delegate handles protocol specific behavior of a quic stream.
   class NET_EXPORT_PRIVATE Delegate {
    public:
@@ -61,6 +63,114 @@
     DISALLOW_COPY_AND_ASSIGN(Delegate);
   };
 
+  // Wrapper for interacting with the session in a restricted fashion.
+  class NET_EXPORT_PRIVATE Handle {
+   public:
+    ~Handle();
+
+    // Returns true if the stream is still connected.
+    bool IsOpen() { return stream_ != nullptr; }
+
+    // Writes |header_block| to the peer. Closes the write side if |fin| is
+    // true. If non-null, |ack_notifier_delegate| will be notified when the
+    // headers are ACK'd by the peer.
+    size_t WriteHeaders(SpdyHeaderBlock header_block,
+                        bool fin,
+                        QuicReferenceCountedPointer<QuicAckListenerInterface>
+                            ack_notifier_delegate);
+
+    // Writes |data| to the peer. Closes the write side if |fin| is true.
+    // If the data could not be written immediately, returns ERR_IO_PENDING
+    // and invokes |callback| asynchronously when the write completes.
+    int WriteStreamData(base::StringPiece data,
+                        bool fin,
+                        const CompletionCallback& callback);
+
+    // Same as WriteStreamData except it writes data from a vector of IOBuffers,
+    // with the length of each buffer at the corresponding index in |lengths|.
+    int WritevStreamData(const std::vector<scoped_refptr<IOBuffer>>& buffers,
+                         const std::vector<int>& lengths,
+                         bool fin,
+                         const CompletionCallback& callback);
+
+    // Reads at most |buf_len| bytes into |buf|. Returns the number of bytes
+    // read.
+    int Read(IOBuffer* buf, int buf_len);
+
+    // Called to notify the stream when the final incoming data is read.
+    void OnFinRead();
+
+    // Prevents the connection from migrating to a new network while this
+    // stream is open.
+    void DisableConnectionMigration();
+
+    // Sets the priority of the stream to |priority|.
+    void SetPriority(SpdyPriority priority);
+
+    // Sends a RST_STREAM frame to the peer and closes the streams.
+    void Reset(QuicRstStreamErrorCode error_code);
+
+    // Clears |delegate_| from this Handle, but does not disconnect the Handle
+    // from |stream_|.
+    void ClearDelegate();
+
+    QuicStreamId id() const;
+    QuicErrorCode connection_error() const;
+    QuicRstStreamErrorCode stream_error() const;
+    bool fin_sent() const;
+    bool fin_received() const;
+    uint64_t stream_bytes_read() const;
+    uint64_t stream_bytes_written() const;
+    size_t NumBytesConsumed() const;
+    bool IsDoneReading() const;
+    bool IsFirstStream() const;
+
+    // TODO(rch): Move these test-only methods to a peer, or else remove.
+    void OnPromiseHeaderList(QuicStreamId promised_id,
+                             size_t frame_len,
+                             const QuicHeaderList& header_list);
+    SpdyPriority priority() const;
+    bool can_migrate();
+
+    Delegate* GetDelegate();
+
+   private:
+    friend class QuicChromiumClientStream;
+
+    // Constucts a new Handle for |stream| with |delegate| set to receive
+    // up calls on various events.
+    Handle(QuicChromiumClientStream* stream, Delegate* delegate);
+
+    // Methods invoked by the stream.
+    void OnInitialHeadersAvailable(const SpdyHeaderBlock& headers,
+                                   size_t frame_len);
+    void OnTrailingHeadersAvailable(const SpdyHeaderBlock& headers,
+                                    size_t frame_len);
+    void OnDataAvailable();
+    void OnClose();
+    void OnError(int error);
+
+    // Saves various fields from the stream before the stream goes away.
+    void SaveState();
+
+    QuicChromiumClientStream* stream_;  // Unowned.
+    Delegate* delegate_;                // Owns this.
+
+    QuicStreamId id_;
+    QuicErrorCode connection_error_;
+    QuicRstStreamErrorCode stream_error_;
+    bool fin_sent_;
+    bool fin_received_;
+    uint64_t stream_bytes_read_;
+    uint64_t stream_bytes_written_;
+    bool is_done_reading_;
+    bool is_first_stream_;
+    size_t num_bytes_consumed_;
+    SpdyPriority priority_;
+
+    DISALLOW_COPY_AND_ASSIGN(Handle);
+  };
+
   QuicChromiumClientStream(QuicStreamId id,
                            QuicClientSessionBase* session,
                            const NetLogWithSource& net_log);
@@ -99,11 +209,15 @@
                        const std::vector<int>& lengths,
                        bool fin,
                        const CompletionCallback& callback);
-  // Set new |delegate|. |delegate| must not be NULL.
-  // If this stream has already received data, OnDataReceived() will be
-  // called on the delegate.
-  void SetDelegate(Delegate* delegate);
-  Delegate* GetDelegate() { return delegate_; }
+
+  // Creates a new Handle for this stream and sets |delegate| on the handle.
+  // Must only be called once.
+  std::unique_ptr<QuicChromiumClientStream::Handle> CreateHandle(
+      QuicChromiumClientStream::Delegate* delegate);
+
+  // Clears |handle_| from this stream.
+  void ClearHandle();
+
   void OnError(int error);
 
   // Reads at most |buf_len| bytes into |buf|. Returns the number of bytes read.
@@ -124,19 +238,19 @@
   using QuicStream::sequencer;
 
  private:
-  void NotifyDelegateOfInitialHeadersAvailableLater(SpdyHeaderBlock headers,
-                                                    size_t frame_len);
-  void NotifyDelegateOfInitialHeadersAvailable(SpdyHeaderBlock headers,
-                                               size_t frame_len);
-  void NotifyDelegateOfTrailingHeadersAvailableLater(SpdyHeaderBlock headers,
-                                                     size_t frame_len);
-  void NotifyDelegateOfTrailingHeadersAvailable(SpdyHeaderBlock headers,
-                                                size_t frame_len);
-  void NotifyDelegateOfDataAvailableLater();
-  void NotifyDelegateOfDataAvailable();
+  void NotifyHandleOfInitialHeadersAvailableLater(SpdyHeaderBlock headers,
+                                                  size_t frame_len);
+  void NotifyHandleOfInitialHeadersAvailable(SpdyHeaderBlock headers,
+                                             size_t frame_len);
+  void NotifyHandleOfTrailingHeadersAvailableLater(SpdyHeaderBlock headers,
+                                                   size_t frame_len);
+  void NotifyHandleOfTrailingHeadersAvailable(SpdyHeaderBlock headers,
+                                              size_t frame_len);
+  void NotifyHandleOfDataAvailableLater();
+  void NotifyHandleOfDataAvailable();
 
   NetLogWithSource net_log_;
-  Delegate* delegate_;
+  Handle* handle_;
 
   bool headers_delivered_;
 
diff --git a/net/quic/chromium/quic_chromium_client_stream_test.cc b/net/quic/chromium/quic_chromium_client_stream_test.cc
index 4efac81..0a3888a 100644
--- a/net/quic/chromium/quic_chromium_client_stream_test.cc
+++ b/net/quic/chromium/quic_chromium_client_stream_test.cc
@@ -206,7 +206,7 @@
     stream_ = new QuicChromiumClientStream(kTestStreamId, &session_,
                                            NetLogWithSource());
     session_.ActivateStream(base::WrapUnique(stream_));
-    stream_->SetDelegate(&delegate_);
+    handle_ = stream_->CreateHandle(&delegate_);
   }
 
   void InitializeHeaders() {
@@ -278,8 +278,10 @@
   }
 
   QuicCryptoClientConfig crypto_config_;
-  testing::StrictMock<MockDelegate> delegate2_;
+  std::unique_ptr<QuicChromiumClientStream::Handle> handle_;
   testing::StrictMock<MockDelegate> delegate_;
+  std::unique_ptr<QuicChromiumClientStream::Handle> handle2_;
+  testing::StrictMock<MockDelegate> delegate2_;
   MockQuicConnectionHelper helper_;
   MockAlarmFactory alarm_factory_;
   MockQuicClientSessionBase session_;
@@ -292,6 +294,105 @@
                         QuicChromiumClientStreamTest,
                         ::testing::ValuesIn(AllSupportedVersions()));
 
+TEST_P(QuicChromiumClientStreamTest, Handle) {
+  EXPECT_TRUE(handle_->IsOpen());
+  EXPECT_EQ(kTestStreamId, handle_->id());
+  EXPECT_EQ(QUIC_NO_ERROR, handle_->connection_error());
+  EXPECT_EQ(QUIC_STREAM_NO_ERROR, handle_->stream_error());
+  EXPECT_TRUE(handle_->IsFirstStream());
+  EXPECT_FALSE(handle_->IsDoneReading());
+  EXPECT_FALSE(handle_->fin_sent());
+  EXPECT_FALSE(handle_->fin_received());
+  EXPECT_EQ(0u, handle_->stream_bytes_read());
+  EXPECT_EQ(0u, handle_->stream_bytes_written());
+  EXPECT_EQ(0u, handle_->NumBytesConsumed());
+
+  InitializeHeaders();
+  QuicStreamOffset offset = 0;
+  ProcessHeadersFull(headers_);
+  QuicStreamFrame frame2(kTestStreamId, true, offset, QuicStringPiece());
+  EXPECT_CALL(delegate_, OnClose());
+  stream_->OnStreamFrame(frame2);
+  EXPECT_TRUE(handle_->fin_received());
+  handle_->OnFinRead();
+
+  const char kData1[] = "hello world";
+  const size_t kDataLen = arraysize(kData1);
+
+  // All data written.
+  EXPECT_CALL(session_, WritevData(stream_, stream_->id(), _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(kDataLen, true)));
+  TestCompletionCallback callback;
+  EXPECT_EQ(OK, handle_->WriteStreamData(QuicStringPiece(kData1, kDataLen),
+                                         true, callback.callback()));
+
+  EXPECT_FALSE(handle_->IsOpen());
+  EXPECT_EQ(kTestStreamId, handle_->id());
+  EXPECT_EQ(QUIC_NO_ERROR, handle_->connection_error());
+  EXPECT_EQ(QUIC_STREAM_NO_ERROR, handle_->stream_error());
+  EXPECT_TRUE(handle_->IsFirstStream());
+  EXPECT_TRUE(handle_->IsDoneReading());
+  EXPECT_TRUE(handle_->fin_sent());
+  EXPECT_TRUE(handle_->fin_received());
+  EXPECT_EQ(0u, handle_->stream_bytes_read());
+  EXPECT_EQ(kDataLen, handle_->stream_bytes_written());
+  EXPECT_EQ(0u, handle_->NumBytesConsumed());
+
+  EXPECT_EQ(ERR_CONNECTION_CLOSED,
+            handle_->WriteStreamData(QuicStringPiece(kData1, kDataLen), true,
+                                     callback.callback()));
+
+  std::vector<scoped_refptr<IOBuffer>> buffers = {
+      scoped_refptr<IOBuffer>(new IOBuffer(10))};
+  std::vector<int> lengths = {10};
+  EXPECT_EQ(
+      ERR_CONNECTION_CLOSED,
+      handle_->WritevStreamData(buffers, lengths, true, callback.callback()));
+
+  SpdyHeaderBlock headers;
+  EXPECT_EQ(0u, handle_->WriteHeaders(std::move(headers), true, nullptr));
+}
+
+TEST_P(QuicChromiumClientStreamTest, HandleAfterConnectionClose) {
+  // Verify that the delegate's OnClose is called after closing the connection.
+  EXPECT_CALL(delegate_, OnClose());
+  EXPECT_CALL(session_,
+              SendRstStream(kTestStreamId, QUIC_RST_ACKNOWLEDGEMENT, 0));
+  stream_->OnConnectionClosed(QUIC_INVALID_FRAME_DATA,
+                              ConnectionCloseSource::FROM_PEER);
+
+  EXPECT_FALSE(handle_->IsOpen());
+  EXPECT_EQ(QUIC_INVALID_FRAME_DATA, handle_->connection_error());
+}
+
+TEST_P(QuicChromiumClientStreamTest, HandleAfterStreamReset) {
+  // Verify that the delegate's OnClose is called after the stream is reset,
+  // but that the Handle still behaves correctly.
+  EXPECT_CALL(delegate_, OnClose());
+  QuicRstStreamFrame rst(kTestStreamId, QUIC_STREAM_CANCELLED, 0);
+  EXPECT_CALL(session_,
+              SendRstStream(kTestStreamId, QUIC_RST_ACKNOWLEDGEMENT, 0));
+  stream_->OnStreamReset(rst);
+
+  EXPECT_FALSE(handle_->IsOpen());
+  EXPECT_EQ(QUIC_STREAM_CANCELLED, handle_->stream_error());
+}
+
+TEST_P(QuicChromiumClientStreamTest, HandleAfterClearDelegate) {
+  EXPECT_TRUE(handle_->IsOpen());
+  handle_->ClearDelegate();
+
+  // Verify that the delegate's OnClose is not called after ClearDelegate.
+  EXPECT_CALL(delegate_, OnClose()).Times(0);
+  QuicRstStreamFrame rst(kTestStreamId, QUIC_STREAM_CANCELLED, 0);
+  EXPECT_CALL(session_,
+              SendRstStream(kTestStreamId, QUIC_RST_ACKNOWLEDGEMENT, 0));
+  stream_->OnStreamReset(rst);
+
+  EXPECT_FALSE(handle_->IsOpen());
+  EXPECT_EQ(QUIC_STREAM_CANCELLED, handle_->stream_error());
+}
+
 TEST_P(QuicChromiumClientStreamTest, OnFinRead) {
   InitializeHeaders();
   QuicStreamOffset offset = 0;
@@ -359,10 +460,10 @@
 }
 
 TEST_P(QuicChromiumClientStreamTest, OnError) {
-  EXPECT_CALL(delegate_, OnError(ERR_INTERNET_DISCONNECTED));
+  EXPECT_CALL(delegate_, OnError(ERR_INTERNET_DISCONNECTED)).Times(1);
 
   stream_->OnError(ERR_INTERNET_DISCONNECTED);
-  EXPECT_FALSE(stream_->GetDelegate());
+  stream_->OnError(ERR_INTERNET_DISCONNECTED);
 }
 
 TEST_P(QuicChromiumClientStreamTest, OnTrailers) {
@@ -640,7 +741,7 @@
   // Now set the delegate and verify that the headers are delivered.
   EXPECT_CALL(delegate2_, OnInitialHeadersAvailableMock(
                               _, header_list.uncompressed_header_bytes()));
-  stream2->SetDelegate(&delegate2_);
+  handle2_ = stream2->CreateHandle(&delegate2_);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(headers_, delegate2_.headers_);
 
@@ -672,7 +773,7 @@
   // not the data, which needs to be read explicitly.
   EXPECT_CALL(delegate2_, OnInitialHeadersAvailableMock(
                               _, header_list.uncompressed_header_bytes()));
-  stream2->SetDelegate(&delegate2_);
+  handle2_ = stream2->CreateHandle(&delegate2_);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(headers_, delegate2_.headers_);
   base::RunLoop().RunUntilIdle();
diff --git a/net/quic/chromium/quic_http_stream.cc b/net/quic/chromium/quic_http_stream.cc
index e28f01f..095f86b 100644
--- a/net/quic/chromium/quic_http_stream.cc
+++ b/net/quic/chromium/quic_http_stream.cc
@@ -108,8 +108,8 @@
 void QuicHttpStream::OnRendezvousResult(QuicSpdyStream* stream) {
   push_handle_ = nullptr;
   if (stream) {
-    stream_ = static_cast<QuicChromiumClientStream*>(stream);
-    stream_->SetDelegate(this);
+    stream_ =
+        static_cast<QuicChromiumClientStream*>(stream)->CreateHandle(this);
   }
 
   // callback_ should only be non-null in the case of asynchronous
@@ -358,7 +358,7 @@
   SaveResponseStatus();
   // Note: the not_reusable flag has no meaning for QUIC streams.
   if (stream_) {
-    stream_->SetDelegate(nullptr);
+    stream_->ClearDelegate();
     stream_->Reset(QUIC_STREAM_CANCELLED);
   }
   ResetStream();
@@ -378,10 +378,9 @@
   // bytes. Change this to include QUIC overhead as well.
   int64_t total_received_bytes = headers_bytes_received_;
   if (stream_) {
-    DCHECK_LE(stream_->sequencer()->NumBytesConsumed(),
-              stream_->stream_bytes_read());
+    DCHECK_LE(stream_->NumBytesConsumed(), stream_->stream_bytes_read());
     // Only count the uniquely received bytes.
-    total_received_bytes += stream_->sequencer()->NumBytesConsumed();
+    total_received_bytes += stream_->NumBytesConsumed();
   } else {
     total_received_bytes += closed_stream_received_bytes_;
   }
@@ -671,7 +670,7 @@
   // |rv| is the result of read from the request body from the last call to
   // DoSendBody().
   if (rv < 0) {
-    stream_->SetDelegate(nullptr);
+    stream_->ClearDelegate();
     stream_->Reset(QUIC_ERROR_PROCESSING_STREAM);
     ResetStream();
     return rv;
@@ -764,7 +763,7 @@
   if (null_stream)
     return rv;
   if (stream_->IsDoneReading()) {
-    stream_->SetDelegate(nullptr);
+    stream_->ClearDelegate();
     stream_->OnFinRead();
     SetResponseStatus(OK);
     ResetStream();
@@ -779,12 +778,12 @@
   }
   if (!stream_)
     return;
-  DCHECK_LE(stream_->sequencer()->NumBytesConsumed(),
-            stream_->stream_bytes_read());
+  DCHECK_LE(stream_->NumBytesConsumed(), stream_->stream_bytes_read());
   // Only count the uniquely received bytes.
-  closed_stream_received_bytes_ = stream_->sequencer()->NumBytesConsumed();
+  closed_stream_received_bytes_ = stream_->NumBytesConsumed();
   closed_stream_sent_bytes_ = stream_->stream_bytes_written();
   closed_is_first_stream_ = stream_->IsFirstStream();
+  stream_->ClearDelegate();
   stream_ = nullptr;
 
   // If |request_body_stream_| is non-NULL, Reset it, to abort any in progress
diff --git a/net/quic/chromium/quic_http_stream.h b/net/quic/chromium/quic_http_stream.h
index fe240f8..eb49000 100644
--- a/net/quic/chromium/quic_http_stream.h
+++ b/net/quic/chromium/quic_http_stream.h
@@ -81,6 +81,9 @@
   bool CheckVary(const SpdyHeaderBlock& client_request,
                  const SpdyHeaderBlock& promise_request,
                  const SpdyHeaderBlock& promise_response) override;
+  // TODO(rch): QuicClientPushPromiseIndex::Delegate is part of shared code.
+  // Figure out how to make the QuicHttpStream receive a Handle in this
+  // case instead of a QuicSpdyStream.
   void OnRendezvousResult(QuicSpdyStream* stream) override;
 
   static HttpResponseInfo::ConnectionInfo ConnectionInfoFromQuicVersion(
@@ -152,7 +155,7 @@
 
   HttpServerProperties* http_server_properties_;  // Unowned.
 
-  QuicChromiumClientStream* stream_;  // Non-owning.
+  std::unique_ptr<QuicChromiumClientStream::Handle> stream_;
 
   // The following three fields are all owned by the caller and must
   // outlive this object, according to the HttpStream contract.
diff --git a/net/quic/chromium/quic_http_stream_test.cc b/net/quic/chromium/quic_http_stream_test.cc
index 1b8af58..ff43b73 100644
--- a/net/quic/chromium/quic_http_stream_test.cc
+++ b/net/quic/chromium/quic_http_stream_test.cc
@@ -178,9 +178,9 @@
 
 class QuicHttpStreamPeer {
  public:
-  static QuicChromiumClientStream* GetQuicChromiumClientStream(
+  static QuicChromiumClientStream::Handle* GetQuicChromiumClientStream(
       QuicHttpStream* stream) {
-    return stream->stream_;
+    return stream->stream_.get();
   }
 };
 
@@ -536,7 +536,7 @@
 
   void ReceivePromise(QuicStreamId id) {
     auto headers = AsHeaderList(push_promise_);
-    QuicChromiumClientStream* stream =
+    QuicChromiumClientStream::Handle* stream =
         QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
     stream->OnPromiseHeaderList(id, headers.uncompressed_header_bytes(),
                                 headers);
@@ -631,7 +631,7 @@
   EXPECT_EQ(OK,
             stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
                                       net_log_.bound(), callback_.callback()));
-  QuicChromiumClientStream* client_stream =
+  QuicChromiumClientStream::Handle* client_stream =
       QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
   EXPECT_FALSE(client_stream->can_migrate());
 }
@@ -1466,7 +1466,7 @@
                                           callback_.callback()));
 
   // Check that priority is highest.
-  QuicChromiumClientStream* reliable_stream =
+  QuicChromiumClientStream::Handle* reliable_stream =
       QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
   DCHECK(reliable_stream);
   DCHECK_EQ(kV3HighestPriority, reliable_stream->priority());
@@ -1517,7 +1517,7 @@
                                           callback_.callback()));
 
   // Check that priority is highest.
-  QuicChromiumClientStream* reliable_stream =
+  QuicChromiumClientStream::Handle* reliable_stream =
       QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
   DCHECK(reliable_stream);
   QuicChromiumClientStream::Delegate* delegate = reliable_stream->GetDelegate();
@@ -1526,9 +1526,8 @@
 
   // Set Delegate to nullptr and make sure Priority returns highest
   // priority.
-  reliable_stream->SetDelegate(nullptr);
+  reliable_stream->ClearDelegate();
   DCHECK_EQ(kV3HighestPriority, reliable_stream->priority());
-  reliable_stream->SetDelegate(delegate);
 
   EXPECT_EQ(0, stream_->GetTotalSentBytes());
   EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
diff --git a/net/reporting/reporting_cache.cc b/net/reporting/reporting_cache.cc
index b3b6010..b8034c8 100644
--- a/net/reporting/reporting_cache.cc
+++ b/net/reporting/reporting_cache.cc
@@ -61,6 +61,18 @@
       reports_.insert(std::make_pair(report.get(), std::move(report)));
   DCHECK(inserted.second);
 
+  if (reports_.size() > context_->policy().max_report_count) {
+    // There should be at most one extra report (the one added above).
+    DCHECK_EQ(context_->policy().max_report_count + 1, reports_.size());
+    const ReportingReport* to_evict = FindReportToEvict();
+    DCHECK_NE(nullptr, to_evict);
+    // The newly-added report isn't pending, so even if all other reports are
+    // pending, the cache should have a report to evict.
+    DCHECK(!base::ContainsKey(pending_reports_, to_evict));
+    size_t erased = reports_.erase(to_evict);
+    DCHECK_EQ(1u, erased);
+  }
+
   context_->NotifyCacheUpdated();
 }
 
@@ -233,6 +245,21 @@
   context_->NotifyCacheUpdated();
 }
 
+const ReportingReport* ReportingCache::FindReportToEvict() const {
+  const ReportingReport* earliest_queued = nullptr;
+
+  for (const auto& it : reports_) {
+    const ReportingReport* report = it.first;
+    if (base::ContainsKey(pending_reports_, report))
+      continue;
+    if (!earliest_queued || report->queued < earliest_queued->queued) {
+      earliest_queued = report;
+    }
+  }
+
+  return earliest_queued;
+}
+
 void ReportingCache::MaybeAddWildcardClient(const ReportingClient* client) {
   if (client->subdomains != ReportingClient::Subdomains::INCLUDE)
     return;
diff --git a/net/reporting/reporting_cache.h b/net/reporting/reporting_cache.h
index 30ad0429..122e0c81 100644
--- a/net/reporting/reporting_cache.h
+++ b/net/reporting/reporting_cache.h
@@ -159,6 +159,8 @@
   }
 
  private:
+  const ReportingReport* FindReportToEvict() const;
+
   void MaybeAddWildcardClient(const ReportingClient* client);
 
   void MaybeRemoveWildcardClient(const ReportingClient* client);
diff --git a/net/reporting/reporting_cache_unittest.cc b/net/reporting/reporting_cache_unittest.cc
index 7302340..19229db2 100644
--- a/net/reporting/reporting_cache_unittest.cc
+++ b/net/reporting/reporting_cache_unittest.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "base/memory/ptr_util.h"
+#include "base/test/simple_test_tick_clock.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "net/reporting/reporting_client.h"
@@ -42,6 +43,12 @@
 
   TestReportingObserver* observer() { return &observer_; }
 
+  size_t report_count() {
+    std::vector<const ReportingReport*> reports;
+    cache()->GetReports(&reports);
+    return reports.size();
+  }
+
   const GURL kUrl1_ = GURL("https://origin1/path");
   const url::Origin kOrigin1_ = url::Origin(GURL("https://origin1/"));
   const url::Origin kOrigin2_ = url::Origin(GURL("https://origin2/"));
@@ -400,5 +407,65 @@
   EXPECT_EQ(kSuperOrigin, clients[0]->origin);
 }
 
+TEST_F(ReportingCacheTest, EvictOldest) {
+  ASSERT_LT(0u, policy().max_report_count);
+  ASSERT_GT(std::numeric_limits<size_t>::max(), policy().max_report_count);
+
+  base::TimeTicks earliest_queued = tick_clock()->NowTicks();
+
+  // Enqueue the maximum number of reports, spaced apart in time.
+  for (size_t i = 0; i < policy().max_report_count; ++i) {
+    cache()->AddReport(kUrl1_, kGroup1_, kType_,
+                       base::MakeUnique<base::DictionaryValue>(),
+                       tick_clock()->NowTicks(), 0);
+    tick_clock()->Advance(base::TimeDelta::FromMinutes(1));
+  }
+  EXPECT_EQ(policy().max_report_count, report_count());
+
+  // Add one more report to force the cache to evict one.
+  cache()->AddReport(kUrl1_, kGroup1_, kType_,
+                     base::MakeUnique<base::DictionaryValue>(), kNow_, 0);
+
+  // Make sure the cache evicted a report to make room for the new one, and make
+  // sure the report evicted was the earliest-queued one.
+  std::vector<const ReportingReport*> reports;
+  cache()->GetReports(&reports);
+  EXPECT_EQ(policy().max_report_count, reports.size());
+  for (const ReportingReport* report : reports)
+    EXPECT_NE(earliest_queued, report->queued);
+}
+
+TEST_F(ReportingCacheTest, DontEvictPendingReports) {
+  ASSERT_LT(0u, policy().max_report_count);
+  ASSERT_GT(std::numeric_limits<size_t>::max(), policy().max_report_count);
+
+  // Enqueue the maximum number of reports, spaced apart in time.
+  for (size_t i = 0; i < policy().max_report_count; ++i) {
+    cache()->AddReport(kUrl1_, kGroup1_, kType_,
+                       base::MakeUnique<base::DictionaryValue>(),
+                       tick_clock()->NowTicks(), 0);
+    tick_clock()->Advance(base::TimeDelta::FromMinutes(1));
+  }
+  EXPECT_EQ(policy().max_report_count, report_count());
+
+  // Mark all of the queued reports pending.
+  std::vector<const ReportingReport*> queued_reports;
+  cache()->GetReports(&queued_reports);
+  cache()->SetReportsPending(queued_reports);
+
+  // Add one more report to force the cache to evict one. Since the cache has
+  // only pending reports, it will be forced to evict the *new* report!
+  cache()->AddReport(kUrl1_, kGroup1_, kType_,
+                     base::MakeUnique<base::DictionaryValue>(), kNow_, 0);
+
+  // Make sure the cache evicted a report, and make sure the report evicted was
+  // the new, non-pending one.
+  std::vector<const ReportingReport*> reports;
+  cache()->GetReports(&reports);
+  EXPECT_EQ(policy().max_report_count, reports.size());
+  for (const ReportingReport* report : reports)
+    EXPECT_TRUE(cache()->IsReportPendingForTesting(report));
+}
+
 }  // namespace
 }  // namespace net
diff --git a/net/reporting/reporting_policy.cc b/net/reporting/reporting_policy.cc
index e439fd7..11f58fd 100644
--- a/net/reporting/reporting_policy.cc
+++ b/net/reporting/reporting_policy.cc
@@ -9,7 +9,8 @@
 namespace net {
 
 ReportingPolicy::ReportingPolicy()
-    : delivery_interval(base::TimeDelta::FromMinutes(1)),
+    : max_report_count(100u),
+      delivery_interval(base::TimeDelta::FromMinutes(1)),
       persistence_interval(base::TimeDelta::FromMinutes(1)),
       persist_reports_across_restarts(false),
       persist_clients_across_restarts(true),
@@ -28,7 +29,8 @@
 }
 
 ReportingPolicy::ReportingPolicy(const ReportingPolicy& other)
-    : delivery_interval(base::TimeDelta::FromMinutes(1)),
+    : max_report_count(other.max_report_count),
+      delivery_interval(other.delivery_interval),
       endpoint_backoff_policy(other.endpoint_backoff_policy),
       persistence_interval(other.persistence_interval),
       persist_reports_across_restarts(other.persist_reports_across_restarts),
diff --git a/net/reporting/reporting_policy.h b/net/reporting/reporting_policy.h
index c400bfff..98e1a4a 100644
--- a/net/reporting/reporting_policy.h
+++ b/net/reporting/reporting_policy.h
@@ -18,6 +18,9 @@
   ReportingPolicy(const ReportingPolicy& other);
   ~ReportingPolicy();
 
+  // Maximum number of reports to queue before evicting the oldest.
+  size_t max_report_count;
+
   // Minimum interval at which to attempt delivery of queued reports.
   base::TimeDelta delivery_interval;
 
diff --git a/remoting/client/chromoting_client_runtime.cc b/remoting/client/chromoting_client_runtime.cc
index 9b12aace..ea71d70 100644
--- a/remoting/client/chromoting_client_runtime.cc
+++ b/remoting/client/chromoting_client_runtime.cc
@@ -84,7 +84,6 @@
 void ChromotingClientRuntime::SetDelegate(
     ChromotingClientRuntime::Delegate* delegate) {
   delegate_ = delegate;
-  delegate_->RequestAuthTokenForLogger();
 }
 
 void ChromotingClientRuntime::CreateLogWriter() {
diff --git a/remoting/client/ios/BUILD.gn b/remoting/client/ios/BUILD.gn
index 365b72b..91d7a2d1 100644
--- a/remoting/client/ios/BUILD.gn
+++ b/remoting/client/ios/BUILD.gn
@@ -31,6 +31,8 @@
     "key_input.h",
     "key_input.mm",
     "key_map_us.h",
+    "keychain_wrapper.h",
+    "keychain_wrapper.mm",
   ]
 
   if (!is_chrome_branded) {
diff --git a/remoting/client/ios/app/app_delegate.mm b/remoting/client/ios/app/app_delegate.mm
index ae4b24f..7dc1369 100644
--- a/remoting/client/ios/app/app_delegate.mm
+++ b/remoting/client/ios/app/app_delegate.mm
@@ -13,6 +13,7 @@
 #include "ui/base/resource/resource_bundle.h"
 
 #import "remoting/client/ios/app/remoting_view_controller.h"
+#import "remoting/client/ios/facade/remoting_authentication.h"
 #import "remoting/client/ios/facade/remoting_service.h"
 
 @implementation AppDelegate
@@ -45,7 +46,7 @@
   }
   NSString* authorizationCode = [components objectForKey:@"code"];
 
-  [[RemotingService SharedInstance]
+  [[RemotingService SharedInstance].authentication
       authenticateWithAuthorizationCode:authorizationCode];
 
   [self launchRemotingViewController];
diff --git a/remoting/client/ios/app/remoting_settings_view_controller.mm b/remoting/client/ios/app/remoting_settings_view_controller.mm
index da3c0cd8..1db1db0 100644
--- a/remoting/client/ios/app/remoting_settings_view_controller.mm
+++ b/remoting/client/ios/app/remoting_settings_view_controller.mm
@@ -10,6 +10,8 @@
 
 #import "ios/third_party/material_components_ios/src/components/AppBar/src/MaterialAppBar.h"
 #import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
+#import "remoting/client/ios/facade/remoting_authentication.h"
+#import "remoting/client/ios/facade/remoting_service.h"
 
 #include "base/strings/stringprintf.h"
 #include "google_apis/google_api_keys.h"
@@ -113,7 +115,7 @@
   self.styler.cellStyle = MDCCollectionViewCellStyleCard;
 
   _content = [NSMutableArray array];
-  [_content addObject:@[ @"Login" ]];
+  [_content addObject:@[ @"Login", @"Logout" ]];
 }
 
 #pragma mark - UICollectionViewDataSource
@@ -146,10 +148,17 @@
                forControlEvents:UIControlEventTouchUpInside];
     accessCodeButton.translatesAutoresizingMaskIntoConstraints = NO;
     cell.accessoryView = accessCodeButton;
-  } else {
-    UISwitch* editingSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
-    cell.accessoryView = editingSwitch;
+  } else if (indexPath.section == 0 && indexPath.item == 1) {
+    MDCRaisedButton* logoutButton = [[MDCRaisedButton alloc] init];
+    [logoutButton setTitle:@"Logout" forState:UIControlStateNormal];
+    [logoutButton sizeToFit];
+    [logoutButton addTarget:self
+                     action:@selector(didTapLogout:)
+           forControlEvents:UIControlEventTouchUpInside];
+    logoutButton.translatesAutoresizingMaskIntoConstraints = NO;
+    cell.accessoryView = logoutButton;
   }
+
   return cell;
 }
 
@@ -192,4 +201,8 @@
   [[UIApplication sharedApplication] openURL:[NSURL URLWithString:authUri]];
 }
 
+- (void)didTapLogout:(id)sender {
+  [[RemotingService SharedInstance].authentication logout];
+}
+
 @end
diff --git a/remoting/client/ios/app/remoting_view_controller.h b/remoting/client/ios/app/remoting_view_controller.h
index fcf4bdd..f3188d7 100644
--- a/remoting/client/ios/app/remoting_view_controller.h
+++ b/remoting/client/ios/app/remoting_view_controller.h
@@ -8,9 +8,7 @@
 #import "ios/third_party/material_components_ios/src/components/FlexibleHeader/src/MaterialFlexibleHeader.h"
 #import "remoting/client/ios/facade/remoting_service.h"
 
-@interface RemotingViewController
-    : MDCFlexibleHeaderContainerViewController<RemotingAuthenticationDelegate,
-                                               RemotingHostListDelegate>
+@interface RemotingViewController : MDCFlexibleHeaderContainerViewController
 
 @end
 
diff --git a/remoting/client/ios/app/remoting_view_controller.mm b/remoting/client/ios/app/remoting_view_controller.mm
index 25bb218..bb3c50f 100644
--- a/remoting/client/ios/app/remoting_view_controller.mm
+++ b/remoting/client/ios/app/remoting_view_controller.mm
@@ -18,6 +18,7 @@
 #import "remoting/client/ios/app/host_view_controller.h"
 #import "remoting/client/ios/app/remoting_settings_view_controller.h"
 #import "remoting/client/ios/domain/client_session_details.h"
+#import "remoting/client/ios/facade/remoting_authentication.h"
 #import "remoting/client/ios/facade/remoting_service.h"
 #import "remoting/client/ios/session/remoting_client.h"
 
@@ -59,7 +60,6 @@
   self = [super initWithContentViewController:collectionVC];
   if (self) {
     _remotingService = [RemotingService SharedInstance];
-    [_remotingService setAuthenticationDelegate:self];
 
     _collectionViewController = collectionVC;
     _collectionViewController.flexHeaderContainerViewController = self;
@@ -100,10 +100,28 @@
 - (void)viewDidLoad {
   [super viewDidLoad];
   [_appBar addSubviewsToParent];
+
+  [[NSNotificationCenter defaultCenter]
+      addObserver:self
+         selector:@selector(hostsDidUpdateNotification:)
+             name:kHostsDidUpdate
+           object:nil];
+  [[NSNotificationCenter defaultCenter]
+      addObserver:self
+         selector:@selector(userDidUpdateNotification:)
+             name:kUserDidUpdate
+           object:nil];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+  [super viewWillAppear:animated];
+
+  [self nowAuthenticated:_remotingService.authentication.user.isAuthenticated];
   [self presentStatus];
 }
 
 - (void)viewDidAppear:(BOOL)animated {
+  [super viewDidAppear:animated];
   if (!_isAuthenticated) {
     // TODO(nicholss): This is used as a demo of the app functionality for the
     // moment but the real app will force the login flow if unauthenticated.
@@ -112,6 +130,8 @@
     MDCSnackbarMessage* message = [[MDCSnackbarMessage alloc] init];
     message.text = @"Please login.";
     [MDCSnackbarManager showMessage:message];
+  } else {
+    [_remotingService requestHostListFetch];
   }
 }
 
@@ -129,6 +149,16 @@
                  collectionHeight);
 }
 
+#pragma mark - Remoting Service Notifications
+
+- (void)hostsDidUpdateNotification:(NSNotification*)notification {
+  [_collectionViewController.collectionView reloadData];
+}
+
+- (void)userDidUpdateNotification:(NSNotification*)notification {
+  [self nowAuthenticated:_remotingService.authentication.user.isAuthenticated];
+}
+
 #pragma mark - RemotingAuthenticationDelegate
 
 - (void)nowAuthenticated:(BOOL)authenticated {
@@ -136,12 +166,10 @@
     MDCSnackbarMessage* message = [[MDCSnackbarMessage alloc] init];
     message.text = @"Logged In!";
     [MDCSnackbarManager showMessage:message];
-    [_remotingService setHostListDelegate:self];
   } else {
     MDCSnackbarMessage* message = [[MDCSnackbarMessage alloc] init];
     message.text = @"Not logged in.";
     [MDCSnackbarManager showMessage:message];
-    [_remotingService setHostListDelegate:nil];
   }
   _isAuthenticated = authenticated;
   [_collectionViewController.collectionView reloadData];
@@ -176,7 +204,7 @@
            completion:(void (^)())completionBlock {
   _client = [[RemotingClient alloc] init];
 
-  [_remotingService
+  [_remotingService.authentication
       callbackWithAccessToken:base::BindBlockArc(^(
                                   remoting::OAuthTokenGetter::Status status,
                                   const std::string& user_email,
@@ -198,13 +226,11 @@
 }
 
 - (NSInteger)getHostCount {
-  NSArray<HostInfo*>* hosts = [_remotingService getHosts];
-  return [hosts count];
+  return _remotingService.hosts.count;
 }
 
 - (HostInfo*)getHostAtIndexPath:(NSIndexPath*)path {
-  NSArray<HostInfo*>* hosts = [_remotingService getHosts];
-  return hosts[path.row];
+  return _remotingService.hosts[path.row];
 }
 
 #pragma mark - UIViewControllerTransitioningDelegate
@@ -244,9 +270,9 @@
 }
 
 - (void)didSelectRefresh {
-  // TODO(nicholss) implement this.
-  NSLog(@"Should refresh...");
-  _dialogTransitionController = [[MDCDialogTransitionController alloc] init];
+  // TODO(nicholss): Might want to rate limit this. Maybe remoting service
+  // controls that.
+  [_remotingService requestHostListFetch];
 }
 
 - (void)didSelectSettings {
@@ -260,9 +286,9 @@
 - (void)presentStatus {
   MDCSnackbarMessage* message = [[MDCSnackbarMessage alloc] init];
   if (_isAuthenticated) {
-    UserInfo* user = [_remotingService getUser];
     message.text = [NSString
-        stringWithFormat:@"Currently signed in as %@.", [user userEmail]];
+        stringWithFormat:@"Currently signed in as %@.",
+                         _remotingService.authentication.user.userEmail];
     [MDCSnackbarManager showMessage:message];
   }
 }
diff --git a/remoting/client/ios/domain/host_info.h b/remoting/client/ios/domain/host_info.h
index a897f8b..ad646a1b 100644
--- a/remoting/client/ios/domain/host_info.h
+++ b/remoting/client/ios/domain/host_info.h
@@ -23,6 +23,8 @@
 // True when |status| is @"ONLINE", anything else is False.
 @property(nonatomic, readonly) bool isOnline;
 
+// Convert a json blob into a |HostInfo| object. Most useful for test.
+// TODO(nicholss): Might move this out into a catagory.
 + (NSMutableArray<HostInfo*>*)parseListFromJSON:(NSMutableData*)data;
 
 // First consider if |isOnline| is greater than anything else, then consider by
diff --git a/remoting/client/ios/domain/user_info.h b/remoting/client/ios/domain/user_info.h
index 5bbae71..8c463317 100644
--- a/remoting/client/ios/domain/user_info.h
+++ b/remoting/client/ios/domain/user_info.h
@@ -14,9 +14,15 @@
 @property(nonatomic, copy) NSString* userId;
 @property(nonatomic, copy) NSString* userFullName;
 @property(nonatomic, copy) NSString* userEmail;
+@property(nonatomic, copy) NSString* refreshToken;
 
+// Convert a json blob into a |UserInfo| object. Most useful for test.
+// TODO(nicholss): Might move this out into a catagory.
 + (UserInfo*)parseListFromJSON:(NSMutableData*)data;
 
+// This returns the authenticated state of the this user info object.
+- (BOOL)isAuthenticated;
+// Compare two |UserInfo| objects.
 - (NSComparisonResult)compare:(UserInfo*)user;
 
 @end
diff --git a/remoting/client/ios/domain/user_info.mm b/remoting/client/ios/domain/user_info.mm
index ece9064..3257fd22 100644
--- a/remoting/client/ios/domain/user_info.mm
+++ b/remoting/client/ios/domain/user_info.mm
@@ -13,6 +13,7 @@
 @synthesize userId = _userId;
 @synthesize userFullName = _userFullName;
 @synthesize userEmail = _userEmail;
+@synthesize refreshToken = _refreshToken;
 
 // Parse jsonData into Host list.
 + (UserInfo*)parseListFromJSON:(NSMutableData*)data {
@@ -26,12 +27,26 @@
   user.userId = [json objectForKey:@"userId"];
   user.userFullName = [json objectForKey:@"userFullName"];
   user.userEmail = [json objectForKey:@"userEmail"];
+  user.refreshToken = [json objectForKey:@"refreshToken"];
 
   return user;
 }
 
+- (BOOL)isAuthenticated {
+  if (_userEmail && _userEmail.length > 0 && _refreshToken &&
+      _refreshToken.length > 0) {
+    return YES;
+  }
+  return NO;
+}
+
 - (NSComparisonResult)compare:(UserInfo*)user {
   return [self.userId compare:user.userId];
 }
 
+- (NSString*)description {
+  return [NSString stringWithFormat:@"UserInfo: userEmail=%@ refreshToken=%@",
+                                    _userEmail, _refreshToken];
+}
+
 @end
diff --git a/remoting/client/ios/facade/BUILD.gn b/remoting/client/ios/facade/BUILD.gn
index db632c7..a02d423 100644
--- a/remoting/client/ios/facade/BUILD.gn
+++ b/remoting/client/ios/facade/BUILD.gn
@@ -14,6 +14,8 @@
     "host_list_fetcher.h",
     "ios_client_runtime_delegate.h",
     "ios_client_runtime_delegate.mm",
+    "remoting_authentication.h",
+    "remoting_authentication.mm",
     "remoting_service.h",
     "remoting_service.mm",
   ]
diff --git a/remoting/client/ios/facade/host_list_fetcher.cc b/remoting/client/ios/facade/host_list_fetcher.cc
index 6c1bb00..e7c4f38 100644
--- a/remoting/client/ios/facade/host_list_fetcher.cc
+++ b/remoting/client/ios/facade/host_list_fetcher.cc
@@ -29,6 +29,8 @@
 // that. For the moment it will work to make progress in the app.
 void HostListFetcher::RetrieveHostlist(const std::string& access_token,
                                        const HostlistCallback& callback) {
+  // TODO(nicholss): There is a bug here if two host list fetches are happening
+  // at the same time there will be a dcheck thrown. Fix this for release.
   DCHECK(!access_token.empty());
   DCHECK(callback);
   DCHECK(!hostlist_callback_);
diff --git a/remoting/client/ios/facade/ios_client_runtime_delegate.mm b/remoting/client/ios/facade/ios_client_runtime_delegate.mm
index 8bd90fc0..b773b2a 100644
--- a/remoting/client/ios/facade/ios_client_runtime_delegate.mm
+++ b/remoting/client/ios/facade/ios_client_runtime_delegate.mm
@@ -9,6 +9,7 @@
 #include "remoting/client/ios/facade/ios_client_runtime_delegate.h"
 
 #import "base/mac/bind_objc_block.h"
+#import "remoting/client/ios/facade/remoting_authentication.h"
 #import "remoting/client/ios/facade/remoting_service.h"
 
 #include "base/bind.h"
@@ -43,18 +44,24 @@
                    base::Unretained(this)));
     return;
   }
-  // TODO(nicholss): Need to work out how to provide the logger with auth token
-  // at the correct time in the app. This was hanging the app bootup. Removing
-  // for now but this needs to happen the correct way soon.
-  // if ([[RemotingService SharedInstance] getUser]) {
-  //   [[RemotingService SharedInstance]
-  //   callbackWithAccessToken:base::BindBlockArc(
-  //       ^(remoting::OAuthTokenGetter::Status status,
-  //         const std::string& user_email, const std::string& access_token) {
-  //         // TODO(nicholss): Check status.
-  //           runtime_->log_writer()->SetAuthToken(access_token);
-  //       })];
-  // }
+  if ([[RemotingService SharedInstance].authentication.user isAuthenticated]) {
+    [[RemotingService SharedInstance].authentication
+        callbackWithAccessToken:base::BindBlockArc(^(
+                                    remoting::OAuthTokenGetter::Status status,
+                                    const std::string& user_email,
+                                    const std::string& access_token) {
+          if (status == remoting::OAuthTokenGetter::Status::SUCCESS) {
+            // Set the new auth token for the log writer on the network thread.
+            runtime_->network_task_runner()->PostTask(
+                FROM_HERE, base::BindBlockArc(^{
+                  runtime_->log_writer()->SetAuthToken(access_token);
+                }));
+          } else {
+            LOG(ERROR) << "Failed to fetch access token for log writer. ("
+                       << status << ")";
+          }
+        })];
+  }
 }
 
 base::WeakPtr<IosClientRuntimeDelegate> IosClientRuntimeDelegate::GetWeakPtr() {
diff --git a/remoting/client/ios/facade/remoting_authentication.h b/remoting/client/ios/facade/remoting_authentication.h
new file mode 100644
index 0000000..1ab7e33
--- /dev/null
+++ b/remoting/client/ios/facade/remoting_authentication.h
@@ -0,0 +1,51 @@
+// 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 REMOTING_CLIENT_IOS_FACADE_REMOTING_AUTHENTICATION_H_
+#define REMOTING_CLIENT_IOS_FACADE_REMOTING_AUTHENTICATION_H_
+
+#import "remoting/client/chromoting_client_runtime.h"
+#import "remoting/client/ios/domain/user_info.h"
+
+#include "base/memory/weak_ptr.h"
+#include "remoting/base/oauth_token_getter.h"
+
+// |RemotingAuthenticationDelegate|s are interested in authentication related
+// notifications.
+@protocol RemotingAuthenticationDelegate<NSObject>
+
+// Notifies the delegate that the user has been updated.
+- (void)userDidUpdate:(UserInfo*)user;
+
+@end
+
+// This is the class that will manage the details around authentication
+// management and currently active user. It will make sure the user object is
+// saved to the keychain correctly and loaded on startup. It also is the entry
+// point for gaining access to an auth token for authrized calls.
+@interface RemotingAuthentication : NSObject
+
+// Provide an |authorizationCode| to authenticate a user as the first time user
+// of the application or OAuth Flow.
+- (void)authenticateWithAuthorizationCode:(NSString*)authorizationCode;
+
+// Fetches an OAuth Access Token and passes it back to the callback if
+// the user is authenticated. Otherwise does nothing.
+// TODO(nicholss): We might want to throw an error or add error message to
+// the callback sig to be able to react to the un-authed case.
+- (void)callbackWithAccessToken:
+    (const remoting::OAuthTokenGetter::TokenCallback&)onAccessToken;
+
+// Forget the current user.
+- (void)logout;
+
+// Returns the currently logged in user or nil.
+@property(strong, nonatomic) UserInfo* user;
+
+// Delegate recieves updates on user changes.
+@property(weak, nonatomic) id<RemotingAuthenticationDelegate> delegate;
+
+@end
+
+#endif  // REMOTING_CLIENT_IOS_FACADE_REMOTING_AUTHENTICATION_H_
diff --git a/remoting/client/ios/facade/remoting_authentication.mm b/remoting/client/ios/facade/remoting_authentication.mm
new file mode 100644
index 0000000..5abe68fb
--- /dev/null
+++ b/remoting/client/ios/facade/remoting_authentication.mm
@@ -0,0 +1,195 @@
+// 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.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/client/ios/facade/remoting_authentication.h"
+
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+
+#import "base/mac/bind_objc_block.h"
+#import "remoting/client/ios/facade/host_info.h"
+#import "remoting/client/ios/facade/host_list_fetcher.h"
+#import "remoting/client/ios/facade/ios_client_runtime_delegate.h"
+#import "remoting/client/ios/facade/remoting_service.h"
+#import "remoting/client/ios/keychain_wrapper.h"
+
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "remoting/base/oauth_token_getter.h"
+#include "remoting/base/oauth_token_getter_impl.h"
+
+static NSString* const kCRDAuthenticatedUserEmailKey =
+    @"kCRDAuthenticatedUserEmailKey";
+
+const char kOauthRedirectUrl[] =
+    "https://chromoting-oauth.talkgadget."
+    "google.com/talkgadget/oauth/chrome-remote-desktop/dev";
+
+std::unique_ptr<remoting::OAuthTokenGetter>
+CreateOAuthTokenGetterWithAuthorizationCode(
+    const std::string& auth_code,
+    const remoting::OAuthTokenGetter::CredentialsUpdatedCallback&
+        on_credentials_update) {
+  std::unique_ptr<remoting::OAuthTokenGetter::OAuthIntermediateCredentials>
+      oauth_credentials(
+          new remoting::OAuthTokenGetter::OAuthIntermediateCredentials(
+              auth_code, /*is_service_account=*/false));
+  oauth_credentials->oauth_redirect_uri = kOauthRedirectUrl;
+
+  std::unique_ptr<remoting::OAuthTokenGetter> oauth_tokenGetter(
+      new remoting::OAuthTokenGetterImpl(
+          std::move(oauth_credentials), on_credentials_update,
+          [RemotingService SharedInstance].runtime->url_requester(),
+          /*auto_refresh=*/true));
+  return oauth_tokenGetter;
+}
+
+std::unique_ptr<remoting::OAuthTokenGetter> CreateOAuthTokenWithRefreshToken(
+    const std::string& refresh_token,
+    const std::string& email) {
+  std::unique_ptr<remoting::OAuthTokenGetter::OAuthAuthorizationCredentials>
+      oauth_credentials(
+          new remoting::OAuthTokenGetter::OAuthAuthorizationCredentials(
+              email, refresh_token, /*is_service_account=*/false));
+
+  std::unique_ptr<remoting::OAuthTokenGetter> oauth_tokenGetter(
+      new remoting::OAuthTokenGetterImpl(
+          std::move(oauth_credentials),
+          [RemotingService SharedInstance].runtime->url_requester(),
+          /*auto_refresh=*/true));
+  return oauth_tokenGetter;
+}
+
+@interface RemotingAuthentication () {
+  std::unique_ptr<remoting::OAuthTokenGetter> _tokenGetter;
+  KeychainWrapper* _keychainWrapper;
+  BOOL _firstLoadUserAttempt;
+}
+@end
+
+@implementation RemotingAuthentication
+
+@synthesize user = _user;
+@synthesize delegate = _delegate;
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    _keychainWrapper = [[KeychainWrapper alloc] init];
+    _user = nil;
+    _firstLoadUserAttempt = YES;
+  }
+  return self;
+}
+
+#pragma mark - Property Overrides
+
+- (UserInfo*)user {
+  if (_firstLoadUserAttempt && _user == nil) {
+    _firstLoadUserAttempt = NO;
+    [self setUser:[self loadUserInfo]];
+  }
+  return _user;
+}
+
+- (void)setUser:(UserInfo*)user {
+  _user = user;
+  [self storeUserInfo:_user];
+  [_delegate userDidUpdate:_user];
+}
+
+#pragma mark - Class Implementation
+
+- (void)authenticateWithAuthorizationCode:(NSString*)authorizationCode {
+  __weak RemotingAuthentication* weakSelf = self;
+  _tokenGetter = CreateOAuthTokenGetterWithAuthorizationCode(
+      std::string(base::SysNSStringToUTF8(authorizationCode)),
+      base::BindBlockArc(
+          ^(const std::string& user_email, const std::string& refresh_token) {
+            // TODO(nicholss): Do something with these new creds.
+            VLOG(1) << "New Creds: " << user_email << " " << refresh_token;
+            UserInfo* user = [[UserInfo alloc] init];
+            user.userEmail = base::SysUTF8ToNSString(user_email);
+            user.refreshToken = base::SysUTF8ToNSString(refresh_token);
+            [weakSelf setUser:user];
+          }));
+  // Stimulate the oAuth Token Getter to fetch and access token, this forces it
+  // to convert the authorization code into a refresh token, and saving the
+  // refresh token will happen automaticly in the above block.
+  [self callbackWithAccessToken:base::BindBlockArc(^(
+                                    remoting::OAuthTokenGetter::Status status,
+                                    const std::string& user_email,
+                                    const std::string& access_token) {
+          if (status == remoting::OAuthTokenGetter::Status::SUCCESS) {
+            VLOG(1) << "Success fetching access token from authorization code.";
+          } else {
+            LOG(ERROR)
+                << "Failed to fetch access token from authorization code. ("
+                << status << ")";
+            // TODO(nicholss): Deal with the sad path for a bad auth token.
+          }
+        })];
+}
+
+#pragma mark - Private
+
+// Provide the |refreshToken| and |email| to authenticate a user as a returning
+// user of the application.
+- (void)authenticateWithRefreshToken:(NSString*)refreshToken
+                               email:(NSString*)email {
+  _tokenGetter = CreateOAuthTokenWithRefreshToken(
+      std::string(base::SysNSStringToUTF8(refreshToken)),
+      base::SysNSStringToUTF8(email));
+}
+
+- (void)callbackWithAccessToken:
+    (const remoting::OAuthTokenGetter::TokenCallback&)onAccessToken {
+  // TODO(nicholss): Be careful here since a failure to reset onAccessToken
+  // will end up with retain cycle and memory leakage.
+  if (_tokenGetter) {
+    _tokenGetter->CallWithToken(onAccessToken);
+  }
+}
+
+- (void)logout {
+  [self storeUserInfo:nil];
+  [self setUser:nil];
+}
+
+#pragma mark - Persistence
+
+- (void)storeUserInfo:(UserInfo*)user {
+  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  if (user) {
+    [defaults setObject:user.userEmail forKey:kCRDAuthenticatedUserEmailKey];
+    // TODO(nicholss): Need to match the token with the email.
+    [_keychainWrapper setRefreshToken:user.refreshToken];
+  } else {
+    [defaults removeObjectForKey:kCRDAuthenticatedUserEmailKey];
+    [_keychainWrapper resetKeychainItem];
+  }
+  [defaults synchronize];
+}
+
+- (UserInfo*)loadUserInfo {
+  UserInfo* user = [[UserInfo alloc] init];
+  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  user.userEmail = [defaults objectForKey:kCRDAuthenticatedUserEmailKey];
+  // TODO(nicholss): Need to match the token with the email.
+  user.refreshToken = [_keychainWrapper refreshToken];
+
+  if (!user || ![user isAuthenticated]) {
+    user = nil;
+  } else {
+    [self authenticateWithRefreshToken:user.refreshToken email:user.userEmail];
+  }
+  return user;
+}
+
+@end
diff --git a/remoting/client/ios/facade/remoting_service.h b/remoting/client/ios/facade/remoting_service.h
index 2f27fba..2a65639c 100644
--- a/remoting/client/ios/facade/remoting_service.h
+++ b/remoting/client/ios/facade/remoting_service.h
@@ -6,29 +6,22 @@
 #define REMOTING_CLIENT_IOS_FACADE_REMOTING_SERVICE_H_
 
 #import "remoting/client/chromoting_client_runtime.h"
-#import "remoting/client/ios/domain/host_info.h"
-#import "remoting/client/ios/domain/user_info.h"
 
 #include "base/memory/weak_ptr.h"
 #include "remoting/base/oauth_token_getter.h"
 
-// |RemotingAuthenticationDelegate|s are interested in authentication related
-// notifications.
-@protocol RemotingAuthenticationDelegate<NSObject>
+@class HostInfo;
+@class UserInfo;
+@class RemotingAuthentication;
 
-// Notifies the delegate that the authentication status of the current user has
-// changed to a new state.
-- (void)nowAuthenticated:(BOOL)authenticated;
+// Eventing related keys:
 
-@end
-
-// |RemotingHostListDelegate|s are interested in notifications related to host
-// list.
-@protocol RemotingHostListDelegate<NSObject>
-
-- (void)hostListUpdated;
-
-@end
+// Hosts did update event.
+extern NSString* const kHostsDidUpdate;
+// User did update event name.
+extern NSString* const kUserDidUpdate;
+// Map key for UserInfo object.
+extern NSString* const kUserInfo;
 
 // |RemotingService| is the centralized place to ask for information about
 // authentication or query the remote services. It also helps deal with the
@@ -39,38 +32,18 @@
 // Access to the singleton shared instance from this method.
 + (RemotingService*)SharedInstance;
 
-// Access to the current |ChromotingClientRuntime| from this method.
-- (remoting::ChromotingClientRuntime*)runtime;
+// Start a request to fetch the host list. This will produce an notification on
+// |kHostsDidUpdate| when a new host is ready.
+- (void)requestHostListFetch;
 
-// Register to be a |RemotingAuthenticationDelegate|.
-- (void)setAuthenticationDelegate:(id<RemotingAuthenticationDelegate>)delegate;
+@property(nonatomic, readonly) RemotingAuthentication* authentication;
 
-// A cached answer if there is a currently authenticated user.
-- (BOOL)isAuthenticated;
+// Returns the current host list.
+@property(nonatomic, readonly) NSArray<HostInfo*>* hosts;
 
-// Provide an |authorizationCode| to authenticate a user as the first time user
-// of the application or OAuth Flow.
-- (void)authenticateWithAuthorizationCode:(NSString*)authorizationCode;
-
-// Provide the |refreshToken| and |email| to authenticate a user as a returning
-// user of the application.
-- (void)authenticateWithRefreshToken:(NSString*)refreshToken
-                               email:(NSString*)email;
-
-// Returns the currently logged in user info from cache, or nil if no
-// currently authenticated user.
-- (UserInfo*)getUser;
-
-// Register to be a |RemotingHostListDelegate|. Side effect of setting this
-// delegate is the application will attempt to fetch a fresh host list.
-- (void)setHostListDelegate:(id<RemotingHostListDelegate>)delegate;
-
-// Returns the currently cached host list or nil if none exist.
-- (NSArray<HostInfo*>*)getHosts;
-
-// Fetches an OAuth Access Token and passes it back to the callback.
-- (void)callbackWithAccessToken:
-    (const remoting::OAuthTokenGetter::TokenCallback&)onAccessToken;
+// The Chromoting Client Runtime, this holds the threads and other shared
+// resources used by the Chromoting clients
+@property(nonatomic, readonly) remoting::ChromotingClientRuntime* runtime;
 
 @end
 
diff --git a/remoting/client/ios/facade/remoting_service.mm b/remoting/client/ios/facade/remoting_service.mm
index f30a823..20f499fa 100644
--- a/remoting/client/ios/facade/remoting_service.mm
+++ b/remoting/client/ios/facade/remoting_service.mm
@@ -9,78 +9,44 @@
 #import "remoting/client/ios/facade/remoting_service.h"
 
 #import <Foundation/Foundation.h>
+#import <Security/Security.h>
 
 #import "base/mac/bind_objc_block.h"
+#import "remoting/client/ios/domain/host_info.h"
+#import "remoting/client/ios/domain/user_info.h"
+#import "remoting/client/ios/facade/host_info.h"
+#import "remoting/client/ios/facade/host_list_fetcher.h"
+#import "remoting/client/ios/facade/ios_client_runtime_delegate.h"
+#import "remoting/client/ios/facade/remoting_authentication.h"
+#import "remoting/client/ios/facade/remoting_service.h"
+#import "remoting/client/ios/keychain_wrapper.h"
 
 #include "base/logging.h"
 #include "base/strings/sys_string_conversions.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "remoting/base/oauth_token_getter.h"
 #include "remoting/base/oauth_token_getter_impl.h"
-#include "remoting/client/ios/facade/host_info.h"
-#include "remoting/client/ios/facade/host_list_fetcher.h"
-#include "remoting/client/ios/facade/ios_client_runtime_delegate.h"
 
-const char kOauthRedirectUrl[] =
-    "https://chromoting-oauth.talkgadget."
-    "google.com/talkgadget/oauth/chrome-remote-desktop/dev";
+static NSString* const kCRDAuthenticatedUserEmailKey =
+    @"kCRDAuthenticatedUserEmailKey";
 
-std::unique_ptr<remoting::OAuthTokenGetter>
-CreateOAuthTokenGetterWithAuthorizationCode(
-    const std::string& auth_code,
-    const remoting::OAuthTokenGetter::CredentialsUpdatedCallback&
-        on_credentials_update) {
-  std::unique_ptr<remoting::OAuthTokenGetter::OAuthIntermediateCredentials>
-      oauth_credentials(
-          new remoting::OAuthTokenGetter::OAuthIntermediateCredentials(
-              auth_code, /*is_service_account=*/false));
-  oauth_credentials->oauth_redirect_uri = kOauthRedirectUrl;
+NSString* const kHostsDidUpdate = @"kHostsDidUpdate";
 
-  std::unique_ptr<remoting::OAuthTokenGetter> oauth_tokenGetter(
-      new remoting::OAuthTokenGetterImpl(
-          std::move(oauth_credentials), on_credentials_update,
-          [[RemotingService SharedInstance] runtime]->url_requester(),
-          /*auto_refresh=*/true));
-  return oauth_tokenGetter;
-}
+NSString* const kUserDidUpdate = @"kUserDidUpdate";
+NSString* const kUserInfo = @"kUserInfo";
 
-std::unique_ptr<remoting::OAuthTokenGetter> CreateOAuthTokenWithRefreshToken(
-    const std::string& refresh_token,
-    const std::string& email) {
-  std::unique_ptr<remoting::OAuthTokenGetter::OAuthAuthorizationCredentials>
-      oauth_credentials(
-          new remoting::OAuthTokenGetter::OAuthAuthorizationCredentials(
-              email, refresh_token, /*is_service_account=*/false));
-
-  std::unique_ptr<remoting::OAuthTokenGetter> oauth_tokenGetter(
-      new remoting::OAuthTokenGetterImpl(
-          std::move(oauth_credentials),
-          [[RemotingService SharedInstance] runtime]->url_requester(),
-          /*auto_refresh=*/true));
-  return oauth_tokenGetter;
-}
-
-@interface RemotingService () {
+@interface RemotingService ()<RemotingAuthenticationDelegate> {
   std::unique_ptr<remoting::OAuthTokenGetter> _tokenGetter;
-  UserInfo* _user;
-  NSArray<HostInfo*>* _hosts;
-  id<RemotingAuthenticationDelegate> _authDelegate;
-  id<RemotingHostListDelegate> _hostListDelegate;
   remoting::HostListFetcher* _hostListFetcher;
   remoting::IosClientRuntimeDelegate* _clientRuntimeDelegate;
 }
-
 @end
 
-//
-// RemodingService will act as the facade to the C++ layer that has not been
-// implemented/integrated yet.
-// TODO(nicholss): Implement/Integrate this class. At the moment it is being
-// used to generate fake data to implement the UI of the app.
-// Update: Half implemented now. User is still fake, but now real hosts lists.
-//
 @implementation RemotingService
 
+@synthesize authentication = _authentication;
+@synthesize hosts = _hosts;
+
 // RemotingService is a singleton.
 + (RemotingService*)SharedInstance {
   static RemotingService* sharedInstance = nil;
@@ -94,10 +60,10 @@
 - (instancetype)init {
   self = [super init];
   if (self) {
-    _user = nil;
+    _authentication = [[RemotingAuthentication alloc] init];
+    _authentication.delegate = self;
     _hosts = nil;
-    _hostListFetcher = new remoting::HostListFetcher(
-        remoting::ChromotingClientRuntime::GetInstance()->url_requester());
+    _hostListFetcher = nil;
     // TODO(nicholss): This might need a pointer back to the service.
     _clientRuntimeDelegate =
         new remoting::IosClientRuntimeDelegate();
@@ -108,161 +74,103 @@
 
 #pragma mark - RemotingService Implementation
 
-// TODO(nicholss): isAuthenticated needs to just kick off a request to
-// authenticate a user. and more than one controller might want to be a delegate
-// for this info so need to change this to be more of the registration types.
-// The remoting_service might also want to be registered for authentication
-// changes and it can update it's cache as it needs.
-
-- (void)setAuthenticationDelegate:(id<RemotingAuthenticationDelegate>)delegate {
-  _authDelegate = delegate;
-  if (_authDelegate) {
-    [_authDelegate nowAuthenticated:[self isAuthenticated]];
-  }
-  if (!_user && _tokenGetter) {
-    _tokenGetter->CallWithToken(base::BindBlockArc(
-        ^(remoting::OAuthTokenGetter::Status status,
-          const std::string& user_email, const std::string& access_token) {
-          if (status == remoting::OAuthTokenGetter::Status::SUCCESS) {
-            _user = [[UserInfo alloc] init];
-            _user.userEmail =
-                [NSString stringWithCString:user_email.c_str()
-                                   encoding:[NSString defaultCStringEncoding]];
-          } else {
-            _user = nil;
-          }
-          if (_authDelegate) {
-            [_authDelegate nowAuthenticated:[self isAuthenticated]];
-          }
-        }));
-  }
-}
-
-- (BOOL)isAuthenticated {
-  if (_user) {
-    return YES;
-  }
-  return NO;
-}
-
 - (void)startHostListFetchWith:(NSString*)accessToken {
-  NSLog(@"startHostListFetchWith : %@ %@", accessToken, _authDelegate);
-  if (_authDelegate) {
-    [_authDelegate nowAuthenticated:YES];
-
-    _hostListFetcher->RetrieveHostlist(
-        base::SysNSStringToUTF8(accessToken),
-        base::BindBlockArc(^(const std::vector<remoting::HostInfo>& hostlist) {
-          NSMutableArray<HostInfo*>* hosts =
-              [NSMutableArray arrayWithCapacity:hostlist.size()];
-          std::string status;
-          for (const remoting::HostInfo& host_info : hostlist) {
-            remoting::HostStatus host_status = host_info.status;
-            switch (host_status) {
-              case remoting::kHostStatusOnline:
-                status = "ONLINE";
-                break;
-              case remoting::kHostStatusOffline:
-                status = "OFFLINE";
-                break;
-              default:
-                NOTREACHED();
-            }
-            // TODO(nicholss): Not yet integrated: createdTime, hostVersion,
-            // kind, offlineReason. Add them as the app will need this info.
-            HostInfo* host = [[HostInfo alloc] init];
-            host.hostId =
-                [NSString stringWithCString:host_info.host_id.c_str()
-                                   encoding:[NSString defaultCStringEncoding]];
-            host.hostName =
-                [NSString stringWithCString:host_info.host_name.c_str()
-                                   encoding:[NSString defaultCStringEncoding]];
-            host.jabberId =
-                [NSString stringWithCString:host_info.host_jid.c_str()
-                                   encoding:[NSString defaultCStringEncoding]];
-            host.publicKey =
-                [NSString stringWithCString:host_info.public_key.c_str()
-                                   encoding:[NSString defaultCStringEncoding]];
-            host.status =
-                [NSString stringWithCString:status.c_str()
-                                   encoding:[NSString defaultCStringEncoding]];
-            [hosts addObject:host];
+  if (!_hostListFetcher) {
+    _hostListFetcher = new remoting::HostListFetcher(
+        remoting::ChromotingClientRuntime::GetInstance()->url_requester());
+  }
+  _hostListFetcher->RetrieveHostlist(
+      base::SysNSStringToUTF8(accessToken),
+      base::BindBlockArc(^(const std::vector<remoting::HostInfo>& hostlist) {
+        NSMutableArray<HostInfo*>* hosts =
+            [NSMutableArray arrayWithCapacity:hostlist.size()];
+        std::string status;
+        for (const remoting::HostInfo& host_info : hostlist) {
+          remoting::HostStatus host_status = host_info.status;
+          switch (host_status) {
+            case remoting::kHostStatusOnline:
+              status = "ONLINE";
+              break;
+            case remoting::kHostStatusOffline:
+              status = "OFFLINE";
+              break;
+            default:
+              NOTREACHED();
           }
-          _hosts = hosts;
-          [_hostListDelegate hostListUpdated];
-        }));
-  }
-}
-
-- (void)authenticateWithAuthorizationCode:(NSString*)authorizationCode {
-  _tokenGetter = CreateOAuthTokenGetterWithAuthorizationCode(
-      std::string(base::SysNSStringToUTF8(authorizationCode)),
-      base::BindBlockArc(
-          ^(const std::string& user_email, const std::string& refresh_token) {
-            // TODO(nicholss): Do something with these new creds.
-            VLOG(1) << "New Creds: " << user_email << " " << refresh_token;
-          }));
-}
-
-- (void)authenticateWithRefreshToken:(NSString*)refreshToken
-                               email:(NSString*)email {
-  _tokenGetter = CreateOAuthTokenWithRefreshToken(
-      std::string(base::SysNSStringToUTF8(refreshToken)),
-      base::SysNSStringToUTF8(email));
-}
-
-- (UserInfo*)getUser {
-  if (![self isAuthenticated]) {
-    return nil;
-  }
-
-  NSMutableString* json = [[NSMutableString alloc] init];
-  [json appendString:@"{"];
-  [json appendString:@"\"userId\":\"AABBCC123\","];
-  [json appendString:@"\"userFullName\":\"John Smith\","];
-  [json appendString:@"\"userEmail\":\"john@example.com\","];
-  [json appendString:@"}"];
-
-  NSMutableData* data = [NSMutableData
-      dataWithData:[[json copy] dataUsingEncoding:NSUTF8StringEncoding]];
-
-  UserInfo* user = [UserInfo parseListFromJSON:data];
-  return user;
-}
-
-- (void)setHostListDelegate:(id<RemotingHostListDelegate>)delegate {
-  bool attemptUpdate = (_hostListDelegate != delegate);
-  _hostListDelegate = delegate;
-  if (attemptUpdate && _hostListDelegate && _tokenGetter) {
-    // TODO(nicholss): It might be cleaner to set the delegate and then have
-    // them ask to refresh the host list rather than start this get hosts call.
-    _tokenGetter->CallWithToken(base::BindBlockArc(
-        ^(remoting::OAuthTokenGetter::Status status,
-          const std::string& user_email, const std::string& access_token) {
-          NSString* accessToken =
-              [NSString stringWithCString:access_token.c_str()
+          // TODO(nicholss): Not yet integrated: createdTime, hostVersion,
+          // kind, offlineReason. Add them as the app will need this info.
+          HostInfo* host = [[HostInfo alloc] init];
+          host.hostId =
+              [NSString stringWithCString:host_info.host_id.c_str()
                                  encoding:[NSString defaultCStringEncoding]];
-          [self startHostListFetchWith:accessToken];
-        }));
-  }
+          host.hostName =
+              [NSString stringWithCString:host_info.host_name.c_str()
+                                 encoding:[NSString defaultCStringEncoding]];
+          host.jabberId =
+              [NSString stringWithCString:host_info.host_jid.c_str()
+                                 encoding:[NSString defaultCStringEncoding]];
+          host.publicKey =
+              [NSString stringWithCString:host_info.public_key.c_str()
+                                 encoding:[NSString defaultCStringEncoding]];
+          host.status =
+              [NSString stringWithCString:status.c_str()
+                                 encoding:[NSString defaultCStringEncoding]];
+          [hosts addObject:host];
+        }
+        _hosts = hosts;
+        [self hostListUpdated];
+      }));
 }
 
-- (NSArray<HostInfo*>*)getHosts {
-  if (![self isAuthenticated]) {
-    return nil;
+#pragma mark - Notifications
+
+- (void)hostListUpdated {
+  [[NSNotificationCenter defaultCenter] postNotificationName:kHostsDidUpdate
+                                                      object:self
+                                                    userInfo:nil];
+}
+
+#pragma mark - RemotingAuthenticationDelegate
+
+- (void)userDidUpdate:(UserInfo*)user {
+  NSDictionary* userInfo = nil;
+  if (user) {
+    userInfo = [NSDictionary dictionaryWithObject:user forKey:kUserInfo];
+  } else {
+    _hosts = nil;
+    [self hostListUpdated];
   }
-  return _hosts;
+  [[NSNotificationCenter defaultCenter] postNotificationName:kUserDidUpdate
+                                                      object:self
+                                                    userInfo:userInfo];
+}
+
+#pragma mark - Properties
+
+- (NSArray<HostInfo*>*)hosts {
+  if ([_authentication.user isAuthenticated]) {
+    return _hosts;
+  }
+  return nil;
 }
 
 - (remoting::ChromotingClientRuntime*)runtime {
   return remoting::ChromotingClientRuntime::GetInstance();
 }
 
-- (void)callbackWithAccessToken:
-    (const remoting::OAuthTokenGetter::TokenCallback&)onAccessToken {
-  if (_tokenGetter) {
-    _tokenGetter->CallWithToken(onAccessToken);
-  }
+#pragma mark - Implementation
+
+- (void)requestHostListFetch {
+  [_authentication
+      callbackWithAccessToken:base::BindBlockArc(^(
+                                  remoting::OAuthTokenGetter::Status status,
+                                  const std::string& user_email,
+                                  const std::string& access_token) {
+        NSString* accessToken =
+            [NSString stringWithCString:access_token.c_str()
+                               encoding:[NSString defaultCStringEncoding]];
+        [self startHostListFetchWith:accessToken];
+      })];
 }
 
 @end
diff --git a/remoting/client/ios/keychain_wrapper.h b/remoting/client/ios/keychain_wrapper.h
new file mode 100644
index 0000000..d92b6ae
--- /dev/null
+++ b/remoting/client/ios/keychain_wrapper.h
@@ -0,0 +1,26 @@
+// 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 REMOTING_CLIENT_IOS_KEYCHAIN_WRAPPER_H_
+#define REMOTING_CLIENT_IOS_KEYCHAIN_WRAPPER_H_
+
+#import <Foundation/Foundation.h>
+
+@class UserInfo;
+
+// Class to abstract the details from how iOS wants to write to the keychain.
+// TODO(nicholss): This will have to be futher refactored when we integrate
+// with the private Google auth.
+@interface KeychainWrapper : NSObject
+
+// Save a refresh token to the keychain.
+- (void)setRefreshToken:(NSString*)refreshToken;
+// Get the refresh token from the keychain, if there is one.
+- (NSString*)refreshToken;
+// Reset the keychain and the cache.
+- (void)resetKeychainItem;
+
+@end
+
+#endif  //  REMOTING_CLIENT_IOS_KEYCHAIN_WRAPPER_H_
diff --git a/remoting/client/ios/keychain_wrapper.mm b/remoting/client/ios/keychain_wrapper.mm
new file mode 100644
index 0000000..c1ae602
--- /dev/null
+++ b/remoting/client/ios/keychain_wrapper.mm
@@ -0,0 +1,206 @@
+// 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.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/client/ios/keychain_wrapper.h"
+
+#import "remoting/client/ios/domain/host_info.h"
+
+static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
+
+@interface KeychainWrapper () {
+  NSMutableDictionary* _keychainData;
+  NSMutableDictionary* _userInfoQuery;
+}
+@end
+
+@implementation KeychainWrapper
+
+- (id)init {
+  if ((self = [super init])) {
+    OSStatus keychainErr = noErr;
+    _userInfoQuery = [[NSMutableDictionary alloc] init];
+    [_userInfoQuery setObject:(__bridge id)kSecClassGenericPassword
+                       forKey:(__bridge id)kSecClass];
+    NSData* keychainItemID =
+        [NSData dataWithBytes:kKeychainItemIdentifier
+                       length:strlen((const char*)kKeychainItemIdentifier)];
+    [_userInfoQuery setObject:keychainItemID
+                       forKey:(__bridge id)kSecAttrGeneric];
+    [_userInfoQuery setObject:(__bridge id)kSecMatchLimitOne
+                       forKey:(__bridge id)kSecMatchLimit];
+    [_userInfoQuery setObject:(__bridge id)kCFBooleanTrue
+                       forKey:(__bridge id)kSecReturnAttributes];
+
+    CFMutableDictionaryRef outDictionary = nil;
+    keychainErr = SecItemCopyMatching((__bridge CFDictionaryRef)_userInfoQuery,
+                                      (CFTypeRef*)&outDictionary);
+    if (keychainErr == noErr) {
+      _keychainData = [self
+          secItemFormatToDictionary:(__bridge_transfer NSMutableDictionary*)
+                                        outDictionary];
+    } else if (keychainErr == errSecItemNotFound) {
+      [self resetKeychainItem];
+
+      if (outDictionary) {
+        CFRelease(outDictionary);
+        _keychainData = nil;
+      }
+    } else {
+      NSLog(@"Serious error.");
+      if (outDictionary) {
+        CFRelease(outDictionary);
+        _keychainData = nil;
+      }
+    }
+  }
+  return self;
+}
+
+- (void)setRefreshToken:(NSString*)refreshToken {
+  [self setObject:refreshToken forKey:(__bridge id)kSecValueData];
+}
+
+- (NSString*)refreshToken {
+  return [self objectForKey:(__bridge id)kSecValueData];
+}
+
+// Implement the mySetObject:forKey method, which writes attributes to the
+// keychain:
+- (void)setObject:(id)inObject forKey:(id)key {
+  if (inObject == nil)
+    return;
+  id currentObject = [_keychainData objectForKey:key];
+  if (![currentObject isEqual:inObject]) {
+    [_keychainData setObject:inObject forKey:key];
+    [self writeToKeychain];
+  }
+}
+
+// Implement the myObjectForKey: method, which reads an attribute value from a
+// dictionary:
+- (id)objectForKey:(id)key {
+  return [_keychainData objectForKey:key];
+}
+
+- (void)resetKeychainItem {
+  if (!_keychainData) {
+    _keychainData = [[NSMutableDictionary alloc] init];
+  } else if (_keychainData) {
+    NSMutableDictionary* tmpDictionary =
+        [self dictionaryToSecItemFormat:_keychainData];
+    OSStatus errorcode = SecItemDelete((__bridge CFDictionaryRef)tmpDictionary);
+    if (errorcode == errSecItemNotFound) {
+      // this is ok.
+    } else if (errorcode != noErr) {
+      NSLog(@"Problem deleting current keychain item.");
+    }
+  }
+
+  [_keychainData setObject:@"gaia_refresh_token"
+                    forKey:(__bridge id)kSecAttrLabel];
+  [_keychainData setObject:@"Gaia fresh token"
+                    forKey:(__bridge id)kSecAttrDescription];
+  [_keychainData setObject:@"" forKey:(__bridge id)kSecValueData];
+}
+
+- (NSMutableDictionary*)dictionaryToSecItemFormat:
+    (NSDictionary*)dictionaryToConvert {
+  NSMutableDictionary* returnDictionary =
+      [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
+
+  NSData* keychainItemID =
+      [NSData dataWithBytes:kKeychainItemIdentifier
+                     length:strlen((const char*)kKeychainItemIdentifier)];
+  [returnDictionary setObject:keychainItemID
+                       forKey:(__bridge id)kSecAttrGeneric];
+  [returnDictionary setObject:(__bridge id)kSecClassGenericPassword
+                       forKey:(__bridge id)kSecClass];
+
+  NSString* passwordString =
+      [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
+  [returnDictionary
+      setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding]
+         forKey:(__bridge id)kSecValueData];
+  return returnDictionary;
+}
+
+- (NSMutableDictionary*)secItemFormatToDictionary:
+    (NSDictionary*)dictionaryToConvert {
+  NSMutableDictionary* returnDictionary =
+      [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
+
+  [returnDictionary setObject:(__bridge id)kCFBooleanTrue
+                       forKey:(__bridge id)kSecReturnData];
+  [returnDictionary setObject:(__bridge id)kSecClassGenericPassword
+                       forKey:(__bridge id)kSecClass];
+
+  CFDataRef passwordData = NULL;
+  OSStatus keychainError = noErr;
+  keychainError = SecItemCopyMatching(
+      (__bridge CFDictionaryRef)returnDictionary, (CFTypeRef*)&passwordData);
+  if (keychainError == noErr) {
+    [returnDictionary removeObjectForKey:(__bridge id)kSecReturnData];
+
+    NSString* password = [[NSString alloc]
+        initWithBytes:[(__bridge_transfer NSData*)passwordData bytes]
+               length:[(__bridge NSData*)passwordData length]
+             encoding:NSUTF8StringEncoding];
+    [returnDictionary setObject:password forKey:(__bridge id)kSecValueData];
+  } else if (keychainError == errSecItemNotFound) {
+    NSLog(@"Nothing was found in the keychain.");
+    if (passwordData) {
+      CFRelease(passwordData);
+      passwordData = nil;
+    }
+  } else {
+    NSLog(@"Serious error.\n");
+    if (passwordData) {
+      CFRelease(passwordData);
+      passwordData = nil;
+    }
+  }
+  return returnDictionary;
+}
+
+- (void)writeToKeychain {
+  CFDictionaryRef attributes = nil;
+  NSMutableDictionary* updateItem = nil;
+
+  if (SecItemCopyMatching((__bridge CFDictionaryRef)_userInfoQuery,
+                          (CFTypeRef*)&attributes) == noErr) {
+    updateItem = [NSMutableDictionary
+        dictionaryWithDictionary:(__bridge_transfer NSDictionary*)attributes];
+
+    [updateItem setObject:[_userInfoQuery objectForKey:(__bridge id)kSecClass]
+                   forKey:(__bridge id)kSecClass];
+
+    NSMutableDictionary* tempCheck =
+        [self dictionaryToSecItemFormat:_keychainData];
+    [tempCheck removeObjectForKey:(__bridge id)kSecClass];
+
+    OSStatus errorcode = SecItemUpdate((__bridge CFDictionaryRef)updateItem,
+                                       (__bridge CFDictionaryRef)tempCheck);
+    if (errorcode != noErr) {
+      NSLog(@"Couldn't update the Keychain Item. %d", (int)errorcode);
+    }
+  } else {
+    OSStatus errorcode =
+        SecItemAdd((__bridge CFDictionaryRef)
+                       [self dictionaryToSecItemFormat:_keychainData],
+                   NULL);
+    if (errorcode != noErr) {
+      NSLog(@"Couldn't add the Keychain Item. %d", (int)errorcode);
+    }
+    if (attributes) {
+      CFRelease(attributes);
+      attributes = nil;
+    }
+  }
+}
+
+@end
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index fd58647a..061f659 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -1598,124 +1598,6 @@
       },
       {
         "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build249-m4--device6",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build249-m4--device6",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build245-m4--device5",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build245-m4--device5",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -7906,183 +7788,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build142-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build142-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build141-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build141-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build148-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build148-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -8201,65 +7906,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build149-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build149-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -14351,183 +13997,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build206-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build206-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build205-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build205-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build212-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build212-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -14646,65 +14115,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build213-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build213-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index c66990f..3f311cb 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -1665,124 +1665,6 @@
       },
       {
         "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build75-b1--device6",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build75-b1--device6",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build73-b1--device5",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build73-b1--device5",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -7953,183 +7835,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build151-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build151-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build152-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build152-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build149-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build149-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -8248,65 +7953,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build149-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build149-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -14398,183 +14044,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build105-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build105-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build106-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build106-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build103-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build103-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -14693,65 +14162,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build103-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build103-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -20823,183 +20233,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build161-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build161-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build162-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build162-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build159-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build159-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -21118,65 +20351,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build159-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build159-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -27228,183 +26402,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build126-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build126-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build127-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build127-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build124-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build124-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -27523,65 +26520,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build124-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build124-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -33633,183 +32571,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build27-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build27-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build28-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build28-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build25-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build25-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -33928,65 +32689,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build25-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build25-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -40038,183 +38740,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build131-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build131-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build132-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build132-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build129-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build129-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -40333,65 +38858,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build129-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build129-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -46443,183 +44909,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build7-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build7-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build8-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build8-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build5-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build5-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -46738,65 +45027,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build5-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build5-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -52848,183 +51078,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build120-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build120-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build180-b4",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build180-b4",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build118-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build118-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -53143,65 +51196,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build118-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build118-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -59253,183 +57247,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build135-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build135-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build136-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build136-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build133-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build133-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -59548,65 +57365,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build133-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build133-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -65718,183 +63476,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build104-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build104-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build105-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build105-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build102-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build102-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -66013,65 +63594,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build102-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build102-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -72203,183 +69725,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build167-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build167-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build168-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build168-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build165-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build165-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -72498,65 +69843,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build165-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build165-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -78668,183 +75954,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build95-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build95-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build96-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build96-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build93-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build93-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -78963,65 +76072,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build93-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build93-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -85133,183 +82183,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build188-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build188-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build189-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build189-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build186-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build186-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -85428,65 +82301,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build186-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build186-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -91598,183 +88412,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build141-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build141-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build142-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build142-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build139-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build139-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -91893,65 +88530,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build139-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build139-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -98043,183 +94621,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build146-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build146-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build147-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build147-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build144-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build144-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -98338,65 +94739,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build144-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build144-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
@@ -104488,183 +100830,6 @@
       },
       {
         "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build33-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.android.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.android.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build33-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build34-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build34-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build31-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.chromeOS4kOnly.tough_video_cases",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.chromeOS4kOnly.tough_video_cases.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build31-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.mse_cases",
           "-v",
           "--upload-results",
@@ -104783,65 +100948,6 @@
       },
       {
         "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build31-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "media.tough_video_cases_extra",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "media.tough_video_cases_extra.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build31-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "media.tough_video_cases_tbmv2",
           "-v",
           "--upload-results",
diff --git a/third_party/WebKit/LayoutTests/W3CImportExpectations b/third_party/WebKit/LayoutTests/W3CImportExpectations
index 8a9f527..1346078 100644
--- a/third_party/WebKit/LayoutTests/W3CImportExpectations
+++ b/third_party/WebKit/LayoutTests/W3CImportExpectations
@@ -291,6 +291,8 @@
 external/wpt/imagebitmap-renderingcontext [ Skip ]
 ## Owners: smcgruer@chromium.org
 # external/wpt/infrastructure [ Pass ]
+## Owners: none; No tests in the directory.
+# external/wpt/interfaces [ Pass ]
 ## Owners: dom-dev@chromium.org
 # external/wpt/innerText [ Pass ]
 external/wpt/js [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/credential-management/idl.https.html b/third_party/WebKit/LayoutTests/external/wpt/credential-management/idl.https.html
index bc779d0..5ccc0c9 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/credential-management/idl.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/credential-management/idl.https.html
@@ -14,26 +14,25 @@
       readonly attribute DOMString type;
     };
 
-    dictionary SiteBoundCredentialData : CredentialData {
-      USVString name;
-      USVString iconURL;
-    };
-
     [NoInterfaceObject, SecureContext]
     interface CredentialUserData {
       readonly attribute USVString name;
       readonly attribute USVString iconURL;
     };
 
-    dictionary PasswordCredentialData : SiteBoundCredentialData {
-      USVString password;
+    dictionary PasswordCredentialData : CredentialData {
+      USVString name;
+      USVString iconURL;
+      required USVString password;
     };
 
     typedef (FormData or URLSearchParams) CredentialBodyType;
 
 
-    dictionary FederatedCredentialData : SiteBoundCredentialData {
-      USVString provider;
+    dictionary FederatedCredentialInit : CredentialData {
+      USVString name;
+      USVString iconURL;
+      required USVString provider;
       DOMString protocol;
     };
 
@@ -69,7 +68,7 @@
     };
     PasswordCredential implements CredentialUserData;
 
-    [Constructor(FederatedCredentialData data),
+    [Constructor(FederatedCredentialInit data),
      Exposed=Window,
      SecureContext]
     interface FederatedCredential : Credential {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/remoteplayback.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/remoteplayback.idl
new file mode 100644
index 0000000..598bf30b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/remoteplayback.idl
@@ -0,0 +1,23 @@
+enum RemotePlaybackState {
+    "connecting",
+    "connected",
+    "disconnected"
+};
+
+callback RemotePlaybackAvailabilityCallback = void(boolean available);
+
+interface RemotePlayback : EventTarget {
+    readonly attribute RemotePlaybackState state;
+    attribute EventHandler onconnecting;
+    attribute EventHandler onconnect;
+    attribute EventHandler ondisconnect;
+
+    Promise<long> watchAvailability(RemotePlaybackAvailabilityCallback callback);
+    Promise<void> cancelWatchAvailability(optional long id);
+    Promise<void> prompt();
+};
+
+partial interface HTMLMediaElement {
+    readonly attribute RemotePlayback remote;
+    attribute boolean disableRemotePlayback;
+};
diff --git a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_pen-manual.html b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_pen-manual.html
index 9d68b21..bb6dcc42 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_pen-manual.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_pen-manual.html
@@ -32,9 +32,11 @@
                 });
 
                 on_event(target0, "pointerleave", function (event) {
-                    detected_pointertypes[event.pointerType] = true;
-                    check_PointerEvent(event);
                     count++;
+                    detected_pointertypes[event.pointerType] = true;
+                    if (count == 1)
+                        check_PointerEvent(event);
+
                     test_pointerEvent.step(function () {
                         assert_equals(event.pointerType, "pen", "Test should be run using a pen as input");
                         assert_equals(event.type, "pointerleave", "The " + event.type + " event was received");
@@ -59,7 +61,7 @@
         </h4>
         <br />
         <div id="target0">
-            Use a pen to hover over then lift up away from this element.
+            Use a pen to hover over then lift up away from this element, and repeat it again.
         </div>
         <div id="complete-notice">
             <p>Test complete: Scroll to Summary to view Pass/Fail Results.</p>
@@ -67,4 +69,4 @@
         </div>
         <div id="log"></div>
     </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/remote-playback/idlharness.html b/third_party/WebKit/LayoutTests/external/wpt/remote-playback/idlharness.html
new file mode 100644
index 0000000..bd9cbf6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/remote-playback/idlharness.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Remote Playback API IDL tests</title>
+<link rel="help" href="https://w3c.github.io/remoteplayback/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+</head>
+<body>
+<h1>Remote Playback API IDL tests</h1>
+<video id='media' width=10 height=10></video>
+<pre id='untested_idl' style='display:none'>
+interface EventHandler {};
+interface HTMLMediaElement : HTMLElement {};
+interface EventTarget {};
+</pre>
+<script>
+"use strict"
+var idl_array = new IdlArray();
+function doTest(idl) {
+  idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
+  idl_array.add_idls(idl);
+  idl_array.add_objects({
+    HTMLVideoElement: [document.getElementById("media")],
+    RemotePlayback: [document.getElementById("media").remote]
+  });
+  idl_array.test();
+}
+
+promise_test(function() {
+  return fetch("/interfaces/remoteplayback.idl")
+      .then(response => response.text())
+      .then(doTest);
+}, "Test driver");
+</script>
+<div id="log"></div>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/canvas/OWNERS b/third_party/WebKit/LayoutTests/fast/canvas/OWNERS
index 4af0e95a..14ca7d4e 100644
--- a/third_party/WebKit/LayoutTests/fast/canvas/OWNERS
+++ b/third_party/WebKit/LayoutTests/fast/canvas/OWNERS
@@ -1 +1,2 @@
-# TEAM: graphics-dev@chromium.org
+# TEAM: paint-dev@chromium.org
+# COMPONENT: Blink>Canvas
diff --git a/third_party/WebKit/LayoutTests/web-animations-api/animation-ready-reject-script-forbidden.html b/third_party/WebKit/LayoutTests/web-animations-api/animation-ready-reject-script-forbidden.html
new file mode 100644
index 0000000..a40fa0e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/web-animations-api/animation-ready-reject-script-forbidden.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<div id=element>x</div>
+<style>
+  #element {
+    transition: transform 2000ms;
+  }
+</style>
+<script>
+  async_test(function(t) {
+      element.offsetTop; // Force recalc
+      element.style.transform = "rotateX(180deg)";
+      element.offsetTop; // Force recalc
+      var animation = element.getAnimations()[0];
+      return animation.ready.catch(function() { t.done(); });
+  });
+</script>
+<style>
+  * { content: ""; }
+</style>
diff --git a/third_party/WebKit/Source/core/animation/Animation.cpp b/third_party/WebKit/Source/core/animation/Animation.cpp
index b6aa9fa..1cbacd8 100644
--- a/third_party/WebKit/Source/core/animation/Animation.cpp
+++ b/third_party/WebKit/Source/core/animation/Animation.cpp
@@ -1038,9 +1038,11 @@
     if (new_play_state == kIdle) {
       if (animation_->ready_promise_->GetState() ==
           AnimationPromise::kPending) {
-        animation_->ready_promise_->Reject(DOMException::Create(kAbortError));
+        animation_->RejectAndResetPromiseMaybeAsync(
+            animation_->ready_promise_.Get());
+      } else {
+        animation_->ready_promise_->Reset();
       }
-      animation_->ready_promise_->Reset();
       animation_->ResolvePromiseMaybeAsync(animation_->ready_promise_.Get());
     } else if (old_play_state == kPending) {
       animation_->ResolvePromiseMaybeAsync(animation_->ready_promise_.Get());
@@ -1055,10 +1057,11 @@
     if (new_play_state == kIdle) {
       if (animation_->finished_promise_->GetState() ==
           AnimationPromise::kPending) {
-        animation_->finished_promise_->Reject(
-            DOMException::Create(kAbortError));
+        animation_->RejectAndResetPromiseMaybeAsync(
+            animation_->finished_promise_.Get());
+      } else {
+        animation_->finished_promise_->Reset();
       }
-      animation_->finished_promise_->Reset();
     } else if (new_play_state == kFinished) {
       animation_->ResolvePromiseMaybeAsync(animation_->finished_promise_.Get());
     } else if (old_play_state == kFinished) {
@@ -1150,6 +1153,22 @@
   }
 }
 
+void Animation::RejectAndResetPromise(AnimationPromise* promise) {
+  promise->Reject(DOMException::Create(kAbortError));
+  promise->Reset();
+}
+
+void Animation::RejectAndResetPromiseMaybeAsync(AnimationPromise* promise) {
+  if (ScriptForbiddenScope::IsScriptForbidden()) {
+    TaskRunnerHelper::Get(TaskType::kDOMManipulation, GetExecutionContext())
+        ->PostTask(BLINK_FROM_HERE,
+                   WTF::Bind(&Animation::RejectAndResetPromise,
+                             WrapPersistent(this), WrapPersistent(promise)));
+  } else {
+    RejectAndResetPromise(promise);
+  }
+}
+
 DEFINE_TRACE(Animation) {
   visitor->Trace(content_);
   visitor->Trace(timeline_);
diff --git a/third_party/WebKit/Source/core/animation/Animation.h b/third_party/WebKit/Source/core/animation/Animation.h
index 32a9121..d95c92a0 100644
--- a/third_party/WebKit/Source/core/animation/Animation.h
+++ b/third_party/WebKit/Source/core/animation/Animation.h
@@ -238,6 +238,8 @@
                                                  Member<Animation>,
                                                  Member<DOMException>>;
   void ResolvePromiseMaybeAsync(AnimationPromise*);
+  void RejectAndResetPromise(AnimationPromise*);
+  void RejectAndResetPromiseMaybeAsync(AnimationPromise*);
 
   String id_;
 
diff --git a/third_party/WebKit/Source/core/exported/BUILD.gn b/third_party/WebKit/Source/core/exported/BUILD.gn
index d7df8835..3ecd359 100644
--- a/third_party/WebKit/Source/core/exported/BUILD.gn
+++ b/third_party/WebKit/Source/core/exported/BUILD.gn
@@ -7,6 +7,9 @@
   sources = [
     "SharedWorkerRepositoryClientImpl.cpp",
     "SharedWorkerRepositoryClientImpl.h",
+    "WebArrayBuffer.cpp",
+    "WebArrayBufferConverter.cpp",
+    "WebArrayBufferView.cpp",
     "WebAssociatedURLLoaderImpl.cpp",
     "WebAssociatedURLLoaderImpl.h",
     "WebBlob.cpp",
diff --git a/third_party/WebKit/Source/web/WebArrayBuffer.cpp b/third_party/WebKit/Source/core/exported/WebArrayBuffer.cpp
similarity index 100%
rename from third_party/WebKit/Source/web/WebArrayBuffer.cpp
rename to third_party/WebKit/Source/core/exported/WebArrayBuffer.cpp
diff --git a/third_party/WebKit/Source/web/WebArrayBufferConverter.cpp b/third_party/WebKit/Source/core/exported/WebArrayBufferConverter.cpp
similarity index 100%
rename from third_party/WebKit/Source/web/WebArrayBufferConverter.cpp
rename to third_party/WebKit/Source/core/exported/WebArrayBufferConverter.cpp
diff --git a/third_party/WebKit/Source/web/WebArrayBufferView.cpp b/third_party/WebKit/Source/core/exported/WebArrayBufferView.cpp
similarity index 100%
rename from third_party/WebKit/Source/web/WebArrayBufferView.cpp
rename to third_party/WebKit/Source/core/exported/WebArrayBufferView.cpp
diff --git a/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.cpp b/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.cpp
index b3f3a4f..221aca3 100644
--- a/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.cpp
+++ b/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.cpp
@@ -68,7 +68,7 @@
 }  // namespace
 
 ImageResourceContent::ImageResourceContent(PassRefPtr<blink::Image> image)
-    : image_(std::move(image)), is_refetchable_data_from_disk_cache_(true) {
+    : is_refetchable_data_from_disk_cache_(true), image_(std::move(image)) {
   DEFINE_STATIC_LOCAL(NullImageResourceInfo, null_info,
                       (new NullImageResourceInfo()));
   info_ = &null_info;
diff --git a/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.h b/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.h
index 6351b931..cd70475 100644
--- a/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.h
+++ b/third_party/WebKit/Source/core/loader/resource/ImageResourceContent.h
@@ -202,22 +202,23 @@
         : AutoReset(&content->is_add_remove_observer_prohibited_, true) {}
   };
 
-  Member<ImageResourceInfo> info_;
   ResourceStatus content_status_ = ResourceStatus::kNotStarted;
 
-  RefPtr<blink::Image> image_;
-
-  HashCountedSet<ImageResourceObserver*> observers_;
-  HashCountedSet<ImageResourceObserver*> finished_observers_;
-
-  Image::SizeAvailability size_available_ = Image::kSizeUnavailable;
-
   // Indicates if this resource's encoded image data can be purged and refetched
   // from disk cache to save memory usage. See crbug/664437.
   bool is_refetchable_data_from_disk_cache_;
 
   mutable bool is_add_remove_observer_prohibited_ = false;
 
+  Image::SizeAvailability size_available_ = Image::kSizeUnavailable;
+
+  Member<ImageResourceInfo> info_;
+
+  RefPtr<blink::Image> image_;
+
+  HashCountedSet<ImageResourceObserver*> observers_;
+  HashCountedSet<ImageResourceObserver*> finished_observers_;
+
 #if DCHECK_IS_ON()
   bool is_update_image_being_called_ = false;
 #endif
diff --git a/third_party/WebKit/Source/devtools/front_end/devices/DevicesView.js b/third_party/WebKit/Source/devtools/front_end/devices/DevicesView.js
index 7dc3609..082c942 100644
--- a/third_party/WebKit/Source/devtools/front_end/devices/DevicesView.js
+++ b/third_party/WebKit/Source/devtools/front_end/devices/DevicesView.js
@@ -151,10 +151,8 @@
    * @param {!Common.Event} event
    */
   _devicesDiscoveryConfigChanged(event) {
-    var discoverUsbDevices = /** @type {boolean} */ (event.data['discoverUsbDevices']);
-    var portForwardingEnabled = /** @type {boolean} */ (event.data['portForwardingEnabled']);
-    var portForwardingConfig = /** @type {!Adb.PortForwardingConfig} */ (event.data['portForwardingConfig']);
-    this._discoveryView.discoveryConfigChanged(discoverUsbDevices, portForwardingEnabled, portForwardingConfig);
+    var config = /** @type {!Adb.Config} */ (event.data);
+    this._discoveryView.discoveryConfigChanged(config);
   }
 
   /**
@@ -186,6 +184,9 @@
    */
   wasShown() {
     super.wasShown();
+    // Retrigger notification first time.
+    if (Runtime.queryParam('nodeFrontend'))
+      InspectorFrontendHost.setDevicesUpdatesEnabled(false);
     InspectorFrontendHost.setDevicesUpdatesEnabled(true);
   }
 
@@ -194,13 +195,13 @@
    */
   willHide() {
     super.wasShown();
-    InspectorFrontendHost.setDevicesUpdatesEnabled(false);
+    if (!Runtime.queryParam('nodeFrontend'))
+      InspectorFrontendHost.setDevicesUpdatesEnabled(false);
   }
 };
 
 
 /**
- * @implements {UI.ListWidget.Delegate}
  * @unrestricted
  */
 Devices.DevicesView.DiscoveryView = class extends UI.VBox {
@@ -216,7 +217,10 @@
     discoverUsbDevicesCheckbox.classList.add('usb-checkbox');
     this.element.appendChild(discoverUsbDevicesCheckbox);
     this._discoverUsbDevicesCheckbox = discoverUsbDevicesCheckbox.checkboxElement;
-    this._discoverUsbDevicesCheckbox.addEventListener('click', this._updateDiscoveryConfig.bind(this), false);
+    this._discoverUsbDevicesCheckbox.addEventListener('click', () => {
+      this._config.discoverUsbDevices = this._discoverUsbDevicesCheckbox.checked;
+      InspectorFrontendHost.setDevicesDiscoveryConfig(this._config);
+    }, false);
 
     var help = this.element.createChild('div', 'discovery-help');
     help.createChild('span').textContent = Common.UIString('Need help? Read Chrome ');
@@ -224,12 +228,56 @@
         'https://developers.google.com/chrome-developer-tools/docs/remote-debugging',
         Common.UIString('remote debugging documentation.')));
 
+    /** @type {!Adb.Config} */
+    this._config;
+
+    this._portForwardingView = new Devices.DevicesView.PortForwardingView((enabled, config) => {
+      this._config.portForwardingEnabled = enabled;
+      this._config.portForwardingConfig = {};
+      for (var rule of config)
+        this._config.portForwardingConfig[rule.port] = rule.address;
+      InspectorFrontendHost.setDevicesDiscoveryConfig(this._config);
+    });
+    this._portForwardingView.show(this.element);
+
+    this._networkDiscoveryView = new Devices.DevicesView.NetworkDiscoveryView(false, (enabled, config) => {
+      this._config.networkDiscoveryEnabled = enabled;
+      this._config.networkDiscoveryConfig = config;
+      InspectorFrontendHost.setDevicesDiscoveryConfig(this._config);
+    });
+    this._networkDiscoveryView.show(this.element);
+  }
+
+  /**
+   * @param {!Adb.Config} config
+   */
+  discoveryConfigChanged(config) {
+    this._config = config;
+    this._discoverUsbDevicesCheckbox.checked = config.discoverUsbDevices;
+    this._portForwardingView.discoveryConfigChanged(config.portForwardingEnabled, config.portForwardingConfig);
+    this._networkDiscoveryView.discoveryConfigChanged(config.networkDiscoveryEnabled, config.networkDiscoveryConfig);
+  }
+};
+
+/**
+ * @implements {UI.ListWidget.Delegate}
+ * @unrestricted
+ */
+Devices.DevicesView.PortForwardingView = class extends UI.VBox {
+  /**
+   * @param {function(boolean, !Array<!Adb.PortForwardingRule>)} callback
+   */
+  constructor(callback) {
+    super();
+    this._callback = callback;
+    this.element.classList.add('port-forwarding-view');
+
     var portForwardingHeader = this.element.createChild('div', 'port-forwarding-header');
     var portForwardingEnabledCheckbox = UI.CheckboxLabel.create(Common.UIString('Port forwarding'));
     portForwardingEnabledCheckbox.classList.add('port-forwarding-checkbox');
     portForwardingHeader.appendChild(portForwardingEnabledCheckbox);
     this._portForwardingEnabledCheckbox = portForwardingEnabledCheckbox.checkboxElement;
-    this._portForwardingEnabledCheckbox.addEventListener('click', this._updateDiscoveryConfig.bind(this), false);
+    this._portForwardingEnabledCheckbox.addEventListener('click', this._update.bind(this), false);
 
     var portForwardingFooter = this.element.createChild('div', 'port-forwarding-footer');
     portForwardingFooter.createChild('span').textContent = Common.UIString(
@@ -252,19 +300,20 @@
     this._portForwardingConfig = [];
   }
 
+  _update() {
+    this._callback.call(null, this._portForwardingEnabledCheckbox.checked, this._portForwardingConfig);
+  }
+
   _addRuleButtonClicked() {
     this._list.addNewItem(this._portForwardingConfig.length, {port: '', address: ''});
   }
 
   /**
-   * @param {boolean} discoverUsbDevices
    * @param {boolean} portForwardingEnabled
    * @param {!Adb.PortForwardingConfig} portForwardingConfig
    */
-  discoveryConfigChanged(discoverUsbDevices, portForwardingEnabled, portForwardingConfig) {
-    this._discoverUsbDevicesCheckbox.checked = discoverUsbDevices;
+  discoveryConfigChanged(portForwardingEnabled, portForwardingConfig) {
     this._portForwardingEnabledCheckbox.checked = portForwardingEnabled;
-
     this._portForwardingConfig = [];
     this._list.clear();
     for (var key of Object.keys(portForwardingConfig)) {
@@ -299,7 +348,7 @@
   removeItemRequested(item, index) {
     this._portForwardingConfig.splice(index, 1);
     this._list.removeItem(index);
-    this._updateDiscoveryConfig();
+    this._update();
   }
 
   /**
@@ -314,7 +363,7 @@
     rule.address = editor.control('address').value.trim();
     if (isNew)
       this._portForwardingConfig.push(rule);
-    this._updateDiscoveryConfig();
+    this._update();
   }
 
   /**
@@ -352,7 +401,7 @@
      * @param {*} item
      * @param {number} index
      * @param {!HTMLInputElement|!HTMLSelectElement} input
-     * @this {Devices.DevicesView.DiscoveryView}
+     * @this {Devices.DevicesView.PortForwardingView}
      * @return {boolean}
      */
     function portValidator(item, index, input) {
@@ -384,13 +433,178 @@
       return port <= 65535;
     }
   }
+};
 
-  _updateDiscoveryConfig() {
-    var configMap = /** @type {!Adb.PortForwardingConfig} */ ({});
-    for (var rule of this._portForwardingConfig)
-      configMap[rule.port] = rule.address;
-    InspectorFrontendHost.setDevicesDiscoveryConfig(
-        this._discoverUsbDevicesCheckbox.checked, this._portForwardingEnabledCheckbox.checked, configMap);
+/**
+ * @implements {UI.ListWidget.Delegate}
+ * @unrestricted
+ */
+Devices.DevicesView.NetworkDiscoveryView = class extends UI.VBox {
+  /**
+   * @param {boolean} nodeFrontend
+   * @param {function(boolean, !Adb.NetworkDiscoveryConfig)} callback
+   */
+  constructor(nodeFrontend, callback) {
+    super();
+    this._nodeFrontend = nodeFrontend;
+    this._callback = callback;
+    this.element.classList.add('network-discovery-view');
+
+    var networkDiscoveryHeader = this.element.createChild('div', 'network-discovery-header');
+    var networkDiscoveryEnabledCheckbox = UI.CheckboxLabel.create(Common.UIString('Network targets'));
+    networkDiscoveryEnabledCheckbox.classList.add('network-discovery-checkbox');
+    networkDiscoveryHeader.appendChild(networkDiscoveryEnabledCheckbox);
+    this._networkDiscoveryEnabledCheckbox = networkDiscoveryEnabledCheckbox.checkboxElement;
+    this._networkDiscoveryEnabledCheckbox.disabled = !!Runtime.queryParam('nodeFrontend');
+    this._networkDiscoveryEnabledCheckbox.checked = !!Runtime.queryParam('nodeFrontend');
+    this._networkDiscoveryEnabledCheckbox.addEventListener('click', this._enabledCheckboxClicked.bind(this), false);
+
+    var networkDiscoveryFooter = this.element.createChild('div', 'network-discovery-footer');
+    if (nodeFrontend) {
+      networkDiscoveryFooter.createChild('span').textContent =
+          Common.UIString('Specify network endpoint and DevTools will connect to it automatically. ');
+      var link = networkDiscoveryFooter.createChild('span', 'link');
+      link.textContent = Common.UIString('Learn more');
+      link.addEventListener('click', () => InspectorFrontendHost.openInNewTab('https://nodejs.org/en/docs/inspector/'));
+    } else {
+      networkDiscoveryFooter.createChild('span').textContent = Common.UIString('Define the target connection address');
+    }
+
+    this._list = new UI.ListWidget(this);
+    this._list.registerRequiredCSS('devices/devicesView.css');
+    this._list.element.classList.add('network-discovery-list');
+    var placeholder = createElementWithClass('div', 'network-discovery-list-empty');
+    placeholder.textContent =
+        nodeFrontend ? Common.UIString('No connections specified') : Common.UIString('No addresses defined');
+    this._list.setEmptyPlaceholder(placeholder);
+    this._list.show(this.element);
+
+    var addButton = UI.createTextButton(
+        nodeFrontend ? Common.UIString('Add connection') : Common.UIString('Add address'),
+        this._addNetworkTargetButtonClicked.bind(this), 'add-network-target-button');
+    this.element.appendChild(addButton);
+
+    /** @type {!Array<{address: string}>} */
+    this._networkDiscoveryConfig = [];
+    this._networkDiscoveryEnabled = false;
+
+    if (nodeFrontend) {
+      this.element.classList.add('node-frontend');
+      this._list.element.classList.add('node-frontend');
+      addButton.classList.add('material-button', 'default');
+    }
+  }
+
+  _update() {
+    var config = this._networkDiscoveryConfig.map(item => item.address);
+    this._callback.call(null, this._networkDiscoveryEnabled, config);
+  }
+
+  _addNetworkTargetButtonClicked() {
+    this._list.addNewItem(this._networkDiscoveryConfig.length, {address: ''});
+  }
+
+  /**
+   * @param {boolean} networkDiscoveryEnabled
+   * @param {!Adb.NetworkDiscoveryConfig} networkDiscoveryConfig
+   */
+  discoveryConfigChanged(networkDiscoveryEnabled, networkDiscoveryConfig) {
+    this._networkDiscoveryEnabled = networkDiscoveryEnabled;
+    if (!Runtime.queryParam('nodeFrontend'))
+      this._networkDiscoveryEnabledCheckbox.checked = networkDiscoveryEnabled;
+    this._networkDiscoveryConfig = [];
+    this._list.clear();
+    for (var address of networkDiscoveryConfig) {
+      var item = {address: address};
+      this._networkDiscoveryConfig.push(item);
+      this._list.appendItem(item, true);
+    }
+  }
+
+  _enabledCheckboxClicked() {
+    if (!Runtime.queryParam('nodeFrontend')) {
+      this._networkDiscoveryEnabled = this._networkDiscoveryEnabledCheckbox.checked;
+      this._update();
+    }
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @param {boolean} editable
+   * @return {!Element}
+   */
+  renderItem(item, editable) {
+    var element = createElementWithClass('div', 'network-discovery-list-item');
+    element.createChild('div', 'network-discovery-value network-discovery-address').textContent =
+        /** @type {string} */ (item.address);
+    return element;
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @param {number} index
+   */
+  removeItemRequested(item, index) {
+    this._networkDiscoveryConfig.splice(index, 1);
+    this._list.removeItem(index);
+    this._update();
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @param {!UI.ListWidget.Editor} editor
+   * @param {boolean} isNew
+   */
+  commitEdit(item, editor, isNew) {
+    item.address = editor.control('address').value.trim();
+    if (isNew)
+      this._networkDiscoveryConfig.push(/** @type {{address: string}} */ (item));
+    this._update();
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @return {!UI.ListWidget.Editor}
+   */
+  beginEdit(item) {
+    var editor = this._createEditor();
+    editor.control('address').value = item.address;
+    return editor;
+  }
+
+  /**
+   * @return {!UI.ListWidget.Editor}
+   */
+  _createEditor() {
+    if (this._editor)
+      return this._editor;
+
+    var editor = new UI.ListWidget.Editor();
+    editor.setMaterial(this._nodeFrontend);
+    this._editor = editor;
+    var content = editor.contentElement();
+    var fields = content.createChild('div', 'network-discovery-edit-row');
+    var input = editor.createInput('address', 'text', 'Network address (e.g. localhost:9229)', addressValidator);
+    fields.createChild('div', 'network-discovery-value network-discovery-address').appendChild(input);
+    return editor;
+
+    /**
+     * @param {*} item
+     * @param {number} index
+     * @param {!HTMLInputElement|!HTMLSelectElement} input
+     * @return {boolean}
+     */
+    function addressValidator(item, index, input) {
+      var match = input.value.trim().match(/^([a-zA-Z0-9\.\-_]+):(\d+)$/);
+      if (!match)
+        return false;
+      var port = parseInt(match[2], 10);
+      return port <= 65535;
+    }
   }
 };
 
@@ -683,3 +897,46 @@
 
 /** @typedef {!{page: ?Adb.Page, element: !Element, title: !Element, url: !Element, inspect: !Element}} */
 Devices.DevicesView.PageSection;
+
+
+Devices.DevicesView.Panel = class extends UI.Panel {
+  constructor() {
+    super('node-connection');
+    this.registerRequiredCSS('devices/devicesView.css');
+    this.contentElement.classList.add('devices-view-panel');
+
+    var container = this.contentElement.createChild('div', 'devices-view-panel-center');
+
+    var image = container.createChild('img', 'devices-view-panel-logo');
+    image.src = 'https://nodejs.org/static/images/logos/nodejs-new-pantone-black.png';
+
+    InspectorFrontendHost.events.addEventListener(
+        InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, this._devicesDiscoveryConfigChanged, this);
+
+    /** @type {!Adb.Config} */
+    this._config;
+
+    this.contentElement.tabIndex = 0;
+    this.setDefaultFocusedElement(this.contentElement);
+
+    // Trigger notification once.
+    InspectorFrontendHost.setDevicesUpdatesEnabled(false);
+    InspectorFrontendHost.setDevicesUpdatesEnabled(true);
+
+    this._networkDiscoveryView = new Devices.DevicesView.NetworkDiscoveryView(true, (enabled, config) => {
+      this._config.networkDiscoveryEnabled = enabled;
+      this._config.networkDiscoveryConfig = config;
+      InspectorFrontendHost.setDevicesDiscoveryConfig(this._config);
+    });
+    this._networkDiscoveryView.show(container);
+  }
+
+  /**
+   * @param {!Common.Event} event
+   */
+  _devicesDiscoveryConfigChanged(event) {
+    this._config = /** @type {!Adb.Config} */ (event.data);
+    this._networkDiscoveryView.discoveryConfigChanged(
+        this._config.networkDiscoveryEnabled, this._config.networkDiscoveryConfig);
+  }
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css b/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css
index c0a7eef..1fec4e6 100644
--- a/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css
+++ b/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css
@@ -189,6 +189,73 @@
     white-space: pre-wrap;
 }
 
+.network-discovery-header {
+    display: flex;
+    align-items: center;
+    flex-direction: row;
+    margin-top: 5px;
+}
+
+.add-network-target-button {
+    margin: 10px 25px;
+    align-self: flex-start;
+}
+
+.network-discovery-list {
+    margin: 10px 0 0 25px;
+    max-width: 500px;
+    flex: none;
+}
+
+.network-discovery-list-empty {
+    flex: auto;
+    height: 30px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.network-discovery-list-item {
+    padding: 3px 5px 3px 5px;
+    height: 30px;
+    display: flex;
+    align-items: center;
+    position: relative;
+    flex: auto 1 1;
+}
+
+.network-discovery-value {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    -webkit-user-select: none;
+    color: #222;
+    flex: 3 1 0;
+    overflow: hidden;
+}
+
+.network-discovery-edit-row {
+    flex: none;
+    display: flex;
+    flex-direction: row;
+    margin: 6px 5px;
+    align-items: center;
+}
+
+.network-discovery-edit-row input {
+    width: 100%;
+    text-align: inherit;
+}
+
+.network-discovery-footer {
+    overflow: hidden;
+    margin: 15px 0 0 25px;
+    max-width: 500px;
+}
+
+.network-discovery-footer > * {
+    white-space: pre-wrap;
+}
+
 .device-view {
     overflow: auto;
     -webkit-user-select: text;
@@ -333,3 +400,65 @@
     transform: scale(1.2);
     background-color: orange;
 }
+
+.node-frontend .network-discovery-header {
+    display: none;
+}
+
+.devices-view-panel {
+    align-items: center;
+    justify-content: flex-start;
+}
+
+.node-frontend.network-discovery-view {
+    min-width: 400px;
+    flex: auto;
+}
+
+.node-frontend .add-network-target-button {
+    align-self: center;
+}
+
+:host-context(.node-frontend) .network-discovery-list-empty {
+    height: 40px;
+}
+
+:host-context(.node-frontend) .network-discovery-list-item {
+    padding: 3px 15px;
+    height: 40px;
+}
+
+.node-frontend .network-discovery-list {
+    margin: 20px 0 5px 0;
+    max-width: 600px;
+    max-height: 202px;
+}
+
+.node-frontend .network-discovery-footer {
+    margin: 0;
+}
+
+.devices-view-panel-center {
+    display: flex;
+    align-items: stretch;
+    justify-content: center;
+    max-width: 600px;
+    flex-direction: column;
+    padding-top: 50px;
+}
+
+.devices-view-panel-logo {
+    align-self: center;
+    width: 400px;
+    margin-bottom: 50px;
+    flex: none;
+}
+
+:host-context(.node-frontend) .network-discovery-edit-row input {
+    height: 30px;
+    padding-left: 5px;
+}
+
+:host-context(.node-frontend) .network-discovery-edit-row {
+    margin: 6px 9px;
+}
diff --git a/third_party/WebKit/Source/devtools/front_end/devices/module.json b/third_party/WebKit/Source/devtools/front_end/devices/module.json
index 4231f0a..f632719 100644
--- a/third_party/WebKit/Source/devtools/front_end/devices/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/devices/module.json
@@ -8,7 +8,18 @@
             "persistence": "closeable",
             "order": 50,
             "className": "Devices.DevicesView",
+            "condition": "!v8only",
             "tags": "usb, android, mobile"
+        },
+        {
+            "type": "view",
+            "location": "panel",
+            "id": "node-connection",
+            "title": "Connection",
+            "order": 0,
+            "className": "Devices.DevicesView.Panel",
+            "condition": "nodeFrontend",
+            "tags": "node"
         }
     ],
     "dependencies": ["platform", "ui", "host", "components"],
diff --git a/third_party/WebKit/Source/devtools/front_end/devtools_compatibility.js b/third_party/WebKit/Source/devtools/front_end/devtools_compatibility.js
index 05a6c5e8..bcaef60d 100644
--- a/third_party/WebKit/Source/devtools/front_end/devtools_compatibility.js
+++ b/third_party/WebKit/Source/devtools/front_end/devtools_compatibility.js
@@ -107,13 +107,10 @@
     }
 
     /**
-     * @param {boolean} discoverUsbDevices
-     * @param {boolean} portForwardingEnabled
-     * @param {!Adb.PortForwardingConfig} portForwardingConfig
+     * @param {!Adb.Config} config
      */
-    devicesDiscoveryConfigChanged(discoverUsbDevices, portForwardingEnabled, portForwardingConfig) {
-      this._dispatchOnInspectorFrontendAPI(
-          'devicesDiscoveryConfigChanged', [discoverUsbDevices, portForwardingEnabled, portForwardingConfig]);
+    devicesDiscoveryConfigChanged(config) {
+      this._dispatchOnInspectorFrontendAPI('devicesDiscoveryConfigChanged', [config]);
     }
 
     /**
@@ -667,14 +664,16 @@
 
     /**
      * @override
-     * @param {boolean} discoverUsbDevices
-     * @param {boolean} portForwardingEnabled
-     * @param {!Adb.PortForwardingConfig} portForwardingConfig
+     * @param {!Adb.Config} config
      */
-    setDevicesDiscoveryConfig(discoverUsbDevices, portForwardingEnabled, portForwardingConfig) {
+    setDevicesDiscoveryConfig(config) {
       DevToolsAPI.sendMessageToEmbedder(
           'setDevicesDiscoveryConfig',
-          [discoverUsbDevices, portForwardingEnabled, JSON.stringify(portForwardingConfig)], null);
+          [
+            config.discoverUsbDevices, config.portForwardingEnabled, JSON.stringify(config.portForwardingConfig),
+            config.networkDiscoveryEnabled, JSON.stringify(config.networkDiscoveryConfig)
+          ],
+          null);
     }
 
     /**
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/module.json b/third_party/WebKit/Source/devtools/front_end/emulation/module.json
index fdf5ad24..254857c 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/module.json
@@ -130,6 +130,7 @@
             "type": "@UI.ActionDelegate",
             "actionId": "emulation.show-sensors",
             "title": "Sensors",
+            "condition": "!v8only",
             "className": "Emulation.SensorsView.ShowActionDelegate"
         },
         {
@@ -137,6 +138,7 @@
             "location": "drawer-view",
             "id": "sensors",
             "title": "Sensors",
+            "condition": "!v8only",
             "persistence": "closeable",
             "order": 100,
             "className": "Emulation.SensorsView",
diff --git a/third_party/WebKit/Source/devtools/front_end/externs.js b/third_party/WebKit/Source/devtools/front_end/externs.js
index a05f31f..67fe898 100644
--- a/third_party/WebKit/Source/devtools/front_end/externs.js
+++ b/third_party/WebKit/Source/devtools/front_end/externs.js
@@ -344,6 +344,18 @@
 Adb.DevicePortForwardingStatus;
 /** @typedef {!Object<string, !Adb.DevicePortForwardingStatus>} */
 Adb.PortForwardingStatus;
+/** @typedef {!Array<string>} */
+Adb.NetworkDiscoveryConfig;
+/**
+ * @typedef {!{
+ *   discoverUsbDevices: boolean,
+ *   portForwardingEnabled: boolean,
+ *   portForwardingConfig: !Adb.PortForwardingConfig,
+ *   networkDiscoveryEnabled: boolean,
+ *   networkDiscoveryConfig: !Adb.NetworkDiscoveryConfig
+ * }}
+ */
+Adb.Config;
 
 /** @const */
 var module = {};
diff --git a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js
index 94f8b8a..197b438 100644
--- a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js
+++ b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js
@@ -375,11 +375,9 @@
 
   /**
    * @override
-   * @param {boolean} discoverUsbDevices
-   * @param {boolean} portForwardingEnabled
-   * @param {!Adb.PortForwardingConfig} portForwardingConfig
+   * @param {!Adb.Config} config
    */
-  setDevicesDiscoveryConfig(discoverUsbDevices, portForwardingEnabled, portForwardingConfig) {
+  setDevicesDiscoveryConfig(config) {
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js
index d00516af..c79e142 100644
--- a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js
+++ b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js
@@ -63,10 +63,7 @@
   [InspectorFrontendHostAPI.Events.ContextMenuCleared, 'contextMenuCleared', []],
   [InspectorFrontendHostAPI.Events.ContextMenuItemSelected, 'contextMenuItemSelected', ['id']],
   [InspectorFrontendHostAPI.Events.DeviceCountUpdated, 'deviceCountUpdated', ['count']],
-  [
-    InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, 'devicesDiscoveryConfigChanged',
-    ['discoverUsbDevices', 'portForwardingEnabled', 'portForwardingConfig']
-  ],
+  [InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, 'devicesDiscoveryConfigChanged', ['config']],
   [
     InspectorFrontendHostAPI.Events.DevicesPortForwardingStatusChanged, 'devicesPortForwardingStatusChanged', ['status']
   ],
@@ -250,11 +247,9 @@
   sendMessageToBackend(message) {},
 
   /**
-   * @param {boolean} discoverUsbDevices
-   * @param {boolean} portForwardingEnabled
-   * @param {!Adb.PortForwardingConfig} portForwardingConfig
+   * @param {!Adb.Config} config
    */
-  setDevicesDiscoveryConfig(discoverUsbDevices, portForwardingEnabled, portForwardingConfig) {},
+  setDevicesDiscoveryConfig(config) {},
 
   /**
    * @param {boolean} enabled
diff --git a/third_party/WebKit/Source/devtools/front_end/inspector.json b/third_party/WebKit/Source/devtools/front_end/inspector.json
index 597a0bf..73ec761 100644
--- a/third_party/WebKit/Source/devtools/front_end/inspector.json
+++ b/third_party/WebKit/Source/devtools/front_end/inspector.json
@@ -26,7 +26,7 @@
         { "name": "resources", "condition": "!v8only" },
         { "name": "audits", "condition": "!v8only" },
         { "name": "audits2", "condition": "!v8only" },
-        { "name": "devices", "condition": "!v8only" },
+        { "name": "devices" },
         { "name": "security", "condition": "!v8only" },
         { "name": "console" },
         { "name": "source_frame" },
diff --git a/third_party/WebKit/Source/devtools/front_end/main/module.json b/third_party/WebKit/Source/devtools/front_end/main/module.json
index 40cee054..2a7f138 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/main/module.json
@@ -367,6 +367,7 @@
             "title": "Rendering",
             "persistence": "closeable",
             "order": 50,
+            "condition": "!v8only",
             "className": "Main.RenderingOptionsView"
         },
         {
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
index a3f59f1..cc8ee55f 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
@@ -410,13 +410,31 @@
     if (Runtime.experiments.isEnabled('autoAttachToCrossProcessSubframes'))
       this._targetAgent.setAttachToFrames(true);
 
-    if (!parentTarget.parentTarget()) {
-      this._targetAgent.setRemoteLocations([{host: 'localhost', port: 9229}]);
+    if (!parentTarget.parentTarget())
       this._targetAgent.setDiscoverTargets(true);
+
+    if (Runtime.queryParam('nodeFrontend') && !this._parentTarget.parentTarget()) {
+      InspectorFrontendHost.setDevicesUpdatesEnabled(true);
+      InspectorFrontendHost.events.addEventListener(
+          InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, this._devicesDiscoveryConfigChanged, this);
     }
   }
 
   /**
+   * @param {!Common.Event} event
+   */
+  _devicesDiscoveryConfigChanged(event) {
+    var config = /** @type {!Adb.Config} */ (event.data);
+    var locations = [];
+    for (var address of config.networkDiscoveryConfig) {
+      var parts = address.split(':');
+      var port = parseInt(parts[1], 10);
+      locations.push({host: parts[0] || 'localhost', port: port || 9229});
+    }
+    this._targetAgent.setRemoteLocations(locations);
+  }
+
+  /**
    * @return {!Promise}
    */
   suspend() {
@@ -431,6 +449,11 @@
   }
 
   dispose() {
+    if (Runtime.queryParam('nodeFrontend') && !this._parentTarget.parentTarget()) {
+      InspectorFrontendHost.events.removeEventListener(
+          InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, this._devicesDiscoveryConfigChanged, this);
+    }
+
     // TODO(dgozman): this is O(n^2) when removing main target.
     var childTargets = this._targetManager._targets.filter(child => child.parentTarget() === this._parentTarget);
     for (var child of childTargets)
@@ -508,6 +531,9 @@
         debuggerModel.pause();
     }
     target.runtimeAgent().runIfWaitingForDebugger();
+
+    if (Runtime.queryParam('nodeFrontend'))
+      InspectorFrontendHost.bringToFront();
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js b/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js
index 2d1aa06..d96d1c6 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ListWidget.js
@@ -308,6 +308,16 @@
   }
 
   /**
+   * @param {boolean} material
+   */
+  setMaterial(material) {
+    this._commitButton.classList.toggle('material-button', material);
+    this._commitButton.classList.toggle('default', material);
+    this._cancelButton.classList.toggle('material-button', material);
+    this.element.classList.toggle('material', material);
+  }
+
+  /**
    * @param {string} name
    * @param {string} type
    * @param {string} title
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/listWidget.css b/third_party/WebKit/Source/devtools/front_end/ui/listWidget.css
index c14b144..18ecc33a 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/listWidget.css
+++ b/third_party/WebKit/Source/devtools/front_end/ui/listWidget.css
@@ -83,6 +83,10 @@
     padding: 5px;
 }
 
+.material .editor-buttons {
+    padding: 5px 9px 9px 9px;
+}
+
 .editor-buttons > button {
     flex: none;
     margin-right: 10px;
diff --git a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.cpp b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.cpp
index e72270f..de43bcd7 100644
--- a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.cpp
+++ b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.cpp
@@ -5,7 +5,7 @@
 #include "modules/credentialmanager/FederatedCredential.h"
 
 #include "bindings/core/v8/ExceptionState.h"
-#include "modules/credentialmanager/FederatedCredentialData.h"
+#include "modules/credentialmanager/FederatedCredentialInit.h"
 #include "platform/credentialmanager/PlatformFederatedCredential.h"
 #include "platform/weborigin/SecurityOrigin.h"
 #include "public/platform/WebFederatedCredential.h"
@@ -18,7 +18,7 @@
 }
 
 FederatedCredential* FederatedCredential::Create(
-    const FederatedCredentialData& data,
+    const FederatedCredentialInit& data,
     ExceptionState& exception_state) {
   if (data.id().IsEmpty()) {
     exception_state.ThrowTypeError("'id' must not be empty.");
diff --git a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.h b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.h
index 9da44cf3..3285ad7f 100644
--- a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.h
+++ b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.h
@@ -14,14 +14,14 @@
 
 namespace blink {
 
-class FederatedCredentialData;
+class FederatedCredentialInit;
 class WebFederatedCredential;
 
 class MODULES_EXPORT FederatedCredential final : public CredentialUserData {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  static FederatedCredential* Create(const FederatedCredentialData&,
+  static FederatedCredential* Create(const FederatedCredentialInit&,
                                      ExceptionState&);
   static FederatedCredential* Create(WebFederatedCredential*);
 
diff --git a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.idl b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.idl
index 9e421aa..efd7e22b 100644
--- a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.idl
+++ b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredential.idl
@@ -6,7 +6,7 @@
 
 [
     RaisesException=Constructor,
-    Constructor(FederatedCredentialData data),
+    Constructor(FederatedCredentialInit data),
     Exposed=Window,
     SecureContext
 ] interface FederatedCredential : Credential {
diff --git a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredentialData.idl b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredentialData.idl
deleted file mode 100644
index e9c92cb..0000000
--- a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredentialData.idl
+++ /dev/null
@@ -1,9 +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.
-
-// https://w3c.github.io/webappsec/specs/credentialmanagement/#dictdef-federatedcredentialdata
-
-dictionary FederatedCredentialData : LocallyStoredCredentialData {
-    USVString provider;
-};
diff --git a/third_party/WebKit/Source/modules/credentialmanager/FederatedCredentialInit.idl b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredentialInit.idl
new file mode 100644
index 0000000..4e4fff4
--- /dev/null
+++ b/third_party/WebKit/Source/modules/credentialmanager/FederatedCredentialInit.idl
@@ -0,0 +1,12 @@
+// 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.
+
+// https://w3c.github.io/webappsec-credential-management/#dictdef-federatedcredentialinit
+
+dictionary FederatedCredentialInit : CredentialData {
+  USVString name;
+  USVString iconURL;
+  required USVString provider;
+  DOMString protocol;
+};
diff --git a/third_party/WebKit/Source/modules/credentialmanager/LocallyStoredCredentialData.idl b/third_party/WebKit/Source/modules/credentialmanager/LocallyStoredCredentialData.idl
deleted file mode 100644
index 2d127f9..0000000
--- a/third_party/WebKit/Source/modules/credentialmanager/LocallyStoredCredentialData.idl
+++ /dev/null
@@ -1,10 +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.
-
-// https://w3c.github.io/webappsec/specs/credentialmanagement/#dictdef-locallystoredcredentialdata
-
-dictionary LocallyStoredCredentialData : CredentialData {
-    DOMString name;
-    DOMString iconURL;
-};
diff --git a/third_party/WebKit/Source/modules/credentialmanager/PasswordCredentialData.idl b/third_party/WebKit/Source/modules/credentialmanager/PasswordCredentialData.idl
index 8739fe66..3c6b2d9 100644
--- a/third_party/WebKit/Source/modules/credentialmanager/PasswordCredentialData.idl
+++ b/third_party/WebKit/Source/modules/credentialmanager/PasswordCredentialData.idl
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://w3c.github.io/webappsec/specs/credentialmanagement/#dictdef-passwordcredentialdata
+// https://w3c.github.io/webappsec-credential-management/#dictdef-passwordcredentialdata
 
-dictionary PasswordCredentialData : LocallyStoredCredentialData {
-    DOMString password;
+dictionary PasswordCredentialData : CredentialData {
+    USVString name;
+    USVString iconURL;
+    required USVString password;
 };
diff --git a/third_party/WebKit/Source/modules/modules_idl_files.gni b/third_party/WebKit/Source/modules/modules_idl_files.gni
index 2b0ab6a..bf0ef00 100644
--- a/third_party/WebKit/Source/modules/modules_idl_files.gni
+++ b/third_party/WebKit/Source/modules/modules_idl_files.gni
@@ -417,10 +417,9 @@
                     "canvas2d/HitRegionOptions.idl",
                     "credentialmanager/CredentialData.idl",
                     "credentialmanager/CredentialRequestOptions.idl",
-                    "credentialmanager/FederatedCredentialData.idl",
+                    "credentialmanager/FederatedCredentialInit.idl",
                     "credentialmanager/FederatedCredentialRequestOptions.idl",
                     "credentialmanager/FormDataOptions.idl",
-                    "credentialmanager/LocallyStoredCredentialData.idl",
                     "credentialmanager/PasswordCredentialData.idl",
                     "device_orientation/DeviceAccelerationInit.idl",
                     "device_orientation/DeviceMotionEventInit.idl",
@@ -738,14 +737,12 @@
   "$blink_modules_output_dir/credentialmanager/CredentialData.h",
   "$blink_modules_output_dir/credentialmanager/CredentialRequestOptions.cpp",
   "$blink_modules_output_dir/credentialmanager/CredentialRequestOptions.h",
-  "$blink_modules_output_dir/credentialmanager/FederatedCredentialData.cpp",
-  "$blink_modules_output_dir/credentialmanager/FederatedCredentialData.h",
+  "$blink_modules_output_dir/credentialmanager/FederatedCredentialInit.cpp",
+  "$blink_modules_output_dir/credentialmanager/FederatedCredentialInit.h",
   "$blink_modules_output_dir/credentialmanager/FederatedCredentialRequestOptions.cpp",
   "$blink_modules_output_dir/credentialmanager/FederatedCredentialRequestOptions.h",
   "$blink_modules_output_dir/credentialmanager/FormDataOptions.cpp",
   "$blink_modules_output_dir/credentialmanager/FormDataOptions.h",
-  "$blink_modules_output_dir/credentialmanager/LocallyStoredCredentialData.cpp",
-  "$blink_modules_output_dir/credentialmanager/LocallyStoredCredentialData.h",
   "$blink_modules_output_dir/credentialmanager/PasswordCredentialData.cpp",
   "$blink_modules_output_dir/credentialmanager/PasswordCredentialData.h",
   "$blink_modules_output_dir/device_orientation/DeviceAccelerationInit.cpp",
diff --git a/third_party/WebKit/Source/platform/DragImage.cpp b/third_party/WebKit/Source/platform/DragImage.cpp
index 74203d82..78502c39 100644
--- a/third_party/WebKit/Source/platform/DragImage.cpp
+++ b/third_party/WebKit/Source/platform/DragImage.cpp
@@ -73,13 +73,13 @@
 
 }  // anonymous namespace
 
-sk_sp<SkImage> DragImage::ResizeAndOrientImage(
-    sk_sp<SkImage> image,
+PaintImage DragImage::ResizeAndOrientImage(
+    const PaintImage& image,
     ImageOrientation orientation,
     FloatSize image_scale,
     float opacity,
     InterpolationQuality interpolation_quality) {
-  IntSize size(image->width(), image->height());
+  IntSize size(image.sk_image()->width(), image.sk_image()->height());
   size.Scale(image_scale.Width(), image_scale.Height());
   AffineTransform transform;
   if (orientation != kDefaultImageOrientation) {
@@ -90,19 +90,19 @@
   transform.ScaleNonUniform(image_scale.Width(), image_scale.Height());
 
   if (size.IsEmpty())
-    return nullptr;
+    return PaintImage();
 
   if (transform.IsIdentity() && opacity == 1) {
     // Nothing to adjust, just use the original.
-    ASSERT(image->width() == size.Width());
-    ASSERT(image->height() == size.Height());
+    DCHECK_EQ(image.sk_image()->width(), size.Width());
+    DCHECK_EQ(image.sk_image()->height(), size.Height());
     return image;
   }
 
   sk_sp<SkSurface> surface =
       SkSurface::MakeRasterN32Premul(size.Width(), size.Height());
   if (!surface)
-    return nullptr;
+    return PaintImage();
 
   SkPaint paint;
   ASSERT(opacity >= 0 && opacity <= 1);
@@ -113,9 +113,10 @@
 
   SkCanvas* canvas = surface->getCanvas();
   canvas->concat(AffineTransformToSkMatrix(transform));
-  canvas->drawImage(image, 0, 0, &paint);
+  canvas->drawImage(image.sk_image(), 0, 0, &paint);
 
-  return surface->makeImageSnapshot();
+  return PaintImage(surface->makeImageSnapshot(), image.animation_type(),
+                    image.completion_state());
 }
 
 FloatSize DragImage::ClampedImageScale(const IntSize& image_size,
@@ -150,8 +151,8 @@
   if (!image)
     return nullptr;
 
-  sk_sp<SkImage> sk_image = image->ImageForCurrentFrame();
-  if (!sk_image)
+  PaintImage paint_image = image->PaintImageForCurrentFrame();
+  if (!paint_image)
     return nullptr;
 
   ImageOrientation orientation;
@@ -160,12 +161,12 @@
     orientation = ToBitmapImage(image)->CurrentFrameOrientation();
 
   SkBitmap bm;
-  sk_sp<SkImage> resized_image =
-      ResizeAndOrientImage(std::move(sk_image), orientation, image_scale,
-                           opacity, interpolation_quality);
-  if (!resized_image ||
-      !resized_image->asLegacyBitmap(&bm, SkImage::kRO_LegacyBitmapMode))
+  paint_image = ResizeAndOrientImage(paint_image, orientation, image_scale,
+                                     opacity, interpolation_quality);
+  if (!paint_image || !paint_image.sk_image()->asLegacyBitmap(
+                          &bm, SkImage::kRO_LegacyBitmapMode)) {
     return nullptr;
+  }
 
   return WTF::WrapUnique(
       new DragImage(bm, device_scale_factor, interpolation_quality));
diff --git a/third_party/WebKit/Source/platform/DragImage.h b/third_party/WebKit/Source/platform/DragImage.h
index 9008ad1..e09aa99 100644
--- a/third_party/WebKit/Source/platform/DragImage.h
+++ b/third_party/WebKit/Source/platform/DragImage.h
@@ -32,13 +32,12 @@
 #include "platform/graphics/GraphicsTypes.h"
 #include "platform/graphics/ImageOrientation.h"
 #include "platform/graphics/paint/DisplayItemClient.h"
+#include "platform/graphics/paint/PaintImage.h"
 #include "platform/wtf/Allocator.h"
 #include "platform/wtf/Forward.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 
-class SkImage;
-
 namespace blink {
 
 class FontDescription;
@@ -74,8 +73,8 @@
 
   void Scale(float scale_x, float scale_y);
 
-  static sk_sp<SkImage> ResizeAndOrientImage(
-      sk_sp<SkImage>,
+  static PaintImage ResizeAndOrientImage(
+      const PaintImage&,
       ImageOrientation,
       FloatSize image_scale = FloatSize(1, 1),
       float opacity = 1.0,
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp
index a4d09f3..9ea5db3b2 100644
--- a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp
+++ b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaper.cpp
@@ -227,9 +227,12 @@
     if (HB_DIRECTION_IS_FORWARD(hb_buffer_get_direction(range_data->buffer))) {
       start_index = glyph_info[last_change_position].cluster;
       if (glyph_index == num_glyphs) {
-        num_characters = current_queue_item.start_index_ +
-                         current_queue_item.num_characters_ -
-                         glyph_info[last_change_position].cluster;
+        // Clamp the end offsets of the queue item to the offsets representing
+        // the shaping window.
+        unsigned shape_end =
+            std::min(range_data->end, current_queue_item.start_index_ +
+                                          current_queue_item.num_characters_);
+        num_characters = shape_end - glyph_info[last_change_position].cluster;
         num_glyphs_to_insert = num_glyphs - last_change_position;
       } else {
         num_characters = glyph_info[glyph_index].cluster -
@@ -240,9 +243,12 @@
       // Direction Backwards
       start_index = glyph_info[glyph_index - 1].cluster;
       if (last_change_position == 0) {
-        num_characters = current_queue_item.start_index_ +
-                         current_queue_item.num_characters_ -
-                         glyph_info[glyph_index - 1].cluster;
+        // Clamp the end offsets of the queue item to the offsets representing
+        // the shaping window.
+        unsigned shape_end =
+            std::min(range_data->end, current_queue_item.start_index_ +
+                                          current_queue_item.num_characters_);
+        num_characters = shape_end - glyph_info[glyph_index - 1].cluster;
       } else {
         num_characters = glyph_info[last_change_position - 1].cluster -
                          glyph_info[glyph_index - 1].cluster;
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp
index 120e2db..5a62bd3 100644
--- a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp
+++ b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp
@@ -30,6 +30,7 @@
   FontDescription font_description;
   Font font;
   unsigned start_index = 0;
+  unsigned num_characters = 0;
   unsigned num_glyphs = 0;
   hb_script_t script = HB_SCRIPT_INVALID;
 };
@@ -225,6 +226,19 @@
   RefPtr<ShapeResult> second = shaper.Shape(&font, direction, 6, 11);
   RefPtr<ShapeResult> third = shaper.Shape(&font, direction, 11, 12);
 
+  ASSERT_TRUE(TestInfo(first)->RunInfoForTesting(0, start_index, num_characters,
+                                                 num_glyphs, script));
+  EXPECT_EQ(0u, start_index);
+  EXPECT_EQ(6u, num_characters);
+  ASSERT_TRUE(TestInfo(second)->RunInfoForTesting(
+      0, start_index, num_characters, num_glyphs, script));
+  EXPECT_EQ(6u, start_index);
+  EXPECT_EQ(5u, num_characters);
+  ASSERT_TRUE(TestInfo(third)->RunInfoForTesting(0, start_index, num_characters,
+                                                 num_glyphs, script));
+  EXPECT_EQ(11u, start_index);
+  EXPECT_EQ(1u, num_characters);
+
   HarfBuzzShaper shaper2(string.Characters16(), 6);
   RefPtr<ShapeResult> first_reference = shaper2.Shape(&font, direction);
 
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.cpp b/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.cpp
index 6f8e240..c04d442 100644
--- a/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.cpp
+++ b/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.cpp
@@ -15,10 +15,12 @@
 
 bool ShapeResultTestInfo::RunInfoForTesting(unsigned run_index,
                                             unsigned& start_index,
+                                            unsigned& num_characters,
                                             unsigned& num_glyphs,
                                             hb_script_t& script) const {
   if (run_index < runs_.size() && runs_[run_index]) {
     start_index = runs_[run_index]->start_index_;
+    num_characters = runs_[run_index]->num_characters_;
     num_glyphs = runs_[run_index]->glyph_data_.size();
     script = runs_[run_index]->script_;
     return true;
@@ -26,6 +28,15 @@
   return false;
 }
 
+bool ShapeResultTestInfo::RunInfoForTesting(unsigned run_index,
+                                            unsigned& start_index,
+                                            unsigned& num_glyphs,
+                                            hb_script_t& script) const {
+  unsigned num_characters;
+  return RunInfoForTesting(run_index, start_index, num_characters, num_glyphs,
+                           script);
+}
+
 uint16_t ShapeResultTestInfo::GlyphForTesting(unsigned run_index,
                                               size_t glyph_index) const {
   return runs_[run_index]->glyph_data_[glyph_index].glyph;
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.h b/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.h
index a0f1ae0..f95d543 100644
--- a/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.h
+++ b/third_party/WebKit/Source/platform/fonts/shaping/ShapeResultTestInfo.h
@@ -19,6 +19,11 @@
                          unsigned& start_index,
                          unsigned& num_glyphs,
                          hb_script_t&) const;
+  bool RunInfoForTesting(unsigned run_index,
+                         unsigned& start_index,
+                         unsigned& num_characters,
+                         unsigned& num_glyphs,
+                         hb_script_t&) const;
   uint16_t GlyphForTesting(unsigned run_index, size_t glyph_index) const;
   float AdvanceForTesting(unsigned run_index, size_t glyph_index) const;
   SimpleFontData* FontDataForTesting(unsigned run_index) const;
diff --git a/third_party/WebKit/Source/platform/graphics/AcceleratedStaticBitmapImage.cpp b/third_party/WebKit/Source/platform/graphics/AcceleratedStaticBitmapImage.cpp
index a7b5a46a..f78253c 100644
--- a/third_party/WebKit/Source/platform/graphics/AcceleratedStaticBitmapImage.cpp
+++ b/third_party/WebKit/Source/platform/graphics/AcceleratedStaticBitmapImage.cpp
@@ -94,6 +94,8 @@
 }
 
 sk_sp<SkImage> AcceleratedStaticBitmapImage::ImageForCurrentFrame() {
+  // TODO(ccameron): This function should not ignore |colorBehavior|.
+  // https://crbug.com/672306
   CheckThread();
   if (!IsValid())
     return nullptr;
@@ -107,15 +109,11 @@
                                         const FloatRect& src_rect,
                                         RespectImageOrientationEnum,
                                         ImageClampingMode image_clamping_mode) {
-  // TODO(ccameron): This function should not ignore |colorBehavior|.
-  // https://crbug.com/672306
-  CheckThread();
-  if (!IsValid())
+  const auto& paint_image = PaintImageForCurrentFrame();
+  if (!paint_image)
     return;
-  CreateImageFromMailboxIfNeeded();
-  sk_sp<SkImage> image = texture_holder_->GetSkImage();
   StaticBitmapImage::DrawHelper(canvas, flags, dst_rect, src_rect,
-                                image_clamping_mode, image);
+                                image_clamping_mode, paint_image);
 }
 
 bool AcceleratedStaticBitmapImage::IsValid() {
diff --git a/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp b/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp
index f1524ed..2c8bbc3 100644
--- a/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp
+++ b/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp
@@ -73,17 +73,17 @@
     : Image(observer),
       current_frame_(0),
       cached_frame_index_(0),
-      repetition_count_(kCAnimationNone),
-      repetition_count_status_(kUnknown),
-      repetitions_complete_(0),
-      desired_frame_start_time_(0),
-      frame_count_(0),
       animation_policy_(kImageAnimationPolicyAllowed),
       animation_finished_(false),
       all_data_received_(false),
       have_size_(false),
       size_available_(false),
-      have_frame_count_(false) {}
+      have_frame_count_(false),
+      repetition_count_status_(kUnknown),
+      repetition_count_(kCAnimationNone),
+      repetitions_complete_(0),
+      desired_frame_start_time_(0),
+      frame_count_(0) {}
 
 BitmapImage::BitmapImage(const SkBitmap& bitmap, ImageObserver* observer)
     : Image(observer),
@@ -91,16 +91,16 @@
       current_frame_(0),
       cached_frame_(SkImage::MakeFromBitmap(bitmap)),
       cached_frame_index_(0),
-      repetition_count_(kCAnimationNone),
-      repetition_count_status_(kUnknown),
-      repetitions_complete_(0),
-      frame_count_(1),
       animation_policy_(kImageAnimationPolicyAllowed),
       animation_finished_(true),
       all_data_received_(true),
       have_size_(true),
       size_available_(true),
-      have_frame_count_(true) {
+      have_frame_count_(true),
+      repetition_count_status_(kUnknown),
+      repetition_count_(kCAnimationNone),
+      repetitions_complete_(0),
+      frame_count_(1) {
   // Since we don't have a decoder, we can't figure out the image orientation.
   // Set m_sizeRespectingOrientation to be the same as m_size so it's not 0x0.
   size_respecting_orientation_ = size_;
@@ -263,12 +263,12 @@
     ImageClampingMode clamp_mode) {
   TRACE_EVENT0("skia", "BitmapImage::draw");
 
-  sk_sp<SkImage> image = ImageForCurrentFrame();
+  PaintImage image = PaintImageForCurrentFrame();
   if (!image)
     return;  // It's too early and we don't have an image yet.
 
   FloatRect adjusted_src_rect = src_rect;
-  adjusted_src_rect.Intersect(SkRect::Make(image->bounds()));
+  adjusted_src_rect.Intersect(SkRect::Make(image.sk_image()->bounds()));
 
   if (adjusted_src_rect.IsEmpty() || dst_rect.IsEmpty())
     return;  // Nothing to draw.
@@ -299,18 +299,11 @@
     }
   }
 
-  uint32_t unique_id = image->uniqueID();
-  bool is_lazy_generated = image->isLazyGenerated();
-  auto animation_type = MaybeAnimated() ? PaintImage::AnimationType::ANIMATED
-                                        : PaintImage::AnimationType::STATIC;
-  auto completion_state = CurrentFrameIsComplete()
-                              ? PaintImage::CompletionState::DONE
-                              : PaintImage::CompletionState::PARTIALLY_DONE;
-
-  canvas->drawImageRect(
-      PaintImage(std::move(image), animation_type, completion_state),
-      adjusted_src_rect, adjusted_dst_rect, &flags,
-      WebCoreClampingModeToSkiaRectConstraint(clamp_mode));
+  uint32_t unique_id = image.sk_image()->uniqueID();
+  bool is_lazy_generated = image.sk_image()->isLazyGenerated();
+  canvas->drawImageRect(std::move(image), adjusted_src_rect, adjusted_dst_rect,
+                        &flags,
+                        WebCoreClampingModeToSkiaRectConstraint(clamp_mode));
 
   if (is_lazy_generated)
     PlatformInstrumentation::DidDrawLazyPixelRef(unique_id);
diff --git a/third_party/WebKit/Source/platform/graphics/BitmapImage.h b/third_party/WebKit/Source/platform/graphics/BitmapImage.h
index 4ee77dc5..902550e 100644
--- a/third_party/WebKit/Source/platform/graphics/BitmapImage.h
+++ b/third_party/WebKit/Source/platform/graphics/BitmapImage.h
@@ -99,7 +99,7 @@
   void AdvanceAnimationForTesting() override { InternalAdvanceAnimation(); }
 
  private:
-  enum RepetitionCountStatus {
+  enum RepetitionCountStatus : uint8_t {
     kUnknown,    // We haven't checked the source's repetition count.
     kUncertain,  // We have a repetition count, but it might be wrong (some GIFs
                  // have a count after the image data, and will report "loop
@@ -193,15 +193,6 @@
   size_t cached_frame_index_;  // Index of the frame that is cached.
 
   std::unique_ptr<Timer<BitmapImage>> frame_timer_;
-  int repetition_count_;  // How many total animation loops we should do.  This
-                          // will be cAnimationNone if this image type is
-                          // incapable of animation.
-  RepetitionCountStatus repetition_count_status_;
-  int repetitions_complete_;         // How many repetitions we've finished.
-  double desired_frame_start_time_;  // The system time at which we hope to see
-                                     // the next call to startAnimation().
-
-  size_t frame_count_;
 
   ImageAnimationPolicy
       animation_policy_;  // Whether or not we can play animation.
@@ -215,6 +206,17 @@
   bool size_available_ : 1;     // Whether we can obtain the size of the first
                                 // image frame from ImageIO yet.
   mutable bool have_frame_count_ : 1;
+
+  RepetitionCountStatus repetition_count_status_;
+  int repetition_count_;  // How many total animation loops we should do.  This
+                          // will be cAnimationNone if this image type is
+                          // incapable of animation.
+  int repetitions_complete_;  // How many repetitions we've finished.
+
+  double desired_frame_start_time_;  // The system time at which we hope to see
+                                     // the next call to startAnimation().
+
+  size_t frame_count_;
 };
 
 DEFINE_IMAGE_TYPE_CASTS(BitmapImage);
diff --git a/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.cpp b/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.cpp
index 4cd7ff8b..cedd523 100644
--- a/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.cpp
+++ b/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.cpp
@@ -84,9 +84,9 @@
 
 DeferredImageDecoder::DeferredImageDecoder(
     std::unique_ptr<ImageDecoder> actual_decoder)
-    : all_data_received_(false),
-      actual_decoder_(std::move(actual_decoder)),
+    : actual_decoder_(std::move(actual_decoder)),
       repetition_count_(kCAnimationNone),
+      all_data_received_(false),
       can_yuv_decode_(false),
       has_hot_spot_(false) {}
 
diff --git a/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.h b/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.h
index 9a842c3a..73eafe8a 100644
--- a/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.h
+++ b/third_party/WebKit/Source/platform/graphics/DeferredImageDecoder.h
@@ -98,16 +98,16 @@
   // Copy of the data that is passed in, used by deferred decoding.
   // Allows creating readonly snapshots that may be read in another thread.
   std::unique_ptr<SkRWBuffer> rw_buffer_;
-  bool all_data_received_;
   std::unique_ptr<ImageDecoder> actual_decoder_;
 
   String filename_extension_;
   IntSize size_;
   int repetition_count_;
   bool has_embedded_color_space_ = false;
-  sk_sp<SkColorSpace> color_space_for_sk_images_;
+  bool all_data_received_;
   bool can_yuv_decode_;
   bool has_hot_spot_;
+  sk_sp<SkColorSpace> color_space_for_sk_images_;
   IntPoint hot_spot_;
 
   // Caches frame state information.
diff --git a/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp b/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp
index b683998..20054d2 100644
--- a/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp
@@ -1051,44 +1051,30 @@
 void GraphicsLayer::SetContentsToImage(
     Image* image,
     RespectImageOrientationEnum respect_image_orientation) {
-  sk_sp<SkImage> sk_image;
-  PaintImage::AnimationType animation_type = PaintImage::AnimationType::UNKNOWN;
-  PaintImage::CompletionState completion_state =
-      PaintImage::CompletionState::UNKNOWN;
-  if (image) {
-    sk_image = image->ImageForCurrentFrame();
-    animation_type = image->MaybeAnimated()
-                         ? PaintImage::AnimationType::ANIMATED
-                         : PaintImage::AnimationType::STATIC;
-    completion_state = image->CurrentFrameIsComplete()
-                           ? PaintImage::CompletionState::DONE
-                           : PaintImage::CompletionState::PARTIALLY_DONE;
+  PaintImage paint_image;
+  if (image)
+    paint_image = image->PaintImageForCurrentFrame();
+
+  if (paint_image && image->IsBitmapImage() &&
+      respect_image_orientation == kRespectImageOrientation) {
+    ImageOrientation image_orientation =
+        ToBitmapImage(image)->CurrentFrameOrientation();
+    paint_image =
+        DragImage::ResizeAndOrientImage(paint_image, image_orientation);
   }
 
-  if (image && sk_image && image->IsBitmapImage()) {
-    if (respect_image_orientation == kRespectImageOrientation) {
-      ImageOrientation image_orientation =
-          ToBitmapImage(image)->CurrentFrameOrientation();
-      sk_image = DragImage::ResizeAndOrientImage(std::move(sk_image),
-                                                 image_orientation);
-    }
-  }
-
-  if (image && sk_image) {
+  if (paint_image) {
     if (!image_layer_) {
       image_layer_ =
           Platform::Current()->CompositorSupport()->CreateImageLayer();
       RegisterContentsLayer(image_layer_->Layer());
     }
-    image_layer_->SetImage(
-        PaintImage(std::move(sk_image), animation_type, completion_state));
+    image_layer_->SetImage(std::move(paint_image));
     image_layer_->Layer()->SetOpaque(image->CurrentFrameKnownToBeOpaque());
     UpdateContentsRect();
-  } else {
-    if (image_layer_) {
-      UnregisterContentsLayer(image_layer_->Layer());
-      image_layer_.reset();
-    }
+  } else if (image_layer_) {
+    UnregisterContentsLayer(image_layer_->Layer());
+    image_layer_.reset();
   }
 
   SetContentsTo(image_layer_ ? image_layer_->Layer() : 0);
diff --git a/third_party/WebKit/Source/platform/graphics/Image.cpp b/third_party/WebKit/Source/platform/graphics/Image.cpp
index 232adc4..8af49d6 100644
--- a/third_party/WebKit/Source/platform/graphics/Image.cpp
+++ b/third_party/WebKit/Source/platform/graphics/Image.cpp
@@ -52,7 +52,7 @@
 namespace blink {
 
 Image::Image(ImageObserver* observer)
-    : image_observer_(observer), image_observer_disabled_(false) {}
+    : image_observer_disabled_(false), image_observer_(observer) {}
 
 Image::~Image() {}
 
@@ -225,32 +225,24 @@
 
 namespace {
 
-sk_sp<PaintShader> CreatePatternShader(sk_sp<const SkImage> image,
+sk_sp<PaintShader> CreatePatternShader(const PaintImage& image,
                                        const SkMatrix& shader_matrix,
                                        const PaintFlags& paint,
                                        const FloatSize& spacing,
                                        SkShader::TileMode tmx,
-                                       SkShader::TileMode tmy,
-                                       bool animated,
-                                       bool complete) {
+                                       SkShader::TileMode tmy) {
   if (spacing.IsZero())
-    return MakePaintShaderImage(image, tmx, tmy, &shader_matrix);
+    return MakePaintShaderImage(image.sk_image(), tmx, tmy, &shader_matrix);
 
   // Arbitrary tiling is currently only supported for SkPictureShader, so we use
   // that instead of a plain bitmap shader to implement spacing.
-  const SkRect tile_rect = SkRect::MakeWH(image->width() + spacing.Width(),
-                                          image->height() + spacing.Height());
+  const SkRect tile_rect =
+      SkRect::MakeWH(image.sk_image()->width() + spacing.Width(),
+                     image.sk_image()->height() + spacing.Height());
 
   PaintRecorder recorder;
   PaintCanvas* canvas = recorder.beginRecording(tile_rect);
-  auto animation_type = animated ? PaintImage::AnimationType::ANIMATED
-                                 : PaintImage::AnimationType::STATIC;
-  auto completion_state = complete
-                              ? PaintImage::CompletionState::DONE
-                              : PaintImage::CompletionState::PARTIALLY_DONE;
-  canvas->drawImage(
-      PaintImage(std::move(image), animation_type, completion_state), 0, 0,
-      &paint);
+  canvas->drawImage(image, 0, 0, &paint);
 
   return MakePaintShaderRecord(recorder.finishRecordingAsPicture(), tmx, tmy,
                                &shader_matrix, nullptr);
@@ -276,13 +268,14 @@
                         const FloatSize& repeat_spacing) {
   TRACE_EVENT0("skia", "Image::drawPattern");
 
-  sk_sp<SkImage> image = ImageForCurrentFrame();
+  PaintImage image = PaintImageForCurrentFrame();
   if (!image)
     return;
 
   FloatRect norm_src_rect = float_src_rect;
 
-  norm_src_rect.Intersect(FloatRect(0, 0, image->width(), image->height()));
+  norm_src_rect.Intersect(
+      FloatRect(0, 0, image.sk_image()->width(), image.sk_image()->height()));
   if (dest_rect.IsEmpty() || norm_src_rect.IsEmpty())
     return;  // nothing to draw
 
@@ -301,15 +294,17 @@
   local_matrix.preScale(scale.Width(), scale.Height());
 
   // Fetch this now as subsetting may swap the image.
-  auto image_id = image->uniqueID();
+  auto image_id = image.sk_image()->uniqueID();
 
-  image = image->makeSubset(EnclosingIntRect(norm_src_rect));
+  image =
+      PaintImage(image.sk_image()->makeSubset(EnclosingIntRect(norm_src_rect)),
+                 image.animation_type(), image.completion_state());
   if (!image)
     return;
 
   const FloatSize tile_size(
-      image->width() * scale.Width() + repeat_spacing.Width(),
-      image->height() * scale.Height() + repeat_spacing.Height());
+      image.sk_image()->width() * scale.Width() + repeat_spacing.Width(),
+      image.sk_image()->height() * scale.Height() + repeat_spacing.Height());
   const auto tmx = ComputeTileMode(dest_rect.X(), dest_rect.MaxX(), adjusted_x,
                                    adjusted_x + tile_size.Width());
   const auto tmy = ComputeTileMode(dest_rect.Y(), dest_rect.MaxY(), adjusted_y,
@@ -322,10 +317,10 @@
       context.ComputeFilterQuality(this, dest_rect, norm_src_rect));
   flags.setAntiAlias(context.ShouldAntialias());
   flags.setShader(
-      CreatePatternShader(std::move(image), local_matrix, flags,
+      CreatePatternShader(image, local_matrix, flags,
                           FloatSize(repeat_spacing.Width() / scale.Width(),
                                     repeat_spacing.Height() / scale.Height()),
-                          tmx, tmy, MaybeAnimated(), CurrentFrameIsComplete()));
+                          tmx, tmy));
   // If the shader could not be instantiated (e.g. non-invertible matrix),
   // draw transparent.
   // Note: we can't simply bail, because of arbitrary blend mode.
@@ -344,6 +339,15 @@
   return image.Release();
 }
 
+PaintImage Image::PaintImageForCurrentFrame() {
+  auto animation_type = MaybeAnimated() ? PaintImage::AnimationType::ANIMATED
+                                        : PaintImage::AnimationType::STATIC;
+  auto completion_state = CurrentFrameIsComplete()
+                              ? PaintImage::CompletionState::DONE
+                              : PaintImage::CompletionState::PARTIALLY_DONE;
+  return PaintImage(ImageForCurrentFrame(), animation_type, completion_state);
+}
+
 bool Image::ApplyShader(PaintFlags& flags, const SkMatrix& local_matrix) {
   // Default shader impl: attempt to build a shader based on the current frame
   // SkImage.
diff --git a/third_party/WebKit/Source/platform/graphics/Image.h b/third_party/WebKit/Source/platform/graphics/Image.h
index 869a0477..64b4b264 100644
--- a/third_party/WebKit/Source/platform/graphics/Image.h
+++ b/third_party/WebKit/Source/platform/graphics/Image.h
@@ -37,6 +37,7 @@
 #include "platform/graphics/ImageOrientation.h"
 #include "platform/graphics/paint/PaintCanvas.h"
 #include "platform/graphics/paint/PaintFlags.h"
+#include "platform/graphics/paint/PaintImage.h"
 #include "platform/wtf/Assertions.h"
 #include "platform/wtf/Noncopyable.h"
 #include "platform/wtf/PassRefPtr.h"
@@ -154,6 +155,8 @@
   virtual sk_sp<SkImage> ImageForCurrentFrame() = 0;
   virtual PassRefPtr<Image> ImageForDefaultFrame();
 
+  PaintImage PaintImageForCurrentFrame();
+
   enum ImageClampingMode {
     kClampImageToSourceRect,
     kDoNotClampImageToSourceRect
@@ -209,6 +212,7 @@
                            const FloatSize& repeat_spacing = FloatSize());
 
  private:
+  bool image_observer_disabled_;
   RefPtr<SharedBuffer> encoded_image_data_;
   // TODO(Oilpan): consider having Image on the Oilpan heap and
   // turn this into a Member<>.
@@ -216,7 +220,6 @@
   // The observer (an ImageResourceContent) is an untraced member, with the
   // ImageResourceContent being responsible for clearing itself out.
   UntracedMember<ImageObserver> image_observer_;
-  bool image_observer_disabled_;
 };
 
 #define DEFINE_IMAGE_TYPE_CASTS(typeName)                          \
diff --git a/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp b/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp
index 4e9bd68..ffa76632 100644
--- a/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp
+++ b/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp
@@ -29,22 +29,15 @@
                                    const FloatRect& dst_rect,
                                    const FloatRect& src_rect,
                                    ImageClampingMode clamp_mode,
-                                   sk_sp<SkImage> image) {
+                                   const PaintImage& image) {
   FloatRect adjusted_src_rect = src_rect;
-  adjusted_src_rect.Intersect(SkRect::Make(image->bounds()));
+  adjusted_src_rect.Intersect(SkRect::Make(image.sk_image()->bounds()));
 
   if (dst_rect.IsEmpty() || adjusted_src_rect.IsEmpty())
     return;  // Nothing to draw.
 
-  auto animation_type = MaybeAnimated() ? PaintImage::AnimationType::ANIMATED
-                                        : PaintImage::AnimationType::STATIC;
-  auto completion_state = CurrentFrameIsComplete()
-                              ? PaintImage::CompletionState::DONE
-                              : PaintImage::CompletionState::PARTIALLY_DONE;
-  canvas->drawImageRect(
-      PaintImage(std::move(image), animation_type, completion_state),
-      adjusted_src_rect, dst_rect, &flags,
-      WebCoreClampingModeToSkiaRectConstraint(clamp_mode));
+  canvas->drawImageRect(image, adjusted_src_rect, dst_rect, &flags,
+                        WebCoreClampingModeToSkiaRectConstraint(clamp_mode));
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.h b/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.h
index 383fef2..6dbde204 100644
--- a/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.h
+++ b/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.h
@@ -72,7 +72,7 @@
                   const FloatRect&,
                   const FloatRect&,
                   ImageClampingMode,
-                  sk_sp<SkImage>);
+                  const PaintImage&);
 
   // These two properties are here because the SkImage API doesn't expose the
   // info. They applied to both UnacceleratedStaticBitmapImage and
diff --git a/third_party/WebKit/Source/platform/graphics/UnacceleratedStaticBitmapImage.cpp b/third_party/WebKit/Source/platform/graphics/UnacceleratedStaticBitmapImage.cpp
index ce8205ef..16c5f4eb 100644
--- a/third_party/WebKit/Source/platform/graphics/UnacceleratedStaticBitmapImage.cpp
+++ b/third_party/WebKit/Source/platform/graphics/UnacceleratedStaticBitmapImage.cpp
@@ -36,7 +36,7 @@
                                           RespectImageOrientationEnum,
                                           ImageClampingMode clamp_mode) {
   StaticBitmapImage::DrawHelper(canvas, flags, dst_rect, src_rect, clamp_mode,
-                                image_);
+                                PaintImageForCurrentFrame());
 }
 
 sk_sp<SkImage> UnacceleratedStaticBitmapImage::ImageForCurrentFrame() {
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceStatus.h b/third_party/WebKit/Source/platform/loader/fetch/ResourceStatus.h
index 47f2f29..1758503 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ResourceStatus.h
+++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceStatus.h
@@ -7,7 +7,7 @@
 
 namespace blink {
 
-enum class ResourceStatus {
+enum class ResourceStatus : uint8_t {
   kNotStarted,
   kPending,  // load in progress
   kCached,   // load completed successfully
diff --git a/third_party/WebKit/Source/platform/mojo/KURLSecurityOriginTest.cpp b/third_party/WebKit/Source/platform/mojo/KURLSecurityOriginTest.cpp
index 942c407..417f3234 100644
--- a/third_party/WebKit/Source/platform/mojo/KURLSecurityOriginTest.cpp
+++ b/third_party/WebKit/Source/platform/mojo/KURLSecurityOriginTest.cpp
@@ -78,12 +78,22 @@
       SecurityOrigin::Create("http", "www.google.com", 80);
   RefPtr<SecurityOrigin> output;
   EXPECT_TRUE(proxy->BounceOrigin(non_unique, &output));
+  EXPECT_TRUE(non_unique->IsSameSchemeHostPortAndSuborigin(output.Get()));
   EXPECT_TRUE(non_unique->IsSameSchemeHostPort(output.Get()));
-  EXPECT_FALSE(non_unique->IsUnique());
+  EXPECT_FALSE(output->HasSuborigin());
+  EXPECT_FALSE(output->IsUnique());
 
   RefPtr<SecurityOrigin> unique = SecurityOrigin::CreateUnique();
   EXPECT_TRUE(proxy->BounceOrigin(unique, &output));
   EXPECT_TRUE(output->IsUnique());
+
+  RefPtr<SecurityOrigin> with_sub_origin =
+      SecurityOrigin::Create("http", "www.google.com", 80, "suborigin");
+  EXPECT_TRUE(proxy->BounceOrigin(with_sub_origin, &output));
+  EXPECT_TRUE(with_sub_origin->IsSameSchemeHostPortAndSuborigin(output.Get()));
+  EXPECT_TRUE(with_sub_origin->IsSameSchemeHostPort(output.Get()));
+  EXPECT_TRUE(output->HasSuborigin());
+  EXPECT_FALSE(output->IsUnique());
 }
 
 }  // namespace url
diff --git a/third_party/WebKit/Source/platform/mojo/SecurityOriginStructTraits.h b/third_party/WebKit/Source/platform/mojo/SecurityOriginStructTraits.h
index e77529d..88551da 100644
--- a/third_party/WebKit/Source/platform/mojo/SecurityOriginStructTraits.h
+++ b/third_party/WebKit/Source/platform/mojo/SecurityOriginStructTraits.h
@@ -23,6 +23,10 @@
   static uint16_t port(const RefPtr<::blink::SecurityOrigin>& origin) {
     return origin->EffectivePort();
   }
+  static WTF::String suborigin(const RefPtr<::blink::SecurityOrigin>& origin) {
+    WTF::String suborigin = origin->GetSuborigin()->GetName();
+    return suborigin.IsNull() ? "" : suborigin;
+  }
   static bool unique(const RefPtr<::blink::SecurityOrigin>& origin) {
     return origin->IsUnique();
   }
@@ -33,10 +37,13 @@
     } else {
       WTF::String scheme;
       WTF::String host;
-      if (!data.ReadScheme(&scheme) || !data.ReadHost(&host))
+      WTF::String suborigin;
+      if (!data.ReadScheme(&scheme) || !data.ReadHost(&host) ||
+          !data.ReadSuborigin(&suborigin))
         return false;
 
-      *out = ::blink::SecurityOrigin::Create(scheme, host, data.port());
+      *out =
+          ::blink::SecurityOrigin::Create(scheme, host, data.port(), suborigin);
     }
 
     // If a unique origin was created, but the unique flag wasn't set, then
diff --git a/third_party/WebKit/Source/web/BUILD.gn b/third_party/WebKit/Source/web/BUILD.gn
index 0ea43ca..07257e5 100644
--- a/third_party/WebKit/Source/web/BUILD.gn
+++ b/third_party/WebKit/Source/web/BUILD.gn
@@ -123,9 +123,6 @@
     "ValidationMessageClientImpl.cpp",
     "ValidationMessageClientImpl.h",
     "WebAXObject.cpp",
-    "WebArrayBuffer.cpp",
-    "WebArrayBufferConverter.cpp",
-    "WebArrayBufferView.cpp",
     "WebCSSParser.cpp",
     "WebColorSuggestion.cpp",
     "WebCryptoNormalize.cpp",
diff --git a/third_party/binutils/Linux_ia32/binutils.tar.bz2.sha1 b/third_party/binutils/Linux_ia32/binutils.tar.bz2.sha1
index 7fd1a938..c07eb32 100644
--- a/third_party/binutils/Linux_ia32/binutils.tar.bz2.sha1
+++ b/third_party/binutils/Linux_ia32/binutils.tar.bz2.sha1
@@ -1 +1 @@
-24f937cfdad77bdcd6ad8cacc542d806f3eb4b0f
\ No newline at end of file
+0ba2f98a7d104eabe3a21ad30a3045526d241602
\ No newline at end of file
diff --git a/third_party/binutils/Linux_x64/binutils.tar.bz2.sha1 b/third_party/binutils/Linux_x64/binutils.tar.bz2.sha1
index 1434c333..7ad0a65 100644
--- a/third_party/binutils/Linux_x64/binutils.tar.bz2.sha1
+++ b/third_party/binutils/Linux_x64/binutils.tar.bz2.sha1
@@ -1 +1 @@
-d9064388bed0e7225b1366d80b59289b1509d7c2
\ No newline at end of file
+5e71702981e5f3b45632f2f209eb3a85d65ca764
\ No newline at end of file
diff --git a/third_party/binutils/README.chromium b/third_party/binutils/README.chromium
index a95ae3e..85714a9b 100644
--- a/third_party/binutils/README.chromium
+++ b/third_party/binutils/README.chromium
@@ -27,6 +27,10 @@
    https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=commit;h=d114b830426300f80302ca03ff4322942f63c615
    (Landed upstream on 2.26 branch Thu, 5 May 2016,
    and on trunk Fri, 5 Feb 2016 - will be in 2.27)
+ * icf-align.patch for https://sourceware.org/bugzilla/show_bug.cgi?id=17704
+   from upstream change
+   https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=commit;h=ac423761af22f7858a1413cda5df3e1d5e88d4e4
+   (Landed upstream Fri, 21 Oct 2016 - is in 2.28)
 
  * (build-all.sh|build-one.sh|upload.sh) scripts for building the binutils
    binaries and uploading them to Google storage.
diff --git a/third_party/binutils/build-all.sh b/third_party/binutils/build-all.sh
index b203699f..1e3d4eb 100755
--- a/third_party/binutils/build-all.sh
+++ b/third_party/binutils/build-all.sh
@@ -56,6 +56,11 @@
   patch -p1 < ../icf-rel.patch
   echo "----------------------------------"
   echo
+  echo "icf-align.patch"
+  echo "=================================="
+  patch -p1 < ../icf-align.patch
+  echo "----------------------------------"
+  echo
 )
 
 for ARCH in i386 amd64; do
diff --git a/third_party/binutils/icf-align.patch b/third_party/binutils/icf-align.patch
new file mode 100644
index 0000000..719f019
--- /dev/null
+++ b/third_party/binutils/icf-align.patch
@@ -0,0 +1,126 @@
+commit ac423761af22f7858a1413cda5df3e1d5e88d4e4
+Author: Gergely Nagy <ngg@tresorit.com>
+Date:   Fri Oct 21 11:08:20 2016 -0700
+
+    Fix PR 17704.
+    
+    This fix keeps the section with the highest alignment when folding sections with ICF.
+    
+            PR gold/17704
+            * icf.cc (match_sections): Add new parameter section_addraligns.
+            Check section alignment and keep the section with the strictest
+            alignment.
+            (find_identical_sections): New local variable section_addraligns.
+            Store each section's alignment.
+            * testsuite/pr17704a_test.s: New file.
+            * testsuite/Makefile.am (pr17704a_test): New test.
+            * testsuite/Makefile.in: Regenerate.
+
+diff --git a/gold/icf.cc b/gold/icf.cc
+index dce0b8b3e2..c09c746ae1 100644
+--- a/gold/icf.cc
++++ b/gold/icf.cc
+@@ -590,6 +590,7 @@ match_sections(unsigned int iteration_num,
+                std::vector<unsigned int>* num_tracked_relocs,
+                std::vector<unsigned int>* kept_section_id,
+                const std::vector<Section_id>& id_section,
++	       const std::vector<uint64_t>& section_addraligns,
+                std::vector<bool>* is_secn_or_group_unique,
+                std::vector<std::string>* section_contents)
+ {
+@@ -630,13 +631,7 @@ match_sections(unsigned int iteration_num,
+         {
+           if ((*kept_section_id)[i] != i)
+             {
+-              // This section is already folded into something.  See
+-              // if it should point to a different kept section.
+-              unsigned int kept_section = (*kept_section_id)[i];
+-              if (kept_section != (*kept_section_id)[kept_section])
+-                {
+-                  (*kept_section_id)[i] = (*kept_section_id)[kept_section];
+-                }
++              // This section is already folded into something.
+               continue;
+             }
+           this_secn_contents = get_section_contents(false, secn, i, NULL,
+@@ -671,7 +666,25 @@ match_sections(unsigned int iteration_num,
+                          this_secn_contents.c_str(),
+                          this_secn_contents.length()) != 0)
+                   continue;
+-              (*kept_section_id)[i] = kept_section;
++
++	      // Check section alignment here.
++	      // The section with the larger alignment requirement
++	      // should be kept.  We assume alignment can only be 
++	      // zero or postive integral powers of two.
++	      uint64_t align_i = section_addraligns[i];
++	      uint64_t align_kept = section_addraligns[kept_section];
++	      if (align_i <= align_kept)
++		{
++		  (*kept_section_id)[i] = kept_section;
++		}
++	      else
++		{
++		  (*kept_section_id)[kept_section] = i;
++		  it->second = i;
++		  full_section_contents[kept_section].swap(
++		      full_section_contents[i]);
++		}
++
+               converged = false;
+               break;
+             }
+@@ -688,6 +701,26 @@ match_sections(unsigned int iteration_num,
+         (*is_secn_or_group_unique)[i] = true;
+     }
+ 
++  // If a section was folded into another section that was later folded
++  // again then the former has to be updated.
++  for (unsigned int i = 0; i < id_section.size(); i++)
++    {
++      // Find the end of the folding chain
++      unsigned int kept = i;
++      while ((*kept_section_id)[kept] != kept)
++        {
++          kept = (*kept_section_id)[kept];
++        }
++      // Update every element of the chain
++      unsigned int current = i;
++      while ((*kept_section_id)[current] != kept)
++        {
++          unsigned int next = (*kept_section_id)[current];
++          (*kept_section_id)[current] = kept;
++          current = next;
++        }
++    }
++
+   return converged;
+ }
+ 
+@@ -719,6 +752,7 @@ Icf::find_identical_sections(const Input_objects* input_objects,
+ {
+   unsigned int section_num = 0;
+   std::vector<unsigned int> num_tracked_relocs;
++  std::vector<uint64_t> section_addraligns;
+   std::vector<bool> is_secn_or_group_unique;
+   std::vector<std::string> section_contents;
+   const Target& target = parameters->target();
+@@ -759,6 +793,7 @@ Icf::find_identical_sections(const Input_objects* input_objects,
+           this->section_id_[Section_id(*p, i)] = section_num;
+           this->kept_section_id_.push_back(section_num);
+           num_tracked_relocs.push_back(0);
++	  section_addraligns.push_back((*p)->section_addralign(i));
+           is_secn_or_group_unique.push_back(false);
+           section_contents.push_back("");
+           section_num++;
+@@ -779,8 +814,8 @@ Icf::find_identical_sections(const Input_objects* input_objects,
+       num_iterations++;
+       converged = match_sections(num_iterations, symtab,
+                                  &num_tracked_relocs, &this->kept_section_id_,
+-                                 this->id_section_, &is_secn_or_group_unique,
+-                                 &section_contents);
++                                 this->id_section_, section_addraligns,
++                                 &is_secn_or_group_unique, &section_contents);
+     }
+ 
+   if (parameters->options().print_icf_sections())
diff --git a/tools/blink_rename_merge_helper/data/idl_blocklist.txt b/tools/blink_rename_merge_helper/data/idl_blocklist.txt
index af7d04e5..d681352 100644
--- a/tools/blink_rename_merge_helper/data/idl_blocklist.txt
+++ b/tools/blink_rename_merge_helper/data/idl_blocklist.txt
@@ -4867,8 +4867,6 @@
 SharedWorkerGlobalScope:::setOnconnect:::1
 SharedWorkerPerformance:::workerStart:::2
 SharedWorker:::port:::0
-SiteBoundCredential:::iconURL:::0
-SiteBoundCredential:::name:::0
 SourceBuffer:::abort:::1
 SourceBuffer:::appendBuffer:::2
 SourceBuffer:::appendWindowEnd:::0
diff --git a/tools/ipc_fuzzer/fuzzer/fuzzer.cc b/tools/ipc_fuzzer/fuzzer/fuzzer.cc
index 159ee81f..a8d3c6c 100644
--- a/tools/ipc_fuzzer/fuzzer/fuzzer.cc
+++ b/tools/ipc_fuzzer/fuzzer/fuzzer.cc
@@ -1616,14 +1616,17 @@
     std::string scheme = p->scheme();
     std::string host = p->host();
     uint16_t port = p->port();
+    std::string suborigin = p->suborigin();
     if (!FuzzParam(&scheme, fuzzer))
       return false;
     if (!FuzzParam(&host, fuzzer))
       return false;
     if (!FuzzParam(&port, fuzzer))
       return false;
+    if (!FuzzParam(&suborigin, fuzzer))
+      return false;
     *p = url::Origin::UnsafelyCreateOriginWithoutNormalization(scheme, host,
-                                                               port);
+                                                               port, suborigin);
 
     // Force a unique origin 1% of the time:
     if (RandInRange(100) == 1)
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 49b780c..eaf175f 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -1694,7 +1694,10 @@
     'goma': {
       # The MB code will properly escape goma_dir if necessary in the GYP
       # code path; the GN code path needs no escaping.
-      'gn_args': 'use_goma=true',
+      # We also set strip_absolute_paths to ensure that we can get deterministic
+      # builds. This isn't just on by default when goma is on so that devs
+      # can do goma builds but still be able to debug by default.
+      'gn_args': 'use_goma=true strip_absolute_paths_from_debug_symbols=true',
     },
 
     'gpu_fyi_tests': {
@@ -1745,7 +1748,7 @@
     },
 
     'minimal_symbols': {
-      'gn_args': 'symbol_level=1 strip_absolute_paths_from_debug_symbols=true',
+      'gn_args': 'symbol_level=1',
     },
 
     'mipsel': {
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index f6914c72..ae4e8be0 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -1122,6 +1122,38 @@
   <description>User closed the Chrome Home bottom sheet.</description>
 </action>
 
+<action name="Android.ChromeHome.ClosedByNTPCloseButton">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User closed the Chrome Home bottom sheet using the NTP's close button.
+  </description>
+</action>
+
+<action name="Android.ChromeHome.ClosedByNavigation">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User closed the Chrome Home bottom sheet by navigating.
+  </description>
+</action>
+
+<action name="Android.ChromeHome.ClosedBySwipe">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User closed the Chrome Home bottom sheet using a swipe gesture.
+  </description>
+</action>
+
+<action name="Android.ChromeHome.ClosedByTapScrim">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User closed the Chrome Home bottom sheet tapping the scrim behind the sheet.
+  </description>
+</action>
+
 <action name="Android.ChromeHome.FullState">
   <owner>mdjones@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
@@ -1142,6 +1174,42 @@
   <owner>mdjones@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <description>User opened the Chrome Home bottom sheet.</description>
+  <obsolete>
+    Deprecated 5/2017. Replaced by the versions with a suffix, which are more
+    descriptive.
+  </obsolete>
+</action>
+
+<action name="Android.ChromeHome.OpenedByExpandButton">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User opened the Chrome Home bottom sheet using the expand button.
+  </description>
+</action>
+
+<action name="Android.ChromeHome.OpenedByNTP">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User opened the Chrome Home bottom sheet by opening a new tab.
+  </description>
+</action>
+
+<action name="Android.ChromeHome.OpenedByOmnibox">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User opened the Chrome Home bottom sheet by focusing the omnibox.
+  </description>
+</action>
+
+<action name="Android.ChromeHome.OpenedBySwipe">
+  <owner>mdjones@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <description>
+    User opened the Chrome Home bottom sheet using a swipe gesture.
+  </description>
 </action>
 
 <action name="Android.ChromeHome.ShowBookmarks">
@@ -15831,6 +15899,62 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="Suggestions.Card.ActionTapped">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>
+    User tapped on a card's button in the content suggestions UI.
+  </description>
+</action>
+
+<action name="Suggestions.Card.SwipedAway">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>
+    User swiped a card away in the content suggestions UI.
+  </description>
+</action>
+
+<action name="Suggestions.Card.Tapped">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>
+    User tapped on a card in the content suggestions UI.
+  </description>
+</action>
+
+<action name="Suggestions.Category.Dismissed">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User dismissed a content suggestions category.</description>
+</action>
+
+<action name="Suggestions.Category.Fetch">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User requested fetching more content suggestions.</description>
+</action>
+
+<action name="Suggestions.Category.ViewAll">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>
+    User navigated to the complete view of a content suggestions section.
+  </description>
+</action>
+
+<action name="Suggestions.Content.Dismissed">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User dismissed a content suggestion.</description>
+</action>
+
+<action name="Suggestions.Content.Opened">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User opened a content suggestion.</description>
+</action>
+
 <action name="Suggestions.ContextMenu.DownloadItem">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>galinap@google.com</owner>
@@ -15881,6 +16005,30 @@
   </description>
 </action>
 
+<action name="Suggestions.Site.RemovalUndone">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User reverted the removal of a suggested site.</description>
+</action>
+
+<action name="Suggestions.Site.Removed">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User removed a suggested site.</description>
+</action>
+
+<action name="Suggestions.SurfaceHidden">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User closed the content suggestions UI.</description>
+</action>
+
+<action name="Suggestions.SurfaceVisible">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>User opened the content suggestions UI.</description>
+</action>
+
 <action name="Suggestions.Tile.RemovalUndone">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>galinap@google.com</owner>
@@ -15888,6 +16036,18 @@
     Android: User tapped the undo button in the snackbar after removing a
     suggested item.
   </description>
+  <obsolete>
+    Deprecated as of 05/2017. Now recorded as
+    &quot;Suggestions.Site.RemovalUndone&quot;.
+  </obsolete>
+</action>
+
+<action name="Suggestions.Tile.Tapped">
+  <owner>finkm@chromium.org</owner>
+  <owner>dgn@chromium.org</owner>
+  <description>
+    User tapped on a tile in the content suggestions UI.
+  </description>
 </action>
 
 <action name="SuspiciousExtensionBubbleDismissed">
@@ -16322,6 +16482,13 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="Tray_NightLight">
+  <owner>afakhry@chromium.org</owner>
+  <description>
+    Records when the user toggles the Night Light feature on/off.
+  </description>
+</action>
+
 <action name="Tray_Overview">
   <owner>bruthig@chromium.org</owner>
   <owner>tdanderson@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4de19ab..b6c66639 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -12189,6 +12189,7 @@
   <int value="1170" label="WEBRTC_AUDIO_PRIVATE_SET_AUDIO_EXPERIMENTS"/>
   <int value="1171" label="AUTOTESTPRIVATE_GETPLAYSTORESTATE"/>
   <int value="1172" label="AUTOTESTPRIVATE_SETPLAYSTOREENABLED"/>
+  <int value="1173" label="APP_CURRENTWINDOWINTERNAL_SETACTIVATEONPOINTER"/>
 </enum>
 
 <enum name="ExtensionIconState" type="int">
@@ -18748,6 +18749,7 @@
   <int value="72" label="SEARCH_GEOLOCATION_DISCLOSURE_INFOBAR_DELEGATE"/>
   <int value="73" label="AUTOMATION_INFOBAR_DELEGATE"/>
   <int value="74" label="VR_SERVICES_UPGRADE_ANDROID"/>
+  <int value="75" label="READER_MODE_INFOBAR_ANDROID"/>
 </enum>
 
 <enum name="InfoBarResponse" type="int">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index ba76f7b..c309a7f 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -79006,6 +79006,17 @@
   </summary>
 </histogram>
 
+<histogram name="WebApk.Update.GooglePlayUpdateResult"
+    enum="WebApkGooglePlayInstallResult">
+  <owner>hanxi@chromium.org</owner>
+  <owner>pkotwicz@chromium.org</owner>
+  <owner>yfriedman@chromium.org</owner>
+  <summary>
+    Records whether updating a WebAPK from Google Play succeeded. If not,
+    records the reason that the update failed.
+  </summary>
+</histogram>
+
 <histogram name="WebApk.Update.RequestQueued" enum="WebApkUpdateRequestQueued">
   <owner>hanxi@chromium.org</owner>
   <owner>pkotwicz@chromium.org</owner>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 3acd28c3..06f9418 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -39,12 +39,9 @@
 loading.mobile,"kouhei@chromium.org, ksakamoto@chromium.org",
 media.android.tough_video_cases,"crouleau@chromium.org, videostack-eng@google.com",Internals>Media
 media.android.tough_video_cases_tbmv2,"johnchen@chromium.org, crouleau@chromium.org",Internals>Media
-media.chromeOS.tough_video_cases,crouleau@chromium.org,Internals>Media
-media.chromeOS4kOnly.tough_video_cases,crouleau@chromium.org,Internals>Media
 media.media_cns_cases,"crouleau@chromium.org, videostack-eng@google.com",Internals>Media
 media.mse_cases,"crouleau@chromium.org, videostack-eng@google.com",Internals>Media>Source
 media.tough_video_cases,crouleau@chromium.org,Internals>Media
-media.tough_video_cases_extra,"crouleau@chromium.org, videostack-eng@google.com",Internals>Media
 media.tough_video_cases_tbmv2,"johnchen@chromium.org, crouleau@chromium.org",Internals>Media
 media_perftests,crouleau@chromium.org,
 memory.blink_memory_mobile,bashi@chromium.org,
diff --git a/tools/perf/benchmarks/media.py b/tools/perf/benchmarks/media.py
index e56c079..0469ca2c 100644
--- a/tools/perf/benchmarks/media.py
+++ b/tools/perf/benchmarks/media.py
@@ -40,7 +40,6 @@
 
 
 # android: See media.android.tough_video_cases below
-# crbug.com/565180: Only include cases that report time_to_play
 @benchmark.Owner(emails=['crouleau@chromium.org'],
                  component='Internals>Media')
 @benchmark.Disabled('android')
@@ -54,6 +53,26 @@
     return 'media.tough_video_cases'
 
 
+@benchmark.Enabled('android')
+@benchmark.Disabled('l', 'android-webview')  # WebView: crbug.com/419689.
+@benchmark.Owner(emails=['crouleau@chromium.org', 'videostack-eng@google.com'],
+                 component='Internals>Media')
+class MediaAndroidToughVideoCases(perf_benchmark.PerfBenchmark):
+  """Obtains media metrics for key user scenarios on Android."""
+  test = media.Media
+  tag = 'android'
+  page_set = page_sets.ToughVideoCasesPageSet
+  options = {'story_tag_filter_exclude': 'is_4k,is_50fps'}
+
+  @classmethod
+  def ShouldDisable(cls, possible_browser):
+    return cls.IsSvelte(possible_browser)
+
+  @classmethod
+  def Name(cls):
+    return 'media.android.tough_video_cases'
+
+
 class _MediaTBMv2Benchmark(perf_benchmark.PerfBenchmark):
   page_set = page_sets.ToughVideoCasesPageSet
 
@@ -73,6 +92,7 @@
     return options
 
 
+# android: See media.android.tough_video_cases below
 @benchmark.Owner(emails=['johnchen@chromium.org', 'crouleau@chromium.org'],
                  component='Internals>Media')
 @benchmark.Disabled('android')
@@ -85,54 +105,6 @@
     return 'media.tough_video_cases_tbmv2'
 
 
-# crbug.com/565180: Only include cases that don't report time_to_play
-@benchmark.Disabled('android')
-@benchmark.Owner(emails=['crouleau@chromium.org', 'videostack-eng@google.com'],
-                 component='Internals>Media')
-class MediaExtra(perf_benchmark.PerfBenchmark):
-  """Obtains extra media metrics for key user scenarios."""
-  test = media.Media
-  page_set = page_sets.ToughVideoCasesExtraPageSet
-
-  @classmethod
-  def Name(cls):
-    return 'media.tough_video_cases_extra'
-
-
-@benchmark.Disabled('all')  # crbug/676345
-@benchmark.Owner(emails=['crouleau@chromium.org', 'videostack-eng@google.com'],
-                 component='Internals>Media')
-class MediaNetworkSimulation(perf_benchmark.PerfBenchmark):
-  """Obtains media metrics under different network simulations."""
-  test = media.Media
-  page_set = page_sets.MediaCnsCasesPageSet
-
-  @classmethod
-  def Name(cls):
-    return 'media.media_cns_cases'
-
-
-@benchmark.Disabled('l', 'android-webview')  # WebView: crbug.com/419689.
-@benchmark.Owner(emails=['crouleau@chromium.org', 'videostack-eng@google.com'],
-                 component='Internals>Media')
-class MediaAndroidToughVideoCases(perf_benchmark.PerfBenchmark):
-  """Obtains media metrics for key user scenarios on Android."""
-  test = media.Media
-  tag = 'android'
-  page_set = page_sets.ToughVideoCasesPageSet
-  options = {'story_tag_filter_exclude': 'is_4k,is_50fps'}
-
-  @classmethod
-  def ShouldDisable(cls, possible_browser):
-    if possible_browser.platform.GetOSName() != "android":
-      return True
-    return cls.IsSvelte(possible_browser)
-
-  @classmethod
-  def Name(cls):
-    return 'media.android.tough_video_cases'
-
-
 @benchmark.Owner(emails=['johnchen@chromium.org', 'crouleau@chromium.org'],
                  component='Internals>Media')
 @benchmark.Enabled('android')
@@ -166,46 +138,17 @@
          '--disable-gesture-requirement-for-media-playback'])
 
 
-# This isn't running anywhere. See crbug/709161.
-@benchmark.Enabled('chromeos')
-@benchmark.Owner(emails=['crouleau@chromium.org'],
+@benchmark.Disabled('all')  # crbug/676345
+@benchmark.Owner(emails=['crouleau@chromium.org', 'videostack-eng@google.com'],
                  component='Internals>Media')
-class MediaChromeOS4kOnly(perf_benchmark.PerfBenchmark):
-  """Benchmark for media performance on ChromeOS using only is_4k test content.
-  """
+class MediaNetworkSimulation(perf_benchmark.PerfBenchmark):
+  """Obtains media metrics under different network simulations."""
   test = media.Media
-  tag = 'chromeOS4kOnly'
-  page_set = page_sets.ToughVideoCasesPageSet
-  options = {
-      'story_tag_filter': 'is_4k',
-      # Exclude is_50fps test files: crbug/331816
-      'story_tag_filter_exclude': 'is_50fps'
-  }
+  page_set = page_sets.MediaCnsCasesPageSet
 
   @classmethod
   def Name(cls):
-    return 'media.chromeOS4kOnly.tough_video_cases'
-
-
-# This isn't running anywhere. See crbug/709161.
-@benchmark.Enabled('chromeos')
-@benchmark.Owner(emails=['crouleau@chromium.org'],
-                 component='Internals>Media')
-class MediaChromeOS(perf_benchmark.PerfBenchmark):
-  """Benchmark for media performance on all ChromeOS platforms.
-
-  This benchmark does not run is_4k content, there's a separate benchmark for
-  that.
-  """
-  test = media.Media
-  tag = 'chromeOS'
-  page_set = page_sets.ToughVideoCasesPageSet
-  # Exclude is_50fps test files: crbug/331816
-  options = {'story_tag_filter_exclude': 'is_4k,is_50fps'}
-
-  @classmethod
-  def Name(cls):
-    return 'media.chromeOS.tough_video_cases'
+    return 'media.media_cns_cases'
 
 
 @benchmark.Disabled('android-webview')  # crbug.com/419689
diff --git a/tools/perf/page_sets/tough_video_cases.py b/tools/perf/page_sets/tough_video_cases.py
index bc1004d6..52fee99 100644
--- a/tools/perf/page_sets/tough_video_cases.py
+++ b/tools/perf/page_sets/tough_video_cases.py
@@ -22,6 +22,9 @@
     # Other filter tags:
     'is_50fps',
     'is_4k',
+    # Play action
+    'seek',
+    'normal_play',
 ]
 
 
@@ -75,7 +78,7 @@
     super(Page4, self).__init__(
       url='file://tough_video_cases/video.html?src=crowd1080.webm',
       page_set=page_set,
-      tags=['is_50fps', 'vp8', 'vorbis', 'audio_video'])
+      tags=['is_50fps', 'vp8', 'vorbis', 'audio_video', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -89,7 +92,7 @@
     super(Page7, self).__init__(
       url='file://tough_video_cases/video.html?src=tulip2.ogg&type=audio',
       page_set=page_set,
-      tags=['vorbis', 'audio_only'])
+      tags=['vorbis', 'audio_only', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -103,7 +106,7 @@
     super(Page8, self).__init__(
       url='file://tough_video_cases/video.html?src=tulip2.wav&type=audio',
       page_set=page_set,
-      tags=['pcm', 'audio_only'])
+      tags=['pcm', 'audio_only', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -117,7 +120,7 @@
     super(Page11, self).__init__(
       url='file://tough_video_cases/video.html?src=crowd1080.mp4',
       page_set=page_set,
-      tags=['is_50fps', 'h264', 'aac', 'audio_video'])
+      tags=['is_50fps', 'h264', 'aac', 'audio_video', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -131,7 +134,7 @@
     super(Page12, self).__init__(
       url='file://tough_video_cases/video.html?src=crowd2160.mp4',
       page_set=page_set,
-      tags=['is_4k', 'is_50fps', 'h264', 'aac', 'audio_video'])
+      tags=['is_4k', 'is_50fps', 'h264', 'aac', 'audio_video', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -145,7 +148,7 @@
     super(Page13, self).__init__(
       url='file://tough_video_cases/video.html?src=tulip2.mp3&type=audio',
       page_set=page_set,
-      tags=['mp3', 'audio_only'])
+      tags=['mp3', 'audio_only', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -159,7 +162,7 @@
     super(Page14, self).__init__(
       url='file://tough_video_cases/video.html?src=tulip2.mp4',
       page_set=page_set,
-      tags=['h264', 'aac', 'audio_video'])
+      tags=['h264', 'aac', 'audio_video', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -173,7 +176,7 @@
     super(Page15, self).__init__(
       url='file://tough_video_cases/video.html?src=tulip2.m4a&type=audio',
       page_set=page_set,
-      tags=['aac', 'audio_only'])
+      tags=['aac', 'audio_only', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -187,7 +190,7 @@
     super(Page16, self).__init__(
       url='file://tough_video_cases/video.html?src=garden2_10s.webm',
       page_set=page_set,
-      tags=['is_4k', 'vp8', 'vorbis', 'audio_video'])
+      tags=['is_4k', 'vp8', 'vorbis', 'audio_video', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -201,7 +204,7 @@
     super(Page17, self).__init__(
       url='file://tough_video_cases/video.html?src=garden2_10s.mp4',
       page_set=page_set,
-      tags=['is_4k', 'h264', 'aac', 'audio_video'])
+      tags=['is_4k', 'h264', 'aac', 'audio_video', 'normal_play'])
 
     self.add_browser_metrics = True
 
@@ -213,9 +216,9 @@
 
   def __init__(self, page_set):
     super(Page19, self).__init__(
-      url='file://tough_video_cases/video.html?src=tulip2.ogg&type=audio',
+      url='file://tough_video_cases/video.html?src=tulip2.ogg&type=audio&seek',
       page_set=page_set,
-      tags=['vorbis', 'audio_only'])
+      tags=['vorbis', 'audio_only', 'seek'])
 
     self.skip_basic_metrics = True
 
@@ -227,9 +230,9 @@
 
   def __init__(self, page_set):
     super(Page20, self).__init__(
-      url='file://tough_video_cases/video.html?src=tulip2.wav&type=audio',
+      url='file://tough_video_cases/video.html?src=tulip2.wav&type=audio&seek',
       page_set=page_set,
-      tags=['pcm', 'audio_only'])
+      tags=['pcm', 'audio_only', 'seek'])
 
     self.skip_basic_metrics = True
 
@@ -241,9 +244,9 @@
 
   def __init__(self, page_set):
     super(Page23, self).__init__(
-      url='file://tough_video_cases/video.html?src=tulip2.mp3&type=audio',
+      url='file://tough_video_cases/video.html?src=tulip2.mp3&type=audio&seek',
       page_set=page_set,
-      tags=['mp3', 'audio_only'])
+      tags=['mp3', 'audio_only', 'seek'])
 
     self.skip_basic_metrics = True
 
@@ -255,9 +258,9 @@
 
   def __init__(self, page_set):
     super(Page24, self).__init__(
-      url='file://tough_video_cases/video.html?src=tulip2.mp4',
+      url='file://tough_video_cases/video.html?src=tulip2.mp4&seek',
       page_set=page_set,
-      tags=['h264', 'aac', 'audio_video'])
+      tags=['h264', 'aac', 'audio_video', 'seek'])
 
     self.skip_basic_metrics = True
 
@@ -269,9 +272,9 @@
 
   def __init__(self, page_set):
     super(Page25, self).__init__(
-      url='file://tough_video_cases/video.html?src=garden2_10s.webm',
+      url='file://tough_video_cases/video.html?src=garden2_10s.webm&seek',
       page_set=page_set,
-      tags=['is_4k', 'vp8', 'vorbis', 'audio_video'])
+      tags=['is_4k', 'vp8', 'vorbis', 'audio_video', 'seek'])
 
     self.skip_basic_metrics = True
 
@@ -283,9 +286,9 @@
 
   def __init__(self, page_set):
     super(Page26, self).__init__(
-      url='file://tough_video_cases/video.html?src=garden2_10s.mp4',
+      url='file://tough_video_cases/video.html?src=garden2_10s.mp4&seek',
       page_set=page_set,
-      tags=['is_4k', 'h264', 'aac', 'audio_video'])
+      tags=['is_4k', 'h264', 'aac', 'audio_video', 'seek'])
 
     self.skip_basic_metrics = True
 
@@ -299,86 +302,92 @@
     super(Page30, self).__init__(
       url='file://tough_video_cases/video.html?src=tulip2.vp9.webm',
       page_set=page_set,
-      tags=['vp9', 'opus', 'audio_video'])
+      tags=['vp9', 'opus', 'audio_video', 'normal_play'])
 
     self.add_browser_metrics = True
 
   def RunPageInteractions(self, action_runner):
     self.PlayAction(action_runner)
 
+
 class Page31(ToughVideoCasesPage):
 
   def __init__(self, page_set):
     super(Page31, self).__init__(
-      url='file://tough_video_cases/video.html?src=tulip2.vp9.webm',
+      url='file://tough_video_cases/video.html?src=tulip2.vp9.webm&seek',
       page_set=page_set,
-      tags=['vp9', 'opus', 'audio_video'])
+      tags=['vp9', 'opus', 'audio_video', 'seek'])
 
     self.skip_basic_metrics = True
 
   def RunPageInteractions(self, action_runner):
     self.SeekBeforeAndAfterPlayhead(action_runner)
 
+
 class Page32(ToughVideoCasesPage):
 
   def __init__(self, page_set):
     super(Page32, self).__init__(
       url='file://tough_video_cases/video.html?src=crowd1080_vp9.webm',
       page_set=page_set,
-      tags=['vp9', 'video_only'])
+      tags=['vp9', 'video_only', 'normal_play'])
 
     self.add_browser_metrics = True
 
   def RunPageInteractions(self, action_runner):
     self.PlayAction(action_runner)
 
+
 class Page33(ToughVideoCasesPage):
 
   def __init__(self, page_set):
     super(Page33, self).__init__(
-      url='file://tough_video_cases/video.html?src=crowd1080_vp9.webm',
+      url='file://tough_video_cases/video.html?src=crowd1080_vp9.webm&seek',
       page_set=page_set,
-      tags=['vp9', 'video_only'])
+      tags=['vp9', 'video_only', 'seek'])
 
     self.skip_basic_metrics = True
 
   def RunPageInteractions(self, action_runner):
     self.SeekBeforeAndAfterPlayhead(action_runner)
 
+
 class Page34(ToughVideoCasesPage):
 
   def __init__(self, page_set):
     super(Page34, self).__init__(
       url='file://tough_video_cases/video.html?src=crowd720_vp9.webm',
       page_set=page_set,
-      tags=['vp9', 'video_only'])
+      tags=['vp9', 'video_only', 'normal_play'])
 
     self.add_browser_metrics = True
 
   def RunPageInteractions(self, action_runner):
     self.PlayAction(action_runner)
 
+
 class Page35(ToughVideoCasesPage):
 
   def __init__(self, page_set):
     super(Page35, self).__init__(
-      url='file://tough_video_cases/video.html?src=crowd720_vp9.webm',
+      url='file://tough_video_cases/video.html?src=crowd720_vp9.webm&seek',
       page_set=page_set,
-      tags=['vp9', 'video_only'])
+      tags=['vp9', 'video_only', 'seek'])
 
     self.skip_basic_metrics = True
 
   def RunPageInteractions(self, action_runner):
     self.SeekBeforeAndAfterPlayhead(action_runner)
 
+
 class Page36(ToughVideoCasesPage):
 
   def __init__(self, page_set):
     super(Page36, self).__init__(
       url=('file://tough_video_cases/video.html?src='
-           'smpte_3840x2160_60fps_vp9.webm'),
+           'smpte_3840x2160_60fps_vp9.webm&seek'),
       page_set=page_set,
-      tags=['is_4k', 'vp9', 'video_only'])
+      tags=['is_4k', 'vp9', 'video_only', 'seek'])
 
     self.add_browser_metrics = True
 
@@ -389,17 +398,14 @@
 
 class ToughVideoCasesPageSet(story.StorySet):
   """
-  Description: Video Stack Perf pages that report time_to_play and many other
-  media-specific and generic metrics.
+  Description: Video Stack Perf pages that report time_to_play, seek time and
+  many other media-specific and generic metrics.
   """
   def __init__(self):
     super(ToughVideoCasesPageSet, self).__init__(
             cloud_storage_bucket=story.PARTNER_BUCKET)
-    # TODO(crouleau): Page 36 is in ToughVideoCasesPageSet even though
-    # it both reports seek time instead of time_to_play.
-    # This may be a non-issue because we plan to merge these two page sets back
-    # together and use tags to allow teams to filter which pages they want.
 
+    # Normal play tests:
     self.AddStory(Page2(self))
     self.AddStory(Page4(self))
     self.AddStory(Page7(self))
@@ -414,17 +420,8 @@
     self.AddStory(Page30(self))
     self.AddStory(Page32(self))
     self.AddStory(Page34(self))
-    self.AddStory(Page36(self))
 
-
-class ToughVideoCasesExtraPageSet(story.StorySet):
-  """
-  Description: Video Stack Perf pages that only report seek time.
-  """
-  def __init__(self):
-    super(ToughVideoCasesExtraPageSet, self).__init__(
-            cloud_storage_bucket=story.PARTNER_BUCKET)
-
+    # Seek tests:
     self.AddStory(Page19(self))
     self.AddStory(Page20(self))
     self.AddStory(Page23(self))
@@ -434,3 +431,4 @@
     self.AddStory(Page31(self))
     self.AddStory(Page33(self))
     self.AddStory(Page35(self))
+    self.AddStory(Page36(self))
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
index ce76c282..372bbfc 100644
--- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
@@ -169,7 +169,8 @@
         boolean hasCameraPermission = mWindowAndroid.hasPermission(Manifest.permission.CAMERA);
         if (mSupportsImageCapture && hasCameraPermission) {
             // GetCameraIntentTask will call LaunchSelectFileWithCameraIntent later.
-            new GetCameraIntentTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+            new GetCameraIntentTask(false, mWindowAndroid, this)
+                    .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
         } else {
             launchSelectFileWithCameraIntent(hasCameraPermission, null);
         }
@@ -272,18 +273,44 @@
                 break;
 
             case PHOTOS_SELECTED:
-                // TODO(finnur): Implement.
-                onFileNotSelected();
+                if (photos.length == 0) {
+                    onFileNotSelected();
+                    return;
+                }
+
+                if (photos.length == 1) {
+                    GetDisplayNameTask task =
+                            new GetDisplayNameTask(ContextUtils.getApplicationContext(), false);
+                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Uri.parse(photos[0]));
+                    return;
+                } else {
+                    Uri[] filePathArray = new Uri[photos.length];
+                    for (int i = 0; i < photos.length; ++i) {
+                        filePathArray[i] = Uri.parse(photos[i]);
+                    }
+                    GetDisplayNameTask task =
+                            new GetDisplayNameTask(ContextUtils.getApplicationContext(), true);
+                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, filePathArray);
+                }
                 break;
 
             case LAUNCH_GALLERY:
-                // TODO(finnur): Implement.
-                onFileNotSelected();
+                Intent intent = new Intent();
+                intent.setType("image/*");
+                if (mAllowMultiple) intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+                intent.setAction(Intent.ACTION_GET_CONTENT);
+                Activity activity = mWindowAndroid.getActivity().get();
+                if (activity != null) {
+                    String label =
+                            activity.getResources().getString(R.string.photo_picker_select_images);
+                    activity.startActivityForResult(
+                            Intent.createChooser(intent, label), PhotoPickerListener.SHOW_GALLERY);
+                }
                 break;
 
             case LAUNCH_CAMERA:
-                // TODO(finnur): Implement.
-                onFileNotSelected();
+                new GetCameraIntentTask(true, mWindowAndroid, this)
+                        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                 break;
         }
     }
@@ -294,6 +321,17 @@
     }
 
     private class GetCameraIntentTask extends AsyncTask<Void, Void, Uri> {
+        private Boolean mDirectToCamera;
+        private WindowAndroid mWindow;
+        private WindowAndroid.IntentCallback mCallback;
+
+        public GetCameraIntentTask(Boolean directToCamera, WindowAndroid window,
+                WindowAndroid.IntentCallback callback) {
+            mDirectToCamera = directToCamera;
+            mWindow = window;
+            mCallback = callback;
+        }
+
         @Override
         public Uri doInBackground(Void...voids) {
             try {
@@ -323,7 +361,11 @@
                         mWindowAndroid.getApplicationContext().getContentResolver(),
                         UiUtils.IMAGE_FILE_PATH, mCameraOutputUri));
             }
-            launchSelectFileWithCameraIntent(true, camera);
+            if (mDirectToCamera) {
+                mWindow.showIntent(camera, mCallback, R.string.low_memory_error);
+            } else {
+                launchSelectFileWithCameraIntent(true, camera);
+            }
         }
     }
 
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index e0686043..f54fb01 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -97,6 +97,8 @@
     "cocoa/constrained_window/constrained_window_animation.mm",
     "cocoa/controls/blue_label_button.h",
     "cocoa/controls/blue_label_button.mm",
+    "cocoa/controls/button_utils.h",
+    "cocoa/controls/button_utils.mm",
     "cocoa/controls/hover_image_menu_button.h",
     "cocoa/controls/hover_image_menu_button.mm",
     "cocoa/controls/hover_image_menu_button_cell.h",
@@ -105,6 +107,8 @@
     "cocoa/controls/hyperlink_button_cell.mm",
     "cocoa/controls/hyperlink_text_view.h",
     "cocoa/controls/hyperlink_text_view.mm",
+    "cocoa/controls/textfield_utils.h",
+    "cocoa/controls/textfield_utils.mm",
     "cocoa/defaults_utils.h",
     "cocoa/defaults_utils.mm",
     "cocoa/find_pasteboard.h",
diff --git a/ui/base/cocoa/controls/button_utils.h b/ui/base/cocoa/controls/button_utils.h
new file mode 100644
index 0000000..8759436
--- /dev/null
+++ b/ui/base/cocoa/controls/button_utils.h
@@ -0,0 +1,30 @@
+// 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_BASE_COCOA_CONTROLS_BUTTON_UTILS_H_
+#define UI_BASE_COCOA_CONTROLS_BUTTON_UTILS_H_
+
+#include "ui/base/ui_base_export.h"
+
+#include <Cocoa/Cocoa.h>
+
+UI_BASE_EXPORT
+@interface ButtonUtils : NSObject
+
+// These methods are a polyfill for convenience constructors that exist on
+// NSButton in macOS 10.12+.
+// TODO(ellyjones): once we target only 10.12+, delete these and migrate callers
+// over to NSButton directly.
++ (NSButton*)buttonWithTitle:(NSString*)title
+                      action:(SEL)action
+                      target:(id)target;
+
++ (NSButton*)checkboxWithTitle:(NSString*)title;
+
++ (NSButton*)linkWithTitle:(NSString*)title
+                    action:(SEL)action
+                    target:(id)target;
+@end
+
+#endif  // UI_BASE_COCOA_CONTROLS_BUTTON_UTILS_H_
diff --git a/ui/base/cocoa/controls/button_utils.mm b/ui/base/cocoa/controls/button_utils.mm
new file mode 100644
index 0000000..51a3b44
--- /dev/null
+++ b/ui/base/cocoa/controls/button_utils.mm
@@ -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.
+
+#import "ui/base/cocoa/controls/button_utils.h"
+
+#import "ui/base/cocoa/controls/hyperlink_button_cell.h"
+
+@implementation ButtonUtils
+
++ (NSButton*)buttonWithTitle:(NSString*)title
+                      action:(SEL)action
+                      target:(id)target {
+  NSButton* button = [[[NSButton alloc] initWithFrame:NSZeroRect] autorelease];
+  [button setAction:action];
+  [button setButtonType:NSMomentaryLightButton];
+  [button setFocusRingType:NSFocusRingTypeExterior];
+  [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+  [button setTarget:target];
+  [button setTitle:title];
+  [[button cell] setBezelStyle:NSRoundedBezelStyle];
+  return button;
+}
+
++ (NSButton*)checkboxWithTitle:(NSString*)title {
+  NSButton* button = [[[NSButton alloc] initWithFrame:NSZeroRect] autorelease];
+  [button setButtonType:NSSwitchButton];
+  [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+  [button setBezelStyle:NSRegularSquareBezelStyle];
+  [button setTitle:title];
+  return button;
+}
+
++ (NSButton*)linkWithTitle:(NSString*)title
+                    action:(SEL)action
+                    target:(id)target {
+  NSButton* button = [[[NSButton alloc] initWithFrame:NSZeroRect] autorelease];
+  // The cell has to be replaced before doing any of the other setup, or it will
+  // not get the new configuration.
+  [button setCell:[[[HyperlinkButtonCell alloc] init] autorelease]];
+  [button setAction:action];
+  [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+  [button setTarget:target];
+  [button setTitle:title];
+  return button;
+}
+
+@end
diff --git a/ui/base/cocoa/controls/textfield_utils.h b/ui/base/cocoa/controls/textfield_utils.h
new file mode 100644
index 0000000..dea04b8
--- /dev/null
+++ b/ui/base/cocoa/controls/textfield_utils.h
@@ -0,0 +1,22 @@
+// 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_BASE_COCOA_CONTROLS_TEXTFIELD_UTILS_H_
+#define UI_BASE_COCOA_CONTROLS_TEXTFIELD_UTILS_H_
+
+#include "ui/base/ui_base_export.h"
+
+#include <Cocoa/Cocoa.h>
+
+UI_BASE_EXPORT
+@interface TextFieldUtils : NSObject
+
+// This method is a polyfill for a method on NSTextField on macOS 10.12+.
+// TODO(ellyjones): Once we target only 10.12+, delete this and convert uses
+// over to NSTextField.
++ (NSTextField*)labelWithString:(NSString*)text;
+
+@end
+
+#endif  // UI_BASE_COCOA_CONTROLS_TEXTFIELD_UTILS_H_
diff --git a/ui/base/cocoa/controls/textfield_utils.mm b/ui/base/cocoa/controls/textfield_utils.mm
new file mode 100644
index 0000000..74f546b9
--- /dev/null
+++ b/ui/base/cocoa/controls/textfield_utils.mm
@@ -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.
+
+#import "ui/base/cocoa/controls/textfield_utils.h"
+
+@implementation TextFieldUtils
+
++ (NSTextField*)labelWithString:(NSString*)text {
+  NSTextField* textfield =
+      [[[NSTextField alloc] initWithFrame:NSZeroRect] autorelease];
+  [textfield setBezeled:NO];
+  [textfield setDrawsBackground:NO];
+  [textfield setEditable:NO];
+  [textfield setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+  [textfield setStringValue:text];
+  return textfield;
+}
+
+@end
diff --git a/ui/views/controls/webview/webview_unittest.cc b/ui/views/controls/webview/webview_unittest.cc
index 5436b29..1d9b617 100644
--- a/ui/views/controls/webview/webview_unittest.cc
+++ b/ui/views/controls/webview/webview_unittest.cc
@@ -10,12 +10,10 @@
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
-#include "base/test/scoped_task_scheduler.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/test_browser_context.h"
-#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/web_contents_tester.h"
 #include "content/test/test_content_browser_client.h"
 #include "ui/events/event.h"
@@ -132,13 +130,7 @@
 // Provides functionality to test a WebView.
 class WebViewUnitTest : public views::test::WidgetTest {
  public:
-  WebViewUnitTest()
-      : ui_thread_(content::BrowserThread::UI, base::MessageLoop::current()),
-        scoped_task_scheduler_(base::MessageLoop::current()),
-        file_blocking_thread_(content::BrowserThread::FILE_USER_BLOCKING,
-                              base::MessageLoop::current()),
-        io_thread_(content::BrowserThread::IO, base::MessageLoop::current()),
-        top_level_widget_(nullptr) {}
+  WebViewUnitTest() = default;
 
   ~WebViewUnitTest() override {}
 
@@ -185,15 +177,12 @@
   }
 
  private:
-  content::TestBrowserThread ui_thread_;
-  base::test::ScopedTaskScheduler scoped_task_scheduler_;
-  content::TestBrowserThread file_blocking_thread_;
-  content::TestBrowserThread io_thread_;
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
   std::unique_ptr<content::TestBrowserContext> browser_context_;
   content::TestContentBrowserClient test_browser_client_;
 
-  Widget* top_level_widget_;
-  WebView* web_view_;
+  Widget* top_level_widget_ = nullptr;
+  WebView* web_view_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(WebViewUnitTest);
 };
diff --git a/ui/views/test/views_test_base.cc b/ui/views/test/views_test_base.cc
index 4147f01..f7d9563a 100644
--- a/ui/views/test/views_test_base.cc
+++ b/ui/views/test/views_test_base.cc
@@ -48,7 +48,9 @@
 }  // namespace
 
 ViewsTestBase::ViewsTestBase()
-    : setup_called_(false),
+    : scoped_task_environment_(
+          base::test::ScopedTaskEnvironment::MainThreadType::UI),
+      setup_called_(false),
       teardown_called_(false),
       has_compositing_manager_(InitializeVisuals()) {}
 
diff --git a/ui/views/test/views_test_base.h b/ui/views/test/views_test_base.h
index 96763d0..af9760e 100644
--- a/ui/views/test/views_test_base.h
+++ b/ui/views/test/views_test_base.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/message_loop/message_loop.h"
+#include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
@@ -58,14 +58,12 @@
     views_delegate_for_setup_.swap(views_delegate);
   }
 
-  base::MessageLoopForUI* message_loop() { return &message_loop_; }
-
   // Returns a context view. In aura builds, this will be the
   // RootWindow. Everywhere else, NULL.
   gfx::NativeWindow GetContext();
 
  private:
-  base::MessageLoopForUI message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::unique_ptr<TestViewsDelegate> views_delegate_for_setup_;
   std::unique_ptr<ScopedViewsTestHelper> test_helper_;
   bool setup_called_;
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc
index 9c16dd9..b1027cf 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/cursor_client.h"
@@ -257,7 +258,7 @@
   // |RunWithDispatcher()| below.
   base::RunLoop run_loop;
   base::Closure quit_runloop = run_loop.QuitClosure();
-  message_loop()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::Bind(&QuitNestedLoopAndCloseWidget, base::Passed(&widget),
                  base::Unretained(&quit_runloop)));
diff --git a/url/mojo/origin.mojom b/url/mojo/origin.mojom
index 884357ba..90d1ef8 100644
--- a/url/mojo/origin.mojom
+++ b/url/mojo/origin.mojom
@@ -8,5 +8,6 @@
   string scheme;
   string host;
   uint16 port;
+  string suborigin;
   bool unique;
 };
diff --git a/url/mojo/origin_struct_traits.h b/url/mojo/origin_struct_traits.h
index 3a10a41..3b8d453 100644
--- a/url/mojo/origin_struct_traits.h
+++ b/url/mojo/origin_struct_traits.h
@@ -18,6 +18,9 @@
   static uint16_t port(const url::Origin& r) {
     return r.port();
   }
+  static const std::string& suborigin(const url::Origin& r) {
+    return r.suborigin();
+  }
   static bool unique(const url::Origin& r) {
     return r.unique();
   }
@@ -25,12 +28,13 @@
     if (data.unique()) {
       *out = url::Origin();
     } else {
-      base::StringPiece scheme, host;
-      if (!data.ReadScheme(&scheme) || !data.ReadHost(&host))
+      base::StringPiece scheme, host, suborigin;
+      if (!data.ReadScheme(&scheme) || !data.ReadHost(&host) ||
+          !data.ReadSuborigin(&suborigin))
         return false;
 
-      *out = url::Origin::UnsafelyCreateOriginWithoutNormalization(scheme, host,
-                                                                   data.port());
+      *out = url::Origin::UnsafelyCreateOriginWithoutNormalization(
+          scheme, host, data.port(), suborigin);
     }
 
     // If a unique origin was created, but the unique flag wasn't set, then
diff --git a/url/mojo/url_gurl_struct_traits_unittest.cc b/url/mojo/url_gurl_struct_traits_unittest.cc
index 8556e0a..d5efe74 100644
--- a/url/mojo/url_gurl_struct_traits_unittest.cc
+++ b/url/mojo/url_gurl_struct_traits_unittest.cc
@@ -76,15 +76,21 @@
 
   // Test basic Origin serialization.
   Origin non_unique = Origin::UnsafelyCreateOriginWithoutNormalization(
-    "http", "www.google.com", 80);
+      "http", "www.google.com", 80, "");
   Origin output;
   EXPECT_TRUE(proxy->BounceOrigin(non_unique, &output));
   EXPECT_EQ(non_unique, output);
-  EXPECT_FALSE(non_unique.unique());
+  EXPECT_FALSE(output.unique());
 
   Origin unique;
   EXPECT_TRUE(proxy->BounceOrigin(unique, &output));
   EXPECT_TRUE(output.unique());
+
+  Origin with_sub_origin = Origin::CreateFromNormalizedTupleWithSuborigin(
+      "http", "www.google.com", 80, "suborigin");
+  EXPECT_TRUE(proxy->BounceOrigin(with_sub_origin, &output));
+  EXPECT_EQ(with_sub_origin, output);
+  EXPECT_FALSE(output.unique());
 }
 
 }  // namespace url
diff --git a/url/origin.cc b/url/origin.cc
index 53600b1..d15ba43f 100644
--- a/url/origin.cc
+++ b/url/origin.cc
@@ -107,8 +107,10 @@
 Origin Origin::UnsafelyCreateOriginWithoutNormalization(
     base::StringPiece scheme,
     base::StringPiece host,
-    uint16_t port) {
-  return Origin(scheme, host, port, "", SchemeHostPort::CHECK_CANONICALIZATION);
+    uint16_t port,
+    base::StringPiece suborigin) {
+  return Origin(scheme, host, port, suborigin,
+                SchemeHostPort::CHECK_CANONICALIZATION);
 }
 
 Origin Origin::CreateFromNormalizedTupleWithSuborigin(
@@ -173,7 +175,8 @@
 }
 
 bool Origin::operator<(const Origin& other) const {
-  return tuple_ < other.tuple_;
+  return tuple_ < other.tuple_ ||
+         (tuple_.Equals(other.tuple_) && suborigin_ < other.suborigin_);
 }
 
 std::ostream& operator<<(std::ostream& out, const url::Origin& origin) {
diff --git a/url/origin.h b/url/origin.h
index 4b838e4..9e6b492a 100644
--- a/url/origin.h
+++ b/url/origin.h
@@ -89,9 +89,9 @@
   // 3. 'file' URLs all parse as ("file", "", 0).
   explicit Origin(const GURL& url);
 
-  // Creates an Origin from a |scheme|, |host|, and |port|. All the parameters
-  // must be valid and canonicalized. Do not use this method to create unique
-  // origins. Use Origin() for that.
+  // Creates an Origin from a |scheme|, |host|, |port| and |suborigin|. All the
+  // parameters must be valid and canonicalized. Do not use this method to
+  // create unique origins. Use Origin() for that.
   //
   // This constructor should be used in order to pass 'Origin' objects back and
   // forth over IPC (as transitioning through GURL would risk potentially
@@ -100,7 +100,8 @@
   static Origin UnsafelyCreateOriginWithoutNormalization(
       base::StringPiece scheme,
       base::StringPiece host,
-      uint16_t port);
+      uint16_t port,
+      base::StringPiece suborigin);
 
   // Creates an origin without sanity checking that the host is canonicalized.
   // This should only be used when converting between already normalized types,
diff --git a/url/origin_unittest.cc b/url/origin_unittest.cc
index 4e1139c..b179591 100644
--- a/url/origin_unittest.cc
+++ b/url/origin_unittest.cc
@@ -363,7 +363,7 @@
     SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":"
                                     << test.port);
     url::Origin origin = url::Origin::UnsafelyCreateOriginWithoutNormalization(
-        test.scheme, test.host, test.port);
+        test.scheme, test.host, test.port, "");
     EXPECT_EQ(test.scheme, origin.scheme());
     EXPECT_EQ(test.host, origin.host());
     EXPECT_EQ(test.port, origin.port());
@@ -400,7 +400,7 @@
     SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":"
                                     << test.port);
     url::Origin origin = url::Origin::UnsafelyCreateOriginWithoutNormalization(
-        test.scheme, test.host, test.port);
+        test.scheme, test.host, test.port, "");
     EXPECT_EQ("", origin.scheme());
     EXPECT_EQ("", origin.host());
     EXPECT_EQ(0, origin.port());
@@ -430,7 +430,7 @@
                                     << test.port);
     url::Origin origin = url::Origin::UnsafelyCreateOriginWithoutNormalization(
         std::string(test.scheme, test.scheme_length),
-        std::string(test.host, test.host_length), test.port);
+        std::string(test.host, test.host_length), test.port, "");
     EXPECT_EQ("", origin.scheme());
     EXPECT_EQ("", origin.host());
     EXPECT_EQ(0, origin.port());