diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 83e59cbb..03b6249 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1170,6 +1170,7 @@
     "frame/caption_buttons/frame_size_button_unittest.cc",
     "frame/custom_frame_view_ash_unittest.cc",
     "frame/default_header_painter_unittest.cc",
+    "ime/ime_controller_unittest.cc",
     "laser/laser_pointer_controller_unittest.cc",
     "laser/laser_pointer_points_unittest.cc",
     "laser/laser_segment_utils_unittest.cc",
diff --git a/ash/ime/ime_controller.cc b/ash/ime/ime_controller.cc
index 95121b5..03c820f 100644
--- a/ash/ime/ime_controller.cc
+++ b/ash/ime/ime_controller.cc
@@ -4,7 +4,8 @@
 
 #include "ash/ime/ime_controller.h"
 
-#include "ash/public/interfaces/ime_info.mojom.h"
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray_notifier.h"
 
 namespace ash {
 
@@ -12,20 +13,23 @@
 
 ImeController::~ImeController() = default;
 
-mojom::ImeInfo ImeController::GetCurrentIme() const {
-  return mojom::ImeInfo();
+void ImeController::RefreshIme(
+    const mojom::ImeInfo& current_ime,
+    const std::vector<mojom::ImeInfo>& available_imes,
+    const std::vector<mojom::ImeMenuItem>& menu_items) {
+  current_ime_ = current_ime;
+  available_imes_ = available_imes;
+  current_ime_menu_items_ = menu_items;
+  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
 }
 
-std::vector<mojom::ImeInfo> ImeController::GetAvailableImes() const {
-  return std::vector<mojom::ImeInfo>();
+void ImeController::SetImesManagedByPolicy(bool managed) {
+  managed_by_policy_ = managed;
+  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
 }
 
-bool ImeController::IsImeManaged() const {
-  return false;
-}
-
-std::vector<mojom::ImeMenuItem> ImeController::GetCurrentImeMenuItems() const {
-  return std::vector<mojom::ImeMenuItem>();
+void ImeController::ShowImeMenuOnShelf(bool show) {
+  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(show);
 }
 
 }  // namespace ash
diff --git a/ash/ime/ime_controller.h b/ash/ime/ime_controller.h
index 22312602..96183636 100644
--- a/ash/ime/ime_controller.h
+++ b/ash/ime/ime_controller.h
@@ -8,37 +8,55 @@
 #include <vector>
 
 #include "ash/ash_export.h"
+#include "ash/public/interfaces/ime_info.mojom.h"
 #include "base/macros.h"
 
 namespace ash {
 
-namespace mojom {
-class ImeInfo;
-class ImeMenuItem;
-}  // namespace mojom
-
 // Connects ash IME users (e.g. the system tray) to the IME implementation,
 // which might live in Chrome browser or in a separate mojo service.
 // TODO(jamescook): Convert to use mojo IME interface to Chrome browser.
 class ASH_EXPORT ImeController {
  public:
   ImeController();
-  virtual ~ImeController();
+  ~ImeController();
 
-  // Returns the currently selected IME.
-  virtual mojom::ImeInfo GetCurrentIme() const;
+  const mojom::ImeInfo& current_ime() const { return current_ime_; }
 
-  // Returns a list of available IMEs. "Available" IMEs are both installed and
-  // enabled by the user in settings.
-  virtual std::vector<mojom::ImeInfo> GetAvailableImes() const;
+  const std::vector<mojom::ImeInfo>& available_imes() const {
+    return available_imes_;
+  }
 
-  // Returns true if the available IMEs are managed by enterprise policy.
-  virtual bool IsImeManaged() const;
+  bool managed_by_policy() const { return managed_by_policy_; }
 
-  // Returns additional menu items for properties of the currently selected IME.
-  virtual std::vector<mojom::ImeMenuItem> GetCurrentImeMenuItems() const;
+  const std::vector<mojom::ImeMenuItem>& current_ime_menu_items() const {
+    return current_ime_menu_items_;
+  }
+
+  // Updates the cached IME information and refreshes the UI.
+  // TODO(jamescook): This should take an ID for current_ime.
+  void RefreshIme(const mojom::ImeInfo& current_ime,
+                  const std::vector<mojom::ImeInfo>& available_imes,
+                  const std::vector<mojom::ImeMenuItem>& menu_items);
+
+  void SetImesManagedByPolicy(bool managed);
+
+  // Shows the IME menu on the shelf instead of inside the system tray menu.
+  void ShowImeMenuOnShelf(bool show);
 
  private:
+  mojom::ImeInfo current_ime_;
+
+  // "Available" IMEs are both installed and enabled by the user in settings.
+  std::vector<mojom::ImeInfo> available_imes_;
+
+  // True if the available IMEs are currently managed by enterprise policy.
+  // For example, can occur at the login screen with device-level policy.
+  bool managed_by_policy_ = false;
+
+  // Additional menu items for properties of the currently selected IME.
+  std::vector<mojom::ImeMenuItem> current_ime_menu_items_;
+
   DISALLOW_COPY_AND_ASSIGN(ImeController);
 };
 
diff --git a/ash/ime/ime_controller_unittest.cc b/ash/ime/ime_controller_unittest.cc
new file mode 100644
index 0000000..e235e6a4
--- /dev/null
+++ b/ash/ime/ime_controller_unittest.cc
@@ -0,0 +1,86 @@
+// 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/ime/ime_controller.h"
+
+#include <vector>
+
+#include "ash/public/interfaces/ime_info.mojom.h"
+#include "ash/shell.h"
+#include "ash/system/ime/ime_observer.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/test/ash_test_base.h"
+
+namespace ash {
+namespace {
+
+class TestImeObserver : public IMEObserver {
+ public:
+  TestImeObserver() = default;
+  ~TestImeObserver() override = default;
+
+  // IMEObserver:
+  void OnIMERefresh() override { refresh_count_++; }
+  void OnIMEMenuActivationChanged(bool is_active) override {
+    ime_menu_active_ = is_active;
+  }
+
+  int refresh_count_ = 0;
+  bool ime_menu_active_ = false;
+};
+
+using ImeControllerTest = test::AshTestBase;
+
+TEST_F(ImeControllerTest, RefreshIme) {
+  ImeController* controller = Shell::Get()->ime_controller();
+  TestImeObserver observer;
+  Shell::Get()->system_tray_notifier()->AddIMEObserver(&observer);
+
+  mojom::ImeInfo ime1;
+  ime1.id = "ime1";
+  mojom::ImeInfo ime2;
+  ime2.id = "ime2";
+
+  mojom::ImeMenuItem item1;
+  item1.key = "key1";
+
+  controller->RefreshIme(ime1, {ime1, ime2}, {item1});
+
+  // Cached data was updated.
+  EXPECT_EQ("ime1", controller->current_ime().id);
+  ASSERT_EQ(2u, controller->available_imes().size());
+  EXPECT_EQ("ime1", controller->available_imes()[0].id);
+  EXPECT_EQ("ime2", controller->available_imes()[1].id);
+  ASSERT_EQ(1u, controller->current_ime_menu_items().size());
+  EXPECT_EQ("key1", controller->current_ime_menu_items()[0].key);
+
+  // Observer was notified.
+  EXPECT_EQ(1, observer.refresh_count_);
+}
+
+TEST_F(ImeControllerTest, SetImesManagedByPolicy) {
+  ImeController* controller = Shell::Get()->ime_controller();
+  TestImeObserver observer;
+  Shell::Get()->system_tray_notifier()->AddIMEObserver(&observer);
+
+  // Defaults to false.
+  EXPECT_FALSE(controller->managed_by_policy());
+
+  // Setting the value notifies observers.
+  controller->SetImesManagedByPolicy(true);
+  EXPECT_TRUE(controller->managed_by_policy());
+  EXPECT_EQ(1, observer.refresh_count_);
+}
+
+TEST_F(ImeControllerTest, ShowImeMenuOnShelf) {
+  ImeController* controller = Shell::Get()->ime_controller();
+  TestImeObserver observer;
+  Shell::Get()->system_tray_notifier()->AddIMEObserver(&observer);
+
+  controller->ShowImeMenuOnShelf(true);
+  EXPECT_TRUE(observer.ime_menu_active_);
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/ash/mojo_interface_factory.cc b/ash/mojo_interface_factory.cc
index 78ea5f4c..10aec6ab 100644
--- a/ash/mojo_interface_factory.cc
+++ b/ash/mojo_interface_factory.cc
@@ -19,6 +19,7 @@
 #include "ash/shutdown_controller.h"
 #include "ash/system/locale/locale_notification_controller.h"
 #include "ash/system/network/vpn_list.h"
+#include "ash/system/night_light/night_light_controller.h"
 #include "ash/system/tray/system_tray_controller.h"
 #include "ash/tray_action/tray_action.h"
 #include "ash/wallpaper/wallpaper_controller.h"
@@ -82,6 +83,12 @@
   Shell::Get()->new_window_controller()->BindRequest(std::move(request));
 }
 
+void BindNightLightControllerRequestOnMainThread(
+    const service_manager::BindSourceInfo& source_info,
+    mojom::NightLightControllerRequest request) {
+  Shell::Get()->night_light_controller()->BindRequest(std::move(request));
+}
+
 void BindSessionControllerRequestOnMainThread(
     const service_manager::BindSourceInfo& source_info,
     mojom::SessionControllerRequest request) {
@@ -157,6 +164,11 @@
   registry->AddInterface(
       base::Bind(&BindNewWindowControllerRequestOnMainThread),
       main_thread_task_runner);
+  if (NightLightController::IsFeatureEnabled()) {
+    registry->AddInterface(
+        base::Bind(&BindNightLightControllerRequestOnMainThread),
+        main_thread_task_runner);
+  }
   registry->AddInterface(base::Bind(&BindSessionControllerRequestOnMainThread),
                          main_thread_task_runner);
   registry->AddInterface(base::Bind(&BindShelfRequestOnMainThread),
diff --git a/ash/mus/manifest.json b/ash/mus/manifest.json
index 3a864e38..32e927d5 100644
--- a/ash/mus/manifest.json
+++ b/ash/mus/manifest.json
@@ -14,6 +14,7 @@
           "ash::mojom::LockScreen",
           "ash::mojom::MediaController",
           "ash::mojom::NewWindowController",
+          "ash::mojom::NightLightController",
           "ash::mojom::SessionController",
           "ash::mojom::ShelfController",
           "ash::mojom::ShutdownController",
diff --git a/ash/mus/shell_delegate_mus.cc b/ash/mus/shell_delegate_mus.cc
index e6a79c8..744ec66 100644
--- a/ash/mus/shell_delegate_mus.cc
+++ b/ash/mus/shell_delegate_mus.cc
@@ -87,10 +87,6 @@
   return new SystemTrayDelegateMus();
 }
 
-ImeController* ShellDelegateMus::GetImeController() {
-  return &stub_ime_controller_;
-}
-
 std::unique_ptr<WallpaperDelegate> ShellDelegateMus::CreateWallpaperDelegate() {
   return base::MakeUnique<WallpaperDelegateMus>();
 }
diff --git a/ash/mus/shell_delegate_mus.h b/ash/mus/shell_delegate_mus.h
index 4c9f266..7736d37 100644
--- a/ash/mus/shell_delegate_mus.h
+++ b/ash/mus/shell_delegate_mus.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "ash/ime/ime_controller.h"
 #include "ash/shell_delegate.h"
 #include "base/macros.h"
 
@@ -37,7 +36,6 @@
   void ShelfInit() override;
   void ShelfShutdown() override;
   SystemTrayDelegate* CreateSystemTrayDelegate() override;
-  ImeController* GetImeController() override;
   std::unique_ptr<WallpaperDelegate> CreateWallpaperDelegate() override;
   AccessibilityDelegate* CreateAccessibilityDelegate() override;
   std::unique_ptr<PaletteDelegate> CreatePaletteDelegate() override;
@@ -55,7 +53,6 @@
  private:
   // |connector_| may be null in tests.
   service_manager::Connector* connector_;
-  ImeController stub_ime_controller_;
 
   DISALLOW_COPY_AND_ASSIGN(ShellDelegateMus);
 };
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index 9c2c920b..623b242 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -22,6 +22,7 @@
     "lock_screen.mojom",
     "media.mojom",
     "new_window.mojom",
+    "night_light_controller.mojom",
     "session_controller.mojom",
     "shelf.mojom",
     "shutdown.mojom",
diff --git a/ash/public/interfaces/night_light_controller.mojom b/ash/public/interfaces/night_light_controller.mojom
new file mode 100644
index 0000000..2da100f
--- /dev/null
+++ b/ash/public/interfaces/night_light_controller.mojom
@@ -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.
+
+module ash.mojom;
+
+// Represents a geolocation position fix. It's "simple" because it doesn't
+// expose all the parameters of the position interface as defined by the
+// Geolocation API Specification:
+// (https://dev.w3.org/geo/api/spec-source.html#position_interface).
+// The NightLightController is only interested in valid latitude and longitude.
+// It also doesn't require any specific accuracy. The more accurate the
+// positions, the more accurate sunset and sunrise times calculations. However,
+// an IP-based geoposition is considered good enough.
+struct SimpleGeoposition {
+  double latitude;
+  double longitude;
+};
+
+// Used by a client (e.g. Chrome) to provide the current user's geoposition.
+interface NightLightController {
+  enum ScheduleType {
+    // Automatic toggling of NightLight is turned off.
+    kNone = 0,
+
+    // Turned automatically on at the user's local sunset time, and off at the
+    // user's local sunrise time.
+    kSunsetToSunrise = 1,
+
+    // Toggled automatically based on the custom set start and end times
+    // selected by the user from the system settings.
+    kCustom = 2,
+  };
+
+  // Sets the client that will be notified of changes in the Night Light
+  // schedule type.
+  SetClient(NightLightClient client);
+
+  // Provides the NightLightController with the user's geoposition so that it
+  // can calculate the sunset and sunrise times. This should only be called when
+  // the schedule type is set to "Sunset to Sunrise".
+  SetCurrentGeoposition(SimpleGeoposition position);
+};
+
+// Used by ash to notify a client (e.g. Chrome) of the changes in the Night
+// Light schedule type.
+interface NightLightClient {
+  // Notifies the client with the new schedule type whenever it changes.
+  OnScheduleTypeChanged(NightLightController.ScheduleType new_type);
+};
\ No newline at end of file
diff --git a/ash/shell.cc b/ash/shell.cc
index 8600cd4..ba18b54 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -571,7 +571,7 @@
           base::MakeUnique<system::BrightnessControllerChromeos>()),
       cast_config_(base::MakeUnique<CastConfigController>()),
       focus_cycler_(base::MakeUnique<FocusCycler>()),
-      ime_controller_(shell_delegate->GetImeController()),
+      ime_controller_(base::MakeUnique<ImeController>()),
       immersive_context_(base::MakeUnique<ImmersiveContextAsh>()),
       keyboard_brightness_control_delegate_(
           base::MakeUnique<KeyboardBrightnessController>()),
@@ -597,7 +597,6 @@
       native_cursor_manager_(nullptr),
       simulate_modal_window_open_for_testing_(false),
       is_touch_hud_projection_enabled_(false) {
-  DCHECK(ime_controller_);
   // TODO(sky): better refactor cash/mash dependencies. Perhaps put all cash
   // state on ShellPortClassic. http://crbug.com/671246.
 
diff --git a/ash/shell.h b/ash/shell.h
index 1150b456..5c2ad3c 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -324,7 +324,7 @@
   }
   ::wm::CompoundEventFilter* env_filter() { return env_filter_.get(); }
   FocusCycler* focus_cycler() { return focus_cycler_.get(); }
-  ImeController* ime_controller() { return ime_controller_; }
+  ImeController* ime_controller() { return ime_controller_.get(); }
   KeyboardBrightnessControlDelegate* keyboard_brightness_control_delegate() {
     return keyboard_brightness_control_delegate_.get();
   }
@@ -693,7 +693,7 @@
   std::unique_ptr<CastConfigController> cast_config_;
   std::unique_ptr<DragDropController> drag_drop_controller_;
   std::unique_ptr<FocusCycler> focus_cycler_;
-  ImeController* const ime_controller_;
+  std::unique_ptr<ImeController> ime_controller_;
   std::unique_ptr<ImmersiveContextAsh> immersive_context_;
   std::unique_ptr<KeyboardBrightnessControlDelegate>
       keyboard_brightness_control_delegate_;
diff --git a/ash/shell/shell_delegate_impl.cc b/ash/shell/shell_delegate_impl.cc
index 5dc0147..81001e1a 100644
--- a/ash/shell/shell_delegate_impl.cc
+++ b/ash/shell/shell_delegate_impl.cc
@@ -110,10 +110,6 @@
   return new SystemTrayDelegate;
 }
 
-ImeController* ShellDelegateImpl::GetImeController() {
-  return &stub_ime_controller_;
-}
-
 std::unique_ptr<WallpaperDelegate>
 ShellDelegateImpl::CreateWallpaperDelegate() {
   return base::MakeUnique<DefaultWallpaperDelegate>();
diff --git a/ash/shell/shell_delegate_impl.h b/ash/shell/shell_delegate_impl.h
index ba873a7..0074e50c 100644
--- a/ash/shell/shell_delegate_impl.h
+++ b/ash/shell/shell_delegate_impl.h
@@ -8,7 +8,6 @@
 #include <memory>
 #include <string>
 
-#include "ash/ime/ime_controller.h"
 #include "ash/shell_delegate.h"
 #include "base/macros.h"
 
@@ -39,7 +38,6 @@
   void ShelfInit() override;
   void ShelfShutdown() override;
   SystemTrayDelegate* CreateSystemTrayDelegate() override;
-  ImeController* GetImeController() override;
   std::unique_ptr<WallpaperDelegate> CreateWallpaperDelegate() override;
   AccessibilityDelegate* CreateAccessibilityDelegate() override;
   std::unique_ptr<PaletteDelegate> CreatePaletteDelegate() override;
@@ -55,8 +53,6 @@
   void UpdateTouchscreenStatusFromPrefs() override;
 
  private:
-  ImeController stub_ime_controller_;
-
   DISALLOW_COPY_AND_ASSIGN(ShellDelegateImpl);
 };
 
diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h
index 583a852..f5c62c3b 100644
--- a/ash/shell_delegate.h
+++ b/ash/shell_delegate.h
@@ -39,7 +39,6 @@
 
 class AccessibilityDelegate;
 class GPUSupport;
-class ImeController;
 class PaletteDelegate;
 class Shelf;
 struct ShelfItem;
@@ -99,9 +98,6 @@
   // Creates a system-tray delegate. Shell takes ownership of the delegate.
   virtual SystemTrayDelegate* CreateSystemTrayDelegate() = 0;
 
-  // TODO(jamescook): Remove in favor of chrome using a mojo interface on ash.
-  virtual ImeController* GetImeController() = 0;
-
   // Creates a wallpaper delegate. Shell takes ownership of the delegate.
   virtual std::unique_ptr<WallpaperDelegate> CreateWallpaperDelegate() = 0;
 
diff --git a/ash/system/ime/tray_ime_chromeos.cc b/ash/system/ime/tray_ime_chromeos.cc
index d481c19b..d79c7642 100644
--- a/ash/system/ime/tray_ime_chromeos.cc
+++ b/ash/system/ime/tray_ime_chromeos.cc
@@ -112,7 +112,7 @@
   }
 
   void CreateExtraTitleRowButtons() override {
-    if (ime_controller_->IsImeManaged()) {
+    if (ime_controller_->managed_by_policy()) {
       controlled_setting_icon_ = TrayPopupUtils::CreateMainImageView();
       controlled_setting_icon_->SetImage(
           gfx::CreateVectorIcon(kSystemMenuBusinessIcon, kMenuIconColor));
@@ -180,14 +180,16 @@
 }
 
 void TrayIME::Update() {
-  UpdateTrayLabel(current_ime_, ime_list_.size());
+  size_t ime_count = ime_controller_->available_imes().size();
+  UpdateTrayLabel(ime_controller_->current_ime(), ime_count);
   if (default_) {
     default_->SetVisible(ShouldDefaultViewBeVisible());
-    default_->UpdateLabel(GetDefaultViewLabel(ime_list_.size() > 1));
+    default_->UpdateLabel(GetDefaultViewLabel(ime_count > 1));
   }
   if (detailed_) {
-    detailed_->Update(ime_list_, property_items_, ShouldShowKeyboardToggle(),
-                      GetSingleImeBehavior());
+    detailed_->Update(ime_controller_->available_imes(),
+                      ime_controller_->current_ime_menu_items(),
+                      ShouldShowKeyboardToggle(), GetSingleImeBehavior());
   }
 }
 
@@ -215,7 +217,7 @@
 
 base::string16 TrayIME::GetDefaultViewLabel(bool show_ime_label) {
   if (show_ime_label) {
-    return ime_controller_->GetCurrentIme().name;
+    return ime_controller_->current_ime().name;
   } else {
     // Display virtual keyboard status instead.
     int id = keyboard::IsKeyboardEnabled()
@@ -239,7 +241,8 @@
 views::View* TrayIME::CreateDefaultView(LoginStatus status) {
   CHECK(default_ == nullptr);
   default_ = new tray::IMEDefaultView(
-      this, GetDefaultViewLabel(ShouldShowImeTrayItem(ime_list_.size())));
+      this, GetDefaultViewLabel(ShouldShowImeTrayItem(
+                ime_controller_->available_imes().size())));
   default_->SetVisible(ShouldDefaultViewBeVisible());
   return default_;
 }
@@ -264,30 +267,23 @@
 }
 
 void TrayIME::OnIMERefresh() {
-  // Caches the current ime state.
-  current_ime_ = ime_controller_->GetCurrentIme();
-  property_items_ = ime_controller_->GetCurrentImeMenuItems();
-  ime_list_ = ime_controller_->GetAvailableImes();
-
   Update();
 }
 
 void TrayIME::OnIMEMenuActivationChanged(bool is_active) {
   is_visible_ = !is_active;
-  if (is_visible_)
-    OnIMERefresh();
-  else
-    Update();
+  Update();
 }
 
 bool TrayIME::IsIMEManaged() {
-  return ime_controller_->IsImeManaged();
+  return ime_controller_->managed_by_policy();
 }
 
 bool TrayIME::ShouldDefaultViewBeVisible() {
   return is_visible_ &&
-         (ShouldShowImeTrayItem(ime_list_.size()) ||
-          property_items_.size() > 1 || ShouldShowKeyboardToggle());
+         (ShouldShowImeTrayItem(ime_controller_->available_imes().size()) ||
+          ime_controller_->current_ime_menu_items().size() > 1 ||
+          ShouldShowKeyboardToggle());
 }
 
 bool TrayIME::ShouldShowImeTrayItem(size_t ime_count) {
diff --git a/ash/system/ime/tray_ime_chromeos.h b/ash/system/ime/tray_ime_chromeos.h
index 3e330e5..2e27f42 100644
--- a/ash/system/ime/tray_ime_chromeos.h
+++ b/ash/system/ime/tray_ime_chromeos.h
@@ -87,12 +87,9 @@
   TrayItemView* tray_label_;
   tray::IMEDefaultView* default_;
   tray::IMEDetailedView* detailed_;
+
   // Whether the virtual keyboard is suppressed.
   bool keyboard_suppressed_;
-  // Cached IME info.
-  std::vector<mojom::ImeInfo> ime_list_;
-  mojom::ImeInfo current_ime_;
-  std::vector<mojom::ImeMenuItem> property_items_;
 
   // Whether the IME label and tray items should be visible.
   bool is_visible_;
diff --git a/ash/system/ime/tray_ime_chromeos_unittest.cc b/ash/system/ime/tray_ime_chromeos_unittest.cc
index b13a4b46..4496c08 100644
--- a/ash/system/ime/tray_ime_chromeos_unittest.cc
+++ b/ash/system/ime/tray_ime_chromeos_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "ash/system/ime/tray_ime_chromeos.h"
 
+#include <memory>
+#include <vector>
+
 #include "ash/accessibility_delegate.h"
 #include "ash/accessibility_types.h"
 #include "ash/ime/ime_controller.h"
@@ -17,31 +20,6 @@
 #include "ui/keyboard/keyboard_util.h"
 
 namespace ash {
-namespace {
-
-class TestImeController : public ImeController {
- public:
-  TestImeController() = default;
-  ~TestImeController() override = default;
-
-  // ImeController:
-  std::vector<mojom::ImeInfo> GetAvailableImes() const override {
-    return available_imes_;
-  }
-  bool IsImeManaged() const override { return is_ime_managed_; }
-  std::vector<mojom::ImeMenuItem> GetCurrentImeMenuItems() const override {
-    return current_ime_menu_items_;
-  }
-
-  std::vector<mojom::ImeMenuItem> current_ime_menu_items_;
-  std::vector<mojom::ImeInfo> available_imes_;
-  bool is_ime_managed_ = false;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TestImeController);
-};
-
-}  // namespace
 
 class TrayIMETest : public test::AshTestBase {
  public:
@@ -67,7 +45,7 @@
 
   views::View* GetImeManagedIcon();
 
-  void AddMenuItemForCurrentIme(mojom::ImeMenuItem item);
+  void SetCurrentImeMenuItems(const std::vector<mojom::ImeMenuItem>& items);
 
   void SuppressKeyboard();
   void RestoreKeyboard();
@@ -77,7 +55,6 @@
   void TearDown() override;
 
  private:
-  TestImeController test_ime_controller_;
   std::unique_ptr<TrayIME> tray_;
   std::unique_ptr<views::View> default_view_;
   std::unique_ptr<views::View> detailed_view_;
@@ -86,6 +63,10 @@
   std::vector<ui::TouchscreenDevice> touchscreen_devices_to_restore_;
   std::vector<ui::InputDevice> keyboard_devices_to_restore_;
 
+  mojom::ImeInfo current_ime_;
+  std::vector<mojom::ImeInfo> available_imes_;
+  std::vector<mojom::ImeMenuItem> menu_items_;
+
   DISALLOW_COPY_AND_ASSIGN(TrayIMETest);
 };
 
@@ -99,12 +80,9 @@
 }
 
 void TrayIMETest::SetActiveImeCount(int count) {
-  test_ime_controller_.available_imes_.clear();
-  mojom::ImeInfo ime;
-  for (int i = 0; i < count; i++) {
-    test_ime_controller_.available_imes_.push_back(ime);
-  }
-  tray_->OnIMERefresh();
+  available_imes_.resize(count);
+  Shell::Get()->ime_controller()->RefreshIme(current_ime_, available_imes_,
+                                             menu_items_);
 }
 
 views::View* TrayIMETest::GetToggleView() const {
@@ -113,17 +91,18 @@
 }
 
 void TrayIMETest::SetImeManaged(bool managed) {
-  test_ime_controller_.is_ime_managed_ = true;
-  tray_->OnIMERefresh();
+  Shell::Get()->ime_controller()->SetImesManagedByPolicy(managed);
 }
 
 views::View* TrayIMETest::GetImeManagedIcon() {
   return tray_->GetControlledSettingIconForTesting();
 }
 
-void TrayIMETest::AddMenuItemForCurrentIme(mojom::ImeMenuItem property) {
-  test_ime_controller_.current_ime_menu_items_.push_back(property);
-  tray_->OnIMERefresh();
+void TrayIMETest::SetCurrentImeMenuItems(
+    const std::vector<mojom::ImeMenuItem>& items) {
+  menu_items_ = items;
+  Shell::Get()->ime_controller()->RefreshIme(current_ime_, available_imes_,
+                                             menu_items_);
 }
 
 void TrayIMETest::SuppressKeyboard() {
@@ -159,7 +138,6 @@
 void TrayIMETest::SetUp() {
   test::AshTestBase::SetUp();
   tray_.reset(new TrayIME(GetPrimarySystemTray()));
-  tray_->ime_controller_ = &test_ime_controller_;
   default_view_.reset(tray_->CreateDefaultView(LoginStatus::USER));
   detailed_view_.reset(tray_->CreateDetailedView(LoginStatus::USER));
 }
@@ -204,11 +182,12 @@
   EXPECT_FALSE(default_view()->visible());
 
   mojom::ImeMenuItem item1;
-  AddMenuItemForCurrentIme(item1);
+  mojom::ImeMenuItem item2;
+
+  SetCurrentImeMenuItems({item1});
   EXPECT_FALSE(default_view()->visible());
 
-  mojom::ImeMenuItem item2;
-  AddMenuItemForCurrentIme(item2);
+  SetCurrentImeMenuItems({item1, item2});
   EXPECT_TRUE(default_view()->visible());
 }
 
diff --git a/ash/system/ime_menu/ime_list_view.cc b/ash/system/ime_menu/ime_list_view.cc
index 6b01718..83c692b 100644
--- a/ash/system/ime_menu/ime_list_view.cc
+++ b/ash/system/ime_menu/ime_list_view.cc
@@ -196,10 +196,9 @@
 void ImeListView::Init(bool show_keyboard_toggle,
                        SingleImeBehavior single_ime_behavior) {
   ImeController* ime_controller = Shell::Get()->ime_controller();
-  std::vector<mojom::ImeInfo> list = ime_controller->GetAvailableImes();
-  std::vector<mojom::ImeMenuItem> property_items =
-      ime_controller->GetCurrentImeMenuItems();
-  Update(list, property_items, show_keyboard_toggle, single_ime_behavior);
+  Update(ime_controller->available_imes(),
+         ime_controller->current_ime_menu_items(), show_keyboard_toggle,
+         single_ime_behavior);
 }
 
 void ImeListView::Update(const std::vector<mojom::ImeInfo>& list,
diff --git a/ash/system/ime_menu/ime_menu_tray.cc b/ash/system/ime_menu/ime_menu_tray.cc
index e354c595..de4071a6 100644
--- a/ash/system/ime_menu/ime_menu_tray.cc
+++ b/ash/system/ime_menu/ime_menu_tray.cc
@@ -412,8 +412,8 @@
   // 4) password input client.
   return InputMethodManager::Get() &&
          InputMethodManager::Get()->IsEmojiHandwritingVoiceOnImeMenuEnabled() &&
-         !current_ime_.third_party && !IsInLoginOrLockScreen() &&
-         !IsInPasswordInputContext();
+         !ime_controller_->current_ime().third_party &&
+         !IsInLoginOrLockScreen() && !IsInPasswordInputContext();
 }
 
 bool ImeMenuTray::ShouldShowKeyboardToggle() const {
@@ -445,10 +445,8 @@
 void ImeMenuTray::OnIMERefresh() {
   UpdateTrayLabel();
   if (bubble_ && ime_list_view_) {
-    std::vector<mojom::ImeInfo> imes = ime_controller_->GetAvailableImes();
-    std::vector<mojom::ImeMenuItem> property_items =
-        ime_controller_->GetCurrentImeMenuItems();
-    ime_list_view_->Update(imes, property_items, false,
+    ime_list_view_->Update(ime_controller_->available_imes(),
+                           ime_controller_->current_ime_menu_items(), false,
                            ImeListView::SHOW_SINGLE_IME);
   }
 }
@@ -534,13 +532,13 @@
 }
 
 void ImeMenuTray::UpdateTrayLabel() {
-  current_ime_ = ime_controller_->GetCurrentIme();
+  const mojom::ImeInfo& current_ime = ime_controller_->current_ime();
 
   // Updates the tray label based on the current input method.
-  if (current_ime_.third_party)
-    label_->SetText(current_ime_.short_name + base::UTF8ToUTF16("*"));
+  if (current_ime.third_party)
+    label_->SetText(current_ime.short_name + base::UTF8ToUTF16("*"));
   else
-    label_->SetText(current_ime_.short_name);
+    label_->SetText(current_ime.short_name);
 }
 
 void ImeMenuTray::DisableVirtualKeyboard() {
diff --git a/ash/system/ime_menu/ime_menu_tray.h b/ash/system/ime_menu/ime_menu_tray.h
index 41b86d9..39867fb 100644
--- a/ash/system/ime_menu/ime_menu_tray.h
+++ b/ash/system/ime_menu/ime_menu_tray.h
@@ -103,7 +103,6 @@
   ImeListView* ime_list_view_;
 
   views::Label* label_;
-  mojom::ImeInfo current_ime_;
   bool show_keyboard_;
   bool force_show_keyboard_;
   bool keyboard_suppressed_;
diff --git a/ash/system/ime_menu/ime_menu_tray_unittest.cc b/ash/system/ime_menu/ime_menu_tray_unittest.cc
index 2cc21c0..7602ac3 100644
--- a/ash/system/ime_menu/ime_menu_tray_unittest.cc
+++ b/ash/system/ime_menu/ime_menu_tray_unittest.cc
@@ -11,7 +11,6 @@
 #include "ash/shell.h"
 #include "ash/system/ime_menu/ime_list_view.h"
 #include "ash/system/status_area_widget.h"
-#include "ash/system/tray/system_tray_notifier.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/status_area_widget_test_helper.h"
 #include "base/run_loop.h"
@@ -29,28 +28,16 @@
 namespace ash {
 namespace {
 
-class TestImeController : public ImeController {
- public:
-  TestImeController() = default;
-  ~TestImeController() override = default;
-
-  // ImeController:
-  mojom::ImeInfo GetCurrentIme() const override { return current_ime_; }
-  std::vector<mojom::ImeInfo> GetAvailableImes() const override {
-    return available_imes_;
-  }
-
-  mojom::ImeInfo current_ime_;
-  std::vector<mojom::ImeInfo> available_imes_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TestImeController);
-};
-
 ImeMenuTray* GetTray() {
   return StatusAreaWidgetTestHelper::GetStatusAreaWidget()->ime_menu_tray();
 }
 
+void SetCurrentIme(mojom::ImeInfo current_ime,
+                   const std::vector<mojom::ImeInfo>& available_imes) {
+  Shell::Get()->ime_controller()->RefreshIme(current_ime, available_imes,
+                                             std::vector<mojom::ImeMenuItem>());
+}
+
 }  // namespace
 
 class ImeMenuTrayTest : public test::AshTestBase {
@@ -58,12 +45,6 @@
   ImeMenuTrayTest() {}
   ~ImeMenuTrayTest() override {}
 
-  // test::AshTestBase:
-  void SetUp() override {
-    test::AshTestBase::SetUp();
-    GetTray()->ime_controller_ = &test_ime_controller_;
-  }
-
  protected:
   // Returns true if the IME menu tray is visible.
   bool IsVisible() { return GetTray()->visible(); }
@@ -109,17 +90,7 @@
     ui::IMEBridge::Get()->SetCurrentInputContext(input_context);
   }
 
-  void SetCurrentIme(mojom::ImeInfo ime) {
-    test_ime_controller_.current_ime_ = ime;
-  }
-
-  void SetAvailableImes(const std::vector<mojom::ImeInfo>& imes) {
-    test_ime_controller_.available_imes_ = imes;
-  }
-
  private:
-  TestImeController test_ime_controller_;
-
   DISALLOW_COPY_AND_ASSIGN(ImeMenuTrayTest);
 };
 
@@ -128,40 +99,41 @@
 TEST_F(ImeMenuTrayTest, ImeMenuTrayVisibility) {
   ASSERT_FALSE(IsVisible());
 
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(true);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(true);
   EXPECT_TRUE(IsVisible());
 
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(false);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(false);
   EXPECT_FALSE(IsVisible());
 }
 
 // Tests that IME menu tray shows the right info of the current IME.
 TEST_F(ImeMenuTrayTest, TrayLabelTest) {
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(true);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(true);
   ASSERT_TRUE(IsVisible());
 
-  // Changes the input method to "ime1".
   mojom::ImeInfo info1;
   info1.id = "ime1";
   info1.name = UTF8ToUTF16("English");
   info1.medium_name = UTF8ToUTF16("English");
   info1.short_name = UTF8ToUTF16("US");
   info1.third_party = false;
-  info1.selected = true;
-  SetCurrentIme(info1);
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
-  EXPECT_EQ(UTF8ToUTF16("US"), GetTrayText());
 
-  // Changes the input method to a third-party IME extension.
   mojom::ImeInfo info2;
   info2.id = "ime2";
   info2.name = UTF8ToUTF16("English UK");
   info2.medium_name = UTF8ToUTF16("English UK");
   info2.short_name = UTF8ToUTF16("UK");
   info2.third_party = true;
+
+  // Changes the input method to "ime1".
+  info1.selected = true;
+  SetCurrentIme(info1, {info1, info2});
+  EXPECT_EQ(UTF8ToUTF16("US"), GetTrayText());
+
+  // Changes the input method to a third-party IME extension.
+  info1.selected = false;
   info2.selected = true;
-  SetCurrentIme(info2);
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
+  SetCurrentIme(info2, {info1, info2});
   EXPECT_EQ(UTF8ToUTF16("UK*"), GetTrayText());
 }
 
@@ -170,7 +142,7 @@
 // menu feature. Also makes sure that the shelf won't autohide as long as the
 // IME menu is open.
 TEST_F(ImeMenuTrayTest, PerformAction) {
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(true);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(true);
   ASSERT_TRUE(IsVisible());
   ASSERT_FALSE(IsTrayBackgroundActive());
   StatusAreaWidget* status = StatusAreaWidgetTestHelper::GetStatusAreaWidget();
@@ -194,7 +166,7 @@
   // element will be deactivated.
   GetTray()->PerformAction(tap);
   EXPECT_TRUE(IsTrayBackgroundActive());
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(false);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(false);
   EXPECT_FALSE(IsVisible());
   EXPECT_FALSE(IsBubbleShown());
   EXPECT_FALSE(IsTrayBackgroundActive());
@@ -236,17 +208,15 @@
 
   std::vector<mojom::ImeInfo> ime_info_list{info1, info2, info3};
 
-  SetAvailableImes(ime_info_list);
-  SetCurrentIme(info1);
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
+  // Switch to ime1.
+  SetCurrentIme(info1, ime_info_list);
   EXPECT_EQ(UTF8ToUTF16("US"), GetTrayText());
   ExpectValidImeList(ime_info_list, info1);
 
+  // Switch to ime3.
   ime_info_list[0].selected = false;
   ime_info_list[2].selected = true;
-  SetAvailableImes(ime_info_list);
-  SetCurrentIme(info3);
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIME();
+  SetCurrentIme(info3, ime_info_list);
   EXPECT_EQ(UTF8ToUTF16("拼"), GetTrayText());
   ExpectValidImeList(ime_info_list, info3);
 
@@ -258,7 +228,7 @@
 
 // Tests that quits Chrome with IME menu openned will not crash.
 TEST_F(ImeMenuTrayTest, QuitChromeWithMenuOpen) {
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(true);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(true);
   ASSERT_TRUE(IsVisible());
   ASSERT_FALSE(IsTrayBackgroundActive());
 
@@ -271,7 +241,7 @@
 
 // Tests using 'Alt+Shift+K' to open the menu.
 TEST_F(ImeMenuTrayTest, TestAccelerator) {
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(true);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(true);
   ASSERT_TRUE(IsVisible());
   ASSERT_FALSE(IsTrayBackgroundActive());
 
@@ -288,7 +258,7 @@
 }
 
 TEST_F(ImeMenuTrayTest, ShowEmojiKeyset) {
-  Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(true);
+  Shell::Get()->ime_controller()->ShowImeMenuOnShelf(true);
   ASSERT_TRUE(IsVisible());
   ASSERT_FALSE(IsTrayBackgroundActive());
 
diff --git a/ash/system/night_light/night_light_controller.cc b/ash/system/night_light/night_light_controller.cc
index 7b82a77d..e5f68eaa 100644
--- a/ash/system/night_light/night_light_controller.cc
+++ b/ash/system/night_light/night_light_controller.cc
@@ -38,15 +38,16 @@
 
 class NightLightControllerDelegateImpl : public NightLightController::Delegate {
  public:
-  NightLightControllerDelegateImpl() {
-    // TODO(afakhry): Implement geolocation position retrieval.
-  }
+  NightLightControllerDelegateImpl() = default;
   ~NightLightControllerDelegateImpl() override = default;
 
   // ash::NightLightController::Delegate:
   base::Time GetNow() const override { return base::Time::Now(); }
   base::Time GetSunsetTime() const override { return GetSunRiseSet(false); }
   base::Time GetSunriseTime() const override { return GetSunRiseSet(true); }
+  void SetGeoposition(mojom::SimpleGeopositionPtr position) override {
+    position_ = std::move(position);
+  }
 
  private:
   base::Time GetSunRiseSet(bool sunrise) const {
@@ -55,7 +56,7 @@
                      : TimeOfDay(kDefaultStartTimeOffsetMinutes).ToTimeToday();
     }
 
-    icu::CalendarAstronomer astro(longitude_, latitude_);
+    icu::CalendarAstronomer astro(position_->longitude, position_->latitude);
     // For sunset and sunrise times calculations to be correct, the time of the
     // icu::CalendarAstronomer object should be set to a time near local noon.
     // This avoids having the computation flopping over into an adjacent day.
@@ -68,14 +69,9 @@
     return base::Time::FromDoubleT(sun_rise_set_ms / 1000.0);
   }
 
-  bool ValidatePosition() const {
-    // TODO(afakhry): Replace with geoposition APIs.
-    return false;
-  }
+  bool ValidatePosition() const { return !!position_; }
 
-  // Stub values to be replaced by actual geoposition APIs.
-  double latitude_ = 0.0;
-  double longitude_ = 0.0;
+  mojom::SimpleGeopositionPtr position_;
 
   DISALLOW_COPY_AND_ASSIGN(NightLightControllerDelegateImpl);
 };
@@ -102,7 +98,9 @@
 
 NightLightController::NightLightController(
     SessionController* session_controller)
-    : session_controller_(session_controller) {
+    : session_controller_(session_controller),
+      delegate_(base::MakeUnique<NightLightControllerDelegateImpl>()),
+      binding_(this) {
   session_controller_->AddObserver(this);
 }
 
@@ -129,6 +127,11 @@
                                 kDefaultEndTimeOffsetMinutes);
 }
 
+void NightLightController::BindRequest(
+    mojom::NightLightControllerRequest request) {
+  binding_.Bind(std::move(request));
+}
+
 void NightLightController::AddObserver(Observer* observer) {
   observers_.AddObserver(observer);
 }
@@ -226,10 +229,30 @@
   // Initial login and user switching in multi profiles.
   pref_change_registrar_.reset();
   active_user_pref_service_ = Shell::Get()->GetActiveUserPrefService();
-  delegate_ = base::MakeUnique<NightLightControllerDelegateImpl>();
   InitFromUserPrefs();
 }
 
+void NightLightController::SetCurrentGeoposition(
+    mojom::SimpleGeopositionPtr position) {
+  delegate_->SetGeoposition(std::move(position));
+
+  // If the schedule type is sunset to sunrise, then a potential change in the
+  // geoposition might mean a change in the start and end times. In this case,
+  // we must trigger a refresh.
+  if (GetScheduleType() == ScheduleType::kSunsetToSunrise)
+    Refresh(true /* did_schedule_change */);
+}
+
+void NightLightController::SetClient(mojom::NightLightClientPtr client) {
+  client_ = std::move(client);
+
+  if (client_) {
+    client_.set_connection_error_handler(base::Bind(
+        &NightLightController::SetClient, base::Unretained(this), nullptr));
+    NotifyClientWithScheduleChange();
+  }
+}
+
 void NightLightController::SetDelegateForTesting(
     std::unique_ptr<Delegate> delegate) {
   delegate_ = std::move(delegate);
@@ -265,15 +288,15 @@
                  base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kNightLightScheduleType,
-      base::Bind(&NightLightController::OnScheduleParamsPrefsChanged,
+      base::Bind(&NightLightController::OnScheduleTypePrefChanged,
                  base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kNightLightCustomStartTime,
-      base::Bind(&NightLightController::OnScheduleParamsPrefsChanged,
+      base::Bind(&NightLightController::OnCustomSchedulePrefsChanged,
                  base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kNightLightCustomEndTime,
-      base::Bind(&NightLightController::OnScheduleParamsPrefsChanged,
+      base::Bind(&NightLightController::OnCustomSchedulePrefsChanged,
                  base::Unretained(this)));
 }
 
@@ -286,6 +309,7 @@
   StartWatchingPrefsChanges();
   Refresh(true /* did_schedule_change */);
   NotifyStatusChanged();
+  NotifyClientWithScheduleChange();
 }
 
 void NightLightController::NotifyStatusChanged() {
@@ -293,6 +317,11 @@
     observer.OnNightLightEnabledChanged(GetEnabled());
 }
 
+void NightLightController::NotifyClientWithScheduleChange() {
+  if (client_)
+    client_->OnScheduleTypeChanged(GetScheduleType());
+}
+
 void NightLightController::OnEnabledPrefChanged() {
   DCHECK(active_user_pref_service_);
   Refresh(false /* did_schedule_change */);
@@ -304,7 +333,13 @@
   RefreshLayersTemperature();
 }
 
-void NightLightController::OnScheduleParamsPrefsChanged() {
+void NightLightController::OnScheduleTypePrefChanged() {
+  DCHECK(active_user_pref_service_);
+  NotifyClientWithScheduleChange();
+  Refresh(true /* did_schedule_change */);
+}
+
+void NightLightController::OnCustomSchedulePrefsChanged() {
   DCHECK(active_user_pref_service_);
   Refresh(true /* did_schedule_change */);
 }
diff --git a/ash/system/night_light/night_light_controller.h b/ash/system/night_light/night_light_controller.h
index b9eef8f..2e9b5c7 100644
--- a/ash/system/night_light/night_light_controller.h
+++ b/ash/system/night_light/night_light_controller.h
@@ -8,12 +8,14 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/public/interfaces/night_light_controller.mojom.h"
 #include "ash/session/session_observer.h"
 #include "ash/system/night_light/time_of_day.h"
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "mojo/public/cpp/bindings/binding.h"
 
 class PrefRegistrySimple;
 class PrefService;
@@ -24,20 +26,11 @@
 
 // Controls the NightLight feature that adjusts the color temperature of the
 // screen.
-class ASH_EXPORT NightLightController : public SessionObserver {
+class ASH_EXPORT NightLightController
+    : public NON_EXPORTED_BASE(mojom::NightLightController),
+      public SessionObserver {
  public:
-  enum class ScheduleType : int {
-    // Automatic toggling of NightLight is turned off.
-    kNone = 0,
-
-    // Turned automatically on at the user's local sunset time, and off at the
-    // user's local sunrise time.
-    kSunsetToSunrise = 1,
-
-    // Toggled automatically based on the custom set start and end times
-    // selected by the user from the system settings.
-    kCustom = 2,
-  };
+  using ScheduleType = mojom::NightLightController::ScheduleType;
 
   enum class AnimationDuration {
     // Short animation (2 seconds) used for manual changes of NightLight status
@@ -64,6 +57,10 @@
     // Gets the sunset and sunrise times.
     virtual base::Time GetSunsetTime() const = 0;
     virtual base::Time GetSunriseTime() const = 0;
+
+    // Provides the delegate with the geoposition so that it can be used to
+    // calculate sunset and sunrise times.
+    virtual void SetGeoposition(mojom::SimpleGeopositionPtr position) = 0;
   };
 
   class Observer {
@@ -89,6 +86,8 @@
   }
   const base::OneShotTimer& timer() const { return timer_; }
 
+  void BindRequest(mojom::NightLightControllerRequest request);
+
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
@@ -113,6 +112,10 @@
   // ash::SessionObserver:
   void OnActiveUserSessionChanged(const AccountId& account_id) override;
 
+  // ash::mojom::NightLightController:
+  void SetCurrentGeoposition(mojom::SimpleGeopositionPtr position) override;
+  void SetClient(mojom::NightLightClientPtr client) override;
+
   void SetDelegateForTesting(std::unique_ptr<Delegate> delegate);
 
  private:
@@ -124,15 +127,20 @@
 
   void NotifyStatusChanged();
 
+  void NotifyClientWithScheduleChange();
+
   // Called when the user pref for the enabled status of NightLight is changed.
   void OnEnabledPrefChanged();
 
   // Called when the user pref for the color temperature is changed.
   void OnColorTemperaturePrefChanged();
 
-  // Called when any of the schedule related prefs (schedule type, custom start
-  // and end times) are changed.
-  void OnScheduleParamsPrefsChanged();
+  // Called when the user pref for the schedule type is changed.
+  void OnScheduleTypePrefChanged();
+
+  // Called when either of the custom schedule prefs (custom start or end times)
+  // are changed.
+  void OnCustomSchedulePrefsChanged();
 
   // Refreshes the state of NightLight according to the currently set
   // parameters. |did_schedule_change| is true when Refresh() is called as a
@@ -182,6 +190,10 @@
 
   base::ObserverList<Observer> observers_;
 
+  mojo::Binding<mojom::NightLightController> binding_;
+
+  mojom::NightLightClientPtr client_;
+
   DISALLOW_COPY_AND_ASSIGN(NightLightController);
 };
 
diff --git a/ash/system/night_light/night_light_controller_unittest.cc b/ash/system/night_light/night_light_controller_unittest.cc
index 267e325e..3745641 100644
--- a/ash/system/night_light/night_light_controller_unittest.cc
+++ b/ash/system/night_light/night_light_controller_unittest.cc
@@ -66,6 +66,16 @@
   DISALLOW_COPY_AND_ASSIGN(TestObserver);
 };
 
+constexpr double kFakePosition1_Latitude = 23.5;
+constexpr double kFakePosition1_Longitude = 35.88;
+constexpr int kFakePosition1_SunsetOffset = 20 * 60;
+constexpr int kFakePosition1_SunriseOffset = 4 * 60;
+
+constexpr double kFakePosition2_Latitude = 37.5;
+constexpr double kFakePosition2_Longitude = -100.5;
+constexpr int kFakePosition2_SunsetOffset = 17 * 60;
+constexpr int kFakePosition2_SunriseOffset = 3 * 60;
+
 class TestDelegate : public NightLightController::Delegate {
  public:
   TestDelegate() = default;
@@ -79,6 +89,19 @@
   base::Time GetNow() const override { return fake_now_; }
   base::Time GetSunsetTime() const override { return fake_sunset_; }
   base::Time GetSunriseTime() const override { return fake_sunrise_; }
+  void SetGeoposition(mojom::SimpleGeopositionPtr position) override {
+    if (position.Equals(mojom::SimpleGeoposition::New(
+            kFakePosition1_Latitude, kFakePosition1_Longitude))) {
+      // Set sunset and sunrise times associated with fake position 1.
+      SetFakeSunset(TimeOfDay(kFakePosition1_SunsetOffset));
+      SetFakeSunrise(TimeOfDay(kFakePosition1_SunriseOffset));
+    } else if (position.Equals(mojom::SimpleGeoposition::New(
+                   kFakePosition2_Latitude, kFakePosition2_Longitude))) {
+      // Set sunset and sunrise times associated with fake position 2.
+      SetFakeSunset(TimeOfDay(kFakePosition2_SunsetOffset));
+      SetFakeSunrise(TimeOfDay(kFakePosition2_SunriseOffset));
+    }
+  }
 
  private:
   base::Time fake_now_;
@@ -480,7 +503,7 @@
     return;
   }
 
-  //       16:00        18:00     20:00      22:00              5:00
+  //      16:00         18:00     20:00      22:00              5:00
   // <----- + ----------- + ------- + -------- + --------------- + ------->
   //        |             |         |          |                 |
   //       now      custom start  sunset   custom end         sunrise
@@ -529,6 +552,79 @@
             controller->timer().GetCurrentDelay());
 }
 
+// Tests the behavior of the sunset to sunrise automatic schedule type when the
+// client sets the geoposition.
+TEST_F(NightLightTest, TestSunsetSunriseGeoposition) {
+  if (Shell::GetAshConfig() == Config::MASH) {
+    // PrefChangeRegistrar doesn't work on mash. crbug.com/721961.
+    return;
+  }
+
+  // Position 1 sunset and sunrise times.
+  //
+  //      16:00       20:00               4:00
+  // <----- + --------- + ---------------- + ------->
+  //        |           |                  |
+  //       now        sunset            sunrise
+  //
+  NightLightController* controller = GetController();
+  delegate()->SetFakeNow(TimeOfDay(16 * 60));  // 4:00 PM.
+  controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New(
+      kFakePosition1_Latitude, kFakePosition1_Longitude));
+
+  // Expect that timer is running and the start is scheduled after 4 hours.
+  controller->SetScheduleType(
+      NightLightController::ScheduleType::kSunsetToSunrise);
+  EXPECT_FALSE(controller->GetEnabled());
+  EXPECT_TRUE(TestLayersTemperature(0.0f));
+  EXPECT_TRUE(controller->timer().IsRunning());
+  EXPECT_EQ(base::TimeDelta::FromHours(4),
+            controller->timer().GetCurrentDelay());
+
+  // Simulate reaching sunset.
+  delegate()->SetFakeNow(TimeOfDay(20 * 60));  // Now is 8:00 PM.
+  controller->timer().user_task().Run();
+  EXPECT_TRUE(controller->GetEnabled());
+  EXPECT_TRUE(TestLayersTemperature(controller->GetColorTemperature()));
+  EXPECT_EQ(NightLightController::AnimationDuration::kLong,
+            controller->last_animation_duration());
+  // Timer is running scheduling the end at sunrise.
+  EXPECT_TRUE(controller->timer().IsRunning());
+  EXPECT_EQ(base::TimeDelta::FromHours(8),
+            controller->timer().GetCurrentDelay());
+
+  // Now simulate user changing position.
+  // Position 2 sunset and sunrise times.
+  //
+  //      17:00       20:00               3:00
+  // <----- + --------- + ---------------- + ------->
+  //        |           |                  |
+  //      sunset       now               sunrise
+  //
+  controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New(
+      kFakePosition2_Latitude, kFakePosition2_Longitude));
+
+  // Expect that the scheduled end delay has been updated, and the status hasn't
+  // changed.
+  EXPECT_TRUE(controller->GetEnabled());
+  EXPECT_TRUE(TestLayersTemperature(controller->GetColorTemperature()));
+  EXPECT_TRUE(controller->timer().IsRunning());
+  EXPECT_EQ(base::TimeDelta::FromHours(7),
+            controller->timer().GetCurrentDelay());
+
+  // Simulate reaching sunrise.
+  delegate()->SetFakeNow(TimeOfDay(3 * 60));  // Now is 5:00 AM.
+  controller->timer().user_task().Run();
+  EXPECT_FALSE(controller->GetEnabled());
+  EXPECT_TRUE(TestLayersTemperature(0.0f));
+  EXPECT_EQ(NightLightController::AnimationDuration::kLong,
+            controller->last_animation_duration());
+  // Timer is running scheduling the start at the next sunset.
+  EXPECT_TRUE(controller->timer().IsRunning());
+  EXPECT_EQ(base::TimeDelta::FromHours(14),
+            controller->timer().GetCurrentDelay());
+}
+
 }  // namespace
 
 }  // namespace ash
diff --git a/ash/test/test_shell_delegate.cc b/ash/test/test_shell_delegate.cc
index e010d9f..df7ef25 100644
--- a/ash/test/test_shell_delegate.cc
+++ b/ash/test/test_shell_delegate.cc
@@ -105,10 +105,6 @@
   return new SystemTrayDelegate;
 }
 
-ImeController* TestShellDelegate::GetImeController() {
-  return &stub_ime_controller_;
-}
-
 std::unique_ptr<WallpaperDelegate>
 TestShellDelegate::CreateWallpaperDelegate() {
   return base::MakeUnique<TestWallpaperDelegate>();
diff --git a/ash/test/test_shell_delegate.h b/ash/test/test_shell_delegate.h
index 84e1131..a5a0467 100644
--- a/ash/test/test_shell_delegate.h
+++ b/ash/test/test_shell_delegate.h
@@ -8,7 +8,6 @@
 #include <memory>
 #include <string>
 
-#include "ash/ime/ime_controller.h"
 #include "ash/shell_delegate.h"
 #include "base/macros.h"
 
@@ -51,7 +50,6 @@
   void ShelfShutdown() override;
   void OpenUrlFromArc(const GURL& url) override;
   SystemTrayDelegate* CreateSystemTrayDelegate() override;
-  ImeController* GetImeController() override;
   std::unique_ptr<WallpaperDelegate> CreateWallpaperDelegate() override;
   AccessibilityDelegate* CreateAccessibilityDelegate() override;
   std::unique_ptr<PaletteDelegate> CreatePaletteDelegate() override;
@@ -82,7 +80,6 @@
   bool touchscreen_enabled_in_local_pref_ = true;
   bool media_sessions_suspended_ = false;
   std::unique_ptr<ShelfInitializer> shelf_initializer_;
-  ImeController stub_ime_controller_;
   PrefService* active_user_pref_service_ = nullptr;  // Not owned.
 
   DISALLOW_COPY_AND_ASSIGN(TestShellDelegate);
diff --git a/cc/base/rtree.cc b/cc/base/rtree.cc
index 0859fcc..852bdce 100644
--- a/cc/base/rtree.cc
+++ b/cc/base/rtree.cc
@@ -133,4 +133,10 @@
   return root_.bounds;
 }
 
+void RTree::Reset() {
+  num_data_elements_ = 0;
+  nodes_.clear();
+  root_.bounds = gfx::Rect();
+}
+
 }  // namespace cc
diff --git a/cc/base/rtree.h b/cc/base/rtree.h
index 3d4d742..a5ac5a13 100644
--- a/cc/base/rtree.h
+++ b/cc/base/rtree.h
@@ -103,6 +103,8 @@
   // Returns the total bounds of all items in this rtree.
   gfx::Rect GetBounds() const;
 
+  void Reset();
+
  private:
   // These values were empirically determined to produce reasonable performance
   // in most cases.
diff --git a/cc/base/rtree_unittest.cc b/cc/base/rtree_unittest.cc
index 487d0c4..b6adc8a 100644
--- a/cc/base/rtree_unittest.cc
+++ b/cc/base/rtree_unittest.cc
@@ -109,7 +109,7 @@
 
 TEST(RTreeTest, GetBoundsEmpty) {
   RTree rtree;
-  ASSERT_EQ(gfx::Rect(), rtree.GetBounds());
+  EXPECT_EQ(gfx::Rect(), rtree.GetBounds());
 }
 
 TEST(RTreeTest, GetBoundsNonOverlapping) {
@@ -120,7 +120,7 @@
   RTree rtree;
   rtree.Build(rects);
 
-  ASSERT_EQ(gfx::Rect(5, 6, 19, 20), rtree.GetBounds());
+  EXPECT_EQ(gfx::Rect(5, 6, 19, 20), rtree.GetBounds());
 }
 
 TEST(RTreeTest, GetBoundsOverlapping) {
@@ -131,7 +131,26 @@
   RTree rtree;
   rtree.Build(rects);
 
-  ASSERT_EQ(gfx::Rect(0, 0, 10, 10), rtree.GetBounds());
+  EXPECT_EQ(gfx::Rect(0, 0, 10, 10), rtree.GetBounds());
+}
+
+TEST(RTreeTest, BuildAfterReset) {
+  std::vector<gfx::Rect> rects;
+  rects.push_back(gfx::Rect(0, 0, 10, 10));
+  rects.push_back(gfx::Rect(0, 0, 10, 10));
+  rects.push_back(gfx::Rect(0, 0, 10, 10));
+  rects.push_back(gfx::Rect(0, 0, 10, 10));
+
+  RTree rtree;
+  rtree.Build(rects);
+
+  // Resetting should give the same as an empty rtree.
+  rtree.Reset();
+  EXPECT_EQ(gfx::Rect(), rtree.GetBounds());
+
+  // Should be able to rebuild from a reset rtree.
+  rtree.Build(rects);
+  EXPECT_EQ(gfx::Rect(0, 0, 10, 10), rtree.GetBounds());
 }
 
 }  // namespace cc
diff --git a/cc/paint/discardable_image_map.cc b/cc/paint/discardable_image_map.cc
index 4b6c58240..475c7cc7 100644
--- a/cc/paint/discardable_image_map.cc
+++ b/cc/paint/discardable_image_map.cc
@@ -59,4 +59,10 @@
   image_map_->EndGeneratingMetadata();
 }
 
+void DiscardableImageMap::Reset() {
+  all_images_.clear();
+  image_id_to_rect_.clear();
+  images_rtree_.Reset();
+}
+
 }  // namespace cc
diff --git a/cc/paint/discardable_image_map.h b/cc/paint/discardable_image_map.h
index f705e89..0509473 100644
--- a/cc/paint/discardable_image_map.h
+++ b/cc/paint/discardable_image_map.h
@@ -51,6 +51,8 @@
                                   std::vector<DrawImage>* images) const;
   gfx::Rect GetRectForImage(PaintImage::Id image_id) const;
 
+  void Reset();
+
  private:
   friend class ScopedMetadataGenerator;
   friend class DiscardableImageMapTest;
diff --git a/cc/paint/discardable_image_map_unittest.cc b/cc/paint/discardable_image_map_unittest.cc
index 4d8368b..c6eb10c 100644
--- a/cc/paint/discardable_image_map_unittest.cc
+++ b/cc/paint/discardable_image_map_unittest.cc
@@ -695,9 +695,11 @@
   display_list->EndPaintOfUnpaired(gfx::Rect(100, 100, 100, 100));
   display_list->Finalize();
 
+  sk_sp<PaintRecord> record2 = display_list->ReleaseAsRecord();
+
   PaintOpBuffer root_buffer;
   root_buffer.push<DrawRecordOp>(internal_record);
-  root_buffer.push<DrawDisplayItemListOp>(display_list);
+  root_buffer.push<DrawRecordOp>(record2);
   DiscardableImageMap image_map_;
   {
     DiscardableImageMap::ScopedMetadataGenerator generator(&image_map_,
diff --git a/cc/paint/discardable_image_store.cc b/cc/paint/discardable_image_store.cc
index 4f635fd6..25fc05a7 100644
--- a/cc/paint/discardable_image_store.cc
+++ b/cc/paint/discardable_image_store.cc
@@ -107,10 +107,6 @@
                                2 * circle_op->radius, 2 * circle_op->radius);
           AddImageFromFlags(rect, circle_op->flags);
         } break;
-        case PaintOpType::DrawDisplayItemList: {
-          auto* list_op = static_cast<DrawDisplayItemListOp*>(op);
-          list_op->list->GatherDiscardableImages(this);
-        } break;
         case PaintOpType::DrawImage: {
           auto* image_op = static_cast<DrawImageOp*>(op);
           const SkImage* sk_image = image_op->image.sk_image().get();
diff --git a/cc/paint/display_item_list.cc b/cc/paint/display_item_list.cc
index cccc2f61..1e4b6c0 100644
--- a/cc/paint/display_item_list.cc
+++ b/cc/paint/display_item_list.cc
@@ -69,8 +69,6 @@
   // If this fails we had more calls to EndPaintOfPairedBegin() than
   // to EndPaintOfPairedEnd().
   DCHECK_EQ(0, in_paired_begin_count_);
-  DCHECK_EQ(visual_rects_range_starts_.size(), visual_rects_.size());
-  DCHECK_GE(paint_op_buffer_.size(), visual_rects_.size());
 
   paint_op_buffer_.ShrinkToFit();
   rtree_.Build(visual_rects_);
@@ -198,4 +196,28 @@
   return image_map_.GetRectForImage(image_id);
 }
 
+void DisplayItemList::Reset() {
+  DCHECK(!in_painting_);
+  DCHECK_EQ(0, in_paired_begin_count_);
+
+  rtree_.Reset();
+  image_map_.Reset();
+  paint_op_buffer_.Reset();
+  visual_rects_.clear();
+  visual_rects_range_starts_.clear();
+  begin_paired_indices_.clear();
+  current_range_start_ = 0;
+  in_paired_begin_count_ = 0;
+  in_painting_ = false;
+  op_count_ = 0u;
+}
+
+sk_sp<PaintRecord> DisplayItemList::ReleaseAsRecord() {
+  sk_sp<PaintRecord> record =
+      sk_make_sp<PaintOpBuffer>(std::move(paint_op_buffer_));
+
+  Reset();
+  return record;
+}
+
 }  // namespace cc
diff --git a/cc/paint/display_item_list.h b/cc/paint/display_item_list.h
index b48a19d..f74456e 100644
--- a/cc/paint/display_item_list.h
+++ b/cc/paint/display_item_list.h
@@ -125,12 +125,18 @@
     return paint_op_buffer_.HasDiscardableImages();
   }
 
+  // Generate a PaintRecord from this DisplayItemList, leaving |this| in
+  // an empty state.
+  sk_sp<PaintRecord> ReleaseAsRecord();
+
  private:
   FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, AsValueWithNoOps);
   FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, AsValueWithOps);
 
   ~DisplayItemList();
 
+  void Reset();
+
   std::unique_ptr<base::trace_event::TracedValue> CreateTracedValue(
       bool include_items) const;
 
diff --git a/cc/paint/paint_canvas.h b/cc/paint/paint_canvas.h
index ca2b0bd..556be62 100644
--- a/cc/paint/paint_canvas.h
+++ b/cc/paint/paint_canvas.h
@@ -15,7 +15,6 @@
 
 namespace cc {
 
-class DisplayItemList;
 class PaintFlags;
 class PaintOpBuffer;
 
@@ -165,9 +164,6 @@
                             SkScalar y,
                             const PaintFlags& flags) = 0;
 
-  virtual void drawDisplayItemList(
-      scoped_refptr<DisplayItemList> display_item_list) = 0;
-
   // Unlike SkCanvas::drawPicture, this only plays back the PaintRecord and does
   // not add an additional clip.  This is closer to SkPicture::playback.
   virtual void drawPicture(sk_sp<const PaintRecord> record) = 0;
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index 0689974..5f08b50 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -21,7 +21,6 @@
   M(DrawArcOp)             \
   M(DrawCircleOp)          \
   M(DrawColorOp)           \
-  M(DrawDisplayItemListOp) \
   M(DrawDRRectOp)          \
   M(DrawImageOp)           \
   M(DrawImageRectOp)       \
@@ -127,16 +126,6 @@
     NOTREACHED();
   }
 };
-template <bool HasFlags>
-struct Rasterizer<DrawDisplayItemListOp, HasFlags> {
-  static void RasterWithAlpha(const DrawDisplayItemListOp* op,
-                              SkCanvas* canvas,
-                              const SkRect& bounds,
-                              uint8_t alpha) {
-    NOTREACHED();
-  }
-};
-
 // TODO(enne): partially specialize RasterWithAlpha for draw color?
 
 static constexpr size_t kNumOpTypes =
@@ -270,13 +259,6 @@
   canvas->drawColor(op->color, op->mode);
 }
 
-void DrawDisplayItemListOp::Raster(const PaintOp* base_op,
-                                   SkCanvas* canvas,
-                                   const SkMatrix& original_ctm) {
-  auto* op = static_cast<const DrawDisplayItemListOp*>(base_op);
-  op->list->Raster(canvas);
-}
-
 void DrawDRRectOp::RasterWithFlags(const PaintOpWithFlags* base_op,
                                    const PaintFlags* flags,
                                    SkCanvas* canvas,
@@ -472,10 +454,6 @@
   return antialias && !path.isConvex() ? 1 : 0;
 }
 
-int DrawDisplayItemListOp::CountSlowPaths() const {
-  return list->NumSlowPaths();
-}
-
 int DrawLineOp::CountSlowPaths() const {
   if (const SkPathEffect* effect = flags.getPathEffect()) {
     SkPathEffect::DashInfo info;
@@ -522,26 +500,6 @@
 
 AnnotateOp::~AnnotateOp() = default;
 
-DrawDisplayItemListOp::DrawDisplayItemListOp(
-    scoped_refptr<DisplayItemList> list)
-    : list(list) {}
-
-size_t DrawDisplayItemListOp::AdditionalBytesUsed() const {
-  return list->BytesUsed();
-}
-
-bool DrawDisplayItemListOp::HasDiscardableImages() const {
-  return list->HasDiscardableImages();
-}
-
-DrawDisplayItemListOp::DrawDisplayItemListOp(const DrawDisplayItemListOp& op) =
-    default;
-
-DrawDisplayItemListOp& DrawDisplayItemListOp::operator=(
-    const DrawDisplayItemListOp& op) = default;
-
-DrawDisplayItemListOp::~DrawDisplayItemListOp() = default;
-
 DrawImageOp::DrawImageOp(const PaintImage& image,
                          SkScalar left,
                          SkScalar top,
@@ -606,10 +564,29 @@
 
 PaintOpBuffer::PaintOpBuffer() = default;
 
+PaintOpBuffer::PaintOpBuffer(PaintOpBuffer&& other) {
+  *this = std::move(other);
+}
+
 PaintOpBuffer::~PaintOpBuffer() {
   Reset();
 }
 
+void PaintOpBuffer::operator=(PaintOpBuffer&& other) {
+  data_ = std::move(other.data_);
+  used_ = other.used_;
+  reserved_ = other.reserved_;
+  op_count_ = other.op_count_;
+  num_slow_paths_ = other.num_slow_paths_;
+  subrecord_bytes_used_ = other.subrecord_bytes_used_;
+  has_discardable_images_ = other.has_discardable_images_;
+
+  // Make sure the other pob can destruct safely.
+  other.used_ = 0;
+  other.op_count_ = 0;
+  other.reserved_ = 0;
+}
+
 void PaintOpBuffer::Reset() {
   for (auto* op : Iterator(this)) {
     auto func = g_destructor_functions[op->type];
@@ -682,13 +659,7 @@
   if (!op->IsDrawOp())
     return nullptr;
 
-  while (op->GetType() == PaintOpType::DrawRecord ||
-         op->GetType() == PaintOpType::DrawDisplayItemList) {
-    if (op->GetType() == PaintOpType::DrawDisplayItemList) {
-      // TODO(danakj): If we could inspect the PaintOpBuffer here, then
-      // we could see if it is a single draw op.
-      return nullptr;
-    }
+  while (op->GetType() == PaintOpType::DrawRecord) {
     auto* draw_record_op = static_cast<const DrawRecordOp*>(op);
     if (draw_record_op->record->size() > 1) {
       // If there's more than one op, then we need to keep the
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index ae40cad4..abda0c6 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -23,7 +23,6 @@
 // See: third_party/skia/src/core/SkLiteDL.h.
 
 namespace cc {
-class DisplayItemList;
 
 class CC_PAINT_EXPORT ThreadsafeMatrix : public SkMatrix {
  public:
@@ -48,7 +47,6 @@
   DrawArc,
   DrawCircle,
   DrawColor,
-  DrawDisplayItemList,
   DrawDRRect,
   DrawImage,
   DrawImageRect,
@@ -364,26 +362,6 @@
   SkBlendMode mode;
 };
 
-struct CC_PAINT_EXPORT DrawDisplayItemListOp final : PaintOp {
-  static constexpr PaintOpType kType = PaintOpType::DrawDisplayItemList;
-  static constexpr bool kIsDrawOp = true;
-  explicit DrawDisplayItemListOp(scoped_refptr<DisplayItemList> list);
-  // Windows wants to generate these when types are exported, so
-  // provide them here explicitly so that DisplayItemList doesn't have
-  // to be defined in this header.
-  DrawDisplayItemListOp(const DrawDisplayItemListOp& op);
-  DrawDisplayItemListOp& operator=(const DrawDisplayItemListOp& op);
-  ~DrawDisplayItemListOp();
-  static void Raster(const PaintOp* op,
-                     SkCanvas* canvas,
-                     const SkMatrix& original_ctm);
-  size_t AdditionalBytesUsed() const;
-  bool HasDiscardableImages() const;
-  int CountSlowPaths() const;
-
-  scoped_refptr<DisplayItemList> list;
-};
-
 struct CC_PAINT_EXPORT DrawDRRectOp final : PaintOpWithFlags {
   static constexpr PaintOpType kType = PaintOpType::DrawDRRect;
   static constexpr bool kIsDrawOp = true;
@@ -781,8 +759,11 @@
   static constexpr size_t PaintOpAlign = alignof(DrawDRRectOp);
 
   PaintOpBuffer();
+  PaintOpBuffer(PaintOpBuffer&& other);
   ~PaintOpBuffer() override;
 
+  void operator=(PaintOpBuffer&& other);
+
   void Reset();
 
   void playback(SkCanvas* canvas,
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 42b2984e..1e6e18cb 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -26,46 +26,125 @@
   EXPECT_EQ(buffer.size(), 0u);
   EXPECT_EQ(buffer.bytes_used(), sizeof(PaintOpBuffer));
   EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false);
+
+  PaintOpBuffer buffer2(std::move(buffer));
+  EXPECT_EQ(buffer.size(), 0u);
+  EXPECT_EQ(buffer.bytes_used(), sizeof(PaintOpBuffer));
+  EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false);
+  EXPECT_EQ(buffer2.size(), 0u);
+  EXPECT_EQ(buffer2.bytes_used(), sizeof(PaintOpBuffer));
+  EXPECT_EQ(PaintOpBuffer::Iterator(&buffer2), false);
 }
 
-TEST(PaintOpBufferTest, SimpleAppend) {
-  SkRect rect = SkRect::MakeXYWH(2, 3, 4, 5);
-  PaintFlags flags;
-  flags.setColor(SK_ColorMAGENTA);
-  flags.setAlpha(100);
-  SkColor draw_color = SK_ColorRED;
-  SkBlendMode blend = SkBlendMode::kSrc;
+class PaintOpAppendTest : public ::testing::Test {
+ public:
+  PaintOpAppendTest() {
+    rect_ = SkRect::MakeXYWH(2, 3, 4, 5);
+    flags_.setColor(SK_ColorMAGENTA);
+    flags_.setAlpha(100);
+  }
 
+  void PushOps(PaintOpBuffer* buffer) {
+    buffer->push<SaveLayerOp>(&rect_, &flags_);
+    buffer->push<SaveOp>();
+    buffer->push<DrawColorOp>(draw_color_, blend_);
+    buffer->push<RestoreOp>();
+    EXPECT_EQ(buffer->size(), 4u);
+  }
+
+  void VerifyOps(PaintOpBuffer* buffer) {
+    EXPECT_EQ(buffer->size(), 4u);
+
+    PaintOpBuffer::Iterator iter(buffer);
+    ASSERT_EQ(iter->GetType(), PaintOpType::SaveLayer);
+    SaveLayerOp* save_op = static_cast<SaveLayerOp*>(*iter);
+    EXPECT_EQ(save_op->bounds, rect_);
+    EXPECT_TRUE(save_op->flags == flags_);
+    ++iter;
+
+    ASSERT_EQ(iter->GetType(), PaintOpType::Save);
+    ++iter;
+
+    ASSERT_EQ(iter->GetType(), PaintOpType::DrawColor);
+    DrawColorOp* op = static_cast<DrawColorOp*>(*iter);
+    EXPECT_EQ(op->color, draw_color_);
+    EXPECT_EQ(op->mode, blend_);
+    ++iter;
+
+    ASSERT_EQ(iter->GetType(), PaintOpType::Restore);
+    ++iter;
+
+    EXPECT_FALSE(iter);
+  }
+
+ private:
+  SkRect rect_;
+  PaintFlags flags_;
+  SkColor draw_color_ = SK_ColorRED;
+  SkBlendMode blend_ = SkBlendMode::kSrc;
+};
+
+TEST_F(PaintOpAppendTest, SimpleAppend) {
   PaintOpBuffer buffer;
-  buffer.push<SaveLayerOp>(&rect, &flags);
-  buffer.push<SaveOp>();
-  buffer.push<DrawColorOp>(draw_color, blend);
-  buffer.push<RestoreOp>();
+  PushOps(&buffer);
+  VerifyOps(&buffer);
 
-  EXPECT_EQ(buffer.size(), 4u);
-
-  PaintOpBuffer::Iterator iter(&buffer);
-  ASSERT_EQ(iter->GetType(), PaintOpType::SaveLayer);
-  SaveLayerOp* save_op = static_cast<SaveLayerOp*>(*iter);
-  EXPECT_EQ(save_op->bounds, rect);
-  EXPECT_TRUE(save_op->flags == flags);
-  ++iter;
-
-  ASSERT_EQ(iter->GetType(), PaintOpType::Save);
-  ++iter;
-
-  ASSERT_EQ(iter->GetType(), PaintOpType::DrawColor);
-  DrawColorOp* op = static_cast<DrawColorOp*>(*iter);
-  EXPECT_EQ(op->color, draw_color);
-  EXPECT_EQ(op->mode, blend);
-  ++iter;
-
-  ASSERT_EQ(iter->GetType(), PaintOpType::Restore);
-  ++iter;
-
-  EXPECT_FALSE(iter);
+  buffer.Reset();
+  PushOps(&buffer);
+  VerifyOps(&buffer);
 }
 
+TEST_F(PaintOpAppendTest, MoveThenDestruct) {
+  PaintOpBuffer original;
+  PushOps(&original);
+  VerifyOps(&original);
+
+  PaintOpBuffer destination(std::move(original));
+  VerifyOps(&destination);
+
+  // Original should be empty, and safe to destruct.
+  EXPECT_EQ(original.size(), 0u);
+  EXPECT_EQ(original.bytes_used(), sizeof(PaintOpBuffer));
+  EXPECT_EQ(PaintOpBuffer::Iterator(&original), false);
+}
+
+TEST_F(PaintOpAppendTest, MoveThenDestructOperatorEq) {
+  PaintOpBuffer original;
+  PushOps(&original);
+  VerifyOps(&original);
+
+  PaintOpBuffer destination;
+  destination = std::move(original);
+  VerifyOps(&destination);
+
+  // Original should be empty, and safe to destruct.
+  EXPECT_EQ(original.size(), 0u);
+  EXPECT_EQ(original.bytes_used(), sizeof(PaintOpBuffer));
+  EXPECT_EQ(PaintOpBuffer::Iterator(&original), false);
+}
+
+TEST_F(PaintOpAppendTest, MoveThenReappend) {
+  PaintOpBuffer original;
+  PushOps(&original);
+
+  PaintOpBuffer destination(std::move(original));
+
+  // Should be possible to reappend to the original and get the same result.
+  PushOps(&original);
+  VerifyOps(&original);
+}
+
+TEST_F(PaintOpAppendTest, MoveThenReappendOperatorEq) {
+  PaintOpBuffer original;
+  PushOps(&original);
+
+  PaintOpBuffer destination;
+  destination = std::move(original);
+
+  // Should be possible to reappend to the original and get the same result.
+  PushOps(&original);
+  VerifyOps(&original);
+}
 
 // Verify that PaintOps with data are stored properly.
 TEST(PaintOpBufferTest, PaintOpData) {
@@ -341,28 +420,6 @@
   EXPECT_TRUE(buffer.HasDiscardableImages());
 }
 
-TEST(PaintOpBufferTest, DiscardableImagesTracking_NestedDrawOp) {
-  sk_sp<PaintRecord> record = sk_make_sp<PaintRecord>();
-  PaintImage image = PaintImage(PaintImage::GetNextId(),
-                                CreateDiscardableImage(gfx::Size(100, 100)));
-  record->push<DrawImageOp>(image, SkIntToScalar(0), SkIntToScalar(0), nullptr);
-
-  PaintOpBuffer buffer;
-  buffer.push<DrawRecordOp>(record);
-  EXPECT_TRUE(buffer.HasDiscardableImages());
-
-  scoped_refptr<DisplayItemList> list = new DisplayItemList;
-  {
-    PaintOpBuffer* buffer = list->StartPaint();
-    buffer->push<DrawRecordOp>(record);
-    list->EndPaintOfUnpaired(gfx::Rect(100, 100));
-  }
-  list->Finalize();
-  PaintOpBuffer new_buffer;
-  new_buffer.push<DrawDisplayItemListOp>(list);
-  EXPECT_TRUE(new_buffer.HasDiscardableImages());
-}
-
 TEST(PaintOpBufferTest, DiscardableImagesTracking_OpWithFlags) {
   PaintOpBuffer buffer;
   PaintFlags flags;
@@ -424,27 +481,6 @@
   EXPECT_EQ(2, buffer2->numSlowPaths());
   buffer2->push<DrawRecordOp>(buffer);
   EXPECT_EQ(4, buffer2->numSlowPaths());
-
-  // Drawing an empty display item list doesn't change anything.
-  auto empty_list = base::MakeRefCounted<DisplayItemList>();
-  buffer2->push<DrawDisplayItemListOp>(empty_list);
-  EXPECT_EQ(4, buffer2->numSlowPaths());
-
-  // Drawing a display item list adds the items from that list.
-  auto slow_path_list = base::MakeRefCounted<DisplayItemList>();
-  {
-    PaintOpBuffer* display_list_buffer = slow_path_list->StartPaint();
-    EXPECT_EQ(0, display_list_buffer->numSlowPaths());
-    display_list_buffer->push<DrawRecordOp>(buffer);
-    EXPECT_EQ(2, display_list_buffer->numSlowPaths());
-    display_list_buffer->push<DrawRecordOp>(buffer);
-    EXPECT_EQ(4, display_list_buffer->numSlowPaths());
-    display_list_buffer->push<DrawRecordOp>(buffer);
-    EXPECT_EQ(6, display_list_buffer->numSlowPaths());
-    slow_path_list->EndPaintOfUnpaired(gfx::Rect(30, 30));
-  }
-  buffer2->push<DrawDisplayItemListOp>(slow_path_list);
-  EXPECT_EQ(10, buffer2->numSlowPaths());
 }
 
 TEST(PaintOpBufferTest, ContiguousRanges) {
diff --git a/cc/paint/record_paint_canvas.cc b/cc/paint/record_paint_canvas.cc
index a4596bf22..97a9f8d 100644
--- a/cc/paint/record_paint_canvas.cc
+++ b/cc/paint/record_paint_canvas.cc
@@ -320,11 +320,6 @@
   buffer_->push<DrawTextBlobOp>(blob, x, y, flags);
 }
 
-void RecordPaintCanvas::drawDisplayItemList(
-    scoped_refptr<DisplayItemList> list) {
-  buffer_->push<DrawDisplayItemListOp>(list);
-}
-
 void RecordPaintCanvas::drawPicture(sk_sp<const PaintRecord> record) {
   // TODO(enne): If this is small, maybe flatten it?
   buffer_->push<DrawRecordOp>(record);
diff --git a/cc/paint/record_paint_canvas.h b/cc/paint/record_paint_canvas.h
index 8730bd7a..d5b6d898 100644
--- a/cc/paint/record_paint_canvas.h
+++ b/cc/paint/record_paint_canvas.h
@@ -113,9 +113,6 @@
                     SkScalar y,
                     const PaintFlags& flags) override;
 
-  void drawDisplayItemList(
-      scoped_refptr<DisplayItemList> display_item_list) override;
-
   void drawPicture(sk_sp<const PaintRecord> record) override;
 
   bool isClipEmpty() const override;
diff --git a/cc/paint/skia_paint_canvas.cc b/cc/paint/skia_paint_canvas.cc
index f2231e74..65a86c9a 100644
--- a/cc/paint/skia_paint_canvas.cc
+++ b/cc/paint/skia_paint_canvas.cc
@@ -255,11 +255,6 @@
   canvas_->drawTextBlob(blob.get(), x, y, ToSkPaint(flags));
 }
 
-void SkiaPaintCanvas::drawDisplayItemList(
-    scoped_refptr<DisplayItemList> display_item_list) {
-  display_item_list->Raster(canvas_);
-}
-
 void SkiaPaintCanvas::drawPicture(sk_sp<const PaintRecord> record) {
   record->playback(canvas_);
 }
diff --git a/cc/paint/skia_paint_canvas.h b/cc/paint/skia_paint_canvas.h
index 15690a2..f3df753 100644
--- a/cc/paint/skia_paint_canvas.h
+++ b/cc/paint/skia_paint_canvas.h
@@ -121,9 +121,6 @@
                     SkScalar y,
                     const PaintFlags& flags) override;
 
-  void drawDisplayItemList(
-      scoped_refptr<DisplayItemList> display_item_list) override;
-
   void drawPicture(sk_sp<const PaintRecord> record) override;
 
   bool isClipEmpty() const override;
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index ea7ec748..4076f147 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -2776,11 +2776,7 @@
     EXPECT_TRUE(sent_gesture_);
     EXPECT_EQ(gfx::Vector2dF(50, 50), inner);
     EXPECT_EQ(2, scale_delta);
-  }
 
-  void DidCommit() override {
-    if (!sent_gesture_)
-      return;
     auto* scroll_layer = layer_tree_host()->inner_viewport_scroll_layer();
     EXPECT_EQ(gfx::ScrollOffset(50, 50), scroll_layer->scroll_offset());
     EndTest();
@@ -2791,8 +2787,7 @@
   bool sent_gesture_;
 };
 
-// Disabled for flakiness, http://crbug.com/733001
-// MULTI_THREAD_TEST_F(ViewportDeltasAppliedDuringPinch);
+MULTI_THREAD_TEST_F(ViewportDeltasAppliedDuringPinch);
 
 class LayerTreeHostTestSetVisible : public LayerTreeHostTest {
  public:
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 b001e96..e9f79858 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1095,7 +1095,7 @@
 
             // We send this intent so that we can enter WebVr presentation mode if needed. This
             // call doesn't consume the intent because it also has the url that we need to load.
-            VrShellDelegate.onNewIntent(intent);
+            VrShellDelegate.onNewIntent(ChromeTabbedActivity.this, intent);
 
             TabModel tabModel = getCurrentTabModel();
             boolean fromLauncherShortcut = IntentUtils.safeGetBooleanExtra(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
index c51fdb5..7746751 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
@@ -12,6 +12,7 @@
 import android.view.ViewGroup;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
@@ -48,6 +49,7 @@
 /** Bridges the user's download history and the UI used to display it. */
 public class DownloadHistoryAdapter extends DateDividedAdapter
         implements DownloadUiObserver, DownloadSharedPreferenceHelper.Observer {
+    private static final String TAG = "DownloadAdapter";
 
     /** Alerted about changes to internal state. */
     static interface TestObserver {
@@ -439,7 +441,13 @@
                 // changed.  This prevents the RecyclerView from detaching and immediately
                 // reattaching the same view, causing janky animations.
                 for (DownloadItemView view : mViews) {
-                    if (TextUtils.equals(item.getId(), view.getItem().getId())) {
+                    DownloadHistoryItemWrapper wrapper = view.getItem();
+                    if (wrapper == null) {
+                        // TODO(qinmin): remove this once crbug.com/731789 is fixed.
+                        Log.e(TAG, "DownloadItemView contains empty DownloadHistoryItemWrapper");
+                        continue;
+                    }
+                    if (TextUtils.equals(item.getId(), wrapper.getId())) {
                         view.displayItem(mBackendProvider, existingWrapper);
                     }
                 }
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 214fb2ec..7156ed1 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
@@ -37,7 +37,7 @@
     final OverviewModeObserver mOverviewModeObserver;
     @Nullable
     final LayoutManagerChrome mLayoutManager;
-    final BottomSheet mBottomSheet;
+    BottomSheet mBottomSheet;
     final String mTitle;
 
     private boolean mShowOverviewOnClose;
@@ -96,9 +96,12 @@
                 // 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);
+                mBottomSheet = mTab.getActivity().getBottomSheet();
+                if (mBottomSheet != null) {
+                    mBottomSheet.getBottomSheetMetrics().setSheetCloseReason(
+                            BottomSheetMetrics.CLOSED_BY_NAVIGATION);
+                    mBottomSheet.setSheetState(BottomSheet.SHEET_STATE_PEEK, true);
+                }
             }
         };
         mTab.addObserver(mTabObserver);
@@ -108,9 +111,11 @@
         boolean tabAlreadyShowing = mTabModelSelector.getCurrentTab() == mTab;
         if (tabAlreadyShowing) onNewTabPageShown();
 
-        mBottomSheet.setSheetState(BottomSheet.SHEET_STATE_HALF, true);
-        mBottomSheet.getBottomSheetMetrics().recordSheetOpenReason(
-                BottomSheetMetrics.OPENED_BY_NEW_TAB_CREATION);
+        if (mBottomSheet != null) {
+            mBottomSheet.setSheetState(BottomSheet.SHEET_STATE_HALF, true);
+            mBottomSheet.getBottomSheetMetrics().recordSheetOpenReason(
+                    BottomSheetMetrics.OPENED_BY_NEW_TAB_CREATION);
+        }
 
         // TODO(twellington): disallow moving the NTP to the other window in Android N+
         //                    multi-window mode.
@@ -168,9 +173,13 @@
         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);
+                mBottomSheet = mTab.getActivity().getBottomSheet();
+                if (mBottomSheet != null) {
+                    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/vr_shell/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
index 482cf95..2c89dc8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
@@ -676,13 +676,12 @@
     /**
      * This is called every time ChromeActivity gets a new intent.
      */
-    public static void onNewIntent(Intent intent) {
+    public static void onNewIntent(ChromeActivity activity, Intent intent) {
         if (IntentUtils.safeGetBooleanExtra(intent, DAYDREAM_VR_EXTRA, false)
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.WEBVR_AUTOPRESENT)
-                && activitySupportsAutopresentation(
-                           ApplicationStatus.getLastTrackedFocusedActivity())
+                && activitySupportsAutopresentation(activity)
                 && IntentHandler.isIntentFromTrustedApp(intent, DAYDREAM_HOME_PACKAGE)) {
-            VrShellDelegate instance = getInstance();
+            VrShellDelegate instance = getInstance(activity);
             if (instance == null) return;
             instance.onAutopresentIntent();
         }
diff --git a/chrome/app/chrome_main.cc b/chrome/app/chrome_main.cc
index 9d09439d..54524a6 100644
--- a/chrome/app/chrome_main.cc
+++ b/chrome/app/chrome_main.cc
@@ -107,10 +107,8 @@
 #endif  // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if BUILDFLAG(ENABLE_OOP_HEAP_PROFILING)
-  if (command_line->GetSwitchValueASCII(switches::kProcessType) ==
-      "profiling") {
+  if (command_line->HasSwitch(switches::kMemlog))
     return profiling::ProfilingMain(*command_line);
-  }
 #endif  // ENABLE_OOP_HEAP_PROFILING
 
 #if defined(OS_CHROMEOS) && BUILDFLAG(ENABLE_PACKAGE_MASH_SERVICES)
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index ee6fff5a..c97c865 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9152,11 +9152,7 @@
         Send feedback to help us fix this issue.
       </message>
       <message name="IDS_PROFILE_ERROR_FEEDBACK_DESCRIPTION" desc="The prepopulated text filled in the feedback report reporting a profile error. The triple single quotes are needed to preserve the line break.">
-Tell us what happened exactly before you got the profile error message:
-'''
-      </message>
-      <message name="IDS_PROFILE_ERROR_FEEDBACK_DIAGNOSTICS_LINE" desc="The prepopulated text filled in the feedback report reporting a profile error, above any diagnostics information. The triple single quotes are needed to preserve the line break.">
-****Please do not change below this line****
+Please help our engineers fix this problem. Tell us what happened right before you got the profile error message:
 '''
       </message>
       <message name="IDS_COULDNT_STARTUP_PROFILE_ERROR" desc="Error displayed when Chrome cannot start because profiles were not opened correctly.">
diff --git a/chrome/browser/android/vr_shell/ui_element_renderer.h b/chrome/browser/android/vr_shell/ui_element_renderer.h
index b14af75..24fd174 100644
--- a/chrome/browser/android/vr_shell/ui_element_renderer.h
+++ b/chrome/browser/android/vr_shell/ui_element_renderer.h
@@ -5,9 +5,13 @@
 #ifndef CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENT_RENDERER_H_
 #define CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENT_RENDERER_H_
 
-#include "device/vr/vr_types.h"
 #include "third_party/skia/include/core/SkColor.h"
 
+namespace gfx {
+class RectF;
+class Transform;
+}  // namespace gfx
+
 namespace vr_shell {
 
 // This is the interface offered by VrShell's GL system to UI elements.
@@ -16,11 +20,11 @@
   virtual ~UiElementRenderer() {}
 
   virtual void DrawTexturedQuad(int texture_data_handle,
-                                const vr::Mat4f& view_proj_matrix,
+                                const gfx::Transform& view_proj_matrix,
                                 const gfx::RectF& copy_rect,
                                 float opacity) = 0;
 
-  virtual void DrawGradientQuad(const vr::Mat4f& view_proj_matrix,
+  virtual void DrawGradientQuad(const gfx::Transform& view_proj_matrix,
                                 const SkColor edge_color,
                                 const SkColor center_color,
                                 float opacity) = 0;
diff --git a/chrome/browser/android/vr_shell/ui_elements/screen_dimmer.cc b/chrome/browser/android/vr_shell/ui_elements/screen_dimmer.cc
index 26b25da..9e1a034 100644
--- a/chrome/browser/android/vr_shell/ui_elements/screen_dimmer.cc
+++ b/chrome/browser/android/vr_shell/ui_elements/screen_dimmer.cc
@@ -6,7 +6,6 @@
 
 #include "chrome/browser/android/vr_shell/color_scheme.h"
 #include "chrome/browser/android/vr_shell/ui_element_renderer.h"
-#include "device/vr/vr_math.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/quaternion.h"
 #include "ui/gfx/transform.h"
@@ -33,7 +32,7 @@
   // Always use normal scheme for dimmer.
   const ColorScheme& color_scheme =
       ColorScheme::GetColorScheme(ColorScheme::kModeNormal);
-  renderer->DrawGradientQuad(vr::ToMat4F(m), color_scheme.dimmer_outer,
+  renderer->DrawGradientQuad(m, color_scheme.dimmer_outer,
                              color_scheme.dimmer_inner, kDimmerOpacity);
 }
 
diff --git a/chrome/browser/android/vr_shell/ui_elements/textured_element.cc b/chrome/browser/android/vr_shell/ui_elements/textured_element.cc
index 842d09f3..b4a065a 100644
--- a/chrome/browser/android/vr_shell/ui_elements/textured_element.cc
+++ b/chrome/browser/android/vr_shell/ui_elements/textured_element.cc
@@ -8,7 +8,6 @@
 #include "cc/paint/skia_paint_canvas.h"
 #include "chrome/browser/android/vr_shell/textures/ui_texture.h"
 #include "chrome/browser/android/vr_shell/ui_element_renderer.h"
-#include "device/vr/vr_math.h"
 #include "third_party/skia/include/core/SkSurface.h"
 #include "ui/gfx/geometry/rect_f.h"
 
@@ -48,8 +47,8 @@
   gfx::SizeF drawn_size = GetTexture()->GetDrawnSize();
   gfx::RectF copy_rect(0, 0, drawn_size.width() / texture_size_.width(),
                        drawn_size.height() / texture_size_.height());
-  renderer->DrawTexturedQuad(texture_handle_, vr::ToMat4F(view_proj_matrix),
-                             copy_rect, opacity());
+  renderer->DrawTexturedQuad(texture_handle_, view_proj_matrix, copy_rect,
+                             opacity());
 }
 
 void TexturedElement::Flush(SkSurface* surface) {
diff --git a/chrome/browser/android/vr_shell/ui_scene.cc b/chrome/browser/android/vr_shell/ui_scene.cc
index d67a011c..a1b7574 100644
--- a/chrome/browser/android/vr_shell/ui_scene.cc
+++ b/chrome/browser/android/vr_shell/ui_scene.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/android/vr_shell/animation.h"
 #include "chrome/browser/android/vr_shell/easing.h"
 #include "chrome/browser/android/vr_shell/ui_elements/ui_element.h"
-#include "device/vr/vr_math.h"
 
 namespace vr_shell {
 
@@ -251,17 +250,15 @@
     ApplyAnchoring(*parent, element->x_anchoring(), element->y_anchoring(),
                    inheritable);
     ApplyRecursiveTransforms(parent);
-    vr::Mat4f product;
-    vr::MatrixMul(vr::ToMat4F(parent->inheritable_transform().to_world),
-                  vr::ToMat4F(inheritable->to_world), &product);
-    inheritable->to_world = vr::ToTransform(product);
+    inheritable->to_world.ConcatTransform(
+        parent->inheritable_transform().to_world);
 
     element->set_computed_opacity(element->computed_opacity() *
                                   parent->opacity());
     element->set_computed_lock_to_fov(parent->lock_to_fov());
   }
 
-  transform->to_world = inheritable->to_world * transform->to_world;
+  transform->to_world.ConcatTransform(inheritable->to_world);
   element->set_dirty(false);
 }
 
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager.h b/chrome/browser/android/vr_shell/ui_scene_manager.h
index 94630e3..d83b4a72 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager.h
+++ b/chrome/browser/android/vr_shell/ui_scene_manager.h
@@ -13,7 +13,6 @@
 #include "chrome/browser/android/vr_shell/color_scheme.h"
 #include "chrome/browser/android/vr_shell/ui_interface.h"
 #include "chrome/browser/android/vr_shell/ui_unsupported_mode.h"
-#include "device/vr/vr_types.h"
 
 namespace vr_shell {
 
diff --git a/chrome/browser/android/vr_shell/ui_scene_unittest.cc b/chrome/browser/android/vr_shell/ui_scene_unittest.cc
index 7e5ea2c..b806a76 100644
--- a/chrome/browser/android/vr_shell/ui_scene_unittest.cc
+++ b/chrome/browser/android/vr_shell/ui_scene_unittest.cc
@@ -14,8 +14,6 @@
 #include "chrome/browser/android/vr_shell/animation.h"
 #include "chrome/browser/android/vr_shell/easing.h"
 #include "chrome/browser/android/vr_shell/ui_elements/ui_element.h"
-#include "device/vr/vr_math.h"
-#include "device/vr/vr_types.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #define TOLERANCE 0.0001
@@ -133,16 +131,14 @@
   scene.AddUiElement(std::move(element));
   const UiElement* child = scene.GetUiElementById(1);
 
-  const gfx::Vector3dF origin(0, 0, 0);
-  const gfx::Vector3dF point(1, 0, 0);
+  gfx::Point3F origin(0, 0, 0);
+  gfx::Point3F point(1, 0, 0);
 
   scene.OnBeginFrame(usToTicks(0));
-  auto new_origin =
-      vr::MatrixVectorMul(vr::ToMat4F(child->TransformMatrix()), origin);
-  auto new_point =
-      vr::MatrixVectorMul(vr::ToMat4F(child->TransformMatrix()), point);
-  EXPECT_VEC3F_NEAR(gfx::Vector3dF(6, 10, 0), new_origin);
-  EXPECT_VEC3F_NEAR(gfx::Vector3dF(0, 10, 0), new_point);
+  child->TransformMatrix().TransformPoint(&origin);
+  child->TransformMatrix().TransformPoint(&point);
+  EXPECT_VEC3F_NEAR(gfx::Point3F(6, 10, 0), origin);
+  EXPECT_VEC3F_NEAR(gfx::Point3F(0, 10, 0), point);
 }
 
 TEST(UiScene, Opacity) {
diff --git a/chrome/browser/android/vr_shell/vr_controller.cc b/chrome/browser/android/vr_shell/vr_controller.cc
index 42864a5..1898cb3b 100644
--- a/chrome/browser/android/vr_shell/vr_controller.cc
+++ b/chrome/browser/android/vr_shell/vr_controller.cc
@@ -11,12 +11,14 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
+#include "cc/base/math_util.h"
 #include "chrome/browser/android/vr_shell/elbow_model.h"
 #include "device/vr/vr_math.h"
 #include "third_party/WebKit/public/platform/WebGestureEvent.h"
 #include "third_party/WebKit/public/platform/WebInputEvent.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr_controller.h"
+#include "ui/gfx/transform.h"
 
 namespace vr_shell {
 
@@ -49,8 +51,8 @@
 constexpr float kLaserStartDisplacement = 0.045;
 
 void ClampTouchpadPosition(gfx::Vector2dF* position) {
-  position->set_x(vr::Clampf(position->x(), 0.0f, 1.0f));
-  position->set_y(vr::Clampf(position->y(), 0.0f, 1.0f));
+  position->set_x(cc::MathUtil::ClampToRange(position->x(), 0.0f, 1.0f));
+  position->set_y(cc::MathUtil::ClampToRange(position->y(), 0.0f, 1.0f));
 }
 
 float DeltaTimeSeconds(int64_t last_timestamp_nanos) {
@@ -109,17 +111,16 @@
   pad.timestamp = controller_state_->GetLastOrientationTimestamp();
   pad.touch_pos.set_x(TouchPosX());
   pad.touch_pos.set_y(TouchPosY());
-  pad.orientation = Orientation();
+  pad.orientation = vr::ToVRQuatF(Orientation());
 
   // Use orientation to rotate acceleration/gyro into seated space.
-  vr::Mat4f pose_mat;
-  vr::QuatToMatrix(pad.orientation, &pose_mat);
+  gfx::Transform pose_mat(Orientation());
   const gvr::Vec3f& accel = controller_state_->GetAccel();
   const gvr::Vec3f& gyro = controller_state_->GetGyro();
-  pad.accel =
-      vr::MatrixVectorMul(pose_mat, gfx::Vector3dF(accel.x, accel.y, accel.z));
-  pad.gyro =
-      vr::MatrixVectorMul(pose_mat, gfx::Vector3dF(gyro.x, gyro.y, gyro.z));
+  pad.accel = gfx::Vector3dF(accel.x, accel.y, accel.z);
+  pose_mat.TransformVector(&pad.accel);
+  pad.gyro = gfx::Vector3dF(gyro.x, gyro.y, gyro.z);
+  pose_mat.TransformVector(&pad.gyro);
 
   pad.is_touching = controller_state_->IsTouching();
   pad.controller_button_pressed =
@@ -141,15 +142,16 @@
   return controller_state_->GetTouchPos().y;
 }
 
-vr::Quatf VrController::Orientation() const {
+gfx::Quaternion VrController::Orientation() const {
   const gvr::Quatf& orientation = controller_state_->GetOrientation();
-  return *reinterpret_cast<vr::Quatf*>(const_cast<gvr::Quatf*>(&orientation));
+  return gfx::Quaternion(orientation.qx, orientation.qy, orientation.qz,
+                         orientation.qw);
 }
 
-void VrController::GetTransform(vr::Mat4f* out) const {
-  QuatToMatrix(vr::ToVRQuatF(elbow_model_->GetControllerRotation()), out);
-  auto position = elbow_model_->GetControllerPosition();
-  vr::TranslateM(*out, vr::ToVector(position), out);
+void VrController::GetTransform(gfx::Transform* out) const {
+  *out = gfx::Transform(elbow_model_->GetControllerRotation());
+  gfx::Point3F p = elbow_model_->GetControllerPosition();
+  out->matrix().postTranslate(p.x(), p.y(), p.z());
 }
 
 float VrController::GetOpacity() const {
@@ -157,12 +159,11 @@
 }
 
 gfx::Point3F VrController::GetPointerStart() const {
-  auto controller_position = elbow_model_->GetControllerPosition();
+  gfx::Point3F controller_position = elbow_model_->GetControllerPosition();
   gfx::Vector3dF pointer_direction{0.0f, -sin(kErgoAngleOffset),
                                    -cos(kErgoAngleOffset)};
-  vr::Mat4f rotation_mat;
-  vr::QuatToMatrix(Orientation(), &rotation_mat);
-  pointer_direction = vr::MatrixVectorRotate(rotation_mat, pointer_direction);
+  gfx::Transform rotation_mat(Orientation());
+  rotation_mat.TransformVector(&pointer_direction);
   return controller_position +
          gfx::ScaleVector3d(pointer_direction, kLaserStartDisplacement);
 }
@@ -215,7 +216,7 @@
   }
 
   const gvr::Vec3f& gvr_gyro = controller_state_->GetGyro();
-  elbow_model_->Update({IsConnected(), vr::ToQuaternion(Orientation()),
+  elbow_model_->Update({IsConnected(), Orientation(),
                         gfx::Vector3dF(gvr_gyro.x, gvr_gyro.y, gvr_gyro.z),
                         head_direction,
                         DeltaTimeSeconds(last_timestamp_nanos_)});
diff --git a/chrome/browser/android/vr_shell/vr_controller.h b/chrome/browser/android/vr_shell/vr_controller.h
index 25b5fc1..de6d03b7 100644
--- a/chrome/browser/android/vr_shell/vr_controller.h
+++ b/chrome/browser/android/vr_shell/vr_controller.h
@@ -11,13 +11,20 @@
 #include "base/macros.h"
 #include "chrome/browser/android/vr_shell/vr_controller_model.h"
 #include "device/vr/android/gvr/gvr_gamepad_data_provider.h"
-#include "device/vr/vr_types.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr_types.h"
+#include "ui/gfx/geometry/point3_f.h"
+#include "ui/gfx/geometry/quaternion.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+#include "ui/gfx/geometry/vector3d_f.h"
 
 namespace blink {
 class WebGestureEvent;
 }
 
+namespace gfx {
+class Transform;
+}
+
 namespace gvr {
 class ControllerState;
 }
@@ -56,8 +63,8 @@
 
   float TouchPosY();
 
-  vr::Quatf Orientation() const;
-  void GetTransform(vr::Mat4f* out) const;
+  gfx::Quaternion Orientation() const;
+  void GetTransform(gfx::Transform* out) const;
   float GetOpacity() const;
   gfx::Point3F GetPointerStart() const;
 
diff --git a/chrome/browser/android/vr_shell/vr_gl_util.cc b/chrome/browser/android/vr_shell/vr_gl_util.cc
index 1c68ee1..e087b892 100644
--- a/chrome/browser/android/vr_shell/vr_gl_util.cc
+++ b/chrome/browser/android/vr_shell/vr_gl_util.cc
@@ -4,20 +4,15 @@
 
 #include "chrome/browser/android/vr_shell/vr_gl_util.h"
 
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/transform.h"
+
 namespace vr_shell {
 
 // This code is adapted from the GVR Treasure Hunt demo source.
-std::array<float, 16> MatrixToGLArray(const vr::Mat4f& matrix) {
-  // Note that this performs a *transpose* to a column-major matrix array, as
-  // expected by GL. The input matrix has translation components at [i][3] for
-  // use with row vectors and premultiplied transforms. In the output, the
-  // translation elements are at the end at positions 3*4+i.
+std::array<float, 16> MatrixToGLArray(const gfx::Transform& transform) {
   std::array<float, 16> result;
-  for (int i = 0; i < 4; ++i) {
-    for (int j = 0; j < 4; ++j) {
-      result[j * 4 + i] = matrix[i][j];
-    }
-  }
+  transform.matrix().asColMajorf(result.data());
   return result;
 }
 
diff --git a/chrome/browser/android/vr_shell/vr_gl_util.h b/chrome/browser/android/vr_shell/vr_gl_util.h
index 59052929..ff0b2e6 100644
--- a/chrome/browser/android/vr_shell/vr_gl_util.h
+++ b/chrome/browser/android/vr_shell/vr_gl_util.h
@@ -8,12 +8,18 @@
 #include <array>
 #include <string>
 
-#include "device/vr/vr_types.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gl/gl_bindings.h"
 
+namespace gfx {
+class RectF;
+class Size;
+class Transform;
+}  // namespace gfx
+
 namespace vr_shell {
 
-std::array<float, 16> MatrixToGLArray(const vr::Mat4f& matrix);
+std::array<float, 16> MatrixToGLArray(const gfx::Transform& matrix);
 
 gfx::Rect CalculatePixelSpaceRect(const gfx::Size& texture_size,
                                   const gfx::RectF& texture_rect);
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.cc b/chrome/browser/android/vr_shell/vr_shell_gl.cc
index 40d60d7f..b7b761d 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.cc
@@ -103,33 +103,46 @@
     gfx::PointF(std::numeric_limits<float>::max(),
                 std::numeric_limits<float>::max());
 
+static constexpr float kEpsilon = 1e-6f;
+
+gfx::Point3F GetRayPoint(const gfx::Point3F& rayOrigin,
+                         const gfx::Vector3dF& rayVector,
+                         float scale) {
+  return rayOrigin + gfx::ScaleVector3d(rayVector, scale);
+}
+
+// Provides the direction the head is looking towards as a 3x1 unit vector.
+gfx::Vector3dF GetForwardVector(const gfx::Transform& head_pose) {
+  // Same as multiplying the inverse of the rotation component of the matrix by
+  // (0, 0, -1, 0).
+  return gfx::Vector3dF(-head_pose.matrix().get(2, 0),
+                        -head_pose.matrix().get(2, 1),
+                        -head_pose.matrix().get(2, 2));
+}
+
 // Generate a quaternion representing the rotation from the negative Z axis
 // (0, 0, -1) to a specified vector. This is an optimized version of a more
 // general vector-to-vector calculation.
-vr::Quatf GetRotationFromZAxis(gfx::Vector3dF vec) {
-  vr::NormalizeVector(&vec);
-  vr::Quatf quat;
-  quat.qw = 1.0f - vec.z();
-  if (quat.qw < 1e-6f) {
+gfx::Quaternion GetRotationFromZAxis(gfx::Vector3dF vec) {
+  vec.GetNormalized(&vec);
+  gfx::Quaternion quat;
+  quat.set_w(1.0f - vec.z());
+  if (quat.w() < kEpsilon) {
     // Degenerate case: vectors are exactly opposite. Replace by an
     // arbitrary 180 degree rotation to avoid invalid normalization.
-    quat.qx = 1.0f;
-    quat.qy = 0.0f;
-    quat.qz = 0.0f;
-    quat.qw = 0.0f;
-  } else {
-    quat.qx = vec.y();
-    quat.qy = -vec.x();
-    quat.qz = 0.0f;
-    vr::NormalizeQuat(&quat);
+    return gfx::Quaternion(1, 0, 0, 0);
   }
-  return quat;
+
+  quat.set_x(vec.y());
+  quat.set_y(-vec.x());
+  quat.set_z(0.0);
+  return quat.Normalized();
 }
 
-gvr::Mat4f PerspectiveMatrixFromView(const gvr::Rectf& fov,
-                                     float z_near,
-                                     float z_far) {
-  gvr::Mat4f result;
+gfx::Transform PerspectiveMatrixFromView(const gvr::Rectf& fov,
+                                         float z_near,
+                                         float z_far) {
+  gfx::Transform result;
   const float x_left = -std::tan(fov.left * M_PI / 180.0f) * z_near;
   const float x_right = std::tan(fov.right * M_PI / 180.0f) * z_near;
   const float y_bottom = -std::tan(fov.bottom * M_PI / 180.0f) * z_near;
@@ -144,18 +157,16 @@
   const float C = (z_near + z_far) / (z_near - z_far);
   const float D = (2 * z_near * z_far) / (z_near - z_far);
 
-  for (int i = 0; i < 4; ++i) {
-    for (int j = 0; j < 4; ++j) {
-      result.m[i][j] = 0.0f;
-    }
-  }
-  result.m[0][0] = X;
-  result.m[0][2] = A;
-  result.m[1][1] = Y;
-  result.m[1][2] = B;
-  result.m[2][2] = C;
-  result.m[2][3] = D;
-  result.m[3][2] = -1;
+  // The gfx::Transform default ctor initializes the transform to the identity,
+  // so we must zero out a few values along the diagonal here.
+  result.matrix().set(0, 0, X);
+  result.matrix().set(0, 2, A);
+  result.matrix().set(1, 1, Y);
+  result.matrix().set(1, 2, B);
+  result.matrix().set(2, 2, C);
+  result.matrix().set(2, 3, D);
+  result.matrix().set(3, 2, -1);
+  result.matrix().set(3, 3, 0);
 
   return result;
 }
@@ -178,21 +189,20 @@
   return mouse_event;
 }
 
-
-void MatfToGvrMat(const vr::Mat4f& in, gvr::Mat4f* out) {
-  // If our std::array implementation doesn't have any non-data members, we can
-  // just cast the gvr matrix to an std::array.
-  static_assert(sizeof(in) == sizeof(*out),
-                "Cannot reinterpret gvr::Mat4f as vr::Matf");
-  *out = *reinterpret_cast<gvr::Mat4f*>(const_cast<vr::Mat4f*>(&in));
+void TransformToGvrMat(const gfx::Transform& in, gvr::Mat4f* out) {
+  for (int i = 0; i < 4; ++i) {
+    for (int j = 0; j < 4; ++j) {
+      out->m[i][j] = in.matrix().get(i, j);
+    }
+  }
 }
 
-void GvrMatToMatf(const gvr::Mat4f& in, vr::Mat4f* out) {
-  // If our std::array implementation doesn't have any non-data members, we can
-  // just cast the gvr matrix to an std::array.
-  static_assert(sizeof(in) == sizeof(*out),
-                "Cannot reinterpret gvr::Mat4f as vr::Matf");
-  *out = *reinterpret_cast<vr::Mat4f*>(const_cast<gvr::Mat4f*>(&in));
+void GvrMatToTransform(const gvr::Mat4f& in, gfx::Transform* out) {
+  for (int i = 0; i < 4; ++i) {
+    for (int j = 0; j < 4; ++j) {
+      out->matrix().set(i, j, in.m[i][j]);
+    }
+  }
 }
 
 gvr::Rectf UVFromGfxRect(gfx::RectF rect) {
@@ -450,8 +460,10 @@
 
 void VrShellGl::InitializeRenderer() {
   gvr_api_->InitializeGl();
-  vr::Mat4f head_pose;
-  device::GvrDelegate::GetGvrPoseWithNeckModel(gvr_api_.get(), &head_pose);
+  gfx::Transform head_pose;
+  vr::Mat4f from_gvr;
+  device::GvrDelegate::GetGvrPoseWithNeckModel(gvr_api_.get(), &from_gvr);
+  head_pose = vr::ToTransform(from_gvr);
   webvr_head_pose_.assign(kPoseRingBufferSize, head_pose);
   webvr_time_pose_.assign(kPoseRingBufferSize, base::TimeTicks());
   webvr_time_js_submit_.assign(kPoseRingBufferSize, base::TimeTicks());
@@ -557,10 +569,9 @@
     controller_quat_ = controller_->Orientation();
   }
 
-  vr::Mat4f mat;
-  QuatToMatrix(controller_quat_, &mat);
-  gfx::Vector3dF controller_direction =
-      vr::MatrixVectorMul(mat, ergo_neutral_pose);
+  gfx::Transform mat(controller_quat_);
+  gfx::Vector3dF controller_direction = ergo_neutral_pose;
+  mat.TransformVector(&controller_direction);
 
   HandleControllerAppButtonActivity(controller_direction);
 
@@ -857,10 +868,9 @@
   // that the sphere is centered at the controller, rather than the eye, for
   // simplicity.
   float distance = scene_->GetBackgroundDistance();
-  target_point =
-      vr::GetRayPoint(pointer_start_, controller_direction, distance);
+  target_point = GetRayPoint(pointer_start_, controller_direction, distance);
   eye_to_target = target_point - kOrigin;
-  vr::NormalizeVector(&eye_to_target);
+  eye_to_target.GetNormalized(&eye_to_target);
 
   // Determine which UI element (if any) intersects the line between the eyes
   // and the controller target position.
@@ -898,7 +908,7 @@
   if (distance_to_plane < 0 || distance_to_plane >= max_distance_to_plane)
     return false;
 
-  target_point = vr::GetRayPoint(kOrigin, eye_to_target, distance_to_plane);
+  target_point = GetRayPoint(kOrigin, eye_to_target, distance_to_plane);
   gfx::PointF unit_xy_point = element.GetUnitRectangleCoordinates(target_point);
 
   target_local_point.set_x(0.5f + unit_xy_point.x());
@@ -922,9 +932,13 @@
     // TODO(asimjour1): We need to refactor the gesture recognition outside of
     // VrShellGl.
     UiInterface::Direction direction = UiInterface::NONE;
-    float gesture_xz_angle;
-    if (vr::XZAngle(controller_start_direction_, controller_direction,
-                    &gesture_xz_angle)) {
+    gfx::Vector3dF a = controller_start_direction_;
+    gfx::Vector3dF b = controller_direction;
+    a.set_y(0);
+    b.set_y(0);
+    if (a.LengthSquared() * b.LengthSquared() > 0.0) {
+      float gesture_xz_angle =
+          acos(gfx::DotProduct(a, b) / a.Length() / b.Length());
       if (fabs(gesture_xz_angle) > kMinAppButtonGestureAngleRad) {
         direction =
             gesture_xz_angle < 0 ? UiInterface::LEFT : UiInterface::RIGHT;
@@ -1024,7 +1038,7 @@
     DrawWebVr();
   }
 
-  vr::Mat4f head_pose;
+  gfx::Transform head_pose;
 
   // When using async reprojection, we need to know which pose was
   // used in the WebVR app for drawing this frame and supply it when
@@ -1036,7 +1050,9 @@
                   "kPoseRingBufferSize must be a power of 2");
     head_pose = webvr_head_pose_[frame_index % kPoseRingBufferSize];
   } else {
-    device::GvrDelegate::GetGvrPoseWithNeckModel(gvr_api_.get(), &head_pose);
+    vr::Mat4f from_gvr;
+    device::GvrDelegate::GetGvrPoseWithNeckModel(gvr_api_.get(), &from_gvr);
+    head_pose = vr::ToTransform(from_gvr);
   }
 
   // Update the render position of all UI elements (including desktop).
@@ -1046,7 +1062,7 @@
     // TODO(crbug.com/704690): Acquire controller state in a way that's timely
     // for both the gamepad API and UI input handling.
     TRACE_EVENT0("gpu", "VrShellGl::UpdateController");
-    auto head_direction = vr::GetForwardVector(head_pose);
+    gfx::Vector3dF head_direction = GetForwardVector(head_pose);
     UpdateController(head_direction);
     HandleControllerInput(head_direction);
   }
@@ -1081,7 +1097,7 @@
 
 void VrShellGl::DrawFrameSubmitWhenReady(int16_t frame_index,
                                          gvr_frame* frame_ptr,
-                                         const vr::Mat4f& head_pose,
+                                         const gfx::Transform& head_pose,
                                          std::unique_ptr<gl::GLFence> fence) {
   if (fence && !fence->HasCompleted()) {
     task_runner_->PostDelayedTask(
@@ -1098,7 +1114,7 @@
 
   gvr::Frame frame(frame_ptr);
   gvr::Mat4f mat;
-  MatfToGvrMat(head_pose, &mat);
+  TransformToGvrMat(head_pose, &mat);
   frame.Submit(*buffer_viewport_list_, mat);
 
   // No need to swap buffers for surfaceless rendering.
@@ -1137,7 +1153,7 @@
   TRACE_COUNTER1("gpu", "WebVR FPS", fps_meter_->GetFPS());
 }
 
-void VrShellGl::DrawWorldElements(const vr::Mat4f& head_pose) {
+void VrShellGl::DrawWorldElements(const gfx::Transform& head_pose) {
   TRACE_EVENT0("gpu", "VrShellGl::DrawWorldElements");
 
   if (ShouldDrawWebVr()) {
@@ -1167,7 +1183,7 @@
              kViewportListPrimaryOffset, draw_reticle);
 }
 
-void VrShellGl::DrawOverlayElements(const vr::Mat4f& head_pose) {
+void VrShellGl::DrawOverlayElements(const gfx::Transform& head_pose) {
   std::vector<const UiElement*> elements = scene_->GetOverlayElements();
   if (elements.empty())
     return;
@@ -1199,13 +1215,12 @@
 
   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-  vr::Mat4f identity_matrix;
-  vr::SetIdentityM(&identity_matrix);
+  gfx::Transform identity_matrix;
   DrawUiView(identity_matrix, elements, render_size_headlocked_,
              kViewportListHeadlockedOffset, false);
 }
 
-void VrShellGl::DrawUiView(const vr::Mat4f& head_pose,
+void VrShellGl::DrawUiView(const gfx::Transform& head_pose,
                            const std::vector<const UiElement*>& elements,
                            const gfx::Size& render_size,
                            int viewport_offset,
@@ -1218,23 +1233,18 @@
     buffer_viewport_list_->GetBufferViewport(eye + viewport_offset,
                                              buffer_viewport_.get());
 
-    vr::Mat4f eye_view_matrix;
-    vr::Mat4f eye_matrix;
-    GvrMatToMatf(gvr_api_->GetEyeFromHeadMatrix(eye), &eye_matrix);
-    vr::MatrixMul(eye_matrix, head_pose, &eye_view_matrix);
+    gfx::Transform eye_matrix;
+    GvrMatToTransform(gvr_api_->GetEyeFromHeadMatrix(eye), &eye_matrix);
+    gfx::Transform eye_view_matrix = eye_matrix * head_pose;
 
     const gfx::RectF& rect = GfxRectFromUV(buffer_viewport_->GetSourceUv());
     const gfx::Rect& pixel_rect = CalculatePixelSpaceRect(render_size, rect);
     glViewport(pixel_rect.x(), pixel_rect.y(), pixel_rect.width(),
                pixel_rect.height());
 
-    vr::Mat4f view_proj_matrix;
-    vr::Mat4f perspective_matrix;
-    GvrMatToMatf(PerspectiveMatrixFromView(buffer_viewport_->GetSourceFov(),
-                                           kZNear, kZFar),
-                 &perspective_matrix);
-
-    vr::MatrixMul(perspective_matrix, eye_view_matrix, &view_proj_matrix);
+    gfx::Transform perspective_matrix = PerspectiveMatrixFromView(
+        buffer_viewport_->GetSourceFov(), kZNear, kZFar);
+    gfx::Transform view_proj_matrix = perspective_matrix * eye_view_matrix;
 
     DrawElements(view_proj_matrix, sorted_elements, draw_reticle);
     if (draw_reticle) {
@@ -1244,7 +1254,7 @@
   }
 }
 
-void VrShellGl::DrawElements(const vr::Mat4f& view_proj_matrix,
+void VrShellGl::DrawElements(const gfx::Transform& view_proj_matrix,
                              const std::vector<const UiElement*>& elements,
                              bool draw_reticle) {
   if (elements.empty())
@@ -1269,11 +1279,9 @@
   vr_shell_renderer_->Flush();
 }
 
-void VrShellGl::DrawElement(const vr::Mat4f& view_proj_matrix,
+void VrShellGl::DrawElement(const gfx::Transform& view_proj_matrix,
                             const UiElement& element) {
-  vr::Mat4f transform;
-  vr::MatrixMul(view_proj_matrix, vr::ToMat4F(element.TransformMatrix()),
-                &transform);
+  gfx::Transform transform = view_proj_matrix * element.TransformMatrix();
 
   switch (element.fill()) {
     case Fill::OPAQUE_GRADIENT: {
@@ -1297,7 +1305,7 @@
       break;
     }
     case Fill::SELF: {
-      element.Render(vr_shell_renderer_.get(), vr::ToTransform(transform));
+      element.Render(vr_shell_renderer_.get(), transform);
       break;
     }
     default:
@@ -1306,7 +1314,7 @@
 }
 
 std::vector<const UiElement*> VrShellGl::GetElementsInDrawOrder(
-    const vr::Mat4f& view_matrix,
+    const gfx::Transform& view_matrix,
     const std::vector<const UiElement*>& elements) {
   std::vector<const UiElement*> sorted_elements = elements;
 
@@ -1321,32 +1329,23 @@
               if (first->draw_phase() != second->draw_phase()) {
                 return first->draw_phase() < second->draw_phase();
               } else {
-                const float first_depth =
-                    vr::GetTranslation(vr::ToMat4F(first->TransformMatrix()))
-                        .z();
-                const float second_depth =
-                    vr::GetTranslation(vr::ToMat4F(second->TransformMatrix()))
-                        .z();
-                return first_depth < second_depth;
+                return first->TransformMatrix().matrix().get(2, 3) <
+                       second->TransformMatrix().matrix().get(2, 3);
               }
             });
 
   return sorted_elements;
 }
 
-void VrShellGl::DrawReticle(const vr::Mat4f& render_matrix) {
-  vr::Mat4f mat;
-  vr::SetIdentityM(&mat);
-
+void VrShellGl::DrawReticle(const gfx::Transform& render_matrix) {
   // Scale the reticle to have a fixed FOV size at any distance.
   const float eye_to_target =
       std::sqrt(target_point_.SquaredDistanceTo(kOrigin));
-  vr::ScaleM(
-      mat,
-      {kReticleWidth * eye_to_target, kReticleHeight * eye_to_target, 1.0f},
-      &mat);
 
-  vr::Quatf rotation;
+  gfx::Transform mat;
+  mat.Scale3d(kReticleWidth * eye_to_target, kReticleHeight * eye_to_target, 1);
+
+  gfx::Quaternion rotation;
   if (reticle_render_target_ != nullptr) {
     // Make the reticle planar to the element it's hitting.
     rotation = GetRotationFromZAxis(reticle_render_target_->GetNormal());
@@ -1354,74 +1353,67 @@
     // Rotate the reticle to directly face the eyes.
     rotation = GetRotationFromZAxis(target_point_ - kOrigin);
   }
-  vr::Mat4f rotation_mat;
-  vr::QuatToMatrix(rotation, &rotation_mat);
-  vr::MatrixMul(rotation_mat, mat, &mat);
+  gfx::Transform rotation_mat(rotation);
+  mat = rotation_mat * mat;
 
   gfx::Point3F target_point = ScalePoint(target_point_, kReticleOffset);
   // Place the pointer slightly in front of the plane intersection point.
-  vr::TranslateM(mat, target_point - kOrigin, &mat);
+  mat.matrix().postTranslate(target_point.x(), target_point.y(),
+                             target_point.z());
 
-  vr::Mat4f transform;
-  vr::MatrixMul(render_matrix, mat, &transform);
+  gfx::Transform transform = render_matrix * mat;
   vr_shell_renderer_->GetReticleRenderer()->Draw(transform);
 }
 
-void VrShellGl::DrawLaser(const vr::Mat4f& render_matrix) {
+void VrShellGl::DrawLaser(const gfx::Transform& render_matrix) {
   gfx::Point3F target_point = ScalePoint(target_point_, kReticleOffset);
   // Find the length of the beam (from hand to target).
   const float laser_length =
       std::sqrt(pointer_start_.SquaredDistanceTo(target_point));
 
-  vr::Mat4f mat;
   // Build a beam, originating from the origin.
-  vr::SetIdentityM(&mat);
+  gfx::Transform mat;
 
   // Move the beam half its height so that its end sits on the origin.
-  vr::TranslateM(mat, {0.0f, 0.5f, 0.0f}, &mat);
-  vr::ScaleM(mat, {kLaserWidth, laser_length, 1}, &mat);
+  mat.matrix().postTranslate(0.0f, 0.5f, 0.0f);
+  mat.matrix().postScale(kLaserWidth, laser_length, 1);
 
   // Tip back 90 degrees to flat, pointing at the scene.
-  const vr::Quatf quat = vr::QuatFromAxisAngle({1.0f, 0.0f, 0.0f, -M_PI / 2});
-  vr::Mat4f rotation_mat;
-  vr::QuatToMatrix(quat, &rotation_mat);
-  vr::MatrixMul(rotation_mat, mat, &mat);
+  const gfx::Quaternion quat(gfx::Vector3dF(1.0f, 0.0f, 0.0f), -M_PI / 2);
+  gfx::Transform rotation_mat(quat);
+  mat = rotation_mat * mat;
 
   const gfx::Vector3dF beam_direction = target_point_ - pointer_start_;
 
-  vr::Mat4f beam_direction_mat;
-  vr::QuatToMatrix(GetRotationFromZAxis(beam_direction), &beam_direction_mat);
+  gfx::Transform beam_direction_mat(GetRotationFromZAxis(beam_direction));
 
   float opacity = controller_->GetOpacity();
   // Render multiple faces to make the laser appear cylindrical.
   const int faces = 4;
-  vr::Mat4f face_transform;
-  vr::Mat4f transform;
+  gfx::Transform face_transform;
+  gfx::Transform transform;
   for (int i = 0; i < faces; i++) {
     // Rotate around Z.
     const float angle = M_PI * 2 * i / faces;
-    const vr::Quatf rot = vr::QuatFromAxisAngle({0.0f, 0.0f, 1.0f, angle});
-    vr::QuatToMatrix(rot, &face_transform);
-    vr::MatrixMul(face_transform, mat, &face_transform);
-    // Orient according to target direction.
-    vr::MatrixMul(beam_direction_mat, face_transform, &face_transform);
+    const gfx::Quaternion rot({0.0f, 0.0f, 1.0f}, angle);
+    face_transform = beam_direction_mat * gfx::Transform(rot) * mat;
 
     // Move the beam origin to the hand.
-    vr::TranslateM(face_transform, pointer_start_ - kOrigin, &face_transform);
-    vr::MatrixMul(render_matrix, face_transform, &transform);
+    face_transform.matrix().postTranslate(
+        pointer_start_.x(), pointer_start_.y(), pointer_start_.z());
+    transform = render_matrix * face_transform;
     vr_shell_renderer_->GetLaserRenderer()->Draw(opacity, transform);
   }
 }
 
-void VrShellGl::DrawController(const vr::Mat4f& view_proj_matrix) {
+void VrShellGl::DrawController(const gfx::Transform& view_proj_matrix) {
   if (!vr_shell_renderer_->GetControllerRenderer()->IsSetUp())
     return;
   auto state = controller_->GetModelState();
   auto opacity = controller_->GetOpacity();
-  vr::Mat4f controller_transform;
+  gfx::Transform controller_transform;
   controller_->GetTransform(&controller_transform);
-  vr::Mat4f transform;
-  vr::MatrixMul(view_proj_matrix, controller_transform, &transform);
+  gfx::Transform transform = view_proj_matrix * controller_transform;
   vr_shell_renderer_->GetControllerRenderer()->Draw(state, opacity, transform);
 }
 
@@ -1610,10 +1602,12 @@
 
   int64_t prediction_nanos = GetPredictedFrameTimeNanos();
 
-  vr::Mat4f head_mat;
+  gfx::Transform head_mat;
+  vr::Mat4f from_gvr;
   device::mojom::VRPosePtr pose =
-      device::GvrDelegate::GetVRPosePtrWithNeckModel(gvr_api_.get(), &head_mat,
+      device::GvrDelegate::GetVRPosePtrWithNeckModel(gvr_api_.get(), &from_gvr,
                                                      prediction_nanos);
+  head_mat = vr::ToTransform(from_gvr);
 
   webvr_head_pose_[frame_index % kPoseRingBufferSize] = head_mat;
   webvr_time_pose_[frame_index % kPoseRingBufferSize] = base::TimeTicks::Now();
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.h b/chrome/browser/android/vr_shell/vr_shell_gl.h
index 6ea66a9..57252186 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.h
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.h
@@ -18,10 +18,11 @@
 #include "chrome/browser/android/vr_shell/vr_controller.h"
 #include "chrome/browser/android/vr_shell/vr_controller_model.h"
 #include "device/vr/vr_service.mojom.h"
-#include "device/vr/vr_types.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr_types.h"
+#include "ui/gfx/geometry/quaternion.h"
+#include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/native_widget_types.h"
 
 namespace blink {
@@ -117,26 +118,27 @@
   void DrawFrame(int16_t frame_index);
   void DrawFrameSubmitWhenReady(int16_t frame_index,
                                 gvr_frame* frame_ptr,
-                                const vr::Mat4f& head_pose,
+                                const gfx::Transform& head_pose,
                                 std::unique_ptr<gl::GLFence> fence);
-  void DrawWorldElements(const vr::Mat4f& head_pose);
-  void DrawOverlayElements(const vr::Mat4f& head_pose);
+  void DrawWorldElements(const gfx::Transform& head_pose);
+  void DrawOverlayElements(const gfx::Transform& head_pose);
   void DrawHeadLockedElements();
-  void DrawUiView(const vr::Mat4f& head_pose,
+  void DrawUiView(const gfx::Transform& head_pose,
                   const std::vector<const UiElement*>& elements,
                   const gfx::Size& render_size,
                   int viewport_offset,
                   bool draw_cursor);
-  void DrawElements(const vr::Mat4f& view_proj_matrix,
+  void DrawElements(const gfx::Transform& view_proj_matrix,
                     const std::vector<const UiElement*>& elements,
                     bool draw_cursor);
-  void DrawElement(const vr::Mat4f& view_proj_matrix, const UiElement& element);
+  void DrawElement(const gfx::Transform& view_proj_matrix,
+                   const UiElement& element);
   std::vector<const UiElement*> GetElementsInDrawOrder(
-      const vr::Mat4f& view_matrix,
+      const gfx::Transform& view_matrix,
       const std::vector<const UiElement*>& elements);
-  void DrawReticle(const vr::Mat4f& view_proj_matrix);
-  void DrawLaser(const vr::Mat4f& view_proj_matrix);
-  void DrawController(const vr::Mat4f& view_proj_matrix);
+  void DrawReticle(const gfx::Transform& view_proj_matrix);
+  void DrawLaser(const gfx::Transform& view_proj_matrix);
+  void DrawController(const gfx::Transform& view_proj_matrix);
   bool ShouldDrawWebVr();
   void DrawWebVr();
   bool WebVrPoseByteIsValid(int pose_index_byte);
@@ -229,7 +231,7 @@
 
   bool cardboard_ = false;
   bool touch_pending_ = false;
-  vr::Quatf controller_quat_;
+  gfx::Quaternion controller_quat_;
 
   gfx::Point3F target_point_;
 
@@ -250,9 +252,11 @@
   gfx::Size content_tex_physical_size_ = {0, 0};
   gfx::Size webvr_surface_size_ = {0, 0};
 
-  std::vector<vr::Mat4f> webvr_head_pose_;
   std::vector<base::TimeTicks> webvr_time_pose_;
   std::vector<base::TimeTicks> webvr_time_js_submit_;
+
+  std::vector<gfx::Transform> webvr_head_pose_;
+
   bool web_vr_mode_;
   bool ready_to_draw_ = false;
   bool surfaceless_rendering_;
diff --git a/chrome/browser/android/vr_shell/vr_shell_renderer.cc b/chrome/browser/android/vr_shell/vr_shell_renderer.cc
index df3705c8..f43a98a5 100644
--- a/chrome/browser/android/vr_shell/vr_shell_renderer.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_renderer.cc
@@ -11,6 +11,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/trace_event/trace_event.h"
 #include "chrome/browser/android/vr_shell/vr_gl_util.h"
+#include "ui/gfx/geometry/rect_f.h"
 
 namespace {
 
@@ -337,7 +338,7 @@
 BaseQuadRenderer::~BaseQuadRenderer() = default;
 
 void BaseQuadRenderer::PrepareToDraw(GLuint view_proj_matrix_handle,
-                                     const vr::Mat4f& view_proj_matrix) {
+                                     const gfx::Transform& view_proj_matrix) {
   glUseProgram(program_handle_);
 
   // Pass in model view project matrix.
@@ -381,7 +382,7 @@
 }
 
 void ExternalTexturedQuadRenderer::Draw(int texture_data_handle,
-                                        const vr::Mat4f& view_proj_matrix,
+                                        const gfx::Transform& view_proj_matrix,
                                         const gfx::RectF& copy_rect,
                                         float opacity) {
   PrepareToDraw(model_view_proj_matrix_handle_, view_proj_matrix);
@@ -419,7 +420,7 @@
 }
 
 void TexturedQuadRenderer::AddQuad(int texture_data_handle,
-                                   const vr::Mat4f& view_proj_matrix,
+                                   const gfx::Transform& view_proj_matrix,
                                    const gfx::RectF& copy_rect,
                                    float opacity) {
   SkiaQuad quad;
@@ -562,7 +563,7 @@
       glGetUniformLocation(program_handle_, "mid_ring_opacity");
 }
 
-void ReticleRenderer::Draw(const vr::Mat4f& view_proj_matrix) {
+void ReticleRenderer::Draw(const gfx::Transform& view_proj_matrix) {
   PrepareToDraw(model_view_proj_matrix_handle_, view_proj_matrix);
 
   glUniform4f(color_handle_, kReticleColor[0], kReticleColor[1],
@@ -604,7 +605,8 @@
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 }
 
-void LaserRenderer::Draw(float opacity, const vr::Mat4f& view_proj_matrix) {
+void LaserRenderer::Draw(float opacity,
+                         const gfx::Transform& view_proj_matrix) {
   PrepareToDraw(model_view_proj_matrix_handle_, view_proj_matrix);
 
   // Link texture data with texture unit.
@@ -688,7 +690,7 @@
 
 void ControllerRenderer::Draw(VrControllerModel::State state,
                               float opacity,
-                              const vr::Mat4f& view_proj_matrix) {
+                              const gfx::Transform& view_proj_matrix) {
   glUseProgram(program_handle_);
 
   glUniform1f(opacity_handle_, opacity);
@@ -730,7 +732,7 @@
   opacity_handle_ = glGetUniformLocation(program_handle_, "u_Opacity");
 }
 
-void GradientQuadRenderer::Draw(const vr::Mat4f& view_proj_matrix,
+void GradientQuadRenderer::Draw(const gfx::Transform& view_proj_matrix,
                                 SkColor edge_color,
                                 SkColor center_color,
                                 float opacity) {
@@ -765,7 +767,7 @@
   lines_count_handle_ = glGetUniformLocation(program_handle_, "u_LinesCount");
 }
 
-void GradientGridRenderer::Draw(const vr::Mat4f& view_proj_matrix,
+void GradientGridRenderer::Draw(const gfx::Transform& view_proj_matrix,
                                 SkColor edge_color,
                                 SkColor center_color,
                                 SkColor grid_color,
@@ -807,14 +809,14 @@
 VrShellRenderer::~VrShellRenderer() = default;
 
 void VrShellRenderer::DrawTexturedQuad(int texture_data_handle,
-                                       const vr::Mat4f& view_proj_matrix,
+                                       const gfx::Transform& view_proj_matrix,
                                        const gfx::RectF& copy_rect,
                                        float opacity) {
   GetTexturedQuadRenderer()->AddQuad(texture_data_handle, view_proj_matrix,
                                      copy_rect, opacity);
 }
 
-void VrShellRenderer::DrawGradientQuad(const vr::Mat4f& view_proj_matrix,
+void VrShellRenderer::DrawGradientQuad(const gfx::Transform& view_proj_matrix,
                                        const SkColor edge_color,
                                        const SkColor center_color,
                                        float opacity) {
diff --git a/chrome/browser/android/vr_shell/vr_shell_renderer.h b/chrome/browser/android/vr_shell/vr_shell_renderer.h
index 85791ab1..eb11c06e 100644
--- a/chrome/browser/android/vr_shell/vr_shell_renderer.h
+++ b/chrome/browser/android/vr_shell/vr_shell_renderer.h
@@ -12,7 +12,7 @@
 #include "base/macros.h"
 #include "chrome/browser/android/vr_shell/ui_element_renderer.h"
 #include "chrome/browser/android/vr_shell/vr_controller_model.h"
-#include "device/vr/vr_types.h"
+#include "ui/gfx/transform.h"
 #include "ui/gl/gl_bindings.h"
 
 namespace vr_shell {
@@ -61,7 +61,7 @@
 
 struct SkiaQuad {
   int texture_data_handle;
-  vr::Mat4f view_proj_matrix;
+  gfx::Transform view_proj_matrix;
   RectF copy_rect;
   float opacity;
 };
@@ -89,7 +89,7 @@
 
  protected:
   void PrepareToDraw(GLuint view_proj_matrix_handle,
-                     const vr::Mat4f& view_proj_matrix);
+                     const gfx::Transform& view_proj_matrix);
 
   static GLuint vertex_buffer_;
 
@@ -103,7 +103,7 @@
 
   // Draw the content rect in the texture quad.
   void Draw(int texture_data_handle,
-            const vr::Mat4f& view_proj_matrix,
+            const gfx::Transform& view_proj_matrix,
             const gfx::RectF& copy_rect,
             float opacity);
 
@@ -123,7 +123,7 @@
 
   // Draw the content rect in the texture quad.
   void AddQuad(int texture_data_handle,
-               const vr::Mat4f& view_proj_matrix,
+               const gfx::Transform& view_proj_matrix,
                const gfx::RectF& copy_rect,
                float opacity);
 
@@ -159,7 +159,7 @@
   ReticleRenderer();
   ~ReticleRenderer() override;
 
-  void Draw(const vr::Mat4f& view_proj_matrix);
+  void Draw(const gfx::Transform& view_proj_matrix);
 
  private:
   GLuint model_view_proj_matrix_handle_;
@@ -179,7 +179,7 @@
   LaserRenderer();
   ~LaserRenderer() override;
 
-  void Draw(float opacity, const vr::Mat4f& view_proj_matrix);
+  void Draw(float opacity, const gfx::Transform& view_proj_matrix);
 
  private:
   GLuint model_view_proj_matrix_handle_;
@@ -201,7 +201,7 @@
   void SetUp(std::unique_ptr<VrControllerModel> model);
   void Draw(VrControllerModel::State state,
             float opacity,
-            const vr::Mat4f& view_proj_matrix);
+            const gfx::Transform& view_proj_matrix);
   bool IsSetUp() const { return setup_; }
 
  private:
@@ -233,7 +233,7 @@
   GradientQuadRenderer();
   ~GradientQuadRenderer() override;
 
-  void Draw(const vr::Mat4f& view_proj_matrix,
+  void Draw(const gfx::Transform& view_proj_matrix,
             SkColor edge_color,
             SkColor center_color,
             float opacity);
@@ -253,7 +253,7 @@
   GradientGridRenderer();
   ~GradientGridRenderer() override;
 
-  void Draw(const vr::Mat4f& view_proj_matrix,
+  void Draw(const gfx::Transform& view_proj_matrix,
             SkColor edge_color,
             SkColor center_color,
             SkColor grid_color,
@@ -279,10 +279,10 @@
 
   // UiElementRenderer interface (exposed to UI elements).
   void DrawTexturedQuad(int texture_data_handle,
-                        const vr::Mat4f& view_proj_matrix,
+                        const gfx::Transform& view_proj_matrix,
                         const gfx::RectF& copy_rect,
                         float opacity) override;
-  void DrawGradientQuad(const vr::Mat4f& view_proj_matrix,
+  void DrawGradientQuad(const gfx::Transform& view_proj_matrix,
                         const SkColor edge_color,
                         const SkColor center_color,
                         float opacity) override;
diff --git a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc
index 5cd67754..156fb72 100644
--- a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc
+++ b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.cc
@@ -92,8 +92,7 @@
       check_webapk_compatibility_(check_webapk_compatibility),
       is_waiting_for_web_application_info_(true),
       is_installable_check_complete_(false),
-      is_icon_saved_(false),
-      is_ready_(false) {
+      is_icon_saved_(false) {
   DCHECK(minimum_icon_size_in_px <= ideal_icon_size_in_px);
   DCHECK(minimum_splash_image_size_in_px <= ideal_splash_image_size_in_px);
 
@@ -344,6 +343,5 @@
 
   is_icon_saved_ = true;
   primary_icon_ = primary_icon;
-  is_ready_ = true;
   weak_observer_->OnDataAvailable(shortcut_info_, primary_icon_, badge_icon_);
 }
diff --git a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.h b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.h
index e70c0f9..fa237db 100644
--- a/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.h
+++ b/chrome/browser/android/webapps/add_to_homescreen_data_fetcher.h
@@ -86,7 +86,6 @@
 
   // Accessors, etc.
   void set_weak_observer(Observer* observer) { weak_observer_ = observer; }
-  bool is_ready() const { return is_ready_; }
   const SkBitmap& badge_icon() const { return badge_icon_; }
   const SkBitmap& primary_icon() const { return primary_icon_; }
   ShortcutInfo& shortcut_info() { return shortcut_info_; }
@@ -146,7 +145,6 @@
   bool is_waiting_for_web_application_info_;
   bool is_installable_check_complete_;
   bool is_icon_saved_;
-  bool is_ready_;
 
   DISALLOW_COPY_AND_ASSIGN(AddToHomescreenDataFetcher);
 };
diff --git a/chrome/browser/android/webapps/add_to_homescreen_manager.cc b/chrome/browser/android/webapps/add_to_homescreen_manager.cc
index 7ee2f60e..f5e8ecd 100644
--- a/chrome/browser/android/webapps/add_to_homescreen_manager.cc
+++ b/chrome/browser/android/webapps/add_to_homescreen_manager.cc
@@ -42,8 +42,7 @@
 }
 
 AddToHomescreenManager::AddToHomescreenManager(JNIEnv* env, jobject obj)
-    : add_shortcut_pending_(false),
-      is_webapk_compatible_(false) {
+    : is_webapk_compatible_(false) {
   java_ref_.Reset(env, obj);
 }
 
@@ -61,18 +60,24 @@
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jstring>& j_user_title) {
-  add_shortcut_pending_ = true;
+  content::WebContents* web_contents = data_fetcher_->web_contents();
+  if (!web_contents)
+    return;
 
   base::string16 user_title =
       base::android::ConvertJavaStringToUTF16(env, j_user_title);
   if (!user_title.empty())
     data_fetcher_->shortcut_info().user_title = user_title;
 
-  if (data_fetcher_->is_ready()) {
-    // If the fetcher isn't ready yet, the shortcut will be added when it is
-    // via OnDataAvailable();
-    AddShortcut(data_fetcher_->shortcut_info(), data_fetcher_->primary_icon());
-  }
+  RecordAddToHomescreen();
+  ShortcutHelper::AddToLauncherWithSkBitmap(web_contents,
+                                            data_fetcher_->shortcut_info(),
+                                            data_fetcher_->primary_icon());
+
+  // Fire the appinstalled event.
+  banners::AppBannerManagerAndroid* app_banner_manager =
+      banners::AppBannerManagerAndroid::FromWebContents(web_contents);
+  app_banner_manager->OnInstall();
 }
 
 void AddToHomescreenManager::Start(content::WebContents* web_contents) {
@@ -105,26 +110,6 @@
   Java_AddToHomescreenManager_showDialog(env, java_ref_);
 }
 
-void AddToHomescreenManager::AddShortcut(const ShortcutInfo& info,
-                                         const SkBitmap& icon) {
-  DCHECK(add_shortcut_pending_);
-  if (!add_shortcut_pending_)
-    return;
-  add_shortcut_pending_ = false;
-
-  content::WebContents* web_contents = data_fetcher_->web_contents();
-  if (!web_contents)
-    return;
-
-  RecordAddToHomescreen();
-  ShortcutHelper::AddToLauncherWithSkBitmap(web_contents, info, icon);
-
-  // Fire the appinstalled event.
-  banners::AppBannerManagerAndroid* app_banner_manager =
-      banners::AppBannerManagerAndroid::FromWebContents(web_contents);
-  app_banner_manager->OnInstall();
-}
-
 void AddToHomescreenManager::RecordAddToHomescreen() {
   // Record that the shortcut has been added, so no banners will be shown
   // for this app.
@@ -181,9 +166,6 @@
 
   JNIEnv* env = base::android::AttachCurrentThread();
   Java_AddToHomescreenManager_onReadyToAdd(env, java_ref_, java_bitmap);
-
-  if (add_shortcut_pending_)
-    AddShortcut(info, primary_icon);
 }
 
 void AddToHomescreenManager::CreateInfoBarForWebApk(
diff --git a/chrome/browser/android/webapps/add_to_homescreen_manager.h b/chrome/browser/android/webapps/add_to_homescreen_manager.h
index 36de2b12..3d7af4d 100644
--- a/chrome/browser/android/webapps/add_to_homescreen_manager.h
+++ b/chrome/browser/android/webapps/add_to_homescreen_manager.h
@@ -47,10 +47,6 @@
   void ShowDialog();
 
   // Called only when the AddToHomescreenDataFetcher has retrieved all of the
-  // data needed to add the shortcut.
-  void AddShortcut(const ShortcutInfo& info, const SkBitmap& icon);
-
-  // Called only when the AddToHomescreenDataFetcher has retrieved all of the
   // data needed to install a WebAPK.
   void CreateInfoBarForWebApk(const ShortcutInfo& info,
                               const SkBitmap& primary_icon,
@@ -71,10 +67,6 @@
   // Points to the Java object.
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
 
-  // Whether the user has requested that a shortcut be added while a fetch was
-  // in progress.
-  bool add_shortcut_pending_;
-
   // Whether the site is WebAPK-compatible.
   bool is_webapk_compatible_;
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index df18b88..ed80234 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -1661,6 +1661,9 @@
       *base::CommandLine::ForCurrentProcess();
 
   static const char* const kCommonSwitchNames[] = {
+#if BUILDFLAG(ENABLE_OOP_HEAP_PROFILING)
+    switches::kMemlogPipe,
+#endif
     switches::kUserAgent,
     switches::kUserDataDir,  // Make logs go to the right file.
   };
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index bee6bdb1..0c2e3f2 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1069,6 +1069,8 @@
     "net/wake_on_wifi_connection_observer.h",
     "net/wake_on_wifi_manager.cc",
     "net/wake_on_wifi_manager.h",
+    "night_light/night_light_client.cc",
+    "night_light/night_light_client.h",
     "note_taking_helper.cc",
     "note_taking_helper.h",
     "options/cert_library.cc",
@@ -1727,6 +1729,7 @@
     "net/network_throttling_observer_unittest.cc",
     "net/tether_notification_presenter_unittest.cc",
     "net/wake_on_wifi_manager_unittest.cc",
+    "night_light/night_light_client_unittest.cc",
     "note_taking_helper_unittest.cc",
     "options/network_property_ui_data_unittest.cc",
     "ownership/fake_owner_settings_service.cc",
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 01148fcd..136cc7c 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "ash/ash_switches.h"
 #include "ash/shell.h"
 #include "ash/sticky_keys/sticky_keys_controller.h"
 #include "base/bind.h"
@@ -70,6 +71,7 @@
 #include "chrome/browser/chromeos/net/network_pref_state_observer.h"
 #include "chrome/browser/chromeos/net/network_throttling_observer.h"
 #include "chrome/browser/chromeos/net/wake_on_wifi_manager.h"
+#include "chrome/browser/chromeos/night_light/night_light_client.h"
 #include "chrome/browser/chromeos/note_taking_helper.h"
 #include "chrome/browser/chromeos/options/cert_library.h"
 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h"
@@ -988,6 +990,13 @@
   // fetch of the initial CrosSettings DeviceRebootOnShutdown policy.
   shutdown_policy_forwarder_ = base::MakeUnique<ShutdownPolicyForwarder>();
 
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          ash::switches::kAshEnableNightLight)) {
+    night_light_client_ = base::MakeUnique<NightLightClient>(
+        g_browser_process->system_request_context());
+    night_light_client_->Start();
+  }
+
   ChromeBrowserMainPartsLinux::PostBrowserStart();
 }
 
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.h b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
index e14676b..e002708c 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.h
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
@@ -14,6 +14,8 @@
 #include "chrome/browser/memory/memory_kills_monitor.h"
 #include "chromeos/system/version_loader.h"
 
+class NightLightClient;
+
 namespace lock_screen_apps {
 class StateController;
 }
@@ -111,6 +113,8 @@
   std::unique_ptr<lock_screen_apps::StateController>
       lock_screen_apps_state_controller_;
 
+  std::unique_ptr<NightLightClient> night_light_client_;
+
   DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainPartsChromeos);
 };
 
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
index d3be1d3..2c2eff8 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
@@ -62,19 +62,6 @@
 
 namespace {
 
-InputMethodManagerImpl* g_instance = nullptr;
-
-ash::mojom::ImeInfo GetAshImeInfo(const InputMethodDescriptor& ime,
-                                  const InputMethodUtil& util) {
-  ash::mojom::ImeInfo info;
-  info.id = ime.id();
-  info.name = util.GetInputMethodLongName(ime);
-  info.medium_name = util.GetInputMethodMediumName(ime);
-  info.short_name = util.GetInputMethodShortName(ime);
-  info.third_party = extension_ime_util::IsExtensionIME(ime.id());
-  return info;
-}
-
 enum InputMethodCategory {
   INPUT_METHOD_CATEGORY_UNKNOWN = 0,
   INPUT_METHOD_CATEGORY_XKB,  // XKB input methods
@@ -962,25 +949,14 @@
       component_extension_ime_manager_->GetAllIMEAsInputMethodDescriptor();
   util_.ResetInputMethods(descriptors);
   chromeos::UserAddingScreen::Get()->AddObserver(this);
-
-  DCHECK(!g_instance);
-  g_instance = this;
 }
 
 InputMethodManagerImpl::~InputMethodManagerImpl() {
-  DCHECK_EQ(g_instance, this);
-  g_instance = nullptr;
-
   if (candidate_window_controller_.get())
     candidate_window_controller_->RemoveObserver(this);
   chromeos::UserAddingScreen::Get()->RemoveObserver(this);
 }
 
-// static
-InputMethodManagerImpl* InputMethodManagerImpl::Get() {
-  return g_instance;
-}
-
 void InputMethodManagerImpl::RecordInputMethodUsage(
     const std::string& input_method_id) {
   UMA_HISTOGRAM_ENUMERATION("InputMethod.Category",
@@ -1369,56 +1345,5 @@
   return base::FeatureList::IsEnabled(features::kEHVInputOnImeMenu);
 }
 
-ash::mojom::ImeInfo InputMethodManagerImpl::GetCurrentIme() const {
-  if (!state_)
-    return ash::mojom::ImeInfo();
-
-  InputMethodDescriptor ime = state_->GetCurrentInputMethod();
-  ash::mojom::ImeInfo info = GetAshImeInfo(ime, util_);
-  info.selected = true;
-  return info;
-}
-
-std::vector<ash::mojom::ImeInfo> InputMethodManagerImpl::GetAvailableImes()
-    const {
-  if (!state_)
-    return std::vector<ash::mojom::ImeInfo>();
-
-  std::vector<ash::mojom::ImeInfo> imes;
-  std::string current_ime_id = state_->GetCurrentInputMethod().id();
-  std::unique_ptr<InputMethodDescriptors> descriptors =
-      state_->GetActiveInputMethods();
-  for (const InputMethodDescriptor& descriptor : *descriptors) {
-    ash::mojom::ImeInfo info = GetAshImeInfo(descriptor, util_);
-    info.selected = descriptor.id() == current_ime_id;
-    imes.push_back(info);
-  }
-  return imes;
-}
-
-bool InputMethodManagerImpl::IsImeManaged() const {
-  if (!state_)
-    return false;
-
-  // Having a non-empty "allowed" list indicates that IMEs are managed.
-  return !state_->GetAllowedInputMethods().empty();
-}
-
-std::vector<ash::mojom::ImeMenuItem>
-InputMethodManagerImpl::GetCurrentImeMenuItems() const {
-  std::vector<ash::mojom::ImeMenuItem> items;
-  ui::ime::InputMethodMenuItemList menu_list =
-      ui::ime::InputMethodMenuManager::GetInstance()
-          ->GetCurrentInputMethodMenuItemList();
-  for (size_t i = 0; i < menu_list.size(); ++i) {
-    ash::mojom::ImeMenuItem property;
-    property.key = menu_list[i].key;
-    property.label = base::UTF8ToUTF16(menu_list[i].label);
-    property.checked = menu_list[i].is_selection_item_checked;
-    items.push_back(property);
-  }
-  return items;
-}
-
 }  // namespace input_method
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.h b/chrome/browser/chromeos/input_method/input_method_manager_impl.h
index db5247b..dfd8fdc4 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.h
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.h
@@ -12,7 +12,6 @@
 #include <string>
 #include <vector>
 
-#include "ash/ime/ime_controller.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "base/threading/thread_checker.h"
@@ -36,9 +35,7 @@
 class ImeKeyboard;
 
 // The implementation of InputMethodManager.
-// TODO(jamescook): Replace ash::ImeController with mojo interface.
 class InputMethodManagerImpl : public InputMethodManager,
-                               public ash::ImeController,
                                public CandidateWindowController::Observer,
                                public UserAddingScreen::Observer {
  public:
@@ -167,8 +164,6 @@
                          bool enable_extension_loading);
   ~InputMethodManagerImpl() override;
 
-  static InputMethodManagerImpl* Get();
-
   // Receives notification of an InputMethodManager::UISessionState transition.
   void SetUISessionState(UISessionState new_ui_session);
 
@@ -196,12 +191,6 @@
   void OverrideKeyboardUrlRef(const std::string& keyset) override;
   bool IsEmojiHandwritingVoiceOnImeMenuEnabled() override;
 
-  // ash::ImeController:
-  ash::mojom::ImeInfo GetCurrentIme() const override;
-  std::vector<ash::mojom::ImeInfo> GetAvailableImes() const override;
-  bool IsImeManaged() const override;
-  std::vector<ash::mojom::ImeMenuItem> GetCurrentImeMenuItems() const override;
-
   // chromeos::UserAddingScreen:
   void OnUserAddingStarted() override;
   void OnUserAddingFinished() override;
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc b/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc
index a74dc0d..3ce9aec 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc
@@ -10,6 +10,8 @@
 #include <memory>
 #include <utility>
 
+#include "ash/ime/ime_controller.h"
+#include "ash/shell.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/compiler_specific.h"
@@ -20,6 +22,7 @@
 #include "chrome/browser/chromeos/input_method/mock_candidate_window_controller.h"
 #include "chrome/browser/chromeos/input_method/mock_input_method_engine.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/ash/ime_controller_client.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
@@ -66,6 +69,14 @@
   return extension_ime_util::GetInputMethodIDByEngineID(id);
 }
 
+std::string GetCurrentImeIdFromAsh() {
+  return ash::Shell::Get()->ime_controller()->current_ime().id;
+}
+
+size_t GetAvailableImeCountFromAsh() {
+  return ash::Shell::Get()->ime_controller()->available_imes().size();
+}
+
 class TestObserver : public InputMethodManager::Observer,
                      public ui::ime::InputMethodMenuManager::Observer {
  public:
@@ -1560,5 +1571,45 @@
                                    ImeIdFromEngineId("xkb:fr::fra")));
 }
 
+// Verifies that the combination of InputMethodManagerImpl and
+// ImeControllerClient sends the correct data to ash.
+TEST_F(InputMethodManagerImplTest, IntegrationWithAsh) {
+  ImeControllerClient ime_controller_client(manager_.get());
+
+  InitComponentExtension();
+  manager_->SetUISessionState(InputMethodManager::STATE_BROWSER_SCREEN);
+  std::vector<std::string> ids;
+  ids.push_back(ImeIdFromEngineId("xkb:us:dvorak:eng"));
+  ids.push_back(ImeIdFromEngineId(kExt2Engine2Id));
+  ids.push_back(ImeIdFromEngineId(kExt2Engine1Id));
+  EXPECT_TRUE(manager_->GetActiveIMEState()->ReplaceEnabledInputMethods(ids));
+
+  EXPECT_EQ(3u, GetAvailableImeCountFromAsh());
+  EXPECT_EQ(ImeIdFromEngineId(ids[0]), GetCurrentImeIdFromAsh());
+
+  // Switch to Mozc.
+  manager_->GetActiveIMEState()->SwitchToNextInputMethod();
+  EXPECT_EQ(ImeIdFromEngineId(ids[1]), GetCurrentImeIdFromAsh());
+
+  // Lock screen
+  scoped_refptr<input_method::InputMethodManager::State> saved_ime_state =
+      manager_->GetActiveIMEState();
+  manager_->SetState(saved_ime_state->Clone());
+  manager_->GetActiveIMEState()->EnableLockScreenLayouts();
+  manager_->SetUISessionState(InputMethodManager::STATE_LOCK_SCREEN);
+  EXPECT_EQ(2u, GetAvailableImeCountFromAsh());  // Qwerty+Dvorak.
+  EXPECT_EQ(ImeIdFromEngineId("xkb:us:dvorak:eng"), GetCurrentImeIdFromAsh());
+
+  manager_->GetActiveIMEState()->SwitchToNextInputMethod();
+  EXPECT_EQ(ImeIdFromEngineId("xkb:us::eng"),  // The hardware keyboard layout.
+            GetCurrentImeIdFromAsh());
+
+  // Unlock screen. The original state, pinyin-dv, is restored.
+  manager_->SetState(saved_ime_state);
+  manager_->SetUISessionState(InputMethodManager::STATE_BROWSER_SCREEN);
+  EXPECT_EQ(3u, GetAvailableImeCountFromAsh());  // Dvorak and 2 IMEs.
+  EXPECT_EQ(ImeIdFromEngineId(ids[1]), GetCurrentImeIdFromAsh());
+}
+
 }  // namespace input_method
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.cc b/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.cc
index 1b49fe0e..599314a 100644
--- a/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.cc
+++ b/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/memory/ptr_util.h"
 #include "ui/base/ime/chromeos/input_method_util.h"
 
 namespace chromeos {
@@ -24,12 +25,8 @@
 
 std::unique_ptr<InputMethodDescriptors>
 MockInputMethodManagerImpl::State::GetActiveInputMethods() const {
-  std::unique_ptr<InputMethodDescriptors> result;
-#if _LIBCPP_STD_VER > 11
-  result = std::make_unique<InputMethodDescriptors>();
-#else
-  result.reset(new InputMethodDescriptors);
-#endif
+  std::unique_ptr<InputMethodDescriptors> result =
+      base::MakeUnique<InputMethodDescriptors>();
   result->push_back(InputMethodUtil::GetFallbackInputMethodDescriptor());
   return result;
 }
@@ -64,24 +61,29 @@
 MockInputMethodManagerImpl::State::~State() {}
 
 MockInputMethodManagerImpl::MockInputMethodManagerImpl()
-    : state_(new State(this)),
-      add_observer_count_(0),
-      remove_observer_count_(0),
-      util_(new InputMethodUtil(&delegate_)),
-      mod3_used_(false) {}
+    : state_(new State(this)), util_(new InputMethodUtil(&delegate_)) {}
 
-MockInputMethodManagerImpl::~MockInputMethodManagerImpl() {}
+MockInputMethodManagerImpl::~MockInputMethodManagerImpl() = default;
 
 void MockInputMethodManagerImpl::AddObserver(
     InputMethodManager::Observer* observer) {
   ++add_observer_count_;
 }
 
+void MockInputMethodManagerImpl::AddImeMenuObserver(ImeMenuObserver* observer) {
+  ++add_menu_observer_count_;
+}
+
 void MockInputMethodManagerImpl::RemoveObserver(
     InputMethodManager::Observer* observer) {
   ++remove_observer_count_;
 }
 
+void MockInputMethodManagerImpl::RemoveImeMenuObserver(
+    ImeMenuObserver* observer) {
+  ++remove_menu_observer_count_;
+}
+
 std::unique_ptr<InputMethodDescriptors>
 MockInputMethodManagerImpl::GetSupportedInputMethods() const {
   std::unique_ptr<InputMethodDescriptors> result;
diff --git a/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.h b/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.h
index 5e6f61a..31516da 100644
--- a/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.h
+++ b/chrome/browser/chromeos/input_method/mock_input_method_manager_impl.h
@@ -51,7 +51,9 @@
 
   // MockInputMethodManager:
   void AddObserver(InputMethodManager::Observer* observer) override;
+  void AddImeMenuObserver(ImeMenuObserver* observer) override;
   void RemoveObserver(InputMethodManager::Observer* observer) override;
+  void RemoveImeMenuObserver(ImeMenuObserver* observer) override;
   std::unique_ptr<InputMethodDescriptors> GetSupportedInputMethods()
       const override;
   bool IsISOLevel5ShiftUsedByCurrentInputMethod() const override;
@@ -78,17 +80,22 @@
   int add_observer_count() const { return add_observer_count_; }
   int remove_observer_count() const { return remove_observer_count_; }
 
+  int add_menu_observer_count() const { return add_menu_observer_count_; }
+  int remove_menu_observer_count() const { return remove_menu_observer_count_; }
+
  protected:
   scoped_refptr<State> state_;
 
  private:
   // TODO(yusukes): Add more variables for counting the numbers of the API calls
-  int add_observer_count_;
-  int remove_observer_count_;
+  int add_observer_count_ = 0;
+  int remove_observer_count_ = 0;
+  int add_menu_observer_count_ = 0;
+  int remove_menu_observer_count_ = 0;
   FakeInputMethodDelegate delegate_;  // used by util_
   std::unique_ptr<InputMethodUtil> util_;
   FakeImeKeyboard keyboard_;
-  bool mod3_used_;
+  bool mod3_used_ = false;
   std::unique_ptr<ComponentExtensionIMEManager> comp_ime_manager_;
 
   DISALLOW_COPY_AND_ASSIGN(MockInputMethodManagerImpl);
diff --git a/chrome/browser/chromeos/login/lock_screen_utils.cc b/chrome/browser/chromeos/login/lock_screen_utils.cc
index 6b681bd..c9d856f 100644
--- a/chrome/browser/chromeos/login/lock_screen_utils.cc
+++ b/chrome/browser/chromeos/login/lock_screen_utils.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/language_preferences.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/ui/ash/ime_controller_client.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/signin/core/account_id/account_id.h"
@@ -114,6 +115,16 @@
   chromeos::input_method::InputMethodManager* imm =
       chromeos::input_method::InputMethodManager::Get();
   imm->GetActiveIMEState()->SetAllowedInputMethods(allowed_input_methods);
+  ImeControllerClient::Get()->SetImesManagedByPolicy(true);
+}
+
+void StopEnforcingPolicyInputMethods() {
+  // Empty means all input methods are allowed
+  std::vector<std::string> allowed_input_methods;
+  chromeos::input_method::InputMethodManager* imm =
+      chromeos::input_method::InputMethodManager::Get();
+  imm->GetActiveIMEState()->SetAllowedInputMethods(allowed_input_methods);
+  ImeControllerClient::Get()->SetImesManagedByPolicy(false);
 }
 
 void SetKeyboardSettings(const AccountId& account_id) {
diff --git a/chrome/browser/chromeos/login/lock_screen_utils.h b/chrome/browser/chromeos/login/lock_screen_utils.h
index 1fe58cb..bf481bca 100644
--- a/chrome/browser/chromeos/login/lock_screen_utils.h
+++ b/chrome/browser/chromeos/login/lock_screen_utils.h
@@ -29,6 +29,9 @@
 // by policy.
 void EnforcePolicyInputMethods(std::string user_input_method);
 
+// Remove any policy limitations on allowed IMEs.
+void StopEnforcingPolicyInputMethods();
+
 // Update the keyboard settings for |account_id|.
 void SetKeyboardSettings(const AccountId& account_id);
 
diff --git a/chrome/browser/chromeos/login/ui/login_feedback.cc b/chrome/browser/chromeos/login/ui/login_feedback.cc
index 6b216df..3906002 100644
--- a/chrome/browser/chromeos/login/ui/login_feedback.cc
+++ b/chrome/browser/chromeos/login/ui/login_feedback.cc
@@ -253,7 +253,7 @@
   extensions::FeedbackPrivateAPI* api =
       extensions::FeedbackPrivateAPI::GetFactoryInstance()->Get(profile_);
   api->RequestFeedbackForFlow(
-      description_, "Login", GURL(),
+      description_, "Login", std::string(), GURL(),
       extensions::api::feedback_private::FeedbackFlow::FEEDBACK_FLOW_LOGIN);
 
   // Make sure there is a feedback app window opened.
diff --git a/chrome/browser/chromeos/net/network_portal_detector_impl.cc b/chrome/browser/chromeos/net/network_portal_detector_impl.cc
index d16ab571..37b92dd5 100644
--- a/chrome/browser/chromeos/net/network_portal_detector_impl.cc
+++ b/chrome/browser/chromeos/net/network_portal_detector_impl.cc
@@ -498,7 +498,18 @@
     attempt_completed_report_.Report();
   }
 
-  state_ = STATE_IDLE;
+  // If Chrome portal detection successfully returns portal state, mark the
+  // state so that Chrome won't schedule detection actively by self.
+  // The exception is when portal side session expires, shill doesn't report
+  // network connection state changed from online to portal. Thus we enable
+  // Chrome's detection by still marking |state_| to STATE_IDLE.
+  if (result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL &&
+      response_code == 200 &&
+      (!network || network->connection_state() != shill::kStateOnline)) {
+    state_ = STATE_BEHIND_PORTAL_IDLE;
+  } else {
+    state_ = STATE_IDLE;
+  }
   attempt_timeout_.Cancel();
 
   CaptivePortalState state;
diff --git a/chrome/browser/chromeos/net/network_portal_detector_impl.h b/chrome/browser/chromeos/net/network_portal_detector_impl.h
index 042f6bd..c28afefd 100644
--- a/chrome/browser/chromeos/net/network_portal_detector_impl.h
+++ b/chrome/browser/chromeos/net/network_portal_detector_impl.h
@@ -84,6 +84,8 @@
     STATE_PORTAL_CHECK_PENDING,
     // Portal check is in progress.
     STATE_CHECKING_FOR_PORTAL,
+    // No portal check when successfully behind portal.
+    STATE_BEHIND_PORTAL_IDLE,
   };
 
   struct DetectionAttemptCompletedReport {
@@ -170,6 +172,9 @@
   bool is_checking_for_portal() const {
     return state_ == STATE_CHECKING_FOR_PORTAL;
   }
+  bool is_behind_portal_idle() const {
+    return state_ == STATE_BEHIND_PORTAL_IDLE;
+  }
 
   int same_detection_result_count_for_testing() const {
     return same_detection_result_count_;
diff --git a/chrome/browser/chromeos/net/network_portal_detector_impl_unittest.cc b/chrome/browser/chromeos/net/network_portal_detector_impl_unittest.cc
index b64467b..c02149e 100644
--- a/chrome/browser/chromeos/net/network_portal_detector_impl_unittest.cc
+++ b/chrome/browser/chromeos/net/network_portal_detector_impl_unittest.cc
@@ -206,6 +206,9 @@
   bool is_state_checking_for_portal() {
     return (NetworkPortalDetectorImpl::STATE_CHECKING_FOR_PORTAL == state());
   }
+  bool is_state_behind_portal_idle() {
+    return (NetworkPortalDetectorImpl::STATE_BEHIND_PORTAL_IDLE == state());
+  }
 
   const base::TimeDelta& next_attempt_delay() {
     return network_portal_detector()->next_attempt_delay_for_testing();
@@ -508,7 +511,7 @@
 
   CompleteURLFetch(net::OK, 200, nullptr);
 
-  ASSERT_FALSE(is_state_idle());
+  ASSERT_TRUE(is_state_behind_portal_idle());
   CheckPortalState(
       NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL, 200, kStubWireless1);
 
@@ -526,7 +529,7 @@
 
   CompleteURLFetch(net::OK, 200, nullptr);
 
-  ASSERT_FALSE(is_state_idle());
+  ASSERT_TRUE(is_state_behind_portal_idle());
   CheckPortalState(
       NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL, 200, kStubWireless1);
 
diff --git a/chrome/browser/chromeos/night_light/night_light_client.cc b/chrome/browser/chromeos/night_light/night_light_client.cc
new file mode 100644
index 0000000..0b8941b
--- /dev/null
+++ b/chrome/browser/chromeos/night_light/night_light_client.cc
@@ -0,0 +1,112 @@
+// 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/chromeos/night_light/night_light_client.h"
+
+#include "ash/public/interfaces/constants.mojom.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace {
+
+// Delay to wait for a response to our geolocation request, if we get a response
+// after which, we will consider the request a failure.
+constexpr base::TimeDelta kGeolocationRequestTimeout =
+    base::TimeDelta::FromSeconds(60);
+
+// Minimum delay to wait to fire a new request after a previous one failing.
+constexpr base::TimeDelta kMinimumDelayAfterFailure =
+    base::TimeDelta::FromSeconds(60);
+
+// Delay to wait to fire a new request after a previous one succeeding.
+constexpr base::TimeDelta kNextRequestDelayAfterSuccess =
+    base::TimeDelta::FromDays(1);
+
+}  // namespace
+
+NightLightClient::NightLightClient(
+    net::URLRequestContextGetter* url_context_getter)
+    : provider_(
+          url_context_getter,
+          chromeos::SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
+      binding_(this),
+      backoff_delay_(kMinimumDelayAfterFailure) {}
+
+NightLightClient::~NightLightClient() {}
+
+void NightLightClient::Start() {
+  if (!night_light_controller_) {
+    service_manager::Connector* connector =
+        content::ServiceManagerConnection::GetForProcess()->GetConnector();
+    connector->BindInterface(ash::mojom::kServiceName,
+                             &night_light_controller_);
+  }
+  ash::mojom::NightLightClientPtr client;
+  binding_.Bind(mojo::MakeRequest(&client));
+  night_light_controller_->SetClient(std::move(client));
+}
+
+void NightLightClient::OnScheduleTypeChanged(
+    ash::mojom::NightLightController::ScheduleType new_type) {
+  if (new_type ==
+      ash::mojom::NightLightController::ScheduleType::kSunsetToSunrise) {
+    // Schedule an immediate request.
+    using_geoposition_ = true;
+    ScheduleNextRequest(base::TimeDelta::FromSeconds(0));
+  } else {
+    using_geoposition_ = false;
+    timer_.Stop();
+  }
+}
+
+void NightLightClient::SetNightLightControllerPtrForTesting(
+    ash::mojom::NightLightControllerPtr controller) {
+  night_light_controller_ = std::move(controller);
+}
+
+void NightLightClient::FlushNightLightControllerForTesting() {
+  night_light_controller_.FlushForTesting();
+}
+
+void NightLightClient::OnGeoposition(const chromeos::Geoposition& position,
+                                     bool server_error,
+                                     const base::TimeDelta elapsed) {
+  if (!using_geoposition_) {
+    // A response might arrive after the schedule type is no longer "sunset to
+    // sunrise", which means we should not push any positions to the
+    // NightLightController.
+    return;
+  }
+
+  if (server_error || !position.Valid() ||
+      elapsed > kGeolocationRequestTimeout) {
+    // Don't send invalid positions to ash.
+    // On failure, we schedule another request after the current backoff delay.
+    ScheduleNextRequest(backoff_delay_);
+
+    // If another failure occurs next, our backoff delay should double.
+    backoff_delay_ *= 2;
+    return;
+  }
+
+  night_light_controller_->SetCurrentGeoposition(
+      ash::mojom::SimpleGeoposition::New(position.latitude,
+                                         position.longitude));
+
+  // On success, reset the backoff delay to its minimum value, and schedule
+  // another request.
+  backoff_delay_ = kMinimumDelayAfterFailure;
+  ScheduleNextRequest(kNextRequestDelayAfterSuccess);
+}
+
+void NightLightClient::ScheduleNextRequest(base::TimeDelta delay) {
+  timer_.Start(FROM_HERE, delay, this, &NightLightClient::RequestGeoposition);
+}
+
+void NightLightClient::RequestGeoposition() {
+  provider_.RequestGeolocation(
+      kGeolocationRequestTimeout, false /* send_wifi_access_points */,
+      false /* send_cell_towers */,
+      base::Bind(&NightLightClient::OnGeoposition, base::Unretained(this)));
+}
diff --git a/chrome/browser/chromeos/night_light/night_light_client.h b/chrome/browser/chromeos/night_light/night_light_client.h
new file mode 100644
index 0000000..0f8580d
--- /dev/null
+++ b/chrome/browser/chromeos/night_light/night_light_client.h
@@ -0,0 +1,73 @@
+// 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_CHROMEOS_NIGHT_LIGHT_NIGHT_LIGHT_CLIENT_H_
+#define CHROME_BROWSER_CHROMEOS_NIGHT_LIGHT_NIGHT_LIGHT_CLIENT_H_
+
+#include "ash/public/interfaces/night_light_controller.mojom.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "chromeos/geolocation/simple_geolocation_provider.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// Periodically requests the IP-based geolocation and provides it to the
+// NightLightController running in ash.
+class NightLightClient
+    : public NON_EXPORTED_BASE(ash::mojom::NightLightClient) {
+ public:
+  explicit NightLightClient(net::URLRequestContextGetter* url_context_getter);
+  ~NightLightClient() override;
+
+  // Starts watching changes in the Night Light schedule type in order to begin
+  // periodically pushing user's IP-based geoposition to NightLightController as
+  // long as the type is set to "sunset to sunrise".
+  void Start();
+
+  // ash::mojom::NightLightClient:
+  void OnScheduleTypeChanged(
+      ash::mojom::NightLightController::ScheduleType new_type) override;
+
+  bool using_geoposition() const { return using_geoposition_; }
+
+  void SetNightLightControllerPtrForTesting(
+      ash::mojom::NightLightControllerPtr controller);
+
+  void FlushNightLightControllerForTesting();
+
+ protected:
+  void OnGeoposition(const chromeos::Geoposition& position,
+                     bool server_error,
+                     const base::TimeDelta elapsed);
+
+ private:
+  void ScheduleNextRequest(base::TimeDelta delay);
+
+  // Virtual so that it can be overriden by a fake implementation in unit tests
+  // that doesn't request actual geopositions.
+  virtual void RequestGeoposition();
+
+  // The IP-based geolocation provider.
+  chromeos::SimpleGeolocationProvider provider_;
+
+  ash::mojom::NightLightControllerPtr night_light_controller_;
+  mojo::Binding<ash::mojom::NightLightClient> binding_;
+
+  // Delay after which a new request is retried after a failed one.
+  base::TimeDelta backoff_delay_;
+
+  base::OneShotTimer timer_;
+
+  // True as long as the schedule type is set to "sunset to sunrise", which
+  // means this client will be retrieving the IP-based geoposition once per day.
+  bool using_geoposition_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(NightLightClient);
+};
+
+#endif  // CHROME_BROWSER_CHROMEOS_NIGHT_LIGHT_NIGHT_LIGHT_CLIENT_H_
diff --git a/chrome/browser/chromeos/night_light/night_light_client_unittest.cc b/chrome/browser/chromeos/night_light/night_light_client_unittest.cc
new file mode 100644
index 0000000..bfe124b
--- /dev/null
+++ b/chrome/browser/chromeos/night_light/night_light_client_unittest.cc
@@ -0,0 +1,147 @@
+// 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/chromeos/night_light/night_light_client.h"
+
+#include "ash/public/interfaces/night_light_controller.mojom.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using ScheduleType = ash::mojom::NightLightController::ScheduleType;
+
+// A fake implementation of NightLightController for testing.
+class FakeNightLightController : public ash::mojom::NightLightController {
+ public:
+  FakeNightLightController() : binding_(this) {}
+  ~FakeNightLightController() override = default;
+
+  int position_pushes_num() const { return position_pushes_num_; }
+
+  ash::mojom::NightLightControllerPtr CreateInterfacePtrAndBind() {
+    ash::mojom::NightLightControllerPtr ptr;
+    binding_.Bind(mojo::MakeRequest(&ptr));
+    return ptr;
+  }
+
+  // ash::mojom::NightLightController:
+  void SetCurrentGeoposition(
+      ash::mojom::SimpleGeopositionPtr position) override {
+    position_ = std::move(position);
+    ++position_pushes_num_;
+  }
+
+  void SetClient(ash::mojom::NightLightClientPtr client) override {
+    client_ = std::move(client);
+  }
+
+  void NotifyScheduleTypeChanged(ScheduleType type) {
+    client_->OnScheduleTypeChanged(type);
+    client_.FlushForTesting();
+  }
+
+ private:
+  ash::mojom::SimpleGeopositionPtr position_;
+  ash::mojom::NightLightClientPtr client_;
+  mojo::Binding<ash::mojom::NightLightController> binding_;
+
+  // The number of times a new position is pushed to this controller.
+  int position_pushes_num_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeNightLightController);
+};
+
+// A fake implementation of NightLightClient that doesn't perform any actual
+// geoposition requests.
+class FakeNightLightClient : public NightLightClient {
+ public:
+  FakeNightLightClient() : NightLightClient(nullptr /* url_context_getter */) {}
+  ~FakeNightLightClient() override = default;
+
+  void set_position_to_send(const chromeos::Geoposition& position) {
+    position_to_send_ = position;
+  }
+
+ private:
+  // night_light::NightLightClient:
+  void RequestGeoposition() override {
+    OnGeoposition(position_to_send_, false, base::TimeDelta());
+  }
+
+  // The position to send to the controller the next time OnGeoposition is
+  // invoked.
+  chromeos::Geoposition position_to_send_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeNightLightClient);
+};
+
+// Base test fixture.
+class NightLightClientTest : public testing::Test {
+ public:
+  NightLightClientTest() = default;
+  ~NightLightClientTest() override = default;
+
+  void SetUp() override {
+    client_.SetNightLightControllerPtrForTesting(
+        controller_.CreateInterfacePtrAndBind());
+    client_.Start();
+    client_.FlushNightLightControllerForTesting();
+  }
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  FakeNightLightController controller_;
+  FakeNightLightClient client_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NightLightClientTest);
+};
+
+// Test that the client is retrieving geoposition periodically only when the
+// schedule type is "sunset to sunrise".
+TEST_F(NightLightClientTest, TestClientRunningOnlyWhenSunsetToSunriseSchedule) {
+  EXPECT_FALSE(client_.using_geoposition());
+  controller_.NotifyScheduleTypeChanged(ScheduleType::kNone);
+  EXPECT_FALSE(client_.using_geoposition());
+  controller_.NotifyScheduleTypeChanged(ScheduleType::kCustom);
+  controller_.NotifyScheduleTypeChanged(ScheduleType::kSunsetToSunrise);
+  scoped_task_environment_.RunUntilIdle();
+  client_.FlushNightLightControllerForTesting();
+  EXPECT_TRUE(client_.using_geoposition());
+
+  // Client should stop retrieving geopositions when schedule type changes to
+  // something else.
+  controller_.NotifyScheduleTypeChanged(ScheduleType::kNone);
+  EXPECT_FALSE(client_.using_geoposition());
+}
+
+// Test that client only pushes valid positions.
+TEST_F(NightLightClientTest, TestPositionPushes) {
+  // Start with a valid position, and expect it to be delivered to the
+  // controller.
+  EXPECT_EQ(0, controller_.position_pushes_num());
+  chromeos::Geoposition position;
+  position.latitude = 32.0;
+  position.longitude = 31.0;
+  position.status = chromeos::Geoposition::STATUS_OK;
+  position.accuracy = 10;
+  position.timestamp = base::Time::Now();
+  client_.set_position_to_send(position);
+  controller_.NotifyScheduleTypeChanged(ScheduleType::kSunsetToSunrise);
+  scoped_task_environment_.RunUntilIdle();
+  client_.FlushNightLightControllerForTesting();
+  EXPECT_EQ(1, controller_.position_pushes_num());
+
+  // Invalid positions should not be sent.
+  position.status = chromeos::Geoposition::STATUS_TIMEOUT;
+  client_.set_position_to_send(position);
+  controller_.NotifyScheduleTypeChanged(ScheduleType::kSunsetToSunrise);
+  scoped_task_environment_.RunUntilIdle();
+  client_.FlushNightLightControllerForTesting();
+  EXPECT_EQ(1, controller_.position_pushes_num());
+}
+
+}  // namespace
diff --git a/chrome/browser/extensions/api/feedback_private/feedback_browsertest.cc b/chrome/browser/extensions/api/feedback_private/feedback_browsertest.cc
index 78d9b7b..2c5385e 100644
--- a/chrome/browser/extensions/api/feedback_private/feedback_browsertest.cc
+++ b/chrome/browser/extensions/api/feedback_private/feedback_browsertest.cc
@@ -54,10 +54,11 @@
             extensions::api::feedback_private::OnFeedbackRequested::kEventName);
   }
 
-  void StartFeedbackUI(FeedbackFlow flow) {
+  void StartFeedbackUI(FeedbackFlow flow,
+                       const std::string& extra_diagnostics) {
     base::Closure callback = base::Bind(&StopMessageLoopCallback);
     extensions::FeedbackPrivateGetStringsFunction::set_test_callback(&callback);
-    InvokeFeedbackUI(flow);
+    InvokeFeedbackUI(flow, extra_diagnostics);
     content::RunMessageLoop();
     extensions::FeedbackPrivateGetStringsFunction::set_test_callback(NULL);
   }
@@ -73,12 +74,14 @@
   }
 
  private:
-  void InvokeFeedbackUI(FeedbackFlow flow) {
+  void InvokeFeedbackUI(FeedbackFlow flow,
+                        const std::string& extra_diagnostics) {
     extensions::FeedbackPrivateAPI* api =
         extensions::FeedbackPrivateAPI::GetFactoryInstance()->Get(
             browser()->profile());
     api->RequestFeedbackForFlow("Test description", "Test tag",
-                                GURL("http://www.test.com"), flow);
+                                extra_diagnostics, GURL("http://www.test.com"),
+                                flow);
   }
 };
 
@@ -86,7 +89,7 @@
   WaitForExtensionViewsToLoad();
 
   ASSERT_TRUE(IsFeedbackAppAvailable());
-  StartFeedbackUI(FeedbackFlow::FEEDBACK_FLOW_REGULAR);
+  StartFeedbackUI(FeedbackFlow::FEEDBACK_FLOW_REGULAR, std::string());
   VerifyFeedbackAppLaunch();
 }
 
@@ -94,7 +97,7 @@
   WaitForExtensionViewsToLoad();
 
   ASSERT_TRUE(IsFeedbackAppAvailable());
-  StartFeedbackUI(FeedbackFlow::FEEDBACK_FLOW_LOGIN);
+  StartFeedbackUI(FeedbackFlow::FEEDBACK_FLOW_LOGIN, std::string());
   VerifyFeedbackAppLaunch();
 
   AppWindow* const window =
@@ -118,7 +121,7 @@
   WaitForExtensionViewsToLoad();
 
   ASSERT_TRUE(IsFeedbackAppAvailable());
-  StartFeedbackUI(FeedbackFlow::FEEDBACK_FLOW_REGULAR);
+  StartFeedbackUI(FeedbackFlow::FEEDBACK_FLOW_REGULAR, std::string());
   VerifyFeedbackAppLaunch();
 
   AppWindow* const window =
@@ -143,4 +146,37 @@
   EXPECT_TRUE(bool_result);
 }
 
+// Ensures that when extra diagnostics are provided with feedback, they are
+// injected properly in the system information.
+IN_PROC_BROWSER_TEST_F(FeedbackTest, ExtraDiagnostics) {
+  WaitForExtensionViewsToLoad();
+
+  ASSERT_TRUE(IsFeedbackAppAvailable());
+  StartFeedbackUI(FeedbackFlow::FEEDBACK_FLOW_REGULAR, "Some diagnostics");
+  VerifyFeedbackAppLaunch();
+
+  AppWindow* const window =
+      PlatformAppBrowserTest::GetFirstAppWindowForBrowser(browser());
+  ASSERT_TRUE(window);
+  content::WebContents* const content = window->web_contents();
+
+  bool bool_result = false;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+      content,
+      "domAutomationController.send("
+      "  ((function() {"
+      "      var sysInfo = feedbackInfo.systemInformation;"
+      "      for (var info in sysInfo) {"
+      "        if (sysInfo[info].key == 'EXTRA_DIAGNOSTICS' &&"
+      "            sysInfo[info].value == 'Some diagnostics') {"
+      "          return true;"
+      "        }"
+      "      }"
+      "      return false;"
+      "    })()));",
+      &bool_result));
+
+  EXPECT_TRUE(bool_result);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc b/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc
index 47ce719d..73c305f 100644
--- a/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc
+++ b/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc
@@ -114,6 +114,7 @@
 void FeedbackPrivateAPI::RequestFeedback(
     const std::string& description_template,
     const std::string& category_tag,
+    const std::string& extra_diagnostics,
     const GURL& page_url) {
 #if defined(OS_WIN)
   // Show prompt for Software Removal Tool if the Reporter component has found
@@ -121,18 +122,20 @@
   if (base::FeatureList::IsEnabled(kSrtPromptOnFeedbackForm) &&
       safe_browsing::ReporterFoundUws() &&
       !safe_browsing::UserHasRunCleaner()) {
-    RequestFeedbackForFlow(description_template, category_tag, page_url,
+    RequestFeedbackForFlow(description_template, category_tag,
+                           extra_diagnostics, page_url,
                            FeedbackFlow::FEEDBACK_FLOW_SHOWSRTPROMPT);
     return;
   }
 #endif
-  RequestFeedbackForFlow(description_template, category_tag, page_url,
-                         FeedbackFlow::FEEDBACK_FLOW_REGULAR);
+  RequestFeedbackForFlow(description_template, category_tag, extra_diagnostics,
+                         page_url, FeedbackFlow::FEEDBACK_FLOW_REGULAR);
 }
 
 void FeedbackPrivateAPI::RequestFeedbackForFlow(
     const std::string& description_template,
     const std::string& category_tag,
+    const std::string& extra_diagnostics,
     const GURL& page_url,
     api::feedback_private::FeedbackFlow flow) {
   if (browser_context_ && EventRouter::Get(browser_context_)) {
@@ -140,10 +143,19 @@
     info.description = description_template;
     info.category_tag = base::MakeUnique<std::string>(category_tag);
     info.page_url = base::MakeUnique<std::string>(page_url.spec());
-    info.system_information.reset(new SystemInformationList);
+    info.system_information = base::MakeUnique<SystemInformationList>();
+
+    // Any extra diagnostics information should be added to the sys info.
+    if (!extra_diagnostics.empty()) {
+      SystemInformation extra_info;
+      extra_info.key = "EXTRA_DIAGNOSTICS";
+      extra_info.value = extra_diagnostics;
+      info.system_information->emplace_back(std::move(extra_info));
+    }
+
     // The manager is only available if tracing is enabled.
     if (TracingManager* manager = TracingManager::Get()) {
-      info.trace_id.reset(new int(manager->RequestTrace()));
+      info.trace_id = base::MakeUnique<int>(manager->RequestTrace());
     }
     info.flow = flow;
 #if defined(OS_MACOSX)
diff --git a/chrome/browser/extensions/api/feedback_private/feedback_private_api.h b/chrome/browser/extensions/api/feedback_private/feedback_private_api.h
index b993acb..e39d263 100644
--- a/chrome/browser/extensions/api/feedback_private/feedback_private_api.h
+++ b/chrome/browser/extensions/api/feedback_private/feedback_private_api.h
@@ -34,10 +34,12 @@
 
   void RequestFeedback(const std::string& description_template,
                        const std::string& category_tag,
+                       const std::string& extra_diagnostics,
                        const GURL& page_url);
 
   void RequestFeedbackForFlow(const std::string& description_template,
                               const std::string& category_tag,
+                              const std::string& extra_diagnostics,
                               const GURL& page_url,
                               api::feedback_private::FeedbackFlow flow);
 
diff --git a/chrome/browser/extensions/extension_service_test_base.cc b/chrome/browser/extensions/extension_service_test_base.cc
index 2e3b693..45ed898 100644
--- a/chrome/browser/extensions/extension_service_test_base.cc
+++ b/chrome/browser/extensions/extension_service_test_base.cc
@@ -14,6 +14,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "chrome/browser/after_startup_task_utils.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/extensions/extension_error_reporter.h"
 #include "chrome/browser/extensions/extension_garbage_collector_factory.h"
@@ -90,6 +91,11 @@
     return;
   }
   data_dir_ = test_data_dir.AppendASCII("extensions");
+
+  // The extension subsystem posts logging tasks to be done after browser
+  // startup. There's no StartupObserver as there normally would be since we're
+  // in a unit test, so we have to explicitly note tasks should be processed.
+  AfterStartupTaskUtils::SetBrowserStartupIsCompleteForTesting();
 }
 
 ExtensionServiceTestBase::~ExtensionServiceTestBase() {
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 04fcdd5a..189b8b41 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -41,7 +41,6 @@
 #include "base/values.h"
 #include "base/version.h"
 #include "build/build_config.h"
-#include "chrome/browser/after_startup_task_utils.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/blacklist.h"
@@ -522,13 +521,7 @@
 class ExtensionServiceTest
     : public extensions::ExtensionServiceTestWithInstall {
  public:
-  ExtensionServiceTest() {
-    // The extension subsystem posts logging tasks to be done after
-    // browser startup. There's no StartupObserver as there normally
-    // would be since we're in a unit test, so we have to explicitly
-    // note tasks should be processed.
-    AfterStartupTaskUtils::SetBrowserStartupIsCompleteForTesting();
-  }
+  ExtensionServiceTest() = default;
 
   MockExternalProvider* AddMockExternalProvider(Manifest::Location location) {
     auto provider = base::MakeUnique<MockExternalProvider>(service(), location);
diff --git a/chrome/browser/feedback/show_feedback_page.cc b/chrome/browser/feedback/show_feedback_page.cc
index b9967ab..f2f9466 100644
--- a/chrome/browser/feedback/show_feedback_page.cc
+++ b/chrome/browser/feedback/show_feedback_page.cc
@@ -21,7 +21,8 @@
 void ShowFeedbackPage(Browser* browser,
                       FeedbackSource source,
                       const std::string& description_template,
-                      const std::string& category_tag) {
+                      const std::string& category_tag,
+                      const std::string& extra_diagnostics) {
   GURL page_url;
   if (browser) {
     page_url = GetTargetTabUrl(browser->session_id().id(),
@@ -47,7 +48,7 @@
       extensions::FeedbackPrivateAPI::GetFactoryInstance()->Get(profile);
 
   api->RequestFeedbackForFlow(
-      description_template, category_tag, page_url,
+      description_template, category_tag, extra_diagnostics, page_url,
       source == kFeedbackSourceSadTabPage
           ? feedback_private::FeedbackFlow::FEEDBACK_FLOW_SADTABCRASH
           : feedback_private::FeedbackFlow::FEEDBACK_FLOW_REGULAR);
diff --git a/chrome/browser/hang_monitor/hang_crash_dump_win.cc b/chrome/browser/hang_monitor/hang_crash_dump_win.cc
index 5943438..56b9f2ee 100644
--- a/chrome/browser/hang_monitor/hang_crash_dump_win.cc
+++ b/chrome/browser/hang_monitor/hang_crash_dump_win.cc
@@ -96,31 +96,3 @@
   TerminateProcess(hprocess, content::RESULT_CODE_HUNG);
   WaitForSingleObject(hprocess, kTerminateTimeoutMS);
 }
-
-void CrashDumpForHangDebugging(HANDLE hprocess) {
-  if (hprocess == GetCurrentProcess()) {
-    typedef void (__cdecl *DumpFunction)();
-    DumpFunction request_dump = reinterpret_cast<DumpFunction>(GetProcAddress(
-        GetModuleHandle(chrome::kChromeElfDllName), "DumpProcessWithoutCrash"));
-    DCHECK(request_dump) << "Failed loading DumpProcessWithoutCrash: error " <<
-        GetLastError();
-    if (request_dump)
-      request_dump();
-  } else {
-    typedef HANDLE (__cdecl *DumpFunction)(HANDLE);
-    DumpFunction request_dump = reinterpret_cast<DumpFunction>(
-        GetProcAddress(GetModuleHandle(chrome::kChromeElfDllName),
-                       "InjectDumpForHangDebugging"));
-    DCHECK(request_dump) << "Failed loading InjectDumpForHangDebugging: error "
-                         << GetLastError();
-    if (request_dump) {
-      HANDLE remote_thread = request_dump(hprocess);
-      DCHECK(remote_thread) << "Failed creating remote thread: error " <<
-          GetLastError();
-      if (remote_thread) {
-        WaitForSingleObject(remote_thread, kGenerateDumpTimeoutMS);
-        CloseHandle(remote_thread);
-      }
-    }
-  }
-}
diff --git a/chrome/browser/hang_monitor/hang_crash_dump_win.h b/chrome/browser/hang_monitor/hang_crash_dump_win.h
index fdec2f3e..42a518b 100644
--- a/chrome/browser/hang_monitor/hang_crash_dump_win.h
+++ b/chrome/browser/hang_monitor/hang_crash_dump_win.h
@@ -17,8 +17,4 @@
     HANDLE hprocess,
     const base::StringPairs& additional_crash_keys);
 
-// TODO(yzshen): Remove when enough information is collected and the hang rate
-// of pepper/renderer processes is reduced.
-void CrashDumpForHangDebugging(HANDLE hprocess);
-
 #endif  // CHROME_BROWSER_HANG_MONITOR_HANG_CRASH_DUMP_WIN_H_
diff --git a/chrome/browser/shell_integration_win.cc b/chrome/browser/shell_integration_win.cc
index fb717d2..3836d9d 100644
--- a/chrome/browser/shell_integration_win.cc
+++ b/chrome/browser/shell_integration_win.cc
@@ -749,9 +749,9 @@
   // This needs to happen (e.g. so that the appid is fixed and the
   // run-time Chrome icon is merged with the taskbar shortcut), but it is not an
   // urgent task.
-  base::PostTaskWithTraits(FROM_HERE,
-                           {base::MayBlock(), base::TaskPriority::BACKGROUND},
-                           base::Bind(&MigrateTaskbarPinsCallback));
+  base::CreateCOMSTATaskRunnerWithTraits(
+      {base::MayBlock(), base::TaskPriority::BACKGROUND})
+      ->PostTask(FROM_HERE, base::Bind(&MigrateTaskbarPinsCallback));
 }
 
 void GetIsPinnedToTaskbarState(
diff --git a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
index a46e50e6b..f6843dbe 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
@@ -1029,7 +1029,33 @@
       std::vector<const char*>{"b", "d"}, {false, true});
 }
 
+IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
+                       NoConfiguration_AllowCreatingNewWindows) {
+  base::HistogramTester tester;
+  const char kWindowOpenPath[] = "/subresource_filter/window_open.html";
+  GURL a_url(embedded_test_server()->GetURL("a.com", kWindowOpenPath));
+  // Only configure |a_url| as a phishing URL.
+  ConfigureAsPhishingURL(a_url);
+
+  // Only necessary so we have a valid ruleset.
+  ASSERT_NO_FATAL_FAILURE(SetRulesetWithRules(std::vector<proto::UrlRule>()));
+
+  // Navigate to a_url, should not trigger the popup blocker.
+  ui_test_utils::NavigateToURL(browser(), a_url);
+  bool opened_window = false;
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  EXPECT_TRUE(content::ExecuteScriptAndExtractBool(web_contents, "openWindow()",
+                                                   &opened_window));
+  EXPECT_TRUE(opened_window);
+  EXPECT_FALSE(TabSpecificContentSettings::FromWebContents(web_contents)
+                   ->IsContentBlocked(CONTENT_SETTINGS_TYPE_POPUPS));
+}
+
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest, BlockCreatingNewWindows) {
+  Configuration config = Configuration::MakePresetForLiveRunOnPhishingSites();
+  config.activation_options.should_strengthen_popup_blocker = true;
+  ResetConfiguration(std::move(config));
   base::HistogramTester tester;
   const char kWindowOpenPath[] = "/subresource_filter/window_open.html";
   GURL a_url(embedded_test_server()->GetURL("a.com", kWindowOpenPath));
@@ -1068,6 +1094,9 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest, BlockOpenURLFromTab) {
+  Configuration config = Configuration::MakePresetForLiveRunOnPhishingSites();
+  config.activation_options.should_strengthen_popup_blocker = true;
+  ResetConfiguration(std::move(config));
   base::HistogramTester tester;
   const char kWindowOpenPath[] =
       "/subresource_filter/window_open_spoof_click.html";
diff --git a/chrome/browser/supervised_user/supervised_user_interstitial.cc b/chrome/browser/supervised_user/supervised_user_interstitial.cc
index 8765cd4..ca2da83 100644
--- a/chrome/browser/supervised_user/supervised_user_interstitial.cc
+++ b/chrome/browser/supervised_user/supervised_user_interstitial.cc
@@ -262,7 +262,8 @@
 #else
     chrome::ShowFeedbackPage(chrome::FindBrowserWithWebContents(web_contents_),
                              chrome::kFeedbackSourceSupervisedUserInterstitial,
-                             message, std::string() /* category_tag */);
+                             message, std::string() /* category_tag */,
+                             std::string() /* extra_diagnostics */);
 #endif
     return;
   }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 09addc8..598001d 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1159,6 +1159,8 @@
       "app_list/search/launcher_search/launcher_search_provider.h",
       "app_list/search/launcher_search/launcher_search_result.cc",
       "app_list/search/launcher_search/launcher_search_result.h",
+      "ash/ime_controller_client.cc",
+      "ash/ime_controller_client.h",
       "ash/lock_screen_client.cc",
       "ash/lock_screen_client.h",
       "ash/session_controller_client.cc",
diff --git a/chrome/browser/ui/android/infobars/infobar_android.cc b/chrome/browser/ui/android/infobars/infobar_android.cc
index 6c130164..b13a333 100644
--- a/chrome/browser/ui/android/infobars/infobar_android.cc
+++ b/chrome/browser/ui/android/infobars/infobar_android.cc
@@ -55,6 +55,10 @@
   return !java_info_bar_.is_null();
 }
 
+void InfoBarAndroid::PlatformSpecificHide(bool animate) {
+  CloseJavaInfoBar();
+}
+
 int InfoBarAndroid::GetInfoBarIdentifier(JNIEnv* env,
                                          const JavaParamRef<jobject>& obj) {
   return delegate()->GetIdentifier();
diff --git a/chrome/browser/ui/android/infobars/infobar_android.h b/chrome/browser/ui/android/infobars/infobar_android.h
index 0115ea6..dd54ab65 100644
--- a/chrome/browser/ui/android/infobars/infobar_android.h
+++ b/chrome/browser/ui/android/infobars/infobar_android.h
@@ -42,6 +42,7 @@
       const base::android::JavaRef<jobject>& java_info_bar);
   jobject GetJavaInfoBar();
   bool HasSetJavaInfoBar() const;
+  void PlatformSpecificHide(bool animate) override;
 
   // Tells the Java-side counterpart of this InfoBar to point to the replacement
   // InfoBar instead of this one.
diff --git a/chrome/browser/ui/android/infobars/infobar_container_android.cc b/chrome/browser/ui/android/infobars/infobar_container_android.cc
index 71a733a..3ac3e6af 100644
--- a/chrome/browser/ui/android/infobars/infobar_container_android.cc
+++ b/chrome/browser/ui/android/infobars/infobar_container_android.cc
@@ -97,8 +97,8 @@
 
 void InfoBarContainerAndroid::PlatformSpecificRemoveInfoBar(
     infobars::InfoBar* infobar) {
-  InfoBarAndroid* android_infobar = static_cast<InfoBarAndroid*>(infobar);
-  android_infobar->CloseJavaInfoBar();
+  // The infobar will be closed by InfoBar::PlatformSpecificHide, this call
+  // is a noop on Android.
 }
 
 
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc
index 616f3cf..db0e420 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc
@@ -32,7 +32,6 @@
 #include "chrome/browser/chromeos/background/ash_wallpaper_delegate.h"
 #include "chrome/browser/chromeos/display/display_configuration_observer.h"
 #include "chrome/browser/chromeos/display/display_preferences.h"
-#include "chrome/browser/chromeos/input_method/input_method_manager_impl.h"
 #include "chrome/browser/chromeos/policy/display_rotation_default_handler.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/system/input_device_settings.h"
@@ -73,7 +72,6 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/common/service_manager_connection.h"
 #include "ui/aura/window.h"
-#include "ui/base/ime/chromeos/input_method_manager.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 
@@ -614,10 +612,6 @@
   return chromeos::CreateSystemTrayDelegate();
 }
 
-ash::ImeController* ChromeShellDelegate::GetImeController() {
-  return chromeos::input_method::InputMethodManagerImpl::Get();
-}
-
 std::unique_ptr<ash::WallpaperDelegate>
 ChromeShellDelegate::CreateWallpaperDelegate() {
   return base::WrapUnique(chromeos::CreateWallpaperDelegate());
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.h b/chrome/browser/ui/ash/chrome_shell_delegate.h
index 34d5717..1002ad8 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.h
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.h
@@ -45,7 +45,6 @@
   void ShelfInit() override;
   void ShelfShutdown() override;
   ash::SystemTrayDelegate* CreateSystemTrayDelegate() override;
-  ash::ImeController* GetImeController() override;
   std::unique_ptr<ash::WallpaperDelegate> CreateWallpaperDelegate() override;
   ash::AccessibilityDelegate* CreateAccessibilityDelegate() override;
   std::unique_ptr<ash::PaletteDelegate> CreatePaletteDelegate() override;
diff --git a/chrome/browser/ui/ash/ime_controller_client.cc b/chrome/browser/ui/ash/ime_controller_client.cc
new file mode 100644
index 0000000..54d6929e
--- /dev/null
+++ b/chrome/browser/ui/ash/ime_controller_client.cc
@@ -0,0 +1,138 @@
+// 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/ash/ime_controller_client.h"
+
+#include <memory>
+#include <vector>
+
+#include "ash/ime/ime_controller.h"
+#include "ash/public/interfaces/ime_info.mojom.h"
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/ime/chromeos/extension_ime_util.h"
+#include "ui/base/ime/chromeos/input_method_descriptor.h"
+#include "ui/base/ime/chromeos/input_method_util.h"
+
+using chromeos::input_method::InputMethodDescriptor;
+using chromeos::input_method::InputMethodManager;
+using chromeos::input_method::InputMethodUtil;
+using ui::ime::InputMethodMenuManager;
+
+namespace {
+
+ImeControllerClient* g_instance = nullptr;
+
+}  // namespace
+
+ImeControllerClient::ImeControllerClient(InputMethodManager* manager)
+    : input_method_manager_(manager) {
+  DCHECK(input_method_manager_);
+  input_method_manager_->AddObserver(this);
+  input_method_manager_->AddImeMenuObserver(this);
+  InputMethodMenuManager::GetInstance()->AddObserver(this);
+
+  // This does not need send the initial state to ash because that happens
+  // via observers when the InputMethodManager initializes its list of IMEs.
+
+  DCHECK(!g_instance);
+  g_instance = this;
+}
+
+ImeControllerClient::~ImeControllerClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+
+  InputMethodMenuManager::GetInstance()->RemoveObserver(this);
+  input_method_manager_->RemoveImeMenuObserver(this);
+  input_method_manager_->RemoveObserver(this);
+}
+
+// static
+ImeControllerClient* ImeControllerClient::Get() {
+  return g_instance;
+}
+
+void ImeControllerClient::SetImesManagedByPolicy(bool managed) {
+  ash::Shell::Get()->ime_controller()->SetImesManagedByPolicy(managed);
+}
+
+// chromeos::input_method::InputMethodManager::Observer:
+void ImeControllerClient::InputMethodChanged(InputMethodManager* manager,
+                                             Profile* profile,
+                                             bool show_message) {
+  RefreshIme();
+}
+
+// chromeos::input_method::InputMethodManager::ImeMenuObserver:
+void ImeControllerClient::ImeMenuActivationChanged(bool is_active) {
+  ash::Shell::Get()->ime_controller()->ShowImeMenuOnShelf(is_active);
+}
+
+void ImeControllerClient::ImeMenuListChanged() {
+  RefreshIme();
+}
+
+void ImeControllerClient::ImeMenuItemsChanged(
+    const std::string& engine_id,
+    const std::vector<InputMethodManager::MenuItem>& items) {}
+
+// ui::ime::InputMethodMenuManager::Observer:
+void ImeControllerClient::InputMethodMenuItemChanged(
+    InputMethodMenuManager* manager) {
+  RefreshIme();
+}
+
+ash::mojom::ImeInfo ImeControllerClient::GetAshImeInfo(
+    const InputMethodDescriptor& ime) const {
+  InputMethodUtil* util = input_method_manager_->GetInputMethodUtil();
+  ash::mojom::ImeInfo info;
+  info.id = ime.id();
+  info.name = util->GetInputMethodLongName(ime);
+  info.medium_name = util->GetInputMethodMediumName(ime);
+  info.short_name = util->GetInputMethodShortName(ime);
+  info.third_party = chromeos::extension_ime_util::IsExtensionIME(ime.id());
+  return info;
+}
+
+void ImeControllerClient::RefreshIme() {
+  ash::ImeController* ime_controller = ash::Shell::Get()->ime_controller();
+  InputMethodManager::State* state =
+      input_method_manager_->GetActiveIMEState().get();
+  if (!state) {
+    ime_controller->RefreshIme(ash::mojom::ImeInfo(),
+                               std::vector<ash::mojom::ImeInfo>(),
+                               std::vector<ash::mojom::ImeMenuItem>());
+    return;
+  }
+
+  InputMethodDescriptor current_descriptor = state->GetCurrentInputMethod();
+  ash::mojom::ImeInfo current_ime = GetAshImeInfo(current_descriptor);
+  current_ime.selected = true;
+
+  std::vector<ash::mojom::ImeInfo> available_imes;
+  std::unique_ptr<std::vector<InputMethodDescriptor>>
+      available_ime_descriptors = state->GetActiveInputMethods();
+  for (const InputMethodDescriptor& descriptor : *available_ime_descriptors) {
+    ash::mojom::ImeInfo info = GetAshImeInfo(descriptor);
+    info.selected = descriptor.id() == current_ime.id;
+    available_imes.push_back(info);
+  }
+
+  std::vector<ash::mojom::ImeMenuItem> ash_menu_items;
+  ui::ime::InputMethodMenuItemList menu_list =
+      ui::ime::InputMethodMenuManager::GetInstance()
+          ->GetCurrentInputMethodMenuItemList();
+  for (const ui::ime::InputMethodMenuItem& menu_item : menu_list) {
+    ash::mojom::ImeMenuItem ash_item;
+    ash_item.key = menu_item.key;
+    ash_item.label = base::UTF8ToUTF16(menu_item.label);
+    ash_item.checked = menu_item.is_selection_item_checked;
+    ash_menu_items.push_back(ash_item);
+  }
+  ash::Shell::Get()->ime_controller()->RefreshIme(current_ime, available_imes,
+                                                  ash_menu_items);
+}
diff --git a/chrome/browser/ui/ash/ime_controller_client.h b/chrome/browser/ui/ash/ime_controller_client.h
new file mode 100644
index 0000000..e7f947a
--- /dev/null
+++ b/chrome/browser/ui/ash/ime_controller_client.h
@@ -0,0 +1,64 @@
+// 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_ASH_IME_CONTROLLER_CLIENT_H_
+#define CHROME_BROWSER_UI_ASH_IME_CONTROLLER_CLIENT_H_
+
+#include "base/macros.h"
+#include "ui/base/ime/chromeos/input_method_manager.h"
+#include "ui/chromeos/ime/input_method_menu_manager.h"
+
+namespace ash {
+namespace mojom {
+class ImeInfo;
+}
+}
+
+// Connects the ImeController in ash to the InputMethodManagerImpl in chrome.
+// TODO(jamescook): Convert to using mojo interfaces.
+class ImeControllerClient
+    : public chromeos::input_method::InputMethodManager::Observer,
+      public chromeos::input_method::InputMethodManager::ImeMenuObserver,
+      public ui::ime::InputMethodMenuManager::Observer {
+ public:
+  explicit ImeControllerClient(
+      chromeos::input_method::InputMethodManager* manager);
+  ~ImeControllerClient() override;
+
+  static ImeControllerClient* Get();
+
+  // Sets whether the list of IMEs is managed by device policy.
+  void SetImesManagedByPolicy(bool managed);
+
+  // chromeos::input_method::InputMethodManager::Observer:
+  void InputMethodChanged(chromeos::input_method::InputMethodManager* manager,
+                          Profile* profile,
+                          bool show_message) override;
+
+  // chromeos::input_method::InputMethodManager::ImeMenuObserver:
+  void ImeMenuActivationChanged(bool is_active) override;
+  void ImeMenuListChanged() override;
+  void ImeMenuItemsChanged(
+      const std::string& engine_id,
+      const std::vector<chromeos::input_method::InputMethodManager::MenuItem>&
+          items) override;
+
+  // ui::ime::InputMethodMenuManager::Observer:
+  void InputMethodMenuItemChanged(
+      ui::ime::InputMethodMenuManager* manager) override;
+
+ private:
+  // Converts IME information from |descriptor| into the ash mojo format.
+  ash::mojom::ImeInfo GetAshImeInfo(
+      const chromeos::input_method::InputMethodDescriptor& descriptor) const;
+
+  // Sends information about current and available IMEs to ash.
+  void RefreshIme();
+
+  chromeos::input_method::InputMethodManager* const input_method_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImeControllerClient);
+};
+
+#endif  // CHROME_BROWSER_UI_ASH_IME_CONTROLLER_CLIENT_H_
diff --git a/chrome/browser/ui/ash/ime_controller_client_unittest.cc b/chrome/browser/ui/ash/ime_controller_client_unittest.cc
new file mode 100644
index 0000000..76317c51
--- /dev/null
+++ b/chrome/browser/ui/ash/ime_controller_client_unittest.cc
@@ -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.
+
+#include "chrome/browser/ui/ash/ime_controller_client.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/chromeos/input_method/mock_input_method_manager_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ImeControllerClientTest = testing::Test;
+
+TEST_F(ImeControllerClientTest, Basics) {
+  chromeos::input_method::MockInputMethodManagerImpl input_method_manager;
+
+  std::unique_ptr<ImeControllerClient> client =
+      base::MakeUnique<ImeControllerClient>(&input_method_manager);
+  EXPECT_EQ(1, input_method_manager.add_observer_count());
+  EXPECT_EQ(1, input_method_manager.add_menu_observer_count());
+
+  client.reset();
+  EXPECT_EQ(1, input_method_manager.remove_observer_count());
+  EXPECT_EQ(1, input_method_manager.remove_menu_observer_count());
+}
+
+// TODO(jamescook): When ImeControllerClient switches to using mojo add
+// additional tests that the correct mojo interface methods are called to send
+// data to ash.
diff --git a/chrome/browser/ui/ash/system_tray_delegate_chromeos.cc b/chrome/browser/ui/ash/system_tray_delegate_chromeos.cc
index 093f814..f96eb3f 100644
--- a/chrome/browser/ui/ash/system_tray_delegate_chromeos.cc
+++ b/chrome/browser/ui/ash/system_tray_delegate_chromeos.cc
@@ -99,10 +99,6 @@
 }
 
 void SystemTrayDelegateChromeOS::Initialize() {
-  input_method::InputMethodManager::Get()->AddObserver(this);
-  input_method::InputMethodManager::Get()->AddImeMenuObserver(this);
-  ui::ime::InputMethodMenuManager::GetInstance()->AddObserver(this);
-
   BrowserList::AddObserver(this);
 
   DBusThreadManager::Get()->GetUpdateEngineClient()->AddObserver(this);
@@ -117,9 +113,6 @@
   // Unregister a11y status subscription.
   accessibility_subscription_.reset();
 
-  input_method::InputMethodManager::Get()->RemoveObserver(this);
-  ui::ime::InputMethodMenuManager::GetInstance()->RemoveObserver(this);
-
   BrowserList::RemoveObserver(this);
   StopObservingAppWindowRegistry();
 
@@ -339,20 +332,6 @@
   GetSystemTrayNotifier()->NotifyTracingModeChanged(value);
 }
 
-// Overridden from InputMethodManager::Observer.
-void SystemTrayDelegateChromeOS::InputMethodChanged(
-    input_method::InputMethodManager* manager,
-    Profile* /* profile */,
-    bool show_message) {
-  GetSystemTrayNotifier()->NotifyRefreshIME();
-}
-
-// Overridden from InputMethodMenuManager::Observer.
-void SystemTrayDelegateChromeOS::InputMethodMenuItemChanged(
-    ui::ime::InputMethodMenuManager* manager) {
-  GetSystemTrayNotifier()->NotifyRefreshIME();
-}
-
 // Overridden from chrome::BrowserListObserver.
 void SystemTrayDelegateChromeOS::OnBrowserRemoved(Browser* browser) {
   NotifyIfLastWindowClosed();
@@ -372,16 +351,6 @@
     OnAccessibilityModeChanged(details.notify);
 }
 
-void SystemTrayDelegateChromeOS::ImeMenuActivationChanged(bool is_active) {
-  GetSystemTrayNotifier()->NotifyRefreshIMEMenu(is_active);
-}
-
-void SystemTrayDelegateChromeOS::ImeMenuListChanged() {}
-
-void SystemTrayDelegateChromeOS::ImeMenuItemsChanged(
-    const std::string& engine_id,
-    const std::vector<input_method::InputMethodManager::MenuItem>& items) {}
-
 void SystemTrayDelegateChromeOS::OnUpdateOverCellularTargetSet(bool success) {
   GetSystemTrayNotifier()->NotifyUpdateOverCellularTargetSet(success);
 }
diff --git a/chrome/browser/ui/ash/system_tray_delegate_chromeos.h b/chrome/browser/ui/ash/system_tray_delegate_chromeos.h
index fa734088..f9712ac 100644
--- a/chrome/browser/ui/ash/system_tray_delegate_chromeos.h
+++ b/chrome/browser/ui/ash/system_tray_delegate_chromeos.h
@@ -24,8 +24,6 @@
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "ui/base/ime/chromeos/ime_keyboard.h"
-#include "ui/base/ime/chromeos/input_method_manager.h"
-#include "ui/chromeos/ime/input_method_menu_manager.h"
 
 namespace ash {
 class SystemTrayNotifier;
@@ -34,13 +32,10 @@
 namespace chromeos {
 
 class SystemTrayDelegateChromeOS
-    : public ui::ime::InputMethodMenuManager::Observer,
-      public ash::SystemTrayDelegate,
+    : public ash::SystemTrayDelegate,
       public content::NotificationObserver,
-      public input_method::InputMethodManager::Observer,
       public chrome::BrowserListObserver,
       public extensions::AppWindowRegistry::Observer,
-      public input_method::InputMethodManager::ImeMenuObserver,
       public UpdateEngineClient::Observer {
  public:
   SystemTrayDelegateChromeOS();
@@ -86,15 +81,6 @@
 
   void UpdatePerformanceTracing();
 
-  // Overridden from InputMethodManager::Observer.
-  void InputMethodChanged(input_method::InputMethodManager* manager,
-                          Profile* profile,
-                          bool show_message) override;
-
-  // Overridden from InputMethodMenuManager::Observer.
-  void InputMethodMenuItemChanged(
-      ui::ime::InputMethodMenuManager* manager) override;
-
   // Overridden from chrome::BrowserListObserver:
   void OnBrowserRemoved(Browser* browser) override;
 
@@ -104,14 +90,6 @@
   void OnAccessibilityStatusChanged(
       const AccessibilityStatusEventDetails& details);
 
-  // input_method::InputMethodManager::ImeMenuObserver:
-  void ImeMenuActivationChanged(bool is_active) override;
-  void ImeMenuListChanged() override;
-  void ImeMenuItemsChanged(
-      const std::string& engine_id,
-      const std::vector<input_method::InputMethodManager::MenuItem>& items)
-      override;
-
   // Overridden from UpdateEngineClient::Observer.
   void OnUpdateOverCellularTargetSet(bool success) override;
 
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index da15e24..7f4d3e7 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -1116,9 +1116,9 @@
 
 void OpenFeedbackDialog(Browser* browser, FeedbackSource source) {
   base::RecordAction(UserMetricsAction("Feedback"));
-  chrome::ShowFeedbackPage(browser, source,
-                           std::string() /* description_template */,
-                           std::string() /* category_tag */);
+  chrome::ShowFeedbackPage(
+      browser, source, std::string() /* description_template */,
+      std::string() /* category_tag */, std::string() /* extra_diagnostics */);
 }
 
 void ToggleBookmarkBar(Browser* browser) {
diff --git a/chrome/browser/ui/chrome_pages.h b/chrome/browser/ui/chrome_pages.h
index c698198e..0db324e 100644
--- a/chrome/browser/ui/chrome_pages.h
+++ b/chrome/browser/ui/chrome_pages.h
@@ -66,7 +66,8 @@
 void ShowFeedbackPage(Browser* browser,
                       FeedbackSource source,
                       const std::string& description_template,
-                      const std::string& category_tag);
+                      const std::string& category_tag,
+                      const std::string& extra_diagnostics);
 
 void ShowHelp(Browser* browser, HelpSource source);
 void ShowHelpForProfile(Profile* profile, HelpSource source);
diff --git a/chrome/browser/ui/hung_plugin_tab_helper.cc b/chrome/browser/ui/hung_plugin_tab_helper.cc
index e8fd91f2..dc93e19 100644
--- a/chrome/browser/ui/hung_plugin_tab_helper.cc
+++ b/chrome/browser/ui/hung_plugin_tab_helper.cc
@@ -10,7 +10,6 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/process/process.h"
-#include "base/rand_util.h"
 #include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -20,77 +19,23 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/infobars/core/confirm_infobar_delegate.h"
 #include "components/infobars/core/infobar.h"
-#include "components/version_info/version_info.h"
 #include "content/public/browser/browser_child_process_host_iterator.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/child_process_data.h"
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/plugin_service.h"
-#include "content/public/browser/render_process_host.h"
 #include "content/public/common/process_type.h"
 #include "content/public/common/result_codes.h"
 #include "ui/base/l10n/l10n_util.h"
 
 #if defined(OS_WIN)
-#include "base/win/scoped_handle.h"
 #include "chrome/browser/hang_monitor/hang_crash_dump_win.h"
 #endif
 
 
 namespace {
 
-#if defined(OS_WIN)
-
-// OwnedHandleVector ----------------------------------------------------------
-
-class OwnedHandleVector {
- public:
-  typedef std::vector<HANDLE> Handles;
-  OwnedHandleVector();
-  ~OwnedHandleVector();
-
-  Handles* data() { return &data_; }
-
- private:
-  Handles data_;
-
-  DISALLOW_COPY_AND_ASSIGN(OwnedHandleVector);
-};
-
-OwnedHandleVector::OwnedHandleVector() {
-}
-
-OwnedHandleVector::~OwnedHandleVector() {
-  for (Handles::iterator iter = data_.begin(); iter != data_.end(); ++iter)
-    ::CloseHandle(*iter);
-}
-
-
-// Helpers --------------------------------------------------------------------
-
-const char kDumpChildProcessesSequenceName[] = "DumpChildProcesses";
-
-void DumpBrowserInBlockingPool() {
-  CrashDumpForHangDebugging(::GetCurrentProcess());
-}
-
-void DumpRenderersInBlockingPool(OwnedHandleVector* renderer_handles) {
-  for (OwnedHandleVector::Handles::const_iterator iter =
-           renderer_handles->data()->begin();
-       iter != renderer_handles->data()->end(); ++iter) {
-    CrashDumpForHangDebugging(*iter);
-  }
-}
-
-void DumpAndTerminatePluginInBlockingPool(
-    base::win::ScopedHandle* plugin_handle) {
-  base::StringPairs crash_keys = {{crash_keys::kHungRendererReason, "plugin"}};
-  CrashDumpAndTerminateHungChildProcess(plugin_handle->Get(), crash_keys);
-}
-
-#endif  // defined(OS_WIN)
-
 // Called on the I/O thread to actually kill the plugin with the given child
 // ID. We specifically don't want this to be a member function since if the
 // user chooses to kill the plugin, we want to kill it even if they close the
@@ -105,16 +50,9 @@
     const content::ChildProcessData& data = iter.GetData();
     if (data.id == child_id) {
 #if defined(OS_WIN)
-      HANDLE handle = NULL;
-      HANDLE current_process = ::GetCurrentProcess();
-      ::DuplicateHandle(current_process, data.handle, current_process, &handle,
-                        0, FALSE, DUPLICATE_SAME_ACCESS);
-      // Run it in blocking pool so that it won't block the I/O thread. Besides,
-      // we would like to make sure that it happens after dumping renderers.
-      content::BrowserThread::PostBlockingPoolSequencedTask(
-          kDumpChildProcessesSequenceName, FROM_HERE,
-          base::Bind(&DumpAndTerminatePluginInBlockingPool,
-                     base::Owned(new base::win::ScopedHandle(handle))));
+      base::StringPairs crash_keys = {
+          {crash_keys::kHungRendererReason, "plugin"}};
+      CrashDumpAndTerminateHungChildProcess(data.handle, crash_keys);
 #else
       base::Process process =
           base::Process::DeprecatedGetProcessFromHandle(data.handle);
@@ -360,43 +298,6 @@
 }
 
 void HungPluginTabHelper::KillPlugin(int child_id) {
-#if defined(OS_WIN)
-  // Dump renderers that are sending or receiving pepper messages, in order to
-  // diagnose inter-process deadlocks.
-  // Only do that on the Canary channel, for 20% of pepper plugin hangs.
-  if (base::RandInt(0, 100) < 20) {
-    version_info::Channel channel = chrome::GetChannel();
-    if (channel == version_info::Channel::CANARY) {
-      std::unique_ptr<OwnedHandleVector> renderer_handles(
-          new OwnedHandleVector);
-      HANDLE current_process = ::GetCurrentProcess();
-      content::RenderProcessHost::iterator renderer_iter =
-          content::RenderProcessHost::AllHostsIterator();
-      for (; !renderer_iter.IsAtEnd(); renderer_iter.Advance()) {
-        content::RenderProcessHost* host = renderer_iter.GetCurrentValue();
-        HANDLE handle = NULL;
-        ::DuplicateHandle(current_process, host->GetHandle(), current_process,
-                          &handle, 0, FALSE, DUPLICATE_SAME_ACCESS);
-        renderer_handles->data()->push_back(handle);
-      }
-      // If there are a lot of renderer processes, it is likely that we will
-      // generate too many crash dumps. They might not all be uploaded/recorded
-      // due to our crash dump uploading restrictions. So we just don't generate
-      // renderer crash dumps in that case.
-      if (renderer_handles->data()->size() > 0 &&
-          renderer_handles->data()->size() < 4) {
-        content::BrowserThread::PostBlockingPoolSequencedTask(
-            kDumpChildProcessesSequenceName, FROM_HERE,
-            base::Bind(&DumpBrowserInBlockingPool));
-        content::BrowserThread::PostBlockingPoolSequencedTask(
-            kDumpChildProcessesSequenceName, FROM_HERE,
-            base::Bind(&DumpRenderersInBlockingPool,
-                       base::Owned(renderer_handles.release())));
-      }
-    }
-  }
-#endif
-
   PluginStateMap::iterator found = hung_plugins_.find(child_id);
   DCHECK(found != hung_plugins_.end());
 
diff --git a/chrome/browser/ui/profile_error_dialog.cc b/chrome/browser/ui/profile_error_dialog.cc
index ec31648..e6a63f03 100644
--- a/chrome/browser/ui/profile_error_dialog.cc
+++ b/chrome/browser/ui/profile_error_dialog.cc
@@ -30,17 +30,10 @@
 
   std::string feedback_description =
       l10n_util::GetStringUTF8(IDS_PROFILE_ERROR_FEEDBACK_DESCRIPTION);
-  if (!diagnostics.empty()) {
-    // TODO(afakhry): Add support to inject diagnostics to the feedback
-    // reports without adding them to the description. crbug.com/708511.
-    feedback_description +=
-        "\n\n" +
-        l10n_util::GetStringUTF8(IDS_PROFILE_ERROR_FEEDBACK_DIAGNOSTICS_LINE) +
-        diagnostics;
-  }
 
   chrome::ShowFeedbackPage(nullptr, chrome::kFeedbackSourceProfileErrorDialog,
-                           feedback_description, kProfileErrorFeedbackCategory);
+                           feedback_description, kProfileErrorFeedbackCategory,
+                           diagnostics);
 }
 #endif  // !defined(OS_ANDROID)
 
diff --git a/chrome/browser/ui/sad_tab.cc b/chrome/browser/ui/sad_tab.cc
index 8fb280c6..5ebf1c7 100644
--- a/chrome/browser/ui/sad_tab.cc
+++ b/chrome/browser/ui/sad_tab.cc
@@ -231,7 +231,7 @@
             l10n_util::GetStringUTF8(kind_ == SAD_TAB_KIND_CRASHED
                                          ? IDS_CRASHED_TAB_FEEDBACK_MESSAGE
                                          : IDS_KILLED_TAB_FEEDBACK_MESSAGE),
-            std::string(kCategoryTagCrash));
+            std::string(kCategoryTagCrash), std::string());
       } else {
         web_contents_->GetController().Reload(content::ReloadType::NORMAL,
                                               true);
diff --git a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc
index ff60812..9e6572e8f 100644
--- a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/ui/ash/cast_config_client_media_router.h"
 #include "chrome/browser/ui/ash/chrome_new_window_client.h"
 #include "chrome/browser/ui/ash/chrome_shell_content_state.h"
+#include "chrome/browser/ui/ash/ime_controller_client.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/lock_screen_client.h"
 #include "chrome/browser/ui/ash/media_client.h"
@@ -32,6 +33,7 @@
 #include "chrome/browser/ui/views/select_file_dialog_extension_factory.h"
 #include "ui/aura/mus/property_converter.h"
 #include "ui/base/class_property.h"
+#include "ui/base/ime/chromeos/input_method_manager.h"
 #include "ui/keyboard/content/keyboard.h"
 #include "ui/keyboard/keyboard_controller.h"
 #include "ui/views/mus/mus_client.h"
@@ -86,6 +88,11 @@
 
   // Must be available at login screen, so initialize before profile.
   system_tray_client_ = base::MakeUnique<SystemTrayClient>();
+  // TODO(jamescook): Enable in mash once converted to mojo.
+  if (!ash_util::IsRunningInMash()) {
+    ime_controller_client_ = base::MakeUnique<ImeControllerClient>(
+        chromeos::input_method::InputMethodManager::Get());
+  }
   new_window_client_ = base::MakeUnique<ChromeNewWindowClient>();
   volume_controller_ = base::MakeUnique<VolumeController>();
   vpn_list_forwarder_ = base::MakeUnique<VpnListForwarder>();
@@ -131,6 +138,7 @@
   vpn_list_forwarder_.reset();
   volume_controller_.reset();
   new_window_client_.reset();
+  ime_controller_client_.reset();
   system_tray_client_.reset();
   lock_screen_client_.reset();
   media_client_.reset();
diff --git a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h
index 8337b50a..4ec4137 100644
--- a/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h
+++ b/chrome/browser/ui/views/ash/chrome_browser_main_extra_parts_ash.h
@@ -20,6 +20,7 @@
 class ChromeNewWindowClient;
 class ChromeShellContentState;
 class LockScreenClient;
+class ImeControllerClient;
 class ImmersiveContextMus;
 class ImmersiveHandlerFactoryMus;
 class MediaClient;
@@ -50,6 +51,7 @@
   std::unique_ptr<ImmersiveContextMus> immersive_context_;
   std::unique_ptr<SessionControllerClient> session_controller_client_;
   std::unique_ptr<SystemTrayClient> system_tray_client_;
+  std::unique_ptr<ImeControllerClient> ime_controller_client_;
   std::unique_ptr<ChromeNewWindowClient> new_window_client_;
   std::unique_ptr<VolumeController> volume_controller_;
   std::unique_ptr<VpnListForwarder> vpn_list_forwarder_;
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 56e0ab4d..8f9b594 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -198,14 +198,6 @@
   return network->name();
 }
 
-void StopEnforcingPolicyInputMethods() {
-  // Empty means all input methods are allowed
-  std::vector<std::string> allowed_input_methods;
-  chromeos::input_method::InputMethodManager* imm =
-      chromeos::input_method::InputMethodManager::Get();
-  imm->GetActiveIMEState()->SetAllowedInputMethods(allowed_input_methods);
-}
-
 }  // namespace
 
 // LoginScreenContext implementation ------------------------------------------
@@ -301,7 +293,7 @@
       chromeos::input_method::InputMethodManager::Get()->GetImeKeyboard();
   if (keyboard)
     keyboard->RemoveObserver(this);
-  StopEnforcingPolicyInputMethods();
+  lock_screen_utils::StopEnforcingPolicyInputMethods();
   weak_factory_.InvalidateWeakPtrs();
   if (delegate_)
     delegate_->SetWebUIHandler(nullptr);
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 2a8196d..9a1b589 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -1122,6 +1122,14 @@
 extern const char kEnableNewAppMenuIcon[] = "enable-new-app-menu-icon";
 #endif
 
+#if BUILDFLAG(ENABLE_OOP_HEAP_PROFILING)
+// Enables the out-of-process memory logging.
+const char kMemlog[] = "memlog";
+
+// Communicates the pipe name for out-of-process memory logging.
+const char kMemlogPipe[] = "memlog-pipe";
+#endif
+
 bool ExtensionsDisabled(const base::CommandLine& command_line) {
   return command_line.HasSwitch(switches::kDisableExtensions) ||
          command_line.HasSwitch(switches::kDisableExtensionsExcept);
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index dec100fe..ca9b97d9 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -350,6 +350,11 @@
 extern const char kEnableNewAppMenuIcon[];
 #endif
 
+#if BUILDFLAG(ENABLE_OOP_HEAP_PROFILING)
+extern const char kMemlog[];
+extern const char kMemlogPipe[];
+#endif
+
 bool ExtensionsDisabled(const base::CommandLine& command_line);
 bool ExtensionsDisabled();
 bool MdFeedbackEnabled();
diff --git a/chrome/installer/setup/BUILD.gn b/chrome/installer/setup/BUILD.gn
index c0bb689..e9720a5 100644
--- a/chrome/installer/setup/BUILD.gn
+++ b/chrome/installer/setup/BUILD.gn
@@ -30,6 +30,8 @@
       "//components/crash/content/app:app",
       "//components/crash/content/app:run_as_crashpad_handler",
     ]
+
+    libs = [ "netapi32.lib" ]
   }
 
   static_library("lib") {
@@ -60,6 +62,8 @@
       "setup_util.h",
       "update_active_setup_version_work_item.cc",
       "update_active_setup_version_work_item.h",
+      "user_experiment.cc",
+      "user_experiment.h",
       "user_hive_visitor.cc",
       "user_hive_visitor.h",
     ]
@@ -80,6 +84,7 @@
       "//rlz:rlz_lib",
       "//third_party/bspatch",
       "//third_party/zlib",
+      "//ui/base:fullscreen_win",
     ]
   }
 
@@ -103,6 +108,7 @@
       "setup_util_unittest.cc",
       "setup_util_unittest.h",
       "update_active_setup_version_work_item_unittest.cc",
+      "user_experiment_unittest.cc",
       "user_hive_visitor_unittest.cc",
     ]
 
@@ -124,5 +130,7 @@
       "//chrome/installer/test/data/",
       "//chrome/test/data/installer/",
     ]
+
+    libs = [ "netapi32.lib" ]
   }
 }
diff --git a/chrome/installer/setup/install.cc b/chrome/installer/setup/install.cc
index 2bb42f1..f6d9fd3 100644
--- a/chrome/installer/setup/install.cc
+++ b/chrome/installer/setup/install.cc
@@ -11,11 +11,13 @@
 #include <memory>
 #include <string>
 
+#include "base/base_paths.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/path_service.h"
 #include "base/process/launch.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -29,6 +31,7 @@
 #include "chrome/installer/setup/setup_constants.h"
 #include "chrome/installer/setup/setup_util.h"
 #include "chrome/installer/setup/update_active_setup_version_work_item.h"
+#include "chrome/installer/setup/user_experiment.h"
 #include "chrome/installer/util/beacons.h"
 #include "chrome/installer/util/browser_distribution.h"
 #include "chrome/installer/util/create_reg_key_work_item.h"
@@ -664,14 +667,14 @@
 // be increased for Active Setup to invoke this again for all users of this
 // install. It may also be invoked again when a system-level chrome install goes
 // through an OS upgrade.
-void HandleActiveSetupForBrowser(const base::FilePath& installation_root,
-                                 const installer::Product& chrome,
+void HandleActiveSetupForBrowser(const InstallerState& installer_state,
                                  bool force) {
   std::unique_ptr<WorkItemList> cleanup_list(WorkItem::CreateWorkItemList());
   cleanup_list->set_log_message("Cleanup deprecated per-user registrations");
   cleanup_list->set_rollback_enabled(false);
   cleanup_list->set_best_effort(true);
-  AddCleanupDeprecatedPerUserRegistrationsWorkItems(chrome, cleanup_list.get());
+  AddCleanupDeprecatedPerUserRegistrationsWorkItems(installer_state.product(),
+                                                    cleanup_list.get());
   cleanup_list->Do();
 
   // Only create shortcuts on Active Setup if the first run sentinel is not
@@ -686,13 +689,28 @@
           ? INSTALL_SHORTCUT_REPLACE_EXISTING
           : INSTALL_SHORTCUT_CREATE_EACH_IF_NO_SYSTEM_LEVEL;
 
-  // Read master_preferences copied beside chrome.exe at install.
+  // Read master_preferences copied beside chrome.exe at install for the sake of
+  // creating/updating shortcuts.
+  const base::FilePath installation_root = installer_state.target_path();
   MasterPreferences prefs(installation_root.AppendASCII(kDefaultMasterPrefs));
   base::FilePath chrome_exe(installation_root.Append(kChromeExe));
-  CreateOrUpdateShortcuts(
-      chrome_exe, chrome, prefs, CURRENT_USER, install_operation);
+  CreateOrUpdateShortcuts(chrome_exe, installer_state.product(), prefs,
+                          CURRENT_USER, install_operation);
 
   UpdateDefaultBrowserBeaconForPath(chrome_exe);
+
+  // This install may have been selected into a study for a retention
+  // experiment following a successful update. In case the experiment was not
+  // able to run immediately after the update (e.g., no user was logged on at
+  // the time), try to run it now that the installer is running in the context
+  // of a user.
+  if (ShouldRunUserExperiment(installer_state)) {
+    base::FilePath setup_exe;
+    if (!base::PathService::Get(base::FILE_EXE, &setup_exe))
+      LOG(ERROR) << "Failed to get path to setup.exe.";
+    else
+      BeginUserExperiment(installer_state, setup_exe, true /* user_context */);
+  }
 }
 
 }  // namespace installer
diff --git a/chrome/installer/setup/install.h b/chrome/installer/setup/install.h
index 20ebd55d..a0af5c2b 100644
--- a/chrome/installer/setup/install.h
+++ b/chrome/installer/setup/install.h
@@ -124,14 +124,9 @@
                                const base::Version& installed_version);
 
 // Performs per-user installation-related tasks on Active Setup (ran on first
-// login for each user post system-level Chrome install).
-// |installation_root|: The root of this install (i.e. the directory in which
-// chrome.exe is installed).
-// Shortcut creation is skipped if the First Run beacon is present (unless
-// |force| is set to true).
-// |chrome| The installed product (must be a browser).
-void HandleActiveSetupForBrowser(const base::FilePath& installation_root,
-                                 const Product& chrome,
+// login for each user post system-level Chrome install). Shortcut creation is
+// skipped if the First Run beacon is present (unless |force| is set to true).
+void HandleActiveSetupForBrowser(const InstallerState& installer_state,
                                  bool force);
 
 }  // namespace installer
diff --git a/chrome/installer/setup/install_worker.cc b/chrome/installer/setup/install_worker.cc
index 0eeac1b2..9a62365 100644
--- a/chrome/installer/setup/install_worker.cc
+++ b/chrome/installer/setup/install_worker.cc
@@ -28,6 +28,7 @@
 #include "base/version.h"
 #include "base/win/registry.h"
 #include "chrome/install_static/install_details.h"
+#include "chrome/install_static/install_modes.h"
 #include "chrome/install_static/install_util.h"
 #include "chrome/installer/setup/installer_state.h"
 #include "chrome/installer/setup/persistent_histogram_storage.h"
@@ -659,6 +660,19 @@
                                            post_install_task_list);
   }
 
+  // Add a best-effort item to create the ClientStateMedium key for system-level
+  // installs. This is ordinarily done by Google Update prior to running
+  // Chrome's installer. Do it here as well so that the key exists for manual
+  // installs.
+  if (install_static::kUseGoogleUpdateIntegration &&
+      installer_state.system_install()) {
+    const base::string16 path =
+        install_static::InstallDetails::Get().GetClientStateMediumKeyPath();
+    post_install_task_list
+        ->AddCreateRegKeyWorkItem(HKEY_LOCAL_MACHINE, path, KEY_WOW64_32KEY)
+        ->set_best_effort(true);
+  }
+
   return true;
 }
 
diff --git a/chrome/installer/setup/setup_constants.cc b/chrome/installer/setup/setup_constants.cc
index ffb70c3..e93f004 100644
--- a/chrome/installer/setup/setup_constants.cc
+++ b/chrome/installer/setup/setup_constants.cc
@@ -32,6 +32,9 @@
 const char kSetDisplayVersionProduct[] = "set-display-version-product";
 const char kSetDisplayVersionValue[] = "set-display-version-value";
 
+// Run setup.exe to conduct a post-update experiment.
+const char kUserExperiment[] = "user-experiment";
+
 }  // namespace switches
 
 }  // namespace installer
diff --git a/chrome/installer/setup/setup_constants.h b/chrome/installer/setup/setup_constants.h
index c5c36b7..359ff06 100644
--- a/chrome/installer/setup/setup_constants.h
+++ b/chrome/installer/setup/setup_constants.h
@@ -24,6 +24,7 @@
 extern const char kDelay[];
 extern const char kSetDisplayVersionProduct[];
 extern const char kSetDisplayVersionValue[];
+extern const char kUserExperiment[];
 
 }  // namespace switches
 
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index 1e8df55..064412f0 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -57,6 +57,7 @@
 #include "chrome/installer/setup/setup_singleton.h"
 #include "chrome/installer/setup/setup_util.h"
 #include "chrome/installer/setup/uninstall.h"
+#include "chrome/installer/setup/user_experiment.h"
 #include "chrome/installer/util/browser_distribution.h"
 #include "chrome/installer/util/delete_after_reboot_helper.h"
 #include "chrome/installer/util/delete_old_versions.h"
@@ -871,13 +872,11 @@
     // NOTE: Should the work done here, on kConfigureUserSettings, change:
     // kActiveSetupVersion in install_worker.cc needs to be increased for Active
     // Setup to invoke this again for all users of this install.
-    const Product& chrome_install = installer_state->product();
     installer::InstallStatus status = installer::INVALID_STATE_FOR_OPTION;
     if (installer_state->system_install()) {
       bool force =
           cmd_line.HasSwitch(installer::switches::kForceConfigureUserSettings);
-      installer::HandleActiveSetupForBrowser(installer_state->target_path(),
-                                             chrome_install, force);
+      installer::HandleActiveSetupForBrowser(*installer_state, force);
       status = installer::INSTALL_REPAIRED;
     } else {
       LOG(DFATAL)
@@ -979,6 +978,11 @@
                   << setup_exe.value();
     }
     *exit_code = InstallUtil::GetInstallReturnCode(status);
+  } else if (cmd_line.HasSwitch(installer::switches::kUserExperiment)) {
+    installer::RunUserExperiment(cmd_line,
+                                 MasterPreferences::ForCurrentProcess(),
+                                 original_state, installer_state);
+    exit_code = 0;
   } else if (cmd_line.HasSwitch(installer::switches::kInactiveUserToast)) {
     // Launch the inactive user toast experiment.
     int flavor = -1;
@@ -1480,6 +1484,15 @@
         InstallProducts(original_state, setup_exe, cmd_line, prefs,
                         &installer_state, &installer_directory);
     DoLegacyCleanups(installer_state, install_status);
+
+    // It may be time to kick off an experiment if this was a successful update.
+    if ((install_status == installer::NEW_VERSION_UPDATED ||
+         install_status == installer::IN_USE_UPDATED) &&
+        installer::ShouldRunUserExperiment(installer_state)) {
+      installer::BeginUserExperiment(
+          installer_state, installer_directory.Append(setup_exe.BaseName()),
+          !system_install);
+    }
   }
 
   UMA_HISTOGRAM_ENUMERATION("Setup.Install.Result", install_status,
diff --git a/chrome/installer/setup/user_experiment.cc b/chrome/installer/setup/user_experiment.cc
new file mode 100644
index 0000000..e7b51cce
--- /dev/null
+++ b/chrome/installer/setup/user_experiment.cc
@@ -0,0 +1,540 @@
+// 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/installer/setup/user_experiment.h"
+
+#include <windows.h>
+
+#include <lm.h>
+#include <shellapi.h>
+#include <wtsapi32.h>
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/process/launch.h"
+#include "base/process/process_info.h"
+#include "base/rand_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/win/registry.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/scoped_process_information.h"
+#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_version.h"
+#include "chrome/install_static/install_modes.h"
+#include "chrome/install_static/install_util.h"
+#include "chrome/installer/setup/installer_state.h"
+#include "chrome/installer/setup/setup_constants.h"
+#include "chrome/installer/setup/setup_singleton.h"
+#include "chrome/installer/setup/setup_util.h"
+#include "chrome/installer/setup/update_active_setup_version_work_item.h"
+#include "chrome/installer/util/experiment.h"
+#include "chrome/installer/util/experiment_storage.h"
+#include "chrome/installer/util/google_update_constants.h"
+#include "chrome/installer/util/google_update_settings.h"
+#include "chrome/installer/util/install_util.h"
+#include "chrome/installer/util/util_constants.h"
+#include "ui/base/fullscreen_win.h"
+
+namespace installer {
+
+namespace {
+
+// The study currently being conducted.
+constexpr ExperimentStorage::Study kCurrentStudy = ExperimentStorage::kStudyOne;
+
+// The primary group for study number two.
+constexpr int kStudyTwoGroup = 0;
+
+// Test switches.
+constexpr char kExperimentEnableForTesting[] = "experiment-enable-for-testing";
+constexpr char kExperimentEnterpriseBypass[] = "experiment-enterprise-bypass";
+constexpr char kExperimentParticipation[] = "experiment-participation";
+constexpr char kExperimentRetryDelay[] = "experiment-retry-delay";
+
+// Returns true if the experiment is enabled for testing.
+bool IsExperimentEnabledForTesting() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      kExperimentEnableForTesting);
+}
+
+// Returns true if the install originated from the MSI or if the machine is
+// joined to a domain. This check can be bypassed via
+// --experiment-enterprise-bypass.
+bool IsEnterpriseInstall(const InstallerState& installer_state) {
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          kExperimentEnterpriseBypass)) {
+    return false;
+  }
+  return installer_state.is_msi() || IsDomainJoined();
+}
+
+// Returns the delay to be used between presentation retries. The default (five
+// minutes) can be overidden via --experiment-retry-delay=SECONDS.
+base::TimeDelta GetRetryDelay() {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  base::string16 value =
+      command_line->GetSwitchValueNative(kExperimentRetryDelay);
+  int seconds;
+  if (!value.empty() && base::StringToInt(value, &seconds))
+    return base::TimeDelta::FromSeconds(seconds);
+  return base::TimeDelta::FromMinutes(5);
+}
+
+// Overrides the participation value for testing if a value is provided via
+// --experiment-participation=value. "value" may be "one" or "two". Any other
+// value (or none at all) results in clearing the persisted state for organic
+// re-evaluation.
+ExperimentStorage::Study HandleParticipationOverride(
+    ExperimentStorage::Study current_participation,
+    ExperimentStorage::Lock* lock) {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(kExperimentParticipation))
+    return current_participation;
+
+  base::string16 participation_override =
+      command_line->GetSwitchValueNative(kExperimentParticipation);
+  ExperimentStorage::Study participation = ExperimentStorage::kNoStudySelected;
+  if (participation_override == L"one")
+    participation = ExperimentStorage::kStudyOne;
+  else if (participation_override == L"two")
+    participation = ExperimentStorage::kStudyTwo;
+
+  if (participation != current_participation)
+    lock->WriteParticipation(participation);
+
+  return participation;
+}
+
+// This function launches setup as the currently logged-in interactive
+// user, that is, the user whose logon session is attached to winsta0\default.
+// It assumes that currently we are running as SYSTEM in a non-interactive
+// window station.
+// The function fails if there is no interactive session active, basically
+// the computer is on but nobody has logged in locally.
+// Remote Desktop sessions do not count as interactive sessions; running this
+// method as a user logged in via remote desktop will do nothing.
+bool LaunchSetupAsConsoleUser(const base::CommandLine& cmd_line) {
+  DWORD console_id = ::WTSGetActiveConsoleSessionId();
+  if (console_id == 0xFFFFFFFF) {
+    PLOG(ERROR) << __func__ << " no session attached to the console";
+    return false;
+  }
+  base::win::ScopedHandle user_token_handle;
+  {
+    HANDLE user_token;
+    if (!::WTSQueryUserToken(console_id, &user_token)) {
+      PLOG(ERROR) << __func__ << " failed to get user token for console_id "
+                  << console_id;
+      return false;
+    }
+    user_token_handle.Set(user_token);
+  }
+  base::LaunchOptions options;
+  options.as_user = user_token_handle.Get();
+  options.empty_desktop_name = true;
+  VLOG(1) << "Spawning experiment process: " << cmd_line.GetCommandLineString();
+  if (base::LaunchProcess(cmd_line, options).IsValid())
+    return true;
+  PLOG(ERROR) << "Failed";
+  return false;
+}
+
+// Returns true if the Windows shell indicates that the machine isn't in
+// presentation mode, running a full-screen D3D app, or in a quiet period.
+bool MayShowNotifications() {
+  QUERY_USER_NOTIFICATION_STATE state = {};
+  HRESULT result = SHQueryUserNotificationState(&state);
+  if (FAILED(result))
+    return true;
+  // Explicitly allow the acceptable states rather than the converse to be sure
+  // there are no surprises should new states be introduced.
+  return state == QUNS_NOT_PRESENT ||          // Locked/screensaver running.
+         state == QUNS_ACCEPTS_NOTIFICATIONS;  // Go for it!
+}
+
+bool UserSessionIsNotYoung() {
+  static constexpr base::TimeDelta kMinSessionLength =
+      base::TimeDelta::FromMinutes(5);
+  base::Time session_start_time = GetConsoleSessionStartTime();
+  if (session_start_time.is_null())
+    return true;
+
+  base::TimeDelta session_length = base::Time::Now() - session_start_time;
+  return session_length >= kMinSessionLength;
+}
+
+bool ActiveWindowIsNotFullscreen() {
+  return !ui::IsFullScreenMode();
+}
+
+// Blocks processing if conditions are not right for presentation. Returns true
+// if presentation should continue, or false otherwise (e.g., another process
+// requires the setup singleton).
+bool WaitForPresentation(
+    const SetupSingleton& setup_singleton,
+    Experiment* experiment,
+    ExperimentStorage* storage,
+    std::unique_ptr<ExperimentStorage::Lock>* storage_lock) {
+  base::TimeDelta retry_delay = GetRetryDelay();
+  bool first_sleep = true;
+  bool loop_again = true;
+
+  do {
+    if (MayShowNotifications() && UserSessionIsNotYoung() &&
+        ActiveWindowIsNotFullscreen()) {
+      return true;
+    }
+
+    // Update the state accordingly if this is the first sleep.
+    if (first_sleep) {
+      experiment->SetState(ExperimentMetrics::kDeferringPresentation);
+      (*storage_lock)->StoreExperiment(*experiment);
+      first_sleep = false;
+    }
+
+    // Release the storage lock and wait on the singleton for five minutes.
+    storage_lock->reset();
+    // Break when another process needs the singleton.
+    loop_again = !setup_singleton.WaitForInterrupt(retry_delay);
+    *storage_lock = storage->AcquireLock();
+  } while (loop_again);
+
+  return false;
+}
+
+}  // namespace
+
+// Execution may be in the context of the system or a user on it, and no
+// guarantee is made regarding the setup singleton.
+bool ShouldRunUserExperiment(const InstallerState& installer_state) {
+  if (!install_static::kUseGoogleUpdateIntegration)
+    return false;
+
+  if (!install_static::SupportsRetentionExperiments())
+    return false;
+
+  // The current experiment only applies to Windows 10 and newer.
+  if (base::win::GetVersion() < base::win::VERSION_WIN10)
+    return false;
+
+  // Enterprise brand codes and domain joined machines are excluded.
+  if (IsEnterpriseInstall(installer_state))
+    return false;
+
+  // Gain exclusive access to the persistent experiment state. Only per-install
+  // state may be queried (participation and metrics are okay; Experiment itself
+  // is not).
+  ExperimentStorage storage;
+  auto lock = storage.AcquireLock();
+
+  // Bail out if this install is not selected into the fraction participating in
+  // the current study.
+  // NOTE: No clients will participate while this feature is under development.
+  if (!IsExperimentEnabledForTesting() ||
+      !IsSelectedForStudy(lock.get(), kCurrentStudy)) {
+    return false;
+  }
+
+  // Skip the experiment if a user on the machine has already reached a terminal
+  // state.
+  ExperimentMetrics metrics;
+  if (!lock->LoadMetrics(&metrics) || metrics.InTerminalState())
+    return false;
+
+  return true;
+}
+
+// Execution is from the context of the installer immediately following a
+// successful update. The setup singleton is held.
+void BeginUserExperiment(const InstallerState& installer_state,
+                         const base::FilePath& setup_path,
+                         bool user_context) {
+  ExperimentStorage storage;
+
+  // Prepare a command line to relaunch the installed setup.exe for the
+  // experiment.
+  base::CommandLine setup_command(setup_path);
+  InstallUtil::AppendModeSwitch(&setup_command);
+  if (installer_state.system_install())
+    setup_command.AppendSwitch(switches::kSystemLevel);
+  if (installer_state.verbose_logging())
+    setup_command.AppendSwitch(switches::kVerboseLogging);
+  setup_command.AppendSwitch(switches::kUserExperiment);
+  // Copy any test switches used by the spawned process.
+  static constexpr const char* kSwitchesToCopy[] = {
+      kExperimentRetryDelay,
+  };
+  setup_command.CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
+                                 kSwitchesToCopy, arraysize(kSwitchesToCopy));
+
+  if (user_context) {
+    // This is either a per-user install or a per-machine install run via
+    // Active Setup as a normal user.
+    DCHECK(!installer_state.system_install() ||
+           base::GetCurrentProcessIntegrityLevel() == base::MEDIUM_INTEGRITY);
+    VLOG(1) << "Spawning experiment process: "
+            << setup_command.GetCommandLineString();
+    // The installer is already running in the context of an ordinary user.
+    // Relaunch directly to run the experiment.
+    base::LaunchOptions launch_options;
+    launch_options.force_breakaway_from_job_ = true;
+    if (!base::LaunchProcess(setup_command, launch_options).IsValid()) {
+      LOG(ERROR) << __func__
+                 << " failed to relaunch installer for user experiment,";
+      WriteInitialState(&storage, ExperimentMetrics::kRelaunchFailed);
+    }
+    return;
+  }
+
+  // The installer is running at high integrity, likely as SYSTEM. Relaunch as
+  // the console user at medium integrity.
+  VLOG(1) << "Attempting to spawn experiment as console user.";
+  if (LaunchSetupAsConsoleUser(setup_command)) {
+    return;
+  }
+
+  // Trigger Active Setup to run on the next user logon if this machine has
+  // never participated in the experiment. This will be done at most once per
+  // machine, even across updates. Doing so more often risks having excessive
+  // active setup activity for some users.
+  auto storage_lock = storage.AcquireLock();
+  ExperimentMetrics experiment_metrics;
+  if (storage_lock->LoadMetrics(&experiment_metrics) &&
+      experiment_metrics.state == ExperimentMetrics::kUninitialized) {
+    UpdateActiveSetupVersionWorkItem item(
+        install_static::GetActiveSetupPath(),
+        UpdateActiveSetupVersionWorkItem::UPDATE_AND_BUMP_SELECTIVE_TRIGGER);
+    if (item.Do()) {
+      VLOG(1) << "Bumped Active Setup Version for user experiment";
+      experiment_metrics.state = ExperimentMetrics::kWaitingForUserLogon;
+      storage_lock->StoreMetrics(experiment_metrics);
+    } else {
+      LOG(ERROR) << "Failed to bump Active Setup Version for user experiment.";
+    }
+  }
+}
+
+// This function executes within the context of a signed-in user, even for the
+// case of system-level installs. In particular, it may be run in a spawned
+// setup.exe immediately after a successful update or following user logon as a
+// result of Active Setup.
+void RunUserExperiment(const base::CommandLine& command_line,
+                       const MasterPreferences& master_preferences,
+                       InstallationState* original_state,
+                       InstallerState* installer_state) {
+  VLOG(1) << __func__;
+
+  ExperimentStorage storage;
+  std::unique_ptr<SetupSingleton> setup_singleton(SetupSingleton::Acquire(
+      command_line, master_preferences, original_state, installer_state));
+  if (!setup_singleton) {
+    VLOG(1) << "Timed out while waiting for setup singleton";
+    WriteInitialState(&storage, ExperimentMetrics::kSingletonWaitTimeout);
+    return;
+  }
+
+  Experiment experiment;
+  auto storage_lock = storage.AcquireLock();
+
+  // Determine the study in which this client participates.
+  ExperimentStorage::Study participation = ExperimentStorage::kNoStudySelected;
+  if (!storage_lock->ReadParticipation(&participation) ||
+      participation == ExperimentStorage::kNoStudySelected) {
+    // ShouldRunUserExperiment should have brought this client into a particular
+    // study. Something is very wrong if it can't be determined here.
+    LOG(ERROR) << "Failed to read study participation.";
+    return;
+  }
+
+  if (!storage_lock->LoadExperiment(&experiment)) {
+    // The metrics correspond to a different user on the machine; nothing to do.
+    VLOG(1) << "Another user is participating in the experiment.";
+    return;
+  }
+
+  // There is nothing to do if the user has already reached a terminal state.
+  if (experiment.metrics().InTerminalState()) {
+    VLOG(1) << "Experiment has reached terminal state: "
+            << experiment.metrics().state;
+    return;
+  }
+
+  // Now the fun begins. Assign this user to a group if this is the first time
+  // through.
+  if (experiment.metrics().InInitialState()) {
+    experiment.AssignGroup(PickGroup(participation));
+    VLOG(1) << "Assigned user to experiment group: "
+            << experiment.metrics().group;
+  }
+
+  // Chrome is considered actively used if it has been run within the last 28
+  // days or if it was installed within the same time period.
+  int days_ago_last_run = GoogleUpdateSettings::GetLastRunTime();
+  if ((days_ago_last_run >= 0 ? days_ago_last_run
+                              : GetInstallAge(*installer_state)) <= 28) {
+    VLOG(1) << "Aborting experiment due to activity.";
+    experiment.SetState(ExperimentMetrics::kIsActive);
+    storage_lock->StoreExperiment(experiment);
+    return;
+  }
+
+  // Check for a dormant user: one for which the machine has been off or the
+  // user has been signed out for more than 90% of the last 28 days.
+  if (false) {  // TODO(grt): Implement this.
+    VLOG(1) << "Aborting experiment due to dormancy.";
+    experiment.SetState(ExperimentMetrics::kIsDormant);
+    storage_lock->StoreExperiment(experiment);
+    return;
+  }
+
+  if (base::win::IsTabletDevice(nullptr)) {
+    VLOG(1) << "Aborting experiment due to tablet device.";
+    experiment.SetState(ExperimentMetrics::kIsTabletDevice);
+    storage_lock->StoreExperiment(experiment);
+    return;
+  }
+
+  if (IsUpdateRenamePending()) {
+    VLOG(1) << "Aborting experiment due to Chrome update rename pending.";
+    experiment.SetState(ExperimentMetrics::kIsUpdateRenamePending);
+    storage_lock->StoreExperiment(experiment);
+    return;
+  }
+
+  if (!WaitForPresentation(*setup_singleton, &experiment, &storage,
+                           &storage_lock)) {
+    VLOG(1) << "Aborting experiment while waiting to present.";
+    experiment.SetState(ExperimentMetrics::kDeferredPresentationAborted);
+    storage_lock->StoreExperiment(experiment);
+    return;
+  }
+
+  VLOG(1) << "Launching Chrome to show the toast.";
+  experiment.SetState(ExperimentMetrics::kLaunchingChrome);
+  storage_lock->StoreExperiment(experiment);
+  LaunchChrome(*installer_state, experiment);
+}
+
+// Writes the initial state |state| to the registry if there is no existing
+// state for this or another user.
+void WriteInitialState(ExperimentStorage* storage,
+                       ExperimentMetrics::State state) {
+  auto storage_lock = storage->AcquireLock();
+
+  // Write the state provided that there is either no existing state or that
+  // any that exists also represents an initial state.
+  ExperimentMetrics experiment_metrics;
+  if (storage_lock->LoadMetrics(&experiment_metrics) &&
+      experiment_metrics.InInitialState()) {
+    experiment_metrics.state = state;
+    storage_lock->StoreMetrics(experiment_metrics);
+  }
+}
+
+bool IsDomainJoined() {
+  LPWSTR buffer = nullptr;
+  NETSETUP_JOIN_STATUS buffer_type = NetSetupUnknownStatus;
+  bool is_joined =
+      ::NetGetJoinInformation(nullptr, &buffer, &buffer_type) == NERR_Success &&
+      buffer_type == NetSetupDomainName;
+  if (buffer)
+    NetApiBufferFree(buffer);
+
+  return is_joined;
+}
+
+bool IsSelectedForStudy(ExperimentStorage::Lock* lock,
+                        ExperimentStorage::Study current_study) {
+  ExperimentStorage::Study participation = ExperimentStorage::kNoStudySelected;
+  if (!lock->ReadParticipation(&participation))
+    return false;
+
+  participation = HandleParticipationOverride(participation, lock);
+
+  if (participation == ExperimentStorage::kNoStudySelected) {
+    // This install has not been evaluated for participation. Do so now. Select
+    // a small population into the first study of the experiment. Everyone else
+    // gets the second study.
+    participation = base::RandDouble() <= 0.02756962532
+                        ? ExperimentStorage::kStudyOne
+                        : ExperimentStorage::kStudyTwo;
+    if (!lock->WriteParticipation(participation))
+      return false;
+  }
+
+  return participation <= current_study;
+}
+
+int PickGroup(ExperimentStorage::Study participation) {
+  DCHECK(participation == ExperimentStorage::kStudyOne ||
+         participation == ExperimentStorage::kStudyTwo);
+  static constexpr int kHoldbackGroup = ExperimentMetrics::kNumGroups - 1;
+
+  if (participation == ExperimentStorage::kStudyOne) {
+    // Evenly distrubute clients among the groups.
+    return base::RandInt(0, ExperimentMetrics::kNumGroups - 1);
+  }
+
+  // 1% holdback, 99% in the winning group.
+  return base::RandDouble() < 0.01 ? kHoldbackGroup : kStudyTwoGroup;
+}
+
+bool IsUpdateRenamePending() {
+  // Consider an update to be pending if an "opv" value is present in the
+  // registry or if Chrome's version as registered with Omaha doesn't match the
+  // current version.
+  base::string16 clients_key_path =
+      install_static::GetClientsKeyPath(install_static::GetAppGuid());
+  const HKEY root = install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE
+                                                      : HKEY_CURRENT_USER;
+  base::win::RegKey clients_key;
+
+  // The failure modes below are generally indicitive of a run from a
+  // non-installed Chrome. Pretend that all is well.
+  if (clients_key.Open(root, clients_key_path.c_str(),
+                       KEY_WOW64_64KEY | KEY_QUERY_VALUE) != ERROR_SUCCESS) {
+    return false;
+  }
+  if (clients_key.HasValue(google_update::kRegOldVersionField))
+    return true;
+  base::string16 product_version;
+  if (clients_key.ReadValue(google_update::kRegVersionField,
+                            &product_version) != ERROR_SUCCESS) {
+    return false;
+  }
+  return product_version != TEXT(CHROME_VERSION_STRING);
+}
+
+void LaunchChrome(const InstallerState& installer_state,
+                  const Experiment& experiment) {
+  const base::FilePath chrome_exe =
+      installer_state.target_path().Append(kChromeExe);
+  base::CommandLine command_line(chrome_exe);
+  command_line.AppendSwitchNative(::switches::kTryChromeAgain,
+                                  base::IntToString16(experiment.group()));
+
+  STARTUPINFOW startup_info = {sizeof(startup_info)};
+  PROCESS_INFORMATION temp_process_info = {};
+  base::string16 writable_command_line_string(
+      command_line.GetCommandLineString());
+  if (!::CreateProcess(
+          chrome_exe.value().c_str(), &writable_command_line_string[0],
+          nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */,
+          FALSE /* bInheritHandles */, CREATE_NO_WINDOW,
+          nullptr /* lpEnvironment */, nullptr /* lpCurrentDirectory */,
+          &startup_info, &temp_process_info)) {
+    PLOG(ERROR) << "Failed to launch: " << writable_command_line_string;
+    return;
+  }
+
+  // Ensure that the thread and process handles of the new process are closed.
+  base::win::ScopedProcessInformation process_info(temp_process_info);
+}
+
+}  // namespace installer
diff --git a/chrome/installer/setup/user_experiment.h b/chrome/installer/setup/user_experiment.h
new file mode 100644
index 0000000..3a72d78
--- /dev/null
+++ b/chrome/installer/setup/user_experiment.h
@@ -0,0 +1,75 @@
+// 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_INSTALLER_SETUP_USER_EXPERIMENT_H_
+#define CHROME_INSTALLER_SETUP_USER_EXPERIMENT_H_
+
+#include "chrome/installer/util/experiment_metrics.h"
+#include "chrome/installer/util/experiment_storage.h"
+#include "chrome/installer/util/util_constants.h"
+
+namespace base {
+class CommandLine;
+class FilePath;
+}  // namespace base
+
+namespace installer {
+
+class Experiment;
+class ExperimentStorage;
+class InstallationState;
+class InstallerState;
+class MasterPreferences;
+
+// Returns true if a user of this Chrome install should participate in a
+// post-update user experiment.
+bool ShouldRunUserExperiment(const InstallerState& installer_state);
+
+// Initiates the user experiment for a user of the current install. May only be
+// called if eligibility had previously been evaluated via
+// ShouldRunUserExperiment. |setup_path| is the path to the version of setup.exe
+// that will be spawned to run the experiment. If |user_context| is true,
+// setup.exe will be spawned directly; otherwise, it will be either be run as
+// the interactive console user or on the next login via Active Setup.
+void BeginUserExperiment(const InstallerState& installer_state,
+                         const base::FilePath& setup_path,
+                         bool user_context);
+
+// Runs the experiment for the current user.
+void RunUserExperiment(const base::CommandLine& command_line,
+                       const MasterPreferences& master_preferences,
+                       InstallationState* original_state,
+                       InstallerState* installer_state);
+
+// Writes the initial state |state| to the registry if there is no existing
+// state for this or another user.
+void WriteInitialState(ExperimentStorage* storage,
+                       ExperimentMetrics::State state);
+
+// Returns true if the install is associated with an enterprise brand code.
+bool IsEnterpriseBrand();
+
+// Returns true if the machine is joined to a Windows domain.
+bool IsDomainJoined();
+
+// Returns true if the machine is selected for participation in |current_study|.
+// Dice are rolled on the first invocation to determine in which study the
+// machine participates.
+bool IsSelectedForStudy(ExperimentStorage::Lock* lock,
+                        ExperimentStorage::Study current_study);
+
+// Returns a group number based on the study in which the client participates.
+int PickGroup(ExperimentStorage::Study participation);
+
+// Returns true if the installed version of Chrome doesn't match the current
+// executable's.
+bool IsUpdateRenamePending();
+
+// Launches Chrome to present the prompt.
+void LaunchChrome(const InstallerState& installer_state,
+                  const Experiment& experiment);
+
+}  // namespace installer
+
+#endif  // CHROME_INSTALLER_SETUP_USER_EXPERIMENT_H_
diff --git a/chrome/installer/setup/user_experiment_unittest.cc b/chrome/installer/setup/user_experiment_unittest.cc
new file mode 100644
index 0000000..a5b81c3
--- /dev/null
+++ b/chrome/installer/setup/user_experiment_unittest.cc
@@ -0,0 +1,194 @@
+// 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/installer/setup/user_experiment.h"
+
+#include "base/macros.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/win/registry.h"
+#include "chrome/common/chrome_version.h"
+#include "chrome/install_static/install_details.h"
+#include "chrome/install_static/install_modes.h"
+#include "chrome/install_static/install_util.h"
+#include "chrome/install_static/test/scoped_install_details.h"
+#include "chrome/installer/util/experiment_metrics.h"
+#include "chrome/installer/util/experiment_storage.h"
+#include "chrome/installer/util/google_update_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace installer {
+
+class UserExperimentTest : public ::testing::TestWithParam<bool> {
+ protected:
+  UserExperimentTest()
+      : system_level_(GetParam()),
+        root_(system_level_ ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER),
+        install_details_(system_level_) {}
+
+  void SetUp() override {
+    ASSERT_NO_FATAL_FAILURE(registry_override_manager_.OverrideRegistry(root_));
+
+    // Create the ClientState key.
+    base::win::RegKey key;
+    ASSERT_EQ(key.Create(root_,
+                         install_static::InstallDetails::Get()
+                             .GetClientStateKeyPath()
+                             .c_str(),
+                         KEY_WOW64_64KEY | KEY_SET_VALUE),
+              ERROR_SUCCESS);
+  }
+
+  void SetProductVersion(const base::char16* version) {
+    SetClientsValue(google_update::kRegVersionField, version);
+  }
+
+  void SetOldProductVersion(const base::char16* version) {
+    SetClientsValue(google_update::kRegOldVersionField, version);
+  }
+
+ private:
+  void SetClientsValue(const base::char16* value_name,
+                       const base::char16* value_data) {
+    base::win::RegKey key(
+        root_,
+        install_static::GetClientsKeyPath(install_static::GetAppGuid()).c_str(),
+        KEY_WOW64_64KEY | KEY_SET_VALUE);
+    ASSERT_TRUE(key.Valid());
+    ASSERT_EQ(key.WriteValue(value_name, value_data), ERROR_SUCCESS);
+  }
+
+  const bool system_level_;
+  const HKEY root_;
+  registry_util::RegistryOverrideManager registry_override_manager_;
+  install_static::ScopedInstallDetails install_details_;
+  DISALLOW_COPY_AND_ASSIGN(UserExperimentTest);
+};
+
+TEST_P(UserExperimentTest, WriteInitialStateNoData) {
+  ExperimentStorage storage;
+
+  // A first call should write the desired state.
+  WriteInitialState(&storage, ExperimentMetrics::kWaitingForUserLogon);
+
+  ExperimentMetrics metrics = ExperimentMetrics();
+  EXPECT_TRUE(storage.AcquireLock()->LoadMetrics(&metrics));
+  EXPECT_EQ(metrics.state, ExperimentMetrics::kWaitingForUserLogon);
+
+  // A subsequent should update it state.
+  WriteInitialState(&storage, ExperimentMetrics::kSingletonWaitTimeout);
+
+  metrics = ExperimentMetrics();
+  EXPECT_TRUE(storage.AcquireLock()->LoadMetrics(&metrics));
+  EXPECT_EQ(metrics.state, ExperimentMetrics::kSingletonWaitTimeout);
+}
+
+// Nothing should be written if the experiment is underway.
+TEST_P(UserExperimentTest, WriteInitialStateInExperiment) {
+  ExperimentStorage storage;
+
+  {
+    ExperimentMetrics metrics = ExperimentMetrics();
+    metrics.state = ExperimentMetrics::kGroupAssigned;
+    storage.AcquireLock()->StoreMetrics(metrics);
+  }
+
+  WriteInitialState(&storage, ExperimentMetrics::kSingletonWaitTimeout);
+
+  ExperimentMetrics metrics = ExperimentMetrics();
+  EXPECT_TRUE(storage.AcquireLock()->LoadMetrics(&metrics));
+  EXPECT_EQ(metrics.state, ExperimentMetrics::kGroupAssigned);
+}
+
+TEST_P(UserExperimentTest, IsDomainJoined) {
+  // Just make sure it doesn't crash or leak.
+  IsDomainJoined();
+}
+
+TEST_P(UserExperimentTest, IsSelectedForStudyFirstCall) {
+  ExperimentStorage storage;
+  auto lock = storage.AcquireLock();
+
+  // The first call will pick a study.
+  bool is_selected =
+      IsSelectedForStudy(lock.get(), ExperimentStorage::kStudyOne);
+
+  // A value must have been written.
+  ExperimentStorage::Study participation = ExperimentStorage::kNoStudySelected;
+  ASSERT_TRUE(lock->ReadParticipation(&participation));
+  EXPECT_GE(participation, ExperimentStorage::kStudyOne);
+  EXPECT_LE(participation, ExperimentStorage::kStudyTwo);
+
+  // is_selected should be set based on the value that was written.
+  if (participation == ExperimentStorage::kStudyOne)
+    EXPECT_TRUE(is_selected);
+  else
+    EXPECT_FALSE(is_selected);
+}
+
+// A user selected into study one participates in both studies.
+TEST_P(UserExperimentTest, IsSelectedForStudyOne) {
+  ExperimentStorage storage;
+  auto lock = storage.AcquireLock();
+
+  ASSERT_TRUE(lock->WriteParticipation(ExperimentStorage::kStudyOne));
+
+  EXPECT_TRUE(IsSelectedForStudy(lock.get(), ExperimentStorage::kStudyOne));
+  EXPECT_TRUE(IsSelectedForStudy(lock.get(), ExperimentStorage::kStudyTwo));
+}
+
+// A user selected into study two only participates in that study.
+TEST_P(UserExperimentTest, IsSelectedForStudyTwo) {
+  ExperimentStorage storage;
+  auto lock = storage.AcquireLock();
+
+  ASSERT_TRUE(lock->WriteParticipation(ExperimentStorage::kStudyTwo));
+
+  EXPECT_FALSE(IsSelectedForStudy(lock.get(), ExperimentStorage::kStudyOne));
+  EXPECT_TRUE(IsSelectedForStudy(lock.get(), ExperimentStorage::kStudyTwo));
+}
+
+// Ensure that group selection is within bounds.
+TEST_P(UserExperimentTest, PickGroupStudyOne) {
+  int group = PickGroup(ExperimentStorage::kStudyOne);
+  EXPECT_GE(group, 0);
+  EXPECT_LT(group, ExperimentMetrics::kNumGroups);
+}
+
+// Ensure that group selection is within bounds.
+TEST_P(UserExperimentTest, PickGroupStudyTwo) {
+  int group = PickGroup(ExperimentStorage::kStudyTwo);
+  EXPECT_GE(group, 0);
+  EXPECT_LT(group, ExperimentMetrics::kNumGroups);
+}
+
+// When there's nothing in the registry, default to false.
+TEST_P(UserExperimentTest, IsUpdateRenamePendingNoRegistration) {
+  EXPECT_FALSE(IsUpdateRenamePending());
+}
+
+// No update is pending if "pv" matches the current version.
+TEST_P(UserExperimentTest, IsUpdateRenamePendingNo) {
+  ASSERT_NO_FATAL_FAILURE(SetProductVersion(TEXT(CHROME_VERSION_STRING)));
+  EXPECT_FALSE(IsUpdateRenamePending());
+}
+
+// An update is pending if an old version needs to be restarted to be the
+// current.
+TEST_P(UserExperimentTest, IsUpdateRenamePendingYes) {
+  static constexpr base::char16 kSillyOldVersion[] = L"47.0.1.0";
+  ASSERT_STRNE(kSillyOldVersion, TEXT(CHROME_VERSION_STRING));
+
+  ASSERT_NO_FATAL_FAILURE(SetProductVersion(TEXT(CHROME_VERSION_STRING)));
+  ASSERT_NO_FATAL_FAILURE(SetOldProductVersion(kSillyOldVersion));
+  EXPECT_TRUE(IsUpdateRenamePending());
+}
+
+INSTANTIATE_TEST_CASE_P(UserLevel,
+                        UserExperimentTest,
+                        ::testing::Values(false));
+INSTANTIATE_TEST_CASE_P(SystemLevel,
+                        UserExperimentTest,
+                        ::testing::Values(true));
+
+}  // namespace installer
diff --git a/chrome/installer/util/experiment_metrics.h b/chrome/installer/util/experiment_metrics.h
index fae52636..4b31d47 100644
--- a/chrome/installer/util/experiment_metrics.h
+++ b/chrome/installer/util/experiment_metrics.h
@@ -26,24 +26,25 @@
     // next logon via Active Setup.
     kWaitingForUserLogon = 1,
 
-    // Waiting in user context for the setup singleton.
-    kWaitingForSingleton = 2,
-
     // Timed out waiting for the setup singleton. Will retry on next update.
-    kSingletonWaitTimeout = 3,
+    kSingletonWaitTimeout = 2,
 
     // A group has been assigned. The experiment has moved out of the initial
     // state at this point. This state is reached under the setup singleton.
-    kGroupAssigned = 4,
+    kGroupAssigned = 3,
 
     // The user is not participating on account of using a tablet-like device.
-    kIsTabletDevice = 5,
+    kIsTabletDevice = 4,
 
     // Chrome has been run within the last 28 days.
-    kIsActive = 6,
+    kIsActive = 5,
 
     // The user has not been active on the machine much in the last 28 days.
-    kIsDormant = 7,
+    kIsDormant = 6,
+
+    // Chrome has received an in-use update for which the rename is pending.
+    // The UX cannot be shown.
+    kIsUpdateRenamePending = 7,
 
     // Deferring presentation until it's okay to show the toast.
     kDeferringPresentation = 8,
diff --git a/chrome/installer/util/experiment_storage.cc b/chrome/installer/util/experiment_storage.cc
index 6f8fe0e..0fd9d3d 100644
--- a/chrome/installer/util/experiment_storage.cc
+++ b/chrome/installer/util/experiment_storage.cc
@@ -157,7 +157,7 @@
   DCHECK(result);
 }
 
-bool ExperimentStorage::Lock::ReadParticipation(Participation* participation) {
+bool ExperimentStorage::Lock::ReadParticipation(Study* participation) {
   base::win::RegKey key;
   // A failure to open the key likely indicates that this isn't running from a
   // real install of Chrome.
@@ -166,28 +166,29 @@
 
   DWORD value = 0;
   LONG result = key.ReadValueDW(kRegValueRetentionStudy, &value);
-  if (result != ERROR_SUCCESS) {
-    // This likely means that the value is not present.
-    *participation = Participation::kNotEvaluated;
-  } else if (value == 0) {
-    *participation = Participation::kNotParticipating;
-  } else {
-    *participation = Participation::kIsParticipating;
-  }
+  // An error most likely means that the value is not present.
+  if (result != ERROR_SUCCESS || value == 0)
+    *participation = kNoStudySelected;
+  else if (value == 1)
+    *participation = kStudyOne;
+  else
+    *participation = kStudyTwo;
   return true;
 }
 
-bool ExperimentStorage::Lock::WriteParticipation(Participation participation) {
+bool ExperimentStorage::Lock::WriteParticipation(Study participation) {
+  DCHECK(participation == kNoStudySelected || participation == kStudyOne ||
+         participation == kStudyTwo);
   base::win::RegKey key;
   // A failure to open the key likely indicates that this isn't running from a
   // real install of Chrome.
   if (!OpenParticipationKey(true /* write_access */, &key))
     return false;
 
-  if (participation == Participation::kNotEvaluated)
+  if (participation == kNoStudySelected)
     return key.DeleteValue(kRegValueRetentionStudy) == ERROR_SUCCESS;
-  const DWORD value = participation == Participation::kIsParticipating ? 1 : 0;
-  return key.WriteValue(kRegValueRetentionStudy, value) == ERROR_SUCCESS;
+  return key.WriteValue(kRegValueRetentionStudy, participation) ==
+         ERROR_SUCCESS;
 }
 
 bool ExperimentStorage::Lock::LoadExperiment(Experiment* experiment) {
diff --git a/chrome/installer/util/experiment_storage.h b/chrome/installer/util/experiment_storage.h
index 40ed1d2..081bbe8 100644
--- a/chrome/installer/util/experiment_storage.h
+++ b/chrome/installer/util/experiment_storage.h
@@ -18,9 +18,9 @@
 
 // Manages the storage of experiment state on the machine.
 //
-// Participation is a per-install property evaluated one time to determine
-// whether or not the install as a whole participates in the study. It is
-// stored in the install's ClientState key.
+// Participation is a per-install property evaluated one time to determine which
+// study the client participates in. It is stored in the install's ClientState
+// key.
 //
 // ExperimentMetrics are stored in a per-install experiment_label, while the
 // fine-grained Experiment data are stored in a per-user key in Chrome's
@@ -38,15 +38,11 @@
 // mutex is used for all reads and writes to ensure consistent state.
 class ExperimentStorage {
  public:
-  enum class Participation {
-    // No participation state was found for the install.
-    kNotEvaluated,
-
-    // The client is not participating in the study.
-    kNotParticipating,
-
-    // The client is participating in the study.
-    kIsParticipating,
+  // An identifier of which study the install participates in.
+  enum Study : uint32_t {
+    kNoStudySelected = 0,
+    kStudyOne = 1,
+    kStudyTwo = 2,
   };
 
   // Grants the holder exclusive access to the data in the registry. Consumers
@@ -56,12 +52,13 @@
     ~Lock();
 
     // Reads the participation state for the install. Returns false in case of
-    // error.
-    bool ReadParticipation(Participation* participation);
+    // error. |participation| is set to kNotEvaluated if no value is present;
+    // otherwise, it is set to the value found.
+    bool ReadParticipation(Study* participation);
 
     // Writes the participation state for the install. Returns false if the
     // write failed.
-    bool WriteParticipation(Participation participation);
+    bool WriteParticipation(Study participation);
 
     // Loads the experiment metrics and data from the registry. Returns false if
     // the state in the registry corresponds to a different user or could not be
diff --git a/chrome/installer/util/experiment_storage_unittest.cc b/chrome/installer/util/experiment_storage_unittest.cc
index 5f4d2d3..f341ce9 100644
--- a/chrome/installer/util/experiment_storage_unittest.cc
+++ b/chrome/installer/util/experiment_storage_unittest.cc
@@ -62,7 +62,7 @@
   metrics.action_delay_bucket = 11;
   metrics.session_length_bucket = 36;
   base::string16 encoded_metrics(ExperimentStorage::EncodeMetrics(metrics));
-  EXPECT_EQ(L"5BIMD4IA", encoded_metrics);
+  EXPECT_EQ(L"5BIMD2IA", encoded_metrics);
   ExperimentMetrics decoded_metrics;
   ASSERT_TRUE(
       ExperimentStorage::DecodeMetrics(encoded_metrics, &decoded_metrics));
@@ -124,10 +124,9 @@
 
 TEST_P(ExperimentStorageTest, TestReadWriteParticipation) {
   ExperimentStorage storage;
-  ExperimentStorage::Participation expected =
-      ExperimentStorage::Participation::kIsParticipating;
+  ExperimentStorage::Study expected = ExperimentStorage::kStudyOne;
   ASSERT_TRUE(storage.AcquireLock()->WriteParticipation(expected));
-  ExperimentStorage::Participation p;
+  ExperimentStorage::Study p;
   ASSERT_TRUE(storage.AcquireLock()->ReadParticipation(&p));
   EXPECT_EQ(expected, p);
 }
@@ -166,7 +165,7 @@
   ASSERT_TRUE(storage.AcquireLock()->StoreMetrics(metrics));
   ExperimentMetrics stored_metrics;
   ASSERT_TRUE(storage.AcquireLock()->LoadMetrics(&stored_metrics));
-  EXPECT_EQ(L"5BIMD4IA", ExperimentStorage::EncodeMetrics(stored_metrics));
+  EXPECT_EQ(L"5BIMD2IA", ExperimentStorage::EncodeMetrics(stored_metrics));
   // Verify that expeirment labels are stored in registry.
   EXPECT_EQ(metrics, stored_metrics);
 }
diff --git a/chrome/profiling/BUILD.gn b/chrome/profiling/BUILD.gn
index 5318bb21..097b41e 100644
--- a/chrome/profiling/BUILD.gn
+++ b/chrome/profiling/BUILD.gn
@@ -7,6 +7,8 @@
 if (enable_oop_heap_profiling) {
   static_library("profiling") {
     sources = [
+      "profiling_globals.cc",
+      "profiling_globals.h",
       "profiling_main.cc",
       "profiling_main.h",
     ]
@@ -14,6 +16,7 @@
     deps = [
       "//base",
       "//chrome/common",
+      "//mojo/edk/system",
     ]
   }
 } else {
diff --git a/chrome/profiling/DEPS b/chrome/profiling/DEPS
new file mode 100644
index 0000000..177020e
--- /dev/null
+++ b/chrome/profiling/DEPS
@@ -0,0 +1,3 @@
+include_rules = [

+  "+mojo/edk/embedder",

+]

diff --git a/chrome/profiling/profiling_globals.cc b/chrome/profiling/profiling_globals.cc
new file mode 100644
index 0000000..5cd552e
--- /dev/null
+++ b/chrome/profiling/profiling_globals.cc
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/profiling/profiling_globals.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "build/build_config.h"
+
+namespace profiling {
+
+ProfilingGlobals::ProfilingGlobals()
+    : io_thread_("IO thread") {
+#if defined(OS_WIN)
+  io_thread_.init_com_with_mta(true);
+#endif
+  base::Thread::Options options;
+  options.message_loop_type = base::MessageLoop::TYPE_IO;
+  io_thread_.StartWithOptions(options);
+}
+
+ProfilingGlobals::~ProfilingGlobals() {}
+
+// static
+ProfilingGlobals* ProfilingGlobals::Get() {
+  static ProfilingGlobals singleton;
+  return &singleton;
+}
+
+base::TaskRunner* ProfilingGlobals::GetIORunner() {
+  return io_thread_.task_runner().get();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner> ProfilingGlobals::GetMainThread()
+    const {
+  CHECK(base::MessageLoop::current() == main_message_loop_);
+  return main_message_loop_->task_runner();
+}
+
+void ProfilingGlobals::RunMainMessageLoop() {
+  // TODO(brettw) if we never add anything interesting on the main thread here,
+  // we can change this so the main thread *is* the I/O thread. This will save
+  // some resources.
+  base::MessageLoopForUI message_loop;
+  DCHECK(!main_message_loop_);
+  main_message_loop_ = &message_loop;
+
+  base::RunLoop run_loop;
+  run_loop.Run();
+
+  main_message_loop_ = nullptr;
+}
+
+void ProfilingGlobals::QuitWhenIdle() {
+  main_message_loop_->QuitWhenIdle();
+}
+
+}  // namespace profiling
diff --git a/chrome/profiling/profiling_globals.h b/chrome/profiling/profiling_globals.h
new file mode 100644
index 0000000..84060a7
--- /dev/null
+++ b/chrome/profiling/profiling_globals.h
@@ -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.
+
+#ifndef CHROME_PROFILING_PROFILING_GLOBALS_H_
+#define CHROME_PROFILING_PROFILING_GLOBALS_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread.h"
+
+namespace base {
+class MessageLoopForUI;
+class SingleThreadTaskRunner;
+class TaskRunner;
+}  // namespace base
+
+namespace profiling {
+
+class ProfilingGlobals {
+ public:
+  static ProfilingGlobals* Get();
+
+  base::TaskRunner* GetIORunner();
+
+  // Returns non-null when inside RunMainMessageLoop. Call only on the
+  // main thread (otherwise there's a shutdown race).
+  scoped_refptr<base::SingleThreadTaskRunner> GetMainThread() const;
+
+  void RunMainMessageLoop();
+  void QuitWhenIdle();
+
+ private:
+  ProfilingGlobals();
+  ~ProfilingGlobals();
+
+  // Non-null inside RunMainMessageLoop.
+  base::MessageLoopForUI* main_message_loop_ = nullptr;
+
+  base::Thread io_thread_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProfilingGlobals);
+};
+
+}  // namespace profiling
+
+#endif  // CHROME_PROFILING_PROFILING_GLOBALS_H_
diff --git a/chrome/profiling/profiling_main.cc b/chrome/profiling/profiling_main.cc
index fd582b6..ccc022b 100644
--- a/chrome/profiling/profiling_main.cc
+++ b/chrome/profiling/profiling_main.cc
@@ -4,8 +4,22 @@
 
 #include "chrome/profiling/profiling_main.h"
 
+#include "base/base_paths.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
 #include "build/build_config.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/profiling/profiling_globals.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/scoped_ipc_support.h"
 
 #if defined(OS_WIN)
 #include "base/win/win_util.h"
@@ -13,8 +27,80 @@
 
 namespace profiling {
 
+namespace {
+
+base::CommandLine MakeBrowserCommandLine(const base::CommandLine& cmdline,
+                                         const std::string& pipe_id) {
+  const base::CommandLine::StringVector& our_argv = cmdline.argv();
+
+  base::CommandLine::StringVector browser_argv;
+  browser_argv.reserve(our_argv.size());
+
+  // Program name.
+  base::FilePath child_path;
+#if defined(OS_LINUX)
+  // Use /proc/self/exe rather than our known binary path so updates
+  // can't swap out the binary from underneath us.
+  // When running under Valgrind, forking /proc/self/exe ends up forking the
+  // Valgrind executable, which then crashes. However, it's almost safe to
+  // assume that the updates won't happen while testing with Valgrind tools.
+  if (!RunningOnValgrind())
+    child_path = base::FilePath(base::kProcSelfExe);
+#endif
+
+  if (child_path.empty())
+    base::PathService::Get(base::FILE_EXE, &child_path);
+  browser_argv.push_back(child_path.value());  // Program name.
+
+  // Remove all memlog flags.
+  for (size_t i = 1; i < our_argv.size(); i++) {
+    if (!base::StartsWith(our_argv[i], FILE_PATH_LITERAL("--memlog"),
+                          base::CompareCase::SENSITIVE))
+      browser_argv.push_back(our_argv[i]);
+  }
+
+  // Append the pipe ID.
+  std::string pipe_switch("--");
+  pipe_switch.append(switches::kMemlogPipe);
+  pipe_switch.push_back('=');
+  pipe_switch.append(pipe_id);
+#if defined(OS_WIN)
+  browser_argv.push_back(base::ASCIIToUTF16(pipe_switch));
+#else
+  browser_argv.push_back(pipe_switch);
+#endif
+
+  return base::CommandLine(browser_argv);
+}
+
+bool LaunchBrowser(const base::CommandLine& our_cmdline,
+                   const std::string& pipe_id) {
+  base::CommandLine browser_cmdline =
+      MakeBrowserCommandLine(our_cmdline, pipe_id);
+
+  base::LaunchOptions options;
+  base::Process process = base::LaunchProcess(browser_cmdline, options);
+
+  return true;
+}
+
+}  // namespace
+
 int ProfilingMain(const base::CommandLine& cmdline) {
-  // TODO(brettw) implement this.
+  ProfilingGlobals* globals = ProfilingGlobals::Get();
+
+  mojo::edk::Init();
+  mojo::edk::ScopedIPCSupport ipc_support(
+      globals->GetIORunner(),
+      mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+  base::Process process = base::Process::Current();
+  std::string pipe_id = base::IntToString(static_cast<int>(process.Pid()));
+
+  if (!LaunchBrowser(cmdline, pipe_id))
+    return 1;
+
+  ProfilingGlobals::Get()->RunMainMessageLoop();
 
 #if defined(OS_WIN)
   base::win::SetShouldCrashOnProcessDetach(false);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index a1c1262e..a9ec2bc 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4164,6 +4164,7 @@
       # Ash implies the app list is enabled (only disabled on mobile).
       "../browser/ui/ash/accessibility/ax_tree_source_aura_unittest.cc",
       "../browser/ui/ash/chrome_screenshot_grabber_unittest.cc",
+      "../browser/ui/ash/ime_controller_client_unittest.cc",
       "../browser/ui/ash/launcher/arc_app_shelf_id_unittest.cc",
       "../browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc",
       "../browser/ui/ash/launcher/launcher_context_menu_unittest.cc",
diff --git a/chrome/tools/test/experiment_tool_win.py b/chrome/tools/test/experiment_tool_win.py
new file mode 100755
index 0000000..2d942197
--- /dev/null
+++ b/chrome/tools/test/experiment_tool_win.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+# 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.
+
+"""A tool for working with state associated with the M60 Chrome on Windows 10
+retention experiment.
+
+For example:
+experiment_tool_win.py --channel beta --system-level --operation prep
+"""
+
+import argparse
+import math
+import sys
+from datetime import datetime, timedelta
+import win32api
+import win32security
+import _winreg
+
+
+def GetUserSidString():
+  """Returns the current user's SID string."""
+  token_handle = win32security.OpenProcessToken(win32api.GetCurrentProcess(),
+                                                win32security.TOKEN_QUERY)
+  user_sid, _ = win32security.GetTokenInformation(token_handle,
+                                                  win32security.TokenUser)
+  return win32security.ConvertSidToStringSid(user_sid)
+
+
+def InternalTimeFromPyTime(pytime):
+  """Returns a Chromium internal time value representing a Python datetime."""
+  # Microseconds since 1601-01-01 00:00:00 UTC
+  delta = pytime - datetime(1601, 1, 1)
+  return math.trunc(delta.total_seconds()) * 1000000 + delta.microseconds
+
+
+class ChromeState:
+  """An object that provides mutations on Chrome's state relating to the
+  user experiment.
+  """
+  _CHANNEL_CONFIGS = {
+    'stable': {
+      'guid': '{8A69D345-D564-463c-AFF1-A69D9E530F96}'
+    },
+    'beta': {
+      'guid': '{8237E44A-0054-442C-B6B6-EA0509993955}'
+    },
+    'dev': {
+      'guid': '{401C381F-E0DE-4B85-8BD8-3F3F14FBDA57}'
+    },
+    'canary': {
+      'guid': '{4ea16ac7-fd5a-47c3-875b-dbf4a2008c20}'
+    },
+   }
+  _GOOGLE_UPDATE_PATH = 'Software\\Google\\Update'
+  _ACTIVE_SETUP_PATH = 'Software\\Microsoft\\Active Setup\\Installed ' + \
+      'Components\\'
+
+  def __init__(self, channel_name, system_level):
+    self._config = ChromeState._CHANNEL_CONFIGS[channel_name]
+    self._system_level = system_level
+    self._registry_root = _winreg.HKEY_LOCAL_MACHINE if self._system_level \
+        else _winreg.HKEY_CURRENT_USER
+
+  def SetRetentionStudyValue(self, study):
+    """Sets the RetentionStudy value for the install."""
+    path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState\\' + \
+        self._config['guid']
+    with _winreg.OpenKey(self._registry_root, path, 0,
+                         _winreg.KEY_WOW64_32KEY |
+                         _winreg.KEY_SET_VALUE) as key:
+      _winreg.SetValueEx(key, 'RetentionStudy', 0, _winreg.REG_DWORD, study)
+
+  def DeleteRetentionStudyValue(self):
+    """Deletes the RetentionStudy value for the install."""
+    path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState\\' + \
+        self._config['guid']
+    try:
+      with _winreg.OpenKey(self._registry_root, path, 0,
+                           _winreg.KEY_WOW64_32KEY |
+                           _winreg.KEY_SET_VALUE) as key:
+        _winreg.DeleteValue(key, 'RetentionStudy')
+    except WindowsError as error:
+      if error.winerror != 2:
+        raise
+
+  def DeleteExperimentLabelsValue(self):
+    """Deletes the experiment_labels for the install."""
+    medium = 'Medium' if self._system_level else ''
+    path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState' + medium + '\\' + \
+        self._config['guid']
+    try:
+      with _winreg.OpenKey(self._registry_root, path, 0,
+                           _winreg.KEY_WOW64_32KEY |
+                           _winreg.KEY_SET_VALUE) as key:
+        _winreg.DeleteValue(key, 'experiment_labels')
+    except WindowsError as error:
+      if error.winerror != 2:
+        raise
+
+  def DeleteRentionKey(self):
+    """Deletes the Retention key for the current user."""
+    medium = 'Medium' if self._system_level else ''
+    path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState' + medium + '\\' + \
+        self._config['guid'] + '\\Retention'
+    try:
+      if self._system_level:
+        _winreg.DeleteKeyEx(self._registry_root,
+                            path + '\\' + GetUserSidString(),
+                            _winreg.KEY_WOW64_32KEY)
+      _winreg.DeleteKeyEx(self._registry_root, path, _winreg.KEY_WOW64_32KEY)
+    except WindowsError as error:
+      if error.winerror != 2:
+        raise
+
+  def SetLastRunTime(self, delta):
+    """Sets Chrome's lastrun time for the current user."""
+    path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState\\' + \
+        self._config['guid']
+    lastrun = InternalTimeFromPyTime(datetime.utcnow() - delta)
+    with _winreg.CreateKeyEx(_winreg.HKEY_CURRENT_USER, path, 0,
+                             _winreg.KEY_WOW64_32KEY |
+                             _winreg.KEY_SET_VALUE) as key:
+      _winreg.SetValueEx(key, 'lastrun', 0, _winreg.REG_SZ, str(lastrun))
+
+  def AdjustActiveSetupCommand(self):
+    """Adds --experiment-enterprise-bypass to system-level Chrome's Active Setup
+    command."""
+    if not self._system_level:
+      return
+    enable_switch = '--experiment-enable-for-testing'
+    bypass_switch = '--experiment-enterprise-bypass'
+    for flag in [_winreg.KEY_WOW64_32KEY, _winreg.KEY_WOW64_64KEY]:
+      try:
+        with _winreg.OpenKey(self._registry_root,
+                             ChromeState._ACTIVE_SETUP_PATH +
+                             self._config['guid'], 0,
+                             _winreg.KEY_SET_VALUE | _winreg.KEY_QUERY_VALUE |
+                             flag) as key:
+          command, _ = _winreg.QueryValueEx(key, 'StubPath')
+          if not bypass_switch in command:
+            command += ' ' + bypass_switch
+          if not enable_switch in command:
+            command += ' ' + enable_switch
+          _winreg.SetValueEx(key, 'StubPath', 0, _winreg.REG_SZ, command)
+      except WindowsError as error:
+        if error.winerror != 2:
+          raise
+
+  def ClearUserActiveSetup(self):
+    """Clears per-user state associated with Active Setup so that it will run
+    again on next login."""
+    if not self._system_level:
+      return
+    paths = [ChromeState._ACTIVE_SETUP_PATH,
+             ChromeState._ACTIVE_SETUP_PATH.replace('Software\\',
+                                                    'Software\\Wow6432Node\\')]
+    for path in paths:
+      try:
+        _winreg.DeleteKeyEx(_winreg.HKEY_CURRENT_USER,
+                            path + self._config['guid'], 0)
+      except WindowsError as error:
+        if error.winerror != 2:
+          raise
+
+
+def DoClean(chrome_state):
+  """Deletes all state associated with the user experiment."""
+  chrome_state.DeleteRetentionStudyValue()
+  chrome_state.DeleteExperimentLabelsValue()
+  chrome_state.DeleteRentionKey()
+  return 0
+
+
+def DoPrep(chrome_state):
+  """Prepares an install for participation in the experiment."""
+  # Clear old state.
+  DoClean(chrome_state)
+  # Make Chrome appear to have been last run 30 days ago.
+  chrome_state.SetLastRunTime(timedelta(30))
+  # Add the enterprise bypass switch to the Active Setup command.
+  chrome_state.AdjustActiveSetupCommand()
+  # Cause Active Setup to be run for the current user on next logon.
+  chrome_state.ClearUserActiveSetup()
+  # Put the machine into the first study.
+  chrome_state.SetRetentionStudyValue(1)
+  return 0
+
+
+def main(options):
+  chrome_state = ChromeState(options.channel, options.system_level)
+  if options.operation == 'clean':
+    return DoClean(chrome_state)
+  if options.operation == 'prep':
+    return DoPrep(chrome_state)
+  return 1
+
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser(
+    description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+  parser.add_argument('--operation', required=True, choices=['clean', 'prep'],
+                      help='The operation to be performed.')
+  parser.add_argument('--channel', default='stable',
+                      choices=['stable', 'beta', 'dev', 'canary'],
+                      help='The install on which to operate (stable by ' \
+                      'default).')
+  parser.add_argument('--system-level', action='store_true',
+                      help='Specify to operate on a system-level install ' \
+                      '(user-level by default).')
+  sys.exit(main(parser.parse_args()))
diff --git a/chromeos/network/network_state_handler.cc b/chromeos/network/network_state_handler.cc
index d6093d4a..e95ba4f9 100644
--- a/chromeos/network/network_state_handler.cc
+++ b/chromeos/network/network_state_handler.cc
@@ -690,13 +690,24 @@
 
 void NetworkStateHandler::SetTetherNetworkStateDisconnected(
     const std::string& guid) {
-  // TODO(khorimoto): Remove the Tether network as the default network.
   SetTetherNetworkStateConnectionState(guid, shill::kStateDisconnect);
 }
 
 void NetworkStateHandler::SetTetherNetworkStateConnecting(
     const std::string& guid) {
-  // TODO(khorimoto): Set the Tether network as the default network.
+  // The default network should only be set if there currently is no default
+  // network. Otherwise, the default network should not change unless the
+  // connection completes successfully and the newly-connected network is
+  // prioritized higher than the existing default network. Note that, in
+  // general, a connected Ethernet network is still considered the default
+  // network even if a Tether or Wi-Fi network becomes connected.
+  if (default_network_path_.empty()) {
+    NET_LOG(EVENT) << "Connecting to Tether network when there is currently no "
+                   << "default network; setting as new default network. GUID: "
+                   << guid;
+    default_network_path_ = guid;
+  }
+
   SetTetherNetworkStateConnectionState(guid, shill::kStateConfiguration);
 }
 
@@ -707,6 +718,9 @@
   DCHECK(GetNetworkStateFromGuid(GetNetworkStateFromGuid(guid)->tether_guid())
              ->tether_guid() == guid);
 
+  // At this point, there should be a default network set.
+  DCHECK(!default_network_path_.empty());
+
   SetTetherNetworkStateConnectionState(guid, shill::kStateOnline);
 }
 
@@ -1259,6 +1273,17 @@
   if (new_service_path == default_network_path_)
     return;
 
+  if (new_service_path.empty()) {
+    // If Shill reports that there is no longer a default network but there is
+    // still an active Tether connection corresponding to the default network,
+    // return early without changing |default_network_path_|. Observers will be
+    // notified of the default network change due to a subsequent call to
+    // SetTetherNetworkStateDisconnected().
+    const NetworkState* old_default_network = DefaultNetwork();
+    if (old_default_network && old_default_network->type() == kTypeTether)
+      return;
+  }
+
   default_network_path_ = service_path;
   NET_LOG_EVENT("DefaultNetworkServiceChanged:", default_network_path_);
   const NetworkState* network = nullptr;
@@ -1271,6 +1296,15 @@
                      << default_network_path_;
       return;
     }
+    if (!network->tether_guid().empty()) {
+      DCHECK(network->type() == shill::kTypeWifi);
+
+      // If the new default network from Shill's point of view is a Wi-Fi
+      // network which corresponds to a hotspot for a Tether network, set the
+      // default network to be the associated Tether network instead.
+      default_network_path_ = network->tether_guid();
+      return;
+    }
   }
   if (network && !network->IsConnectedState()) {
     if (network->IsConnectingState()) {
diff --git a/chromeos/network/network_state_handler_unittest.cc b/chromeos/network/network_state_handler_unittest.cc
index ad8af8e..e6c467c0 100644
--- a/chromeos/network/network_state_handler_unittest.cc
+++ b/chromeos/network/network_state_handler_unittest.cc
@@ -969,60 +969,268 @@
           kTetherGuid1, kWifiGuid1));
 }
 
-TEST_F(NetworkStateHandlerTest, SetTetherNetworkStateConnectionState) {
+TEST_F(NetworkStateHandlerTest,
+       SetTetherNetworkStateConnectionState_NoDefaultNetworkToStart) {
   network_state_handler_->SetTetherTechnologyState(
       NetworkStateHandler::TECHNOLOGY_ENABLED);
 
+  // Disconnect Ethernet and Wi-Fi so that there is no default network. For the
+  // purpose of this test, the default Wi-Fi network will serve as the Tether
+  // network's underlying Wi-Fi hotspot.
+  const std::string eth1 = kShillManagerClientStubDefaultService;
+  const std::string wifi1 = kShillManagerClientStubDefaultWifi;
+  service_test_->SetServiceProperty(eth1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(std::string(), test_observer_->default_network());
+
+  // Simulate a host scan, and reset the change counts for the connection flow.
   network_state_handler_->AddTetherNetworkState(
       kTetherGuid1, kTetherName1, kTetherCarrier1, kTetherBatteryPercentage1,
       kTetherSignalStrength1, kTetherHasConnectedToHost1);
+  test_observer_->reset_change_counts();
+  test_observer_->reset_updates();
 
-  // Add corresponding Wi-Fi network.
-  const std::string profile = "/profile/profile1";
-  const std::string wifi_path = "/service/wifi_with_guid";
-  AddService(wifi_path, kWifiGuid1, kWifiName1, shill::kTypeWifi,
-             shill::kStateOnline);
-  profile_test_->AddProfile(profile, "" /* userhash */);
-  EXPECT_TRUE(profile_test_->AddService(profile, wifi_path));
-  UpdateManagerProperties();
+  // Preconditions.
+  EXPECT_EQ(0, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(0, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+  EXPECT_EQ("", test_observer_->default_network());
+  EXPECT_EQ("", test_observer_->default_network_connection_state());
+  EXPECT_EQ(nullptr, network_state_handler_->DefaultNetwork());
+
+  // Set the Tether network state to "connecting." This is expected to be called
+  // before the connection to the underlying hotspot Wi-Fi network begins.
+  const NetworkState* tether_network =
+      network_state_handler_->GetNetworkStateFromGuid(kTetherGuid1);
+  network_state_handler_->SetTetherNetworkStateConnecting(kTetherGuid1);
+  EXPECT_TRUE(tether_network->IsConnectingState());
+  EXPECT_EQ(1, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(1, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+  EXPECT_EQ(tether_network, network_state_handler_->DefaultNetwork());
 
   // Associate Tether and Wi-Fi networks.
   network_state_handler_->AssociateTetherNetworkStateWithWifiNetwork(
-      kTetherGuid1, kWifiGuid1);
-
-  EXPECT_EQ(0, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
-  EXPECT_EQ(1, test_observer_->PropertyUpdatesForService(kTetherGuid1));
-
-  const NetworkState* tether_network =
-      network_state_handler_->GetNetworkStateFromGuid(kTetherGuid1);
-
-  EXPECT_FALSE(tether_network->IsConnectingState());
-  EXPECT_FALSE(tether_network->IsConnectedState());
-
-  network_state_handler_->SetTetherNetworkStateConnecting(kTetherGuid1);
-  EXPECT_TRUE(tether_network->IsConnectingState());
-
+      kTetherGuid1, "wifi1_guid");
   EXPECT_EQ(1, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
   EXPECT_EQ(2, test_observer_->PropertyUpdatesForService(kTetherGuid1));
 
+  // Connect to the underlying Wi-Fi network. The default network should not
+  // change yet.
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+
+  // Now, set the Tether network state to "connected." This should result in a
+  // default network change event.
   network_state_handler_->SetTetherNetworkStateConnected(kTetherGuid1);
   EXPECT_TRUE(tether_network->IsConnectedState());
-
   EXPECT_EQ(2, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
   EXPECT_EQ(3, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(1u, test_observer_->default_network_change_count());
+  EXPECT_EQ(kTetherGuid1, test_observer_->default_network());
+  EXPECT_EQ(shill::kStateOnline,
+            test_observer_->default_network_connection_state());
+  EXPECT_EQ(tether_network, network_state_handler_->DefaultNetwork());
 
-  network_state_handler_->SetTetherNetworkStateConnecting(kTetherGuid1);
-  EXPECT_TRUE(tether_network->IsReconnecting());
+  // Disconnect from the underlying Wi-Fi network. The default network should
+  // not change yet.
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, test_observer_->default_network_change_count());
 
-  EXPECT_EQ(3, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
-  EXPECT_EQ(4, test_observer_->PropertyUpdatesForService(kTetherGuid1));
-
+  // Now, set the Tether network state to "disconnected." This should result in
+  // a default network change event.
   network_state_handler_->SetTetherNetworkStateDisconnected(kTetherGuid1);
   EXPECT_FALSE(tether_network->IsConnectingState());
   EXPECT_FALSE(tether_network->IsConnectedState());
+  EXPECT_EQ(3, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(4, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(2u, test_observer_->default_network_change_count());
+  EXPECT_EQ("", test_observer_->default_network());
+  EXPECT_EQ("", test_observer_->default_network_connection_state());
+  EXPECT_EQ(nullptr, network_state_handler_->DefaultNetwork());
+}
 
-  EXPECT_EQ(4, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
-  EXPECT_EQ(5, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+TEST_F(NetworkStateHandlerTest,
+       SetTetherNetworkStateConnectionState_EthernetIsDefaultNetwork) {
+  network_state_handler_->SetTetherTechnologyState(
+      NetworkStateHandler::TECHNOLOGY_ENABLED);
+
+  // The ethernet corresponding to |eth1| will be left connected this entire
+  // test. It should be expected to remain the default network during the Tether
+  // connection.
+  const std::string eth1 = kShillManagerClientStubDefaultService;
+
+  // Disconnect the Wi-Fi network, which will serve as the underlying connection
+  // for the Tether network under test.
+  const std::string wifi1 = kShillManagerClientStubDefaultWifi;
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(eth1, test_observer_->default_network());
+
+  // Simulate a host scan, and reset the change counts for the connection flow.
+  network_state_handler_->AddTetherNetworkState(
+      kTetherGuid1, kTetherName1, kTetherCarrier1, kTetherBatteryPercentage1,
+      kTetherSignalStrength1, kTetherHasConnectedToHost1);
+  test_observer_->reset_change_counts();
+  test_observer_->reset_updates();
+
+  // Preconditions.
+  EXPECT_EQ(0, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(0, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+  EXPECT_EQ(eth1, test_observer_->default_network());
+  EXPECT_EQ(shill::kStateOnline,
+            test_observer_->default_network_connection_state());
+
+  // Set the Tether network state to "connecting." This is expected to be called
+  // before the connection to the underlying hotspot Wi-Fi network begins.
+  const NetworkState* tether_network =
+      network_state_handler_->GetNetworkStateFromGuid(kTetherGuid1);
+  network_state_handler_->SetTetherNetworkStateConnecting(kTetherGuid1);
+  EXPECT_TRUE(tether_network->IsConnectingState());
+  EXPECT_EQ(1, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(1, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+
+  // Associate Tether and Wi-Fi networks.
+  network_state_handler_->AssociateTetherNetworkStateWithWifiNetwork(
+      kTetherGuid1, "wifi1_guid");
+  EXPECT_EQ(1, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(2, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+
+  // Connect to the underlying Wi-Fi network.
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
+
+  // Now, set the Tether network state to "connected."
+  network_state_handler_->SetTetherNetworkStateConnected(kTetherGuid1);
+  EXPECT_TRUE(tether_network->IsConnectedState());
+  EXPECT_EQ(2, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(3, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+
+  // Disconnect from the underlying Wi-Fi network.
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  base::RunLoop().RunUntilIdle();
+
+  // Now, set the Tether network state to "disconnected."
+  network_state_handler_->SetTetherNetworkStateDisconnected(kTetherGuid1);
+  EXPECT_FALSE(tether_network->IsConnectingState());
+  EXPECT_FALSE(tether_network->IsConnectedState());
+  EXPECT_EQ(3, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(4, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+
+  // The Ethernet network should still be the default network, and no changes
+  // should have occurred during this test.
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+  EXPECT_EQ(eth1, test_observer_->default_network());
+  EXPECT_EQ(shill::kStateOnline,
+            test_observer_->default_network_connection_state());
+}
+
+TEST_F(NetworkStateHandlerTest,
+       SetTetherNetworkStateConnectionState_NoDefaultThenTetherThenEthernet) {
+  network_state_handler_->SetTetherTechnologyState(
+      NetworkStateHandler::TECHNOLOGY_ENABLED);
+
+  // Disconnect Ethernet and Wi-Fi so that there is no default network. For the
+  // purpose of this test, the default Wi-Fi network will serve as the Tether
+  // network's underlying Wi-Fi hotspot.
+  const std::string eth1 = kShillManagerClientStubDefaultService;
+  const std::string wifi1 = kShillManagerClientStubDefaultWifi;
+  service_test_->SetServiceProperty(eth1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(std::string(), test_observer_->default_network());
+
+  // Simulate a host scan, and reset the change counts for the connection flow.
+  network_state_handler_->AddTetherNetworkState(
+      kTetherGuid1, kTetherName1, kTetherCarrier1, kTetherBatteryPercentage1,
+      kTetherSignalStrength1, kTetherHasConnectedToHost1);
+  test_observer_->reset_change_counts();
+  test_observer_->reset_updates();
+
+  // Preconditions.
+  EXPECT_EQ(0, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(0, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+  EXPECT_EQ("", test_observer_->default_network());
+  EXPECT_EQ("", test_observer_->default_network_connection_state());
+  EXPECT_EQ(nullptr, network_state_handler_->DefaultNetwork());
+
+  // Set the Tether network state to "connecting." This is expected to be called
+  // before the connection to the underlying hotspot Wi-Fi network begins.
+  const NetworkState* tether_network =
+      network_state_handler_->GetNetworkStateFromGuid(kTetherGuid1);
+  network_state_handler_->SetTetherNetworkStateConnecting(kTetherGuid1);
+  EXPECT_TRUE(tether_network->IsConnectingState());
+  EXPECT_EQ(1, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(1, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+  EXPECT_EQ(tether_network, network_state_handler_->DefaultNetwork());
+
+  // Associate Tether and Wi-Fi networks.
+  network_state_handler_->AssociateTetherNetworkStateWithWifiNetwork(
+      kTetherGuid1, "wifi1_guid");
+  EXPECT_EQ(1, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(2, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+
+  // Connect to the underlying Wi-Fi network. The default network should not
+  // change yet.
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, test_observer_->default_network_change_count());
+
+  // Now, set the Tether network state to "connected." This should result in a
+  // default network change event.
+  network_state_handler_->SetTetherNetworkStateConnected(kTetherGuid1);
+  EXPECT_TRUE(tether_network->IsConnectedState());
+  EXPECT_EQ(2, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(3, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(1u, test_observer_->default_network_change_count());
+  EXPECT_EQ(kTetherGuid1, test_observer_->default_network());
+  EXPECT_EQ(shill::kStateOnline,
+            test_observer_->default_network_connection_state());
+  EXPECT_EQ(tether_network, network_state_handler_->DefaultNetwork());
+
+  // Now, connect the Ethernet network. This should be the new default network.
+  service_test_->SetServiceProperty(eth1, shill::kStateProperty,
+                                    base::Value(shill::kStateOnline));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, test_observer_->default_network_change_count());
+  EXPECT_EQ(eth1, test_observer_->default_network());
+  EXPECT_EQ(shill::kStateOnline,
+            test_observer_->default_network_connection_state());
+
+  // Disconnect from the underlying Wi-Fi network. The default network should
+  // still be the Ethernet network.
+  service_test_->SetServiceProperty(wifi1, shill::kStateProperty,
+                                    base::Value(shill::kStateIdle));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, test_observer_->default_network_change_count());
+
+  // Now, set the Tether network state to "disconnected." The default network
+  // should still be the Ethernet network.
+  network_state_handler_->SetTetherNetworkStateDisconnected(kTetherGuid1);
+  EXPECT_FALSE(tether_network->IsConnectingState());
+  EXPECT_FALSE(tether_network->IsConnectedState());
+  EXPECT_EQ(3, test_observer_->ConnectionStateChangesForService(kTetherGuid1));
+  EXPECT_EQ(4, test_observer_->PropertyUpdatesForService(kTetherGuid1));
+  EXPECT_EQ(2u, test_observer_->default_network_change_count());
+  EXPECT_EQ(eth1, test_observer_->default_network());
+  EXPECT_EQ(shill::kStateOnline,
+            test_observer_->default_network_connection_state());
 }
 
 TEST_F(NetworkStateHandlerTest, NetworkConnectionStateChanged) {
diff --git a/components/crash/content/app/breakpad_win.cc b/components/crash/content/app/breakpad_win.cc
index c236230..3324f1f 100644
--- a/components/crash/content/app/breakpad_win.cc
+++ b/components/crash/content/app/breakpad_win.cc
@@ -121,41 +121,13 @@
 
 namespace {
 
-// We need to prevent ICF from folding DumpForHangDebuggingThread() and
-// DumpProcessWithoutCrashThread() together, since that makes them
-// indistinguishable in crash dumps. We do this by making the function
-// bodies unique, and prevent optimization from shuffling things around.
-MSVC_DISABLE_OPTIMIZE()
-MSVC_PUSH_DISABLE_WARNING(4748)
-
 DWORD WINAPI DumpProcessWithoutCrashThread(void*) {
   DumpProcessWithoutCrash();
   return 0;
 }
 
-// The following two functions do exactly the same thing as the two above. But
-// we want the signatures to be different so that we can easily track them in
-// crash reports.
-// TODO(yzshen): Remove when enough information is collected and the hang rate
-// of pepper/renderer processes is reduced.
-DWORD WINAPI DumpForHangDebuggingThread(void*) {
-  DumpProcessWithoutCrash();
-  VLOG(1) << "dumped for hang debugging";
-  return 0;
-}
-
-MSVC_POP_WARNING()
-MSVC_ENABLE_OPTIMIZE()
-
 }  // namespace
 
-// Injects a thread into a remote process to dump state when there is no crash.
-extern "C" HANDLE __declspec(dllexport) __cdecl InjectDumpProcessWithoutCrash(
-    HANDLE process) {
-  return CreateRemoteThread(process, NULL, 0, DumpProcessWithoutCrashThread, 0,
-                            0, NULL);
-}
-
 extern "C" HANDLE __declspec(dllexport) __cdecl InjectDumpForHungInput(
     HANDLE process,
     void* serialized_crash_keys) {
@@ -174,12 +146,6 @@
                             0, NULL);
 }
 
-extern "C" HANDLE __declspec(dllexport) __cdecl
-InjectDumpForHangDebugging(HANDLE process) {
-  return CreateRemoteThread(process, NULL, 0, DumpForHangDebuggingThread,
-                            0, 0, NULL);
-}
-
 // Returns a string containing a list of all modifiers for the loaded profile.
 std::wstring GetProfileType() {
   std::wstring profile_type;
diff --git a/components/crash/content/app/crashpad_win.cc b/components/crash/content/app/crashpad_win.cc
index 6fdfd83d..77ccc323 100644
--- a/components/crash/content/app/crashpad_win.cc
+++ b/components/crash/content/app/crashpad_win.cc
@@ -170,21 +170,13 @@
 
 namespace {
 
-// We need to prevent ICF from folding DumpForHangDebuggingThread(),
-// DumpProcessForHungInputThread(), DumpProcessForHungInputNoCrashKeysThread()
-// and DumpProcessWithoutCrashThread() together, since that makes them
+// We need to prevent ICF from folding DumpProcessForHungInputThread(),
+// DumpProcessForHungInputNoCrashKeysThread() together, since that makes them
 // indistinguishable in crash dumps. We do this by making the function
 // bodies unique, and prevent optimization from shuffling things around.
 MSVC_DISABLE_OPTIMIZE()
 MSVC_PUSH_DISABLE_WARNING(4748)
 
-// Note that this function must be in a namespace for the [Renderer hang]
-// annotations to work on the crash server.
-DWORD WINAPI DumpProcessWithoutCrashThread(void*) {
-  DumpProcessWithoutCrash();
-  return 0;
-}
-
 // TODO(dtapuska): Remove when enough information is gathered where the crash
 // reports without crash keys come from.
 DWORD WINAPI DumpProcessForHungInputThread(void* crash_keys_str) {
@@ -213,14 +205,6 @@
   return 0;
 }
 
-// TODO(yzshen): Remove when enough information is collected and the hang rate
-// of pepper/renderer processes is reduced.
-DWORD WINAPI DumpForHangDebuggingThread(void*) {
-  DumpProcessWithoutCrash();
-  VLOG(1) << "dumped for hang debugging";
-  return 0;
-}
-
 MSVC_POP_WARNING()
 MSVC_ENABLE_OPTIMIZE()
 
@@ -244,15 +228,6 @@
 }
 
 // Injects a thread into a remote process to dump state when there is no crash.
-HANDLE __declspec(dllexport) __cdecl InjectDumpProcessWithoutCrash(
-    HANDLE process) {
-  return CreateRemoteThread(
-      process, nullptr, 0,
-      crash_reporter::internal::DumpProcessWithoutCrashThread, nullptr, 0,
-      nullptr);
-}
-
-// Injects a thread into a remote process to dump state when there is no crash.
 // |serialized_crash_keys| is a nul terminated string that represents serialized
 // crash keys sent from the browser. Keys and values are separated by ':', and
 // key/value pairs are separated by ','. All keys should be previously
@@ -278,13 +253,6 @@
       reinterpret_cast<void*>(reason), 0, nullptr);
 }
 
-HANDLE __declspec(dllexport) __cdecl InjectDumpForHangDebugging(
-    HANDLE process) {
-  return CreateRemoteThread(
-      process, nullptr, 0, crash_reporter::internal::DumpForHangDebuggingThread,
-      0, 0, nullptr);
-}
-
 #if defined(ARCH_CPU_X86_64)
 
 static int CrashForExceptionInNonABICompliantCodeRange(
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
index 663aa6e..ace2c7d 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
@@ -101,6 +101,10 @@
                                         ActivationLevel::ENABLED);
 }
 
+bool ContentSubresourceFilterDriverFactory::AllowStrongPopupBlocking() {
+  return activation_options_.should_strengthen_popup_blocker;
+}
+
 void ContentSubresourceFilterDriverFactory::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
   if (navigation_handle->IsInMainFrame() &&
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.h b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.h
index bbb2d6b..aafc7dd 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.h
+++ b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.h
@@ -69,6 +69,7 @@
 
   // ContentSubresourceFilterThrottleManager::Delegate:
   void OnFirstSubresourceLoadDisallowed() override;
+  bool AllowStrongPopupBlocking() override;
 
   ContentSubresourceFilterThrottleManager* throttle_manager() {
     return throttle_manager_.get();
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
index e4cc209..6c1cff4 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
@@ -27,6 +27,11 @@
 
 namespace subresource_filter {
 
+bool ContentSubresourceFilterThrottleManager::Delegate::
+    AllowStrongPopupBlocking() {
+  return false;
+}
+
 ContentSubresourceFilterThrottleManager::
     ContentSubresourceFilterThrottleManager(
         Delegate* delegate,
@@ -210,7 +215,8 @@
   // subresource filter specific UI here.
   return state.activation_level == ActivationLevel::ENABLED &&
          !state.filtering_disabled_for_document &&
-         !state.generic_blocking_rules_disabled;
+         !state.generic_blocking_rules_disabled &&
+         delegate_->AllowStrongPopupBlocking();
 }
 
 std::unique_ptr<SubframeNavigationFilteringThrottle>
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
index 4240c57..7bde89b 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
@@ -57,6 +57,7 @@
     // The embedder may be interested in displaying UI to the user when the
     // first load is disallowed for a given page load.
     virtual void OnFirstSubresourceLoadDisallowed() {}
+    virtual bool AllowStrongPopupBlocking();
   };
 
   ContentSubresourceFilterThrottleManager(
diff --git a/components/subresource_filter/core/browser/subresource_filter_features.cc b/components/subresource_filter/core/browser/subresource_filter_features.cc
index 6621c92..a1ca0b6 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features.cc
+++ b/components/subresource_filter/core/browser/subresource_filter_features.cc
@@ -172,6 +172,10 @@
       ParseBool(TakeVariationParamOrReturnEmpty(
           params, kWhitelistSiteOnReloadParameterName));
 
+  configuration.activation_options.should_strengthen_popup_blocker =
+      ParseBool(TakeVariationParamOrReturnEmpty(
+          params, kStrengthenPopupBlockerParameterName));
+
   // GeneralSettings:
   configuration.general_settings.ruleset_flavor =
       TakeVariationParamOrReturnEmpty(params, kRulesetFlavorParameterName);
@@ -259,6 +263,7 @@
     "performance_measurement_rate";
 const char kSuppressNotificationsParameterName[] = "suppress_notifications";
 const char kWhitelistSiteOnReloadParameterName[] = "whitelist_site_on_reload";
+const char kStrengthenPopupBlockerParameterName[] = "strengthen_popup_blocker";
 
 const char kRulesetFlavorParameterName[] = "ruleset_flavor";
 
@@ -310,6 +315,7 @@
                     config.activation_options.performance_measurement_rate,
                     config.activation_options.should_whitelist_site_on_reload,
                     config.activation_options.should_suppress_notifications,
+                    config.activation_options.should_strengthen_popup_blocker,
                     config.general_settings.ruleset_flavor);
   };
   return tie(*this) == tie(rhs);
@@ -335,6 +341,8 @@
                     activation_options.should_suppress_notifications);
   value->SetBoolean("should_whitelist_site_on_reload",
                     activation_options.should_whitelist_site_on_reload);
+  value->SetBoolean("should_strengthen_popup_blocker",
+                    activation_options.should_strengthen_popup_blocker);
   value->SetString("ruleset_flavor",
                    StreamToString(general_settings.ruleset_flavor));
   return value;
diff --git a/components/subresource_filter/core/browser/subresource_filter_features.h b/components/subresource_filter/core/browser/subresource_filter_features.h
index a828a4e..7fc4dca 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features.h
+++ b/components/subresource_filter/core/browser/subresource_filter_features.h
@@ -83,6 +83,9 @@
     // Whether to whitelist a site when a page loaded from that site is
     // reloaded.
     bool should_whitelist_site_on_reload = false;
+
+    // Whether to apply a more powerful popup blocker on pages with activation.
+    bool should_strengthen_popup_blocker = false;
   };
 
   // General settings that apply outside of the scope of a navigation.
@@ -201,6 +204,8 @@
 
 extern const char kWhitelistSiteOnReloadParameterName[];
 
+extern const char kStrengthenPopupBlockerParameterName[];
+
 extern const char kRulesetFlavorParameterName[];
 
 extern const char kEnablePresetsParameterName[];
diff --git a/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc b/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc
index 6b7263d..5ab04be 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc
+++ b/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc
@@ -428,6 +428,40 @@
   }
 }
 
+TEST(SubresourceFilterFeaturesTest, StrengthenPopupBlocker) {
+  const struct {
+    bool feature_enabled;
+    const char* strengthen_popup_blocker_param;
+    bool expected_strengthen_popup_blocker_value;
+  } kTestCases[] = {{false, "", false},
+                    {false, "true", false},
+                    {false, "false", false},
+                    {false, "invalid value", false},
+                    {true, "", false},
+                    {true, "false", false},
+                    {true, "invalid value", false},
+                    {true, "True", true},
+                    {true, "TRUE", true},
+                    {true, "true", true}};
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(::testing::Message("Enabled = ") << test_case.feature_enabled);
+    SCOPED_TRACE(::testing::Message("StrengthenPopupBlockerParam = \"")
+                 << test_case.strengthen_popup_blocker_param << "\"");
+
+    ScopedExperimentalStateToggle scoped_experimental_state(
+        test_case.feature_enabled ? base::FeatureList::OVERRIDE_ENABLE_FEATURE
+                                  : base::FeatureList::OVERRIDE_USE_DEFAULT,
+        {{kStrengthenPopupBlockerParameterName,
+          test_case.strengthen_popup_blocker_param}});
+
+    Configuration actual_configuration;
+    ExpectAndRetrieveExactlyOneEnabledConfig(&actual_configuration);
+    EXPECT_EQ(test_case.expected_strengthen_popup_blocker_value,
+              actual_configuration.activation_options
+                  .should_strengthen_popup_blocker);
+  }
+}
+
 TEST(SubresourceFilterFeaturesTest, RulesetFlavor) {
   const struct {
     bool feature_enabled;
diff --git a/content/browser/presentation/presentation_service_impl.h b/content/browser/presentation/presentation_service_impl.h
index 5b63387..d23ad25 100644
--- a/content/browser/presentation/presentation_service_impl.h
+++ b/content/browser/presentation/presentation_service_impl.h
@@ -65,40 +65,44 @@
       const service_manager::BindSourceInfo& source_info,
       mojo::InterfaceRequest<blink::mojom::PresentationService> request);
 
+  // PresentationService implementation.
+  void SetDefaultPresentationUrls(
+      const std::vector<GURL>& presentation_urls) override;
+  void SetClient(blink::mojom::PresentationServiceClientPtr client) override;
+  void ListenForScreenAvailability(const GURL& url) override;
+  void StopListeningForScreenAvailability(const GURL& url) override;
+  void StartPresentation(const std::vector<GURL>& presentation_urls,
+                         NewPresentationCallback callback) override;
+  void ReconnectPresentation(const std::vector<GURL>& presentation_urls,
+                             const base::Optional<std::string>& presentation_id,
+                             NewPresentationCallback callback) override;
+  void CloseConnection(const GURL& presentation_url,
+                       const std::string& presentation_id) override;
+  void Terminate(const GURL& presentation_url,
+                 const std::string& presentation_id) override;
+  void ListenForConnectionMessages(
+      const PresentationInfo& presentation_info) override;
+  void SetPresentationConnection(
+      const PresentationInfo& presentation_info,
+      blink::mojom::PresentationConnectionPtr controller_connection_ptr,
+      blink::mojom::PresentationConnectionRequest receiver_connection_request)
+      override;
+
  private:
   friend class PresentationServiceImplTest;
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest, Reset);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest, DidNavigateThisFrame);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-      DidNavigateOtherFrame);
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest, ThisRenderFrameDeleted);
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
       OtherRenderFrameDeleted);
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest, DelegateFails);
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           SetDefaultPresentationUrls);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           SetSameDefaultPresentationUrls);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           ClearDefaultPresentationUrls);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           ListenForDefaultPresentationStart);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           ListenForDefaultPresentationStartAfterSet);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           DefaultPresentationStartReset);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           ReceiveConnectionMessagesAfterReset);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           MaxPendingStartPresentationRequests);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           MaxPendingReconnectPresentationRequests);
-  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
                            ListenForConnectionStateChange);
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
                            ListenForConnectionClose);
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
-                           SetPresentationConnection);
+                           MaxPendingStartPresentationRequests);
+  FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
+                           MaxPendingReconnectPresentationRequests);
   FRIEND_TEST_ALL_PREFIXES(PresentationServiceImplTest,
                            ReceiverPresentationServiceDelegate);
 
@@ -159,29 +163,6 @@
       ControllerPresentationServiceDelegate* controller_delegate,
       ReceiverPresentationServiceDelegate* receiver_delegate);
 
-  // PresentationService implementation.
-  void SetDefaultPresentationUrls(
-      const std::vector<GURL>& presentation_urls) override;
-  void SetClient(blink::mojom::PresentationServiceClientPtr client) override;
-  void ListenForScreenAvailability(const GURL& url) override;
-  void StopListeningForScreenAvailability(const GURL& url) override;
-  void StartPresentation(const std::vector<GURL>& presentation_urls,
-                         NewPresentationCallback callback) override;
-  void ReconnectPresentation(const std::vector<GURL>& presentation_urls,
-                             const base::Optional<std::string>& presentation_id,
-                             NewPresentationCallback callback) override;
-  void CloseConnection(const GURL& presentation_url,
-                       const std::string& presentation_id) override;
-  void Terminate(const GURL& presentation_url,
-                 const std::string& presentation_id) override;
-  void ListenForConnectionMessages(
-      const PresentationInfo& presentation_info) override;
-  void SetPresentationConnection(
-      const PresentationInfo& presentation_info,
-      blink::mojom::PresentationConnectionPtr controller_connection_ptr,
-      blink::mojom::PresentationConnectionRequest receiver_connection_request)
-      override;
-
   // Creates a binding between this object and |request|.
   void Bind(blink::mojom::PresentationServiceRequest request);
 
diff --git a/content/browser/presentation/presentation_service_impl_unittest.cc b/content/browser/presentation/presentation_service_impl_unittest.cc
index f6937ca5..fe47a55 100644
--- a/content/browser/presentation/presentation_service_impl_unittest.cc
+++ b/content/browser/presentation/presentation_service_impl_unittest.cc
@@ -13,11 +13,8 @@
 #include <utility>
 #include <vector>
 
-#include "base/location.h"
 #include "base/run_loop.h"
-#include "base/single_thread_task_runner.h"
-#include "base/test/test_timeouts.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/test/mock_callback.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/presentation_service_delegate.h"
 #include "content/public/common/presentation_connection_message.h"
@@ -29,19 +26,25 @@
 #include "testing/gmock/include/gmock/gmock.h"
 
 using ::testing::_;
-using ::testing::ByRef;
 using ::testing::Eq;
-using ::testing::Invoke;
-using ::testing::InvokeWithoutArgs;
 using ::testing::Mock;
 using ::testing::Return;
 using ::testing::SaveArg;
-using ::testing::WithArgs;
+using NewPresentationCallback =
+    content::PresentationServiceImpl::NewPresentationCallback;
 
 namespace content {
 
 namespace {
 
+MATCHER(OptionalIsNotNull, "") {
+  return !!arg;
+}
+
+MATCHER(OptionalIsNull, "") {
+  return !arg;
+}
+
 // Matches content::PresentationInfo.
 MATCHER_P(InfoEquals, expected, "") {
   return expected.presentation_url == arg.presentation_url &&
@@ -247,13 +250,11 @@
     // when AppendChild is called.
     NavigateAndCommit(GURL("about:blank"));
 
-    auto request = mojo::MakeRequest(&service_ptr_);
     EXPECT_CALL(mock_delegate_, AddObserver(_, _, _)).Times(1);
     TestRenderFrameHost* render_frame_host = contents()->GetMainFrame();
     render_frame_host->InitializeRenderFrameIfNeeded();
     service_impl_.reset(new PresentationServiceImpl(
         render_frame_host, contents(), &mock_delegate_, nullptr));
-    service_impl_->Bind(std::move(request));
 
     blink::mojom::PresentationServiceClientPtr client_ptr;
     client_binding_.reset(
@@ -266,7 +267,6 @@
   }
 
   void TearDown() override {
-    service_ptr_.reset();
     if (service_impl_.get()) {
       EXPECT_CALL(mock_delegate_, RemoveObserver(_, _)).Times(1);
       service_impl_.reset();
@@ -287,49 +287,25 @@
 
   void ListenForScreenAvailabilityAndWait(const GURL& url,
                                           bool delegate_success) {
-    base::RunLoop run_loop;
-    // This will call to |service_impl_| via mojo. Process the message
-    // using RunLoop.
-    // The callback shouldn't be invoked since there is no availability
-    // result yet.
     EXPECT_CALL(mock_delegate_, AddScreenAvailabilityListener())
-        .WillOnce(DoAll(
-            InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
-            Return(delegate_success)));
-    service_ptr_->ListenForScreenAvailability(url);
-    run_loop.Run();
+        .WillOnce(Return(delegate_success));
+    service_impl_->ListenForScreenAvailability(url);
 
     EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_delegate_));
   }
 
-  void RunLoopFor(base::TimeDelta duration) {
-    base::RunLoop run_loop;
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE, run_loop.QuitClosure(), duration);
-    run_loop.Run();
-  }
-
-  void SaveQuitClosureAndRunLoop() {
-    base::RunLoop run_loop;
-    run_loop_quit_closure_ = run_loop.QuitClosure();
-    run_loop.Run();
-    run_loop_quit_closure_.Reset();
-  }
-
   void SimulateScreenAvailabilityChangeAndWait(const GURL& url,
                                                bool available) {
     auto listener_it = service_impl_->screen_availability_listeners_.find(url);
     ASSERT_TRUE(listener_it->second);
 
-    base::RunLoop run_loop;
     blink::mojom::ScreenAvailability expected_availability =
         available ? blink::mojom::ScreenAvailability::AVAILABLE
                   : blink::mojom::ScreenAvailability::UNAVAILABLE;
     EXPECT_CALL(mock_client_,
-                OnScreenAvailabilityUpdated(url, expected_availability))
-        .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+                OnScreenAvailabilityUpdated(url, expected_availability));
     listener_it->second->OnScreenAvailabilityChanged(available);
-    run_loop.Run();
+    base::RunLoop().RunUntilIdle();
   }
 
   void ExpectReset() {
@@ -343,44 +319,15 @@
         service_impl_->screen_availability_listeners_.end());
   }
 
-  void ExpectNewPresentationCallbackSuccess(
-      const base::Optional<PresentationInfo>& info,
-      const base::Optional<PresentationError>& error) {
-    EXPECT_TRUE(info);
-    EXPECT_FALSE(error);
-    if (!run_loop_quit_closure_.is_null())
-      run_loop_quit_closure_.Run();
-  }
-
-  void ExpectNewPresentationCallbackError(
-      const base::Optional<PresentationInfo>& info,
-      const base::Optional<PresentationError>& error) {
-    EXPECT_FALSE(info);
-    EXPECT_TRUE(error);
-    if (!run_loop_quit_closure_.is_null())
-      run_loop_quit_closure_.Run();
-  }
-
-  void ExpectConnectionMessages(
-      const std::vector<PresentationConnectionMessage>& expected_msgs,
-      const std::vector<PresentationConnectionMessage>& actual_msgs) {
-    EXPECT_EQ(expected_msgs.size(), actual_msgs.size());
-    for (size_t i = 0; i < actual_msgs.size(); ++i)
-      EXPECT_EQ(expected_msgs[i], actual_msgs[i]);
-  }
-
   MockPresentationServiceDelegate mock_delegate_;
   MockReceiverPresentationServiceDelegate mock_receiver_delegate_;
 
   std::unique_ptr<PresentationServiceImpl> service_impl_;
-  mojo::InterfacePtr<blink::mojom::PresentationService> service_ptr_;
 
   MockPresentationServiceClient mock_client_;
   std::unique_ptr<mojo::Binding<blink::mojom::PresentationServiceClient>>
       client_binding_;
 
-  base::Closure run_loop_quit_closure_;
-
   GURL presentation_url1_;
   GURL presentation_url2_;
   GURL presentation_url3_;
@@ -395,6 +342,14 @@
   SimulateScreenAvailabilityChangeAndWait(presentation_url1_, true);
 }
 
+TEST_F(PresentationServiceImplTest, ScreenAvailabilityNotSupported) {
+  mock_delegate_.set_screen_availability_listening_supported(false);
+  EXPECT_CALL(mock_client_,
+              OnScreenAvailabilityNotSupported(presentation_url1_));
+  ListenForScreenAvailabilityAndWait(presentation_url1_, false);
+  base::RunLoop().RunUntilIdle();
+}
+
 TEST_F(PresentationServiceImplTest, Reset) {
   ListenForScreenAvailabilityAndWait(presentation_url1_, true);
 
@@ -429,14 +384,17 @@
   // Since the frame matched the service, |service_impl_| will be deleted.
   PresentationServiceImpl* service = service_impl_.release();
   EXPECT_CALL(mock_delegate_, RemoveObserver(_, _)).Times(1);
-  service->RenderFrameDeleted(contents()->GetMainFrame());
+  service->RenderFrameDeleted(main_rfh());
 }
 
 TEST_F(PresentationServiceImplTest, OtherRenderFrameDeleted) {
   ListenForScreenAvailabilityAndWait(presentation_url1_, true);
 
-  // TODO(imcheng): How to get a different RenderFrameHost?
-  service_impl_->RenderFrameDeleted(nullptr);
+  // Create a new frame and RFH.
+  RenderFrameHost* rfh = main_rfh();
+  RenderFrameHostTester* rfh_tester = RenderFrameHostTester::For(rfh);
+  rfh = rfh_tester->AppendChild("subframe");
+  service_impl_->RenderFrameDeleted(rfh);
 
   // Availability is reported and callback should be invoked since listener
   // has not been deleted.
@@ -446,8 +404,8 @@
 TEST_F(PresentationServiceImplTest, DelegateFails) {
   ListenForScreenAvailabilityAndWait(presentation_url1_, false);
   ASSERT_EQ(
-      service_impl_->screen_availability_listeners_.find(presentation_url1_),
-      service_impl_->screen_availability_listeners_.end());
+      service_impl_->screen_availability_listeners_.end(),
+      service_impl_->screen_availability_listeners_.find(presentation_url1_));
 }
 
 TEST_F(PresentationServiceImplTest, SetDefaultPresentationUrls) {
@@ -468,13 +426,11 @@
 
   PresentationInfo presentation_info(presentation_url2_, kPresentationId);
 
-  base::RunLoop run_loop;
   EXPECT_CALL(mock_client_,
-              OnDefaultPresentationStarted(InfoEquals(presentation_info)))
-      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+              OnDefaultPresentationStarted(InfoEquals(presentation_info)));
   EXPECT_CALL(mock_delegate_, ListenForConnectionStateChange(_, _, _, _));
   callback.Run(PresentationInfo(presentation_url2_, kPresentationId));
-  run_loop.Run();
+  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(PresentationServiceImplTest, ListenForConnectionStateChange) {
@@ -487,16 +443,12 @@
       .WillOnce(SaveArg<3>(&state_changed_cb));
   service_impl_->ListenForConnectionStateChange(connection);
 
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(mock_client_, OnConnectionStateChanged(
-                                  InfoEquals(presentation_connection),
-                                  PRESENTATION_CONNECTION_STATE_TERMINATED))
-        .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
-    state_changed_cb.Run(PresentationConnectionStateChangeInfo(
-        PRESENTATION_CONNECTION_STATE_TERMINATED));
-    run_loop.Run();
-  }
+  EXPECT_CALL(mock_client_, OnConnectionStateChanged(
+                                InfoEquals(presentation_connection),
+                                PRESENTATION_CONNECTION_STATE_TERMINATED));
+  state_changed_cb.Run(PresentationConnectionStateChangeInfo(
+      PRESENTATION_CONNECTION_STATE_TERMINATED));
+  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(PresentationServiceImplTest, ListenForConnectionClose) {
@@ -509,21 +461,17 @@
   // Trigger connection close. It should be propagated back up to
   // |mock_client_|.
   PresentationInfo presentation_connection(presentation_url1_, kPresentationId);
-  {
-    base::RunLoop run_loop;
-    PresentationConnectionStateChangeInfo closed_info(
-        PRESENTATION_CONNECTION_STATE_CLOSED);
-    closed_info.close_reason = PRESENTATION_CONNECTION_CLOSE_REASON_WENT_AWAY;
-    closed_info.message = "Foo";
+  PresentationConnectionStateChangeInfo closed_info(
+      PRESENTATION_CONNECTION_STATE_CLOSED);
+  closed_info.close_reason = PRESENTATION_CONNECTION_CLOSE_REASON_WENT_AWAY;
+  closed_info.message = "Foo";
 
-    EXPECT_CALL(mock_client_,
-                OnConnectionClosed(
-                    InfoEquals(presentation_connection),
-                    PRESENTATION_CONNECTION_CLOSE_REASON_WENT_AWAY, "Foo"))
-        .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
-    state_changed_cb.Run(closed_info);
-    run_loop.Run();
-  }
+  EXPECT_CALL(mock_client_,
+              OnConnectionClosed(InfoEquals(presentation_connection),
+                                 PRESENTATION_CONNECTION_CLOSE_REASON_WENT_AWAY,
+                                 "Foo"));
+  state_changed_cb.Run(closed_info);
+  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(PresentationServiceImplTest, SetSameDefaultPresentationUrls) {
@@ -539,92 +487,107 @@
 }
 
 TEST_F(PresentationServiceImplTest, StartPresentationSuccess) {
-  service_ptr_->StartPresentation(
-      presentation_urls_,
-      base::Bind(
-          &PresentationServiceImplTest::ExpectNewPresentationCallbackSuccess,
-          base::Unretained(this)));
-  base::RunLoop run_loop;
+  base::MockCallback<NewPresentationCallback> mock_presentation_cb;
   base::Callback<void(const PresentationInfo&)> success_cb;
   EXPECT_CALL(mock_delegate_, StartPresentation(_, _, presentation_urls_, _, _))
-      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
-                      SaveArg<3>(&success_cb)));
-  run_loop.Run();
-
+      .WillOnce(SaveArg<3>(&success_cb));
+  service_impl_->StartPresentation(presentation_urls_,
+                                   mock_presentation_cb.Get());
+  EXPECT_FALSE(success_cb.is_null());
   EXPECT_CALL(mock_delegate_, ListenForConnectionStateChange(_, _, _, _))
       .Times(1);
+  EXPECT_CALL(mock_presentation_cb, Run(OptionalIsNotNull(), OptionalIsNull()));
   success_cb.Run(PresentationInfo(presentation_url1_, kPresentationId));
-  SaveQuitClosureAndRunLoop();
 }
 
 TEST_F(PresentationServiceImplTest, StartPresentationError) {
-  service_ptr_->StartPresentation(
-      presentation_urls_,
-      base::Bind(
-          &PresentationServiceImplTest::ExpectNewPresentationCallbackError,
-          base::Unretained(this)));
-  base::RunLoop run_loop;
+  base::MockCallback<NewPresentationCallback> mock_presentation_cb;
   base::Callback<void(const PresentationError&)> error_cb;
   EXPECT_CALL(mock_delegate_, StartPresentation(_, _, presentation_urls_, _, _))
-      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
-                      SaveArg<4>(&error_cb)));
-  run_loop.Run();
+      .WillOnce(SaveArg<4>(&error_cb));
+  service_impl_->StartPresentation(presentation_urls_,
+                                   mock_presentation_cb.Get());
+  EXPECT_FALSE(error_cb.is_null());
+  EXPECT_CALL(mock_presentation_cb, Run(OptionalIsNull(), OptionalIsNotNull()));
   error_cb.Run(PresentationError(PRESENTATION_ERROR_UNKNOWN, "Error message"));
-  SaveQuitClosureAndRunLoop();
+}
+
+TEST_F(PresentationServiceImplTest, StartPresentationInProgress) {
+  EXPECT_CALL(mock_delegate_, StartPresentation(_, _, presentation_urls_, _, _))
+      .Times(1);
+  // Uninvoked callbacks must outlive |service_impl_| since they get invoked
+  // at |service_impl_|'s destruction.
+  service_impl_->StartPresentation(presentation_urls_, base::Bind(&DoNothing));
+
+  // This request should fail immediately, since there is already a
+  // StartPresentation in progress.
+  base::MockCallback<NewPresentationCallback> mock_presentation_cb;
+  EXPECT_CALL(mock_presentation_cb, Run(OptionalIsNull(), OptionalIsNotNull()));
+  service_impl_->StartPresentation(presentation_urls_,
+                                   mock_presentation_cb.Get());
 }
 
 TEST_F(PresentationServiceImplTest, ReconnectPresentationSuccess) {
-  service_ptr_->ReconnectPresentation(
-      presentation_urls_, base::Optional<std::string>(kPresentationId),
-      base::Bind(
-          &PresentationServiceImplTest::ExpectNewPresentationCallbackSuccess,
-          base::Unretained(this)));
-  base::RunLoop run_loop;
+  base::MockCallback<NewPresentationCallback> mock_presentation_cb;
   base::Callback<void(const PresentationInfo&)> success_cb;
   EXPECT_CALL(mock_delegate_, ReconnectPresentation(_, _, presentation_urls_,
                                                     kPresentationId, _, _))
-      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
-                      SaveArg<4>(&success_cb)));
-  run_loop.Run();
-
+      .WillOnce(SaveArg<4>(&success_cb));
+  service_impl_->ReconnectPresentation(
+      presentation_urls_, base::Optional<std::string>(kPresentationId),
+      mock_presentation_cb.Get());
+  EXPECT_FALSE(success_cb.is_null());
   EXPECT_CALL(mock_delegate_, ListenForConnectionStateChange(_, _, _, _))
       .Times(1);
+  EXPECT_CALL(mock_presentation_cb, Run(OptionalIsNotNull(), OptionalIsNull()));
   success_cb.Run(PresentationInfo(presentation_url1_, kPresentationId));
-  SaveQuitClosureAndRunLoop();
 }
 
 TEST_F(PresentationServiceImplTest, ReconnectPresentationError) {
-  service_ptr_->ReconnectPresentation(
-      presentation_urls_, base::Optional<std::string>(kPresentationId),
-      base::Bind(
-          &PresentationServiceImplTest::ExpectNewPresentationCallbackError,
-          base::Unretained(this)));
-  base::RunLoop run_loop;
+  base::MockCallback<NewPresentationCallback> mock_presentation_cb;
   base::Callback<void(const PresentationError&)> error_cb;
   EXPECT_CALL(mock_delegate_, ReconnectPresentation(_, _, presentation_urls_,
                                                     kPresentationId, _, _))
-      .WillOnce(DoAll(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
-                      SaveArg<5>(&error_cb)));
-  run_loop.Run();
+      .WillOnce(SaveArg<5>(&error_cb));
+  service_impl_->ReconnectPresentation(
+      presentation_urls_, base::Optional<std::string>(kPresentationId),
+      mock_presentation_cb.Get());
+  EXPECT_FALSE(error_cb.is_null());
+  EXPECT_CALL(mock_presentation_cb, Run(OptionalIsNull(), OptionalIsNotNull()));
   error_cb.Run(PresentationError(PRESENTATION_ERROR_UNKNOWN, "Error message"));
-  SaveQuitClosureAndRunLoop();
+}
+
+TEST_F(PresentationServiceImplTest, MaxPendingReconnectPresentationRequests) {
+  const char* presentation_url = "http://fooUrl%d";
+  const char* presentation_id = "presentationId%d";
+  int num_requests = PresentationServiceImpl::kMaxQueuedRequests;
+  int i = 0;
+  EXPECT_CALL(mock_delegate_, ReconnectPresentation(_, _, _, _, _, _))
+      .Times(num_requests);
+  for (; i < num_requests; ++i) {
+    std::vector<GURL> urls = {GURL(base::StringPrintf(presentation_url, i))};
+    // Uninvoked callbacks must outlive |service_impl_| since they get invoked
+    // at |service_impl_|'s destruction.
+    service_impl_->ReconnectPresentation(
+        urls, base::StringPrintf(presentation_id, i), base::Bind(&DoNothing));
+  }
+
+  std::vector<GURL> urls = {GURL(base::StringPrintf(presentation_url, i))};
+  // Exceeded maximum queue size, should invoke mojo callback with error.
+  base::MockCallback<NewPresentationCallback> mock_presentation_cb;
+  EXPECT_CALL(mock_presentation_cb, Run(OptionalIsNull(), OptionalIsNotNull()));
+  service_impl_->ReconnectPresentation(
+      urls, base::StringPrintf(presentation_id, i), mock_presentation_cb.Get());
 }
 
 TEST_F(PresentationServiceImplTest, CloseConnection) {
-  service_ptr_->CloseConnection(presentation_url1_, kPresentationId);
-
-  base::RunLoop run_loop;
-  EXPECT_CALL(mock_delegate_, CloseConnection(_, _, Eq(kPresentationId)))
-      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
-  run_loop.Run();
+  EXPECT_CALL(mock_delegate_, CloseConnection(_, _, Eq(kPresentationId)));
+  service_impl_->CloseConnection(presentation_url1_, kPresentationId);
 }
 
 TEST_F(PresentationServiceImplTest, Terminate) {
-  service_ptr_->Terminate(presentation_url1_, kPresentationId);
-  base::RunLoop run_loop;
-  EXPECT_CALL(mock_delegate_, Terminate(_, _, Eq(kPresentationId)))
-      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
-  run_loop.Run();
+  EXPECT_CALL(mock_delegate_, Terminate(_, _, Eq(kPresentationId)));
+  service_impl_->Terminate(presentation_url1_, kPresentationId);
 }
 
 TEST_F(PresentationServiceImplTest, SetPresentationConnection) {
@@ -647,9 +610,10 @@
 
 TEST_F(PresentationServiceImplTest, ReceiverPresentationServiceDelegate) {
   MockReceiverPresentationServiceDelegate mock_receiver_delegate;
+  EXPECT_CALL(mock_receiver_delegate, AddObserver(_, _, _)).Times(1);
 
-  PresentationServiceImpl service_impl(contents()->GetMainFrame(), contents(),
-                                       nullptr, &mock_receiver_delegate);
+  PresentationServiceImpl service_impl(main_rfh(), contents(), nullptr,
+                                       &mock_receiver_delegate);
 
   ReceiverConnectionAvailableCallback callback;
   EXPECT_CALL(mock_receiver_delegate,
@@ -665,58 +629,23 @@
   EXPECT_FALSE(callback.is_null());
 
   // NO-OP for ControllerPresentationServiceDelegate API functions
-  EXPECT_CALL(mock_delegate_, ListenForConnectionMessages(_, _, _, _)).Times(0);
-
   PresentationInfo presentation_info(presentation_url1_, kPresentationId);
+  EXPECT_CALL(mock_delegate_, ListenForConnectionMessages(_, _, _, _)).Times(0);
   service_impl.ListenForConnectionMessages(presentation_info);
-}
 
-TEST_F(PresentationServiceImplTest, StartPresentationInProgress) {
-  EXPECT_CALL(mock_delegate_, StartPresentation(_, _, presentation_urls_, _, _))
-      .Times(1);
-  service_ptr_->StartPresentation(presentation_urls_, base::Bind(&DoNothing));
-
-  // This request should fail immediately, since there is already a
-  // StartPresentation in progress.
-  service_ptr_->StartPresentation(
-      presentation_urls_,
-      base::Bind(
-          &PresentationServiceImplTest::ExpectNewPresentationCallbackError,
-          base::Unretained(this)));
-  SaveQuitClosureAndRunLoop();
-}
-
-TEST_F(PresentationServiceImplTest, MaxPendingReconnectPresentationRequests) {
-  const char* presentation_url = "http://fooUrl%d";
-  const char* presentation_id = "presentationId%d";
-  int num_requests = PresentationServiceImpl::kMaxQueuedRequests;
-  int i = 0;
-  EXPECT_CALL(mock_delegate_, ReconnectPresentation(_, _, _, _, _, _))
-      .Times(num_requests);
-  for (; i < num_requests; ++i) {
-    std::vector<GURL> urls = {GURL(base::StringPrintf(presentation_url, i))};
-    service_ptr_->ReconnectPresentation(
-        urls, base::StringPrintf(presentation_id, i), base::Bind(&DoNothing));
-  }
-
-  std::vector<GURL> urls = {GURL(base::StringPrintf(presentation_url, i))};
-  // Exceeded maximum queue size, should invoke mojo callback with error.
-  service_ptr_->ReconnectPresentation(
-      urls, base::StringPrintf(presentation_id, i),
-      base::Bind(
-          &PresentationServiceImplTest::ExpectNewPresentationCallbackError,
-          base::Unretained(this)));
-  SaveQuitClosureAndRunLoop();
-}
-
-TEST_F(PresentationServiceImplTest, ScreenAvailabilityNotSupported) {
-  mock_delegate_.set_screen_availability_listening_supported(false);
-  base::RunLoop run_loop;
+  // Client gets notified of receiver connections.
+  blink::mojom::PresentationConnectionPtr controller_connection;
+  MockPresentationConnection mock_presentation_connection;
+  mojo::Binding<blink::mojom::PresentationConnection> connection_binding(
+      &mock_presentation_connection, mojo::MakeRequest(&controller_connection));
+  blink::mojom::PresentationConnectionPtr receiver_connection;
   EXPECT_CALL(mock_client_,
-              OnScreenAvailabilityNotSupported(presentation_url1_))
-      .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
-  ListenForScreenAvailabilityAndWait(presentation_url1_, false);
-  run_loop.Run();
+              OnReceiverConnectionAvailable(InfoEquals(presentation_info)));
+  callback.Run(presentation_info, std::move(controller_connection),
+               mojo::MakeRequest(&receiver_connection));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_CALL(mock_receiver_delegate, RemoveObserver(_, _)).Times(1);
 }
 
 }  // namespace content
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index 2e4fe72..3c1bc45 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -17,6 +17,7 @@
 #include "base/time/time.h"
 #include "content/browser/tracing/background_memory_tracing_observer.h"
 #include "content/browser/tracing/background_tracing_rule.h"
+#include "content/browser/tracing/trace_message_filter.h"
 #include "content/browser/tracing/tracing_controller_impl.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
@@ -189,7 +190,7 @@
   }
 
   // Notify observers before starting tracing.
-  for (auto* observer : background_tracing_observer_list_)
+  for (auto* observer : background_tracing_observers_)
     observer->OnScenarioActivated(config_.get());
 
   StartTracingIfConfigNeedsIt();
@@ -203,25 +204,57 @@
 
 void BackgroundTracingManagerImpl::OnStartTracingDone(
     BackgroundTracingConfigImpl::CategoryPreset preset) {
-  DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
-  for (auto* observer : background_tracing_observer_list_)
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  for (auto* observer : background_tracing_observers_)
     observer->OnTracingEnabled(preset);
 }
 
 void BackgroundTracingManagerImpl::AddEnabledStateObserver(
     EnabledStateObserver* observer) {
-  DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
-  background_tracing_observer_list_.push_back(observer);
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  background_tracing_observers_.insert(observer);
 }
 
 void BackgroundTracingManagerImpl::RemoveEnabledStateObserver(
     EnabledStateObserver* observer) {
-  DCHECK(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
-  std::vector<EnabledStateObserver*>::iterator it =
-      std::find(background_tracing_observer_list_.begin(),
-                background_tracing_observer_list_.end(), observer);
-  if (it != background_tracing_observer_list_.end())
-    background_tracing_observer_list_.erase(it);
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  background_tracing_observers_.erase(observer);
+}
+
+void BackgroundTracingManagerImpl::AddTraceMessageFilter(
+    TraceMessageFilter* trace_message_filter) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  trace_message_filters_.insert(trace_message_filter);
+
+  for (auto* observer : trace_message_filter_observers_)
+    observer->OnTraceMessageFilterAdded(trace_message_filter);
+}
+
+void BackgroundTracingManagerImpl::RemoveTraceMessageFilter(
+    TraceMessageFilter* trace_message_filter) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  for (auto* observer : trace_message_filter_observers_)
+    observer->OnTraceMessageFilterRemoved(trace_message_filter);
+
+  trace_message_filters_.erase(trace_message_filter);
+}
+
+void BackgroundTracingManagerImpl::AddTraceMessageFilterObserver(
+    TraceMessageFilterObserver* observer) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  trace_message_filter_observers_.insert(observer);
+
+  for (auto& filter : trace_message_filters_)
+    observer->OnTraceMessageFilterAdded(filter.get());
+}
+
+void BackgroundTracingManagerImpl::RemoveTraceMessageFilterObserver(
+    TraceMessageFilterObserver* observer) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  trace_message_filter_observers_.erase(observer);
+
+  for (auto& filter : trace_message_filters_)
+    observer->OnTraceMessageFilterRemoved(filter.get());
 }
 
 bool BackgroundTracingManagerImpl::IsTracingForTesting() {
diff --git a/content/browser/tracing/background_tracing_manager_impl.h b/content/browser/tracing/background_tracing_manager_impl.h
index 4e329a3f..d1625be 100644
--- a/content/browser/tracing/background_tracing_manager_impl.h
+++ b/content/browser/tracing/background_tracing_manager_impl.h
@@ -5,7 +5,10 @@
 #ifndef CONTENT_BROWSER_TRACING_BACKGROUND_TRACING_MANAGER_IMPL_H_
 #define CONTENT_BROWSER_TRACING_BACKGROUND_TRACING_MANAGER_IMPL_H_
 
+#include <map>
 #include <memory>
+#include <set>
+#include <string>
 
 #include "base/lazy_instance.h"
 #include "base/macros.h"
@@ -19,6 +22,7 @@
 namespace content {
 
 class BackgroundTracingRule;
+class TraceMessageFilter;
 class TracingDelegate;
 
 class BackgroundTracingManagerImpl : public BackgroundTracingManager {
@@ -40,6 +44,12 @@
     virtual ~EnabledStateObserver() = default;
   };
 
+  class TraceMessageFilterObserver {
+   public:
+    virtual void OnTraceMessageFilterAdded(TraceMessageFilter* filter) = 0;
+    virtual void OnTraceMessageFilterRemoved(TraceMessageFilter* filter) = 0;
+  };
+
   CONTENT_EXPORT static BackgroundTracingManagerImpl* GetInstance();
 
   bool SetActiveScenario(std::unique_ptr<BackgroundTracingConfig>,
@@ -64,6 +74,12 @@
   CONTENT_EXPORT void RemoveEnabledStateObserver(
       EnabledStateObserver* observer);
 
+  // Add/remove TraceMessageFilter{Observer}.
+  void AddTraceMessageFilter(TraceMessageFilter* trace_message_filter);
+  void RemoveTraceMessageFilter(TraceMessageFilter* trace_message_filter);
+  void AddTraceMessageFilterObserver(TraceMessageFilterObserver* observer);
+  void RemoveTraceMessageFilterObserver(TraceMessageFilterObserver* observer);
+
   // For tests
   void InvalidateTriggerHandlesForTesting() override;
   CONTENT_EXPORT void SetRuleTriggeredCallbackForTesting(
@@ -125,7 +141,12 @@
 
   TriggerHandle triggered_named_event_handle_;
 
-  std::vector<EnabledStateObserver*> background_tracing_observer_list_;
+  // There is no need to use base::ObserverList to store observers because we
+  // only access |background_tracing_observers_| and
+  // |trace_message_filter_observers_| from the UI thread.
+  std::set<EnabledStateObserver*> background_tracing_observers_;
+  std::set<scoped_refptr<TraceMessageFilter>> trace_message_filters_;
+  std::set<TraceMessageFilterObserver*> trace_message_filter_observers_;
 
   IdleCallback idle_callback_;
   base::Closure tracing_enabled_callback_for_testing_;
diff --git a/content/browser/tracing/background_tracing_rule.cc b/content/browser/tracing/background_tracing_rule.cc
index 527d64a..3d339f0c 100644
--- a/content/browser/tracing/background_tracing_rule.cc
+++ b/content/browser/tracing/background_tracing_rule.cc
@@ -141,8 +141,9 @@
   std::string named_event_;
 };
 
-class HistogramRule : public BackgroundTracingRule,
-                      public TracingControllerImpl::TraceMessageFilterObserver {
+class HistogramRule
+    : public BackgroundTracingRule,
+      public BackgroundTracingManagerImpl::TraceMessageFilterObserver {
  private:
   HistogramRule(const std::string& histogram_name,
                 int histogram_lower_value,
@@ -186,8 +187,8 @@
 
   ~HistogramRule() override {
     base::StatisticsRecorder::ClearCallback(histogram_name_);
-    TracingControllerImpl::GetInstance()->RemoveTraceMessageFilterObserver(
-        this);
+    BackgroundTracingManagerImpl::GetInstance()
+        ->RemoveTraceMessageFilterObserver(this);
   }
 
   // BackgroundTracingRule implementation
@@ -198,7 +199,8 @@
                    base::Unretained(this), histogram_name_,
                    histogram_lower_value_, histogram_upper_value_, repeat_));
 
-    TracingControllerImpl::GetInstance()->AddTraceMessageFilterObserver(this);
+    BackgroundTracingManagerImpl::GetInstance()->AddTraceMessageFilterObserver(
+        this);
   }
 
   void IntoDict(base::DictionaryValue* dict) const override {
@@ -231,7 +233,7 @@
             base::Unretained(BackgroundTracingManagerImpl::GetInstance())));
   }
 
-  // TracingControllerImpl::TraceMessageFilterObserver implementation
+  // BackgroundTracingManagerImpl::TraceMessageFilterObserver implementation
   void OnTraceMessageFilterAdded(TraceMessageFilter* filter) override {
     filter->Send(
         new TracingMsg_SetUMACallback(histogram_name_, histogram_lower_value_,
diff --git a/content/browser/tracing/trace_message_filter.cc b/content/browser/tracing/trace_message_filter.cc
index a0e97d1b3..dcf2ac3 100644
--- a/content/browser/tracing/trace_message_filter.cc
+++ b/content/browser/tracing/trace_message_filter.cc
@@ -4,10 +4,13 @@
 
 #include "content/browser/tracing/trace_message_filter.h"
 
+#include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "components/tracing/common/tracing_messages.h"
 #include "content/browser/tracing/background_tracing_manager_impl.h"
 #include "content/browser/tracing/tracing_controller_impl.h"
 #include "content/common/child_process_host_impl.h"
+#include "content/public/browser/browser_thread.h"
 
 namespace content {
 
@@ -31,7 +34,9 @@
     if (is_awaiting_buffer_percent_full_ack_)
       OnTraceLogStatusReply(base::trace_event::TraceLogStatus());
 
-    TracingControllerImpl::GetInstance()->RemoveTraceMessageFilter(this);
+    BrowserThread::PostTask(
+        BrowserThread::UI, FROM_HERE,
+        base::Bind(&TraceMessageFilter::Unregister, base::RetainedRef(this)));
   }
 }
 
@@ -85,7 +90,9 @@
 
 void TraceMessageFilter::OnChildSupportsTracing() {
   has_child_ = true;
-  TracingControllerImpl::GetInstance()->AddTraceMessageFilter(this);
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::Bind(&TraceMessageFilter::Register, base::RetainedRef(this)));
 }
 
 void TraceMessageFilter::OnEndTracingAck(
@@ -101,6 +108,16 @@
   }
 }
 
+void TraceMessageFilter::Register() {
+  BackgroundTracingManagerImpl::GetInstance()->AddTraceMessageFilter(this);
+  TracingControllerImpl::GetInstance()->AddTraceMessageFilter(this);
+}
+
+void TraceMessageFilter::Unregister() {
+  BackgroundTracingManagerImpl::GetInstance()->RemoveTraceMessageFilter(this);
+  TracingControllerImpl::GetInstance()->RemoveTraceMessageFilter(this);
+}
+
 void TraceMessageFilter::OnTraceDataCollected(const std::string& data) {
   scoped_refptr<base::RefCountedString> data_ptr(new base::RefCountedString());
   data_ptr->data() = data;
diff --git a/content/browser/tracing/trace_message_filter.h b/content/browser/tracing/trace_message_filter.h
index 97ae0a2..10936c0 100644
--- a/content/browser/tracing/trace_message_filter.h
+++ b/content/browser/tracing/trace_message_filter.h
@@ -49,6 +49,11 @@
   void OnTriggerBackgroundTrace(const std::string& histogram_name);
   void OnAbortBackgroundTrace();
 
+  // For registering/unregistering the filter to different tracing
+  // managers/controllers.
+  void Register();
+  void Unregister();
+
   // ChildTraceMessageFilter exists:
   bool has_child_;
 
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc
index 6bb7164..e3b447c 100644
--- a/content/browser/tracing/tracing_controller_impl.cc
+++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -30,6 +30,7 @@
 #include "content/browser/tracing/tracing_ui.h"
 #include "content/common/child_process_messages.h"
 #include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/gpu_data_manager.h"
 #include "content/public/browser/tracing_delegate.h"
@@ -407,14 +408,7 @@
 
 void TracingControllerImpl::AddTraceMessageFilter(
     TraceMessageFilter* trace_message_filter) {
-  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
-    BrowserThread::PostTask(
-        BrowserThread::UI, FROM_HERE,
-        base::Bind(&TracingControllerImpl::AddTraceMessageFilter,
-                   base::Unretained(this),
-                   base::RetainedRef(trace_message_filter)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
 #if defined(OS_LINUX)
   // On Linux the browser process dumps process metrics for child process due to
@@ -428,21 +422,11 @@
     trace_message_filter->SendBeginTracing(
         TraceLog::GetInstance()->GetCurrentTraceConfig());
   }
-
-  for (auto& observer : trace_message_filter_observers_)
-    observer.OnTraceMessageFilterAdded(trace_message_filter);
 }
 
 void TracingControllerImpl::RemoveTraceMessageFilter(
     TraceMessageFilter* trace_message_filter) {
-  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
-    BrowserThread::PostTask(
-        BrowserThread::UI, FROM_HERE,
-        base::Bind(&TracingControllerImpl::RemoveTraceMessageFilter,
-                   base::Unretained(this),
-                   base::RetainedRef(trace_message_filter)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
 #if defined(OS_LINUX)
   tracing::ProcessMetricsMemoryDumpProvider::UnregisterForProcess(
@@ -924,22 +908,4 @@
   return metadata_dict;
 }
 
-void TracingControllerImpl::AddTraceMessageFilterObserver(
-    TraceMessageFilterObserver* observer) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  trace_message_filter_observers_.AddObserver(observer);
-
-  for (auto& filter : trace_message_filters_)
-    observer->OnTraceMessageFilterAdded(filter.get());
-}
-
-void TracingControllerImpl::RemoveTraceMessageFilterObserver(
-    TraceMessageFilterObserver* observer) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  trace_message_filter_observers_.RemoveObserver(observer);
-
-  for (auto& filter : trace_message_filters_)
-    observer->OnTraceMessageFilterRemoved(filter.get());
-}
-
 }  // namespace content
diff --git a/content/browser/tracing/tracing_controller_impl.h b/content/browser/tracing/tracing_controller_impl.h
index 93d2c04c..be873583 100644
--- a/content/browser/tracing/tracing_controller_impl.h
+++ b/content/browser/tracing/tracing_controller_impl.h
@@ -87,14 +87,6 @@
       const std::string& sync_id,
       const RecordClockSyncMarkerCallback& callback) override;
 
-  class TraceMessageFilterObserver {
-   public:
-    virtual void OnTraceMessageFilterAdded(TraceMessageFilter* filter) = 0;
-    virtual void OnTraceMessageFilterRemoved(TraceMessageFilter* filter) = 0;
-  };
-  void AddTraceMessageFilterObserver(TraceMessageFilterObserver* observer);
-  void RemoveTraceMessageFilterObserver(TraceMessageFilterObserver* observer);
-
  private:
   friend struct base::LazyInstanceTraitsBase<TracingControllerImpl>;
   friend class TraceMessageFilter;
@@ -120,7 +112,8 @@
     return pending_trace_buffer_usage_callback_.is_null();
   }
 
-  // Methods for use by TraceMessageFilter.
+  // Methods for use by TraceMessageFilter. These should be called on the UI
+  // thread.
   void AddTraceMessageFilter(TraceMessageFilter* trace_message_filter);
   void RemoveTraceMessageFilter(TraceMessageFilter* trace_message_filter);
 
@@ -200,9 +193,6 @@
   GetCategoriesDoneCallback pending_get_categories_done_callback_;
   GetTraceBufferUsageCallback pending_trace_buffer_usage_callback_;
 
-  base::ObserverList<TraceMessageFilterObserver>
-      trace_message_filter_observers_;
-
   std::set<std::string> known_category_groups_;
   std::set<TracingUI*> tracing_uis_;
   scoped_refptr<TraceDataSink> trace_data_sink_;
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 1dc6cdf6..2e686b4 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1693,8 +1693,8 @@
 void RenderThreadImpl::OnProcessBackgrounded(bool backgrounded) {
   ChildThreadImpl::OnProcessBackgrounded(backgrounded);
 
+  renderer_scheduler_->SetRendererBackgrounded(backgrounded);
   if (backgrounded) {
-    renderer_scheduler_->OnRendererBackgrounded();
     needs_to_record_first_active_paint_ = false;
     GetRendererScheduler()->DefaultTaskRunner()->PostDelayedTask(
         FROM_HERE,
@@ -1714,7 +1714,6 @@
                    process_foregrounded_count_),
         base::TimeDelta::FromMinutes(15));
   } else {
-    renderer_scheduler_->OnRendererForegrounded();
     process_foregrounded_count_++;
   }
 }
@@ -2501,6 +2500,7 @@
   // scheduled by the RendererScheduler - http://crbug.com/469210.
   if (!GetContentClient()->renderer()->RunIdleHandlerWhenWidgetsHidden())
     return;
+  renderer_scheduler_->SetRendererHidden(true);
   ScheduleIdleHandler(kInitialIdleHandlerDelayMs);
 }
 
@@ -2508,6 +2508,7 @@
   blink::MainThreadIsolate()->IsolateInForegroundNotification();
   if (!GetContentClient()->renderer()->RunIdleHandlerWhenWidgetsHidden())
     return;
+  renderer_scheduler_->SetRendererHidden(false);
   ScheduleIdleHandler(kLongIdleHandlerDelayMs);
 }
 
diff --git a/content/test/data/accessibility/aria/aria-alertdialog-expected-android.txt b/content/test/data/accessibility/aria/aria-alertdialog-expected-android.txt
index a9ff011..961b9c9 100644
--- a/content/test/data/accessibility/aria/aria-alertdialog-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-alertdialog-expected-android.txt
@@ -1,2 +1,2 @@
 android.webkit.WebView focusable focused scrollable
-++android.view.View role_description='alert_dialog' live_region_type=2
\ No newline at end of file
+++android.view.View role_description='alert_dialog'
diff --git a/content/test/data/accessibility/aria/aria-alertdialog-expected-blink.txt b/content/test/data/accessibility/aria/aria-alertdialog-expected-blink.txt
new file mode 100644
index 0000000..ee6a33a
--- /dev/null
+++ b/content/test/data/accessibility/aria/aria-alertdialog-expected-blink.txt
@@ -0,0 +1,2 @@
+rootWebArea
+++alertDialog
diff --git a/content/test/data/accessibility/aria/aria-alertdialog-expected-mac.txt b/content/test/data/accessibility/aria/aria-alertdialog-expected-mac.txt
index 79b9e2d4..3dbdd687 100644
--- a/content/test/data/accessibility/aria/aria-alertdialog-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-alertdialog-expected-mac.txt
@@ -1,2 +1,2 @@
 AXWebArea AXRoleDescription='HTML content'
-++AXGroup AXSubrole=AXApplicationAlertDialog AXRoleDescription='alertdialog' AXARIABusy='0' AXARIALive='assertive'
+++AXGroup AXSubrole=AXApplicationAlertDialog AXRoleDescription='alertdialog'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-alertdialog-expected-win.txt b/content/test/data/accessibility/aria/aria-alertdialog-expected-win.txt
index 4981145..448b4d1e 100644
--- a/content/test/data/accessibility/aria/aria-alertdialog-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-alertdialog-expected-win.txt
@@ -1,2 +1,2 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
-++ROLE_SYSTEM_DIALOG xml-roles:alertdialog live:assertive relevant:additions text atomic:false busy:false container-live:assertive container-relevant:additions text container-atomic:false container-busy:false
+++ROLE_SYSTEM_DIALOG xml-roles:alertdialog
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-current-expected-blink.txt b/content/test/data/accessibility/aria/aria-current-expected-blink.txt
new file mode 100644
index 0000000..70ad9d4f
--- /dev/null
+++ b/content/test/data/accessibility/aria/aria-current-expected-blink.txt
@@ -0,0 +1,30 @@
+rootWebArea
+++link name='Section one'
+++++staticText name='Section one'
+++++++inlineTextBox name='Section one'
+++link name='Section two'
+++++staticText name='Section two'
+++++++inlineTextBox name='Section two'
+++link name='Section three' ariaCurrentState=5
+++++staticText name='Section three'
+++++++inlineTextBox name='Section three'
+++lineBreak name='<newline>'
+++heading name='Section one heading'
+++++staticText name='Section one heading'
+++++++inlineTextBox name='Section one heading'
+++heading name='Section two heading'
+++++staticText name='Section two heading'
+++++++inlineTextBox name='Section two heading'
+++heading name='Section three heading'
+++++staticText name='Section three heading'
+++++++inlineTextBox name='Section three heading'
+++lineBreak name='<newline>'
+++++inlineTextBox name='<newline>'
+++genericContainer
+++++staticText name='Span 1'
+++++++inlineTextBox name='Span 1'
+++++genericContainer ariaCurrentState=2
+++++++staticText name='Span 2'
+++++++++inlineTextBox name='Span 2'
+++++staticText name='Span 3'
+++++++inlineTextBox name='Span 3'
diff --git a/content/test/data/accessibility/aria/aria-current-expected-win.txt b/content/test/data/accessibility/aria/aria-current-expected-win.txt
index 7aaf6fb..a80fce7f 100644
--- a/content/test/data/accessibility/aria/aria-current-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-current-expected-win.txt
@@ -12,3 +12,9 @@
 ++++ROLE_SYSTEM_STATICTEXT name='Section two heading'
 ++IA2_ROLE_HEADING name='Section three heading'
 ++++ROLE_SYSTEM_STATICTEXT name='Section three heading'
+++ROLE_SYSTEM_WHITESPACE name='<newline>'
+++IA2_ROLE_SECTION
+++++ROLE_SYSTEM_STATICTEXT name='Span 1'
+++++IA2_ROLE_SECTION current:true
+++++++ROLE_SYSTEM_STATICTEXT name='Span 2'
+++++ROLE_SYSTEM_STATICTEXT name='Span 3'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-current.html b/content/test/data/accessibility/aria/aria-current.html
index ea0eb34..e6106b63 100644
--- a/content/test/data/accessibility/aria/aria-current.html
+++ b/content/test/data/accessibility/aria/aria-current.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <!--
 @WIN-ALLOW:current:*
+@BLINK-ALLOW:ariaCurrentState*
 -->
 <html>
 <body>
@@ -11,5 +12,11 @@
   <h1 id="section1">Section one heading</h1>
   <h1 id="section2">Section two heading</h1>
   <h1 id="section3">Section three heading</h1>
+  <br>
+  <div>
+    <span>Span 1</span>
+    <span aria-current="true">Span 2</span>
+    <span>Span 3</span>
+  </div>
 </body>
 </html>
diff --git a/extensions/browser/lazy_background_task_queue.h b/extensions/browser/lazy_background_task_queue.h
index 842ee0f..2f24e27a 100644
--- a/extensions/browser/lazy_background_task_queue.h
+++ b/extensions/browser/lazy_background_task_queue.h
@@ -48,9 +48,6 @@
   // |context|.
   static LazyBackgroundTaskQueue* Get(content::BrowserContext* context);
 
-  // Returns the number of extensions having pending tasks.
-  size_t extensions_with_pending_tasks() { return pending_tasks_.size(); }
-
   // Returns true if the task should be added to the queue (that is, if the
   // extension has a lazy background page that isn't ready yet). If the
   // extension has a lazy background page that is being suspended this method
@@ -69,6 +66,7 @@
       const PendingTask& task);
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(LazyBackgroundTaskQueueTest, AddPendingTask);
   FRIEND_TEST_ALL_PREFIXES(LazyBackgroundTaskQueueTest, ProcessPendingTasks);
 
   // A map between a BrowserContext/extension_id pair and the queue of tasks
diff --git a/extensions/browser/lazy_background_task_queue_unittest.cc b/extensions/browser/lazy_background_task_queue_unittest.cc
index be75177..17d6148c 100644
--- a/extensions/browser/lazy_background_task_queue_unittest.cc
+++ b/extensions/browser/lazy_background_task_queue_unittest.cc
@@ -160,7 +160,7 @@
                        no_background->id(),
                        base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
                                   base::Unretained(this)));
-  EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+  EXPECT_EQ(1u, queue.pending_tasks_.size());
   EXPECT_EQ(0, task_run_count());
 
   // Another task on the same extension doesn't increase the number of
@@ -169,7 +169,7 @@
                        no_background->id(),
                        base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
                                   base::Unretained(this)));
-  EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+  EXPECT_EQ(1u, queue.pending_tasks_.size());
   EXPECT_EQ(0, task_run_count());
 
   // Adding a task on an extension with a lazy background page tries to create
@@ -179,7 +179,7 @@
                        lazy_background->id(),
                        base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
                                   base::Unretained(this)));
-  EXPECT_EQ(2u, queue.extensions_with_pending_tasks());
+  EXPECT_EQ(2u, queue.pending_tasks_.size());
   // The process manager tried to create a background host.
   EXPECT_EQ(1, process_manager->create_count());
   // The task ran immediately because the creation failed.
@@ -201,19 +201,19 @@
                        base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
                                   base::Unretained(this)));
   EXPECT_EQ(0, task_run_count());
-  EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+  EXPECT_EQ(1u, queue.pending_tasks_.size());
 
   // Trying to run tasks for an unrelated BrowserContext should do nothing.
   content::TestBrowserContext unrelated_context;
   queue.ProcessPendingTasks(NULL, &unrelated_context, extension.get());
   EXPECT_EQ(0, task_run_count());
-  EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+  EXPECT_EQ(1u, queue.pending_tasks_.size());
 
   // Processing tasks when there is one pending runs the task and removes the
   // extension from the list of extensions with pending tasks.
   queue.ProcessPendingTasks(NULL, browser_context(), extension.get());
   EXPECT_EQ(1, task_run_count());
-  EXPECT_EQ(0u, queue.extensions_with_pending_tasks());
+  EXPECT_EQ(0u, queue.pending_tasks_.size());
 }
 
 }  // namespace extensions
diff --git a/ios/build/bots/chromium.fyi/ios-simulator.json b/ios/build/bots/chromium.fyi/ios-simulator.json
index 872baca2..65dab5a 100644
--- a/ios/build/bots/chromium.fyi/ios-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios-simulator.json
@@ -6,7 +6,6 @@
   "gn_args": [
     "additional_target_cpus=[\"x86\"]",
     "goma_dir=\"$(goma_dir)\"",
-    "ios_enable_code_signing=false",
     "is_component_build=false",
     "is_debug=true",
     "target_cpu=\"x64\"",
@@ -75,6 +74,12 @@
       "device type": "iPad Retina",
       "os": "9.0",
       "xcode version": "8.0"
+    },
+    {
+      "include": "eg_tests.json",
+      "device type": "iPhone 6s",
+      "os": "10.0",
+      "xcode version": "8.0"
     }
   ]
 }
diff --git a/ios/build/bots/chromium.mac/ios-simulator.json b/ios/build/bots/chromium.mac/ios-simulator.json
index 79d61520..d4e05cf4 100644
--- a/ios/build/bots/chromium.mac/ios-simulator.json
+++ b/ios/build/bots/chromium.mac/ios-simulator.json
@@ -6,7 +6,6 @@
   "gn_args": [
     "additional_target_cpus=[\"x86\"]",
     "goma_dir=\"$(goma_dir)\"",
-    "ios_enable_code_signing=false",
     "is_component_build=false",
     "is_debug=true",
     "target_cpu=\"x64\"",
@@ -60,6 +59,12 @@
       "device type": "iPad Air 2",
       "os": "9.0",
       "xcode version": "8.0"
+    },
+    {
+      "include": "eg_tests.json",
+      "device type": "iPhone 6s",
+      "os": "10.0",
+      "xcode version": "8.0"
     }
   ]
 }
diff --git a/ios/build/bots/tests/eg_tests.json b/ios/build/bots/tests/eg_tests.json
new file mode 100644
index 0000000..b55089c
--- /dev/null
+++ b/ios/build/bots/tests/eg_tests.json
@@ -0,0 +1,8 @@
+{
+  "tests": [
+    {
+      "app": "ios_chrome_integration_egtests",
+      "xctest": true
+    }
+  ]
+}
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_service.h b/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
index 201bb61..17054c7f 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
@@ -9,7 +9,6 @@
 #include <string>
 #include <vector>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 
@@ -118,25 +117,6 @@
       ios::ChromeBrowserState* browser_state,
       id<ChromeIdentityInteractionManagerDelegate> delegate) const;
 
-  // Deprecated. Returns a new account details controller to present. A cancel
-  // button is present as leading navigation item.
-  virtual base::scoped_nsobject<UINavigationController> NewAccountDetails(
-      ChromeIdentity* identity,
-      id<ChromeIdentityBrowserOpener> browser_opener);
-
-  // Deprecated. Returns a new Web and App Setting Details controller to
-  // present.
-  virtual base::scoped_nsobject<UINavigationController>
-  NewWebAndAppSettingDetails(ChromeIdentity* identity,
-                             id<ChromeIdentityBrowserOpener> browser_opener);
-
-  // Deprecated. Returns a new ChromeIdentityInteractionManager with |delegate|
-  // as its delegate.
-  virtual base::scoped_nsobject<ChromeIdentityInteractionManager>
-  NewChromeIdentityInteractionManager(
-      ios::ChromeBrowserState* browser_state,
-      id<ChromeIdentityInteractionManagerDelegate> delegate) const;
-
   // Returns YES if |identity| is valid and if the service has it in its list of
   // identitites.
   virtual bool IsValidIdentity(ChromeIdentity* identity) const;
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm b/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
index 05cbbbd7..c2e347e 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
@@ -43,27 +43,6 @@
   return nil;
 }
 
-base::scoped_nsobject<UINavigationController>
-ChromeIdentityService::NewAccountDetails(
-    ChromeIdentity* identity,
-    id<ChromeIdentityBrowserOpener> browser_opener) {
-  return base::scoped_nsobject<UINavigationController>();
-}
-
-base::scoped_nsobject<UINavigationController>
-ChromeIdentityService::NewWebAndAppSettingDetails(
-    ChromeIdentity* identity,
-    id<ChromeIdentityBrowserOpener> browser_opener) {
-  return base::scoped_nsobject<UINavigationController>();
-}
-
-base::scoped_nsobject<ChromeIdentityInteractionManager>
-ChromeIdentityService::NewChromeIdentityInteractionManager(
-    ios::ChromeBrowserState* browser_state,
-    id<ChromeIdentityInteractionManagerDelegate> delegate) const {
-  return base::scoped_nsobject<ChromeIdentityInteractionManager>();
-}
-
 bool ChromeIdentityService::IsValidIdentity(ChromeIdentity* identity) const {
   return false;
 }
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index 68aee907..c94914c4 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -907,7 +907,7 @@
       if (!transaction->entry())
         return;
     }
-    if (success && did_truncate) {
+    if (success && (did_truncate || is_partial)) {
       entry->writer = nullptr;
       // Restart already validated transactions so that they are able to read
       // the truncated status of the entry.
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index 9bc8326..dd17ae5 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -149,6 +149,7 @@
 
 HttpCache::Transaction::Transaction(RequestPriority priority, HttpCache* cache)
     : next_state_(STATE_NONE),
+      initial_request_(nullptr),
       request_(NULL),
       priority_(priority),
       cache_(cache->GetWeakPtr()),
@@ -270,7 +271,8 @@
   if (!cache_.get())
     return ERR_UNEXPECTED;
 
-  SetRequest(net_log, request);
+  initial_request_ = request;
+  SetRequest(net_log);
 
   // We have to wait until the backend is initialized so we start the SM.
   next_state_ = STATE_GET_BACKEND;
@@ -984,9 +986,6 @@
   // This is only set if we have something to do with the response.
   range_requested_ = (partial_.get() != NULL);
 
-  // mode_ may change later, save the initial mode in case we need to restart
-  // this request.
-  restart_info_.mode = mode_;
   return OK;
 }
 
@@ -1822,13 +1821,14 @@
   // If its the Start state machine and it cannot proceed due to a cache
   // failure, restart this transaction.
   DCHECK(!reading_);
-  TransitionToState(STATE_INIT_ENTRY);
-  cache_entry_status_ = restart_info_.cache_entry_status;
+
+  SetRequest(net_log_);
+
   entry_ = nullptr;
-  mode_ = restart_info_.mode;
   if (network_trans_)
     network_trans_.reset();
 
+  TransitionToState(STATE_GET_BACKEND);
   return OK;
 }
 
@@ -2053,10 +2053,17 @@
 
 //-----------------------------------------------------------------------------
 
-void HttpCache::Transaction::SetRequest(const NetLogWithSource& net_log,
-                                        const HttpRequestInfo* request) {
+void HttpCache::Transaction::SetRequest(const NetLogWithSource& net_log) {
+  // Reset the variables that might get set in this function. This is done
+  // because this function can be invoked multiple times for a transaction.
+  cache_entry_status_ = CacheEntryStatus::ENTRY_UNDEFINED;
+  external_validation_.Reset();
+  range_requested_ = false;
+  partial_.reset();
+  custom_request_.reset();
+
   net_log_ = net_log;
-  request_ = request;
+  request_ = initial_request_;
   effective_load_flags_ = request_->load_flags;
   method_ = request_->method;
 
@@ -2152,7 +2159,6 @@
       partial_.reset(NULL);
     }
   }
-  restart_info_.cache_entry_status = cache_entry_status_;
 }
 
 bool HttpCache::Transaction::ShouldPassThrough() {
diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h
index 080d8b9..79274f0 100644
--- a/net/http/http_cache_transaction.h
+++ b/net/http/http_cache_transaction.h
@@ -183,6 +183,11 @@
     ValidationHeaders() : initialized(false) {}
 
     std::string values[kNumValidationHeaders];
+    void Reset() {
+      initialized = false;
+      for (auto& value : values)
+        value.clear();
+    }
     bool initialized;
   };
 
@@ -320,8 +325,7 @@
   int DoCacheWriteTruncatedResponseComplete(int result);
 
   // Sets request_ and fields derived from it.
-  void SetRequest(const NetLogWithSource& net_log,
-                  const HttpRequestInfo* request);
+  void SetRequest(const NetLogWithSource& net_log);
 
   // Returns true if the request should be handled exclusively by the network
   // layer (skipping the cache entirely).
@@ -465,7 +469,12 @@
   void TransitionToState(State state);
 
   State next_state_;
+
+  // Initial request with which Start() was invoked.
+  const HttpRequestInfo* initial_request_;
+
   const HttpRequestInfo* request_;
+
   std::string method_;
   RequestPriority priority_;
   NetLogWithSource net_log_;
@@ -542,12 +551,6 @@
   // True if the Transaction is currently processing the DoLoop.
   bool in_do_loop_;
 
-  // Used to restore some members when the state machine is restarted after it
-  // has already been added to an entry e.g after |this| has completed
-  // validation and the writer transaction fails to completely write the
-  // response to the cache.
-  RestartInfo restart_info_;
-
   base::WeakPtrFactory<Transaction> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(Transaction);
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 62269b79..d2f0971 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -1492,15 +1492,15 @@
 
   // Let 1st transaction complete headers phase for ranges 40-49.
   std::string first_read;
+  MockHttpRequest request1(transaction);
   {
-    MockHttpRequest request(transaction);
     auto& c = context_list[0];
     c->result = cache.CreateTransaction(&c->trans);
     ASSERT_THAT(c->result, IsOk());
     EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
 
     c->result =
-        c->trans->Start(&request, c->callback.callback(), NetLogWithSource());
+        c->trans->Start(&request1, c->callback.callback(), NetLogWithSource());
     base::RunLoop().RunUntilIdle();
 
     // Start writing to the cache so that MockDiskEntry::CouldBeSparse() returns
@@ -1518,16 +1518,16 @@
   }
 
   // 2nd transaction requests ranges 30-39.
+  transaction.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER;
+  MockHttpRequest request2(transaction);
   {
-    transaction.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER;
-    MockHttpRequest request(transaction);
     auto& c = context_list[1];
     c->result = cache.CreateTransaction(&c->trans);
     ASSERT_THAT(c->result, IsOk());
     EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
 
     c->result =
-        c->trans->Start(&request, c->callback.callback(), NetLogWithSource());
+        c->trans->Start(&request2, c->callback.callback(), NetLogWithSource());
     base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
@@ -1590,15 +1590,15 @@
 
   // Let 1st transaction complete headers phase for ranges 40-49.
   std::string first_read;
+  MockHttpRequest request1(transaction);
   {
-    MockHttpRequest request(transaction);
     auto& c = context_list[0];
     c->result = cache.CreateTransaction(&c->trans);
     ASSERT_THAT(c->result, IsOk());
     EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
 
     c->result =
-        c->trans->Start(&request, c->callback.callback(), NetLogWithSource());
+        c->trans->Start(&request1, c->callback.callback(), NetLogWithSource());
     base::RunLoop().RunUntilIdle();
 
     // Start writing to the cache so that MockDiskEntry::CouldBeSparse() returns
@@ -1616,16 +1616,16 @@
   }
 
   // 2nd transaction requests ranges 30-49.
+  transaction.request_headers = "Range: bytes = 30-49\r\n" EXTRA_HEADER;
+  MockHttpRequest request2(transaction);
   {
-    transaction.request_headers = "Range: bytes = 30-49\r\n" EXTRA_HEADER;
-    MockHttpRequest request(transaction);
     auto& c = context_list[1];
     c->result = cache.CreateTransaction(&c->trans);
     ASSERT_THAT(c->result, IsOk());
     EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
 
     c->result =
-        c->trans->Start(&request, c->callback.callback(), NetLogWithSource());
+        c->trans->Start(&request2, c->callback.callback(), NetLogWithSource());
     base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
@@ -1674,6 +1674,102 @@
   EXPECT_EQ(1, cache.disk_cache()->create_count());
 }
 
+// Tests parallel validation on range requests with overlapping ranges and the
+// impact of deleting the writer on transactions that have validated.
+TEST(HttpCache, RangeGET_ParallelValidationRestartDoneHeaders) {
+  MockHttpCache cache;
+
+  ScopedMockTransaction transaction(kRangeGET_TransactionOK);
+
+  std::vector<std::unique_ptr<Context>> context_list;
+  const int kNumTransactions = 2;
+
+  for (int i = 0; i < kNumTransactions; ++i) {
+    context_list.push_back(base::MakeUnique<Context>());
+  }
+
+  // Let 1st transaction complete headers phase for ranges 40-59.
+  std::string first_read;
+  transaction.request_headers = "Range: bytes = 40-59\r\n" EXTRA_HEADER;
+  MockHttpRequest request1(transaction);
+  {
+    auto& c = context_list[0];
+    c->result = cache.CreateTransaction(&c->trans);
+    ASSERT_THAT(c->result, IsOk());
+    EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
+
+    c->result =
+        c->trans->Start(&request1, c->callback.callback(), NetLogWithSource());
+    base::RunLoop().RunUntilIdle();
+
+    // Start writing to the cache so that MockDiskEntry::CouldBeSparse() returns
+    // true.
+    const int kBufferSize = 10;
+    scoped_refptr<IOBuffer> buffer(new IOBuffer(kBufferSize));
+    ReleaseBufferCompletionCallback cb(buffer.get());
+    c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback());
+    EXPECT_EQ(kBufferSize, cb.GetResult(c->result));
+
+    std::string data_read(buffer->data(), kBufferSize);
+    first_read = data_read;
+
+    EXPECT_EQ(LOAD_STATE_READING_RESPONSE, c->trans->GetLoadState());
+  }
+
+  // 2nd transaction requests ranges 30-59.
+  transaction.request_headers = "Range: bytes = 30-59\r\n" EXTRA_HEADER;
+  MockHttpRequest request2(transaction);
+  {
+    auto& c = context_list[1];
+    c->result = cache.CreateTransaction(&c->trans);
+    ASSERT_THAT(c->result, IsOk());
+    EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
+
+    c->result =
+        c->trans->Start(&request2, c->callback.callback(), NetLogWithSource());
+    base::RunLoop().RunUntilIdle();
+
+    EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState());
+  }
+
+  EXPECT_TRUE(cache.IsWriterPresent(kRangeGET_TransactionOK.url));
+  EXPECT_EQ(1, cache.GetCountDoneHeadersQueue(kRangeGET_TransactionOK.url));
+
+  EXPECT_EQ(2, cache.network_layer()->transaction_count());
+  EXPECT_EQ(0, cache.disk_cache()->open_count());
+  EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+  // Delete the writer transaction.
+  context_list[0].reset();
+
+  base::RunLoop().RunUntilIdle();
+
+  transaction.data = "rg: 30-39 rg: 40-49 rg: 50-59 ";
+  ReadAndVerifyTransaction(context_list[1]->trans.get(), transaction);
+
+  // Create another network transaction since the 2nd transaction is restarted.
+  // 30-39 will be read from network, 40-49 from the cache and 50-59 from the
+  // network.
+  EXPECT_EQ(4, cache.network_layer()->transaction_count());
+  EXPECT_EQ(0, cache.disk_cache()->open_count());
+  EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+  // Fetch from the cache to check that ranges 30-49 have been successfully
+  // cached.
+  {
+    MockTransaction transaction(kRangeGET_TransactionOK);
+    transaction.request_headers = "Range: bytes = 30-49\r\n" EXTRA_HEADER;
+    transaction.data = "rg: 30-39 rg: 40-49 ";
+    std::string headers;
+    RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+    Verify206Response(headers, 30, 49);
+  }
+
+  EXPECT_EQ(4, cache.network_layer()->transaction_count());
+  EXPECT_EQ(1, cache.disk_cache()->open_count());
+  EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
 // Parallel validation results in 200.
 TEST(HttpCache, SimpleGET_ParallelValidationNoMatch) {
   MockHttpCache cache;
diff --git a/net/quic/chromium/quic_http_stream.cc b/net/quic/chromium/quic_http_stream.cc
index 140dbc0a..8e27f43 100644
--- a/net/quic/chromium/quic_http_stream.cc
+++ b/net/quic/chromium/quic_http_stream.cc
@@ -608,12 +608,13 @@
   bool has_upload_data = request_body_stream_ != nullptr;
 
   next_state_ = STATE_SEND_HEADERS_COMPLETE;
-  size_t frame_len = stream_->WriteHeaders(std::move(request_headers_),
-                                           !has_upload_data, nullptr);
-  headers_bytes_sent_ += frame_len;
+  int rv = stream_->WriteHeaders(std::move(request_headers_), !has_upload_data,
+                                 nullptr);
+  if (rv > 0)
+    headers_bytes_sent_ += rv;
 
   request_headers_ = SpdyHeaderBlock();
-  return static_cast<int>(frame_len);
+  return rv;
 }
 
 int QuicHttpStream::DoSendHeadersComplete(int rv) {
diff --git a/net/quic/chromium/quic_http_stream_test.cc b/net/quic/chromium/quic_http_stream_test.cc
index d22ea64..9e48dc9b 100644
--- a/net/quic/chromium/quic_http_stream_test.cc
+++ b/net/quic/chromium/quic_http_stream_test.cc
@@ -1530,6 +1530,9 @@
                                       net_log_.bound(), callback_.callback()));
   ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
             stream_->SendRequest(headers_, &response_, callback_.callback()));
+
+  EXPECT_LE(0, stream_->GetTotalSentBytes());
+  EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
 }
 
 TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendBodyComplete) {
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index ab2057d..443cbfbf 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -1606,7 +1606,7 @@
             }
           ],
           "expiration": 10800,
-          "hard_timeout": 960,
+          "hard_timeout": 1200,
           "output_links": [
             {
               "link": [
@@ -1616,7 +1616,8 @@
               ],
               "name": "shard #${SHARD_INDEX} logcats"
             }
-          ]
+          ],
+          "shards": 2
         },
         "test": "unit_tests"
       },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index a9141a1..410a968 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -504,6 +504,21 @@
             ]
         }
     ],
+    "CustomFeedbackUi": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "CustomFeedbackUi"
+                    ]
+                }
+            ]
+        }
+    ],
     "DataCompressionProxyDevRollout": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index d7022c0..97f3895 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -9798,7 +9798,6 @@
 crbug.com/591099 fast/css/rtl-ordering.html [ Failure ]
 crbug.com/591099 fast/css/rtl-to-viewport.html [ Failure ]
 crbug.com/591099 fast/css/run-in-crash.html [ Failure ]
-crbug.com/591099 fast/css/scroll-snap-parsing.html [ Failure ]
 crbug.com/591099 fast/css/scrollbar-crash.html [ Failure ]
 crbug.com/591099 fast/css/selector-set-attribute.html [ Failure ]
 crbug.com/591099 fast/css/selector-text-escape.html [ Crash Failure ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 04a4f551..1989869 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1233,8 +1233,6 @@
 crbug.com/498845 [ Win ] fast/multicol/vertical-rl/float-content-break.html [ Failure ]
 crbug.com/443615 [ Linux Win ] external/wpt/css/css-shapes-1/shape-outside/supported-shapes/circle/shape-outside-circle-027.html [ Failure ]
 
-crbug.com/443379 external/wpt/gamepad/idlharness.html [ Failure Timeout ]
-
 crbug.com/381684 [ Mac Win ] fonts/family-fallback-gardiner.html [ Skip ]
 crbug.com/467635 fast/dom/HTMLImageElement/image-sizes-meta-viewport.html [ Skip ]
 
@@ -1519,12 +1517,6 @@
 crbug.com/582836 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-02.html [ Failure ]
 
 crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-break-inside-001a.html [ Failure ]
-crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-001.html [ Failure ]
-crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-002.html [ Failure ]
-crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-003.html [ Failure ]
-crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-004.html [ Failure ]
-crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-005.html [ Failure ]
-crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-005a.html [ Failure ]
 crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-006.html [ Failure ]
 crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-006a.html [ Failure ]
 crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-007.html [ Failure ]
@@ -1815,10 +1807,10 @@
 
 # These wpt/mixed-content tests have console errors that are different on different test runs.
 crbug.com/679742 external/wpt/mixed-content/allowed/http-csp/same-host-wss/websocket-request/top-level/keep-scheme-redirect/websocket-allowed.https.html [ Failure ]
+crbug.com/679742 external/wpt/mixed-content/allowed/http-csp/same-host-wss/websocket-request/top-level/no-redirect/websocket-allowed.https.html [ Failure ]
 crbug.com/679742 external/wpt/mixed-content/allowed/meta-csp/same-host-wss/websocket-request/top-level/no-redirect/websocket-allowed.https.html [ Failure ]
 crbug.com/679742 external/wpt/mixed-content/allowed/no-opt-in/same-host-wss/websocket-request/top-level/keep-scheme-redirect/websocket-allowed.https.html [ Failure ]
 crbug.com/679742 external/wpt/mixed-content/allowed/no-opt-in/same-host-wss/websocket-request/top-level/no-redirect/websocket-allowed.https.html [ Failure ]
-crbug.com/679742 external/wpt/mixed-content/allowed/http-csp/same-host-wss/websocket-request/top-level/no-redirect/websocket-allowed.https.html [ Failure ]
 
 # These wpt/mixed-content tests consistently time out.
 crbug.com/626703 external/wpt/mixed-content/blockable/http-csp/cross-origin-http/form-tag/top-level/keep-scheme-redirect/opt-in-blocks.https.html [ Timeout ]
@@ -1850,14 +1842,6 @@
 crbug.com/626703 external/wpt/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/no-redirect/no-opt-in-blocks.https.html [ Timeout ]
 crbug.com/626703 external/wpt/mixed-content/blockable/no-opt-in/same-host-http/iframe-tag/top-level/swap-scheme-redirect/no-opt-in-blocks.https.html [ Timeout ]
 
-# These expectations are temporarily commented-out while the test directory is disabled (see top of file).
-crbug.com/695270 [ Linux ] external/wpt/css/css-writing-modes-3/overconstrained-rel-pos-ltr-top-bottom-vlr-003.xht [ Failure ]
-crbug.com/695270 [ Linux ] external/wpt/css/css-writing-modes-3/overconstrained-rel-pos-ltr-top-bottom-vrl-002.xht [ Failure ]
-crbug.com/695270 [ Linux ] external/wpt/css/css-writing-modes-3/overconstrained-rel-pos-rtl-left-right-vlr-009.xht [ Failure ]
-crbug.com/695270 [ Linux ] external/wpt/css/css-writing-modes-3/overconstrained-rel-pos-rtl-left-right-vrl-008.xht [ Failure ]
-crbug.com/695270 [ Linux ] external/wpt/css/css-writing-modes-3/overconstrained-rel-pos-rtl-top-bottom-vlr-007.xht [ Failure ]
-crbug.com/695270 [ Linux ] external/wpt/css/css-writing-modes-3/overconstrained-rel-pos-rtl-top-bottom-vrl-006.xht [ Failure ]
-
 crbug.com/626703 external/wpt/domxpath/001.html [ Failure ]
 
 # These HTTP tests that started failing after a web-platform-tests import.
@@ -1966,12 +1950,10 @@
 crbug.com/626703 [ Android Linux Win ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-baseline-multi-item-vert-001b.html [ Failure ]
 crbug.com/626703 [ Android Mac ] external/wpt/pointerevents/pointerevent_disabled_form_control-manual.html [ Timeout ]
 crbug.com/626703 [ Mac ] external/wpt/pointerevents/extension/pointerevent_coalesced_events_attributes-manual.html [ Timeout ]
-crbug.com/626703 crbug.com/717157 external/wpt/fetch/api/basic/integrity-sharedworker.html [ Crash Timeout ]
 crbug.com/626703 external/wpt/compat/webkit-text-fill-color-property-005.html [ Failure ]
 crbug.com/626703 external/wpt/content-security-policy/connect-src/connect-src-eventsource-blocked.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-display-3/display-contents-dynamic-before-after-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-display-3/display-contents-dynamic-before-after-first-letter-001.html [ Failure ]
-crbug.com/626703 external/wpt/css/css-ui-3/box-sizing-026.html [ Failure ]
 crbug.com/626703 external/wpt/css/CSS2/linebox/inline-formatting-context-012.xht [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001a.xhtml [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001b.xhtml [ Failure ]
@@ -1985,7 +1967,6 @@
 crbug.com/626703 external/wpt/cssom-view/matchMedia.xht [ Timeout ]
 crbug.com/626703 external/wpt/eventsource/eventsource-request-cancellation.htm [ Timeout ]
 crbug.com/626703 external/wpt/fullscreen/api/document-exit-fullscreen-nested-in-iframe-manual.html [ Timeout Pass ]
-crbug.com/626703 external/wpt/html/browsers/offline/no-appcache-in-shared-workers-historical.html [ Timeout ]
 crbug.com/626703 external/wpt/html/browsers/the-window-object/window-open-noopener.html [ Timeout ]
 crbug.com/626703 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html [ Timeout ]
 crbug.com/626703 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html [ Timeout ]
@@ -2001,13 +1982,9 @@
 crbug.com/626703 external/wpt/html/webappapis/idle-callbacks/callback-exception.html [ Timeout ]
 crbug.com/626703 external/wpt/html/webappapis/idle-callbacks/callback-iframe.html [ Timeout ]
 crbug.com/626703 external/wpt/html/webappapis/idle-callbacks/callback-invoked.html [ Timeout ]
-crbug.com/626703 external/wpt/html/webappapis/idle-callbacks/callback-multiple-calls.html [ Timeout ]
 crbug.com/626703 external/wpt/html/webappapis/idle-callbacks/callback-timeout.html [ Timeout ]
 crbug.com/626703 external/wpt/html/webappapis/idle-callbacks/cancel-invoked.html [ Timeout ]
 crbug.com/626703 external/wpt/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html [ Timeout ]
-crbug.com/626703 external/wpt/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html [ Timeout ]
-crbug.com/626703 external/wpt/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html [ Timeout ]
-crbug.com/626703 external/wpt/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html [ Timeout ]
 crbug.com/626703 external/wpt/http/basic-auth-cache-test.html [ Timeout ]
 crbug.com/626703 external/wpt/media-source/mediasource-appendbuffer-quota-exceeded.html [ Timeout ]
 crbug.com/626703 external/wpt/media-source/mediasource-avtracks.html [ Failure Crash ]
@@ -2045,33 +2022,7 @@
 crbug.com/626703 external/wpt/secure-contexts/basic-shared-worker.https.html [ Timeout ]
 crbug.com/626703 external/wpt/secure-contexts/shared-worker-insecure-first.https.html [ Timeout ]
 crbug.com/626703 external/wpt/secure-contexts/shared-worker-secure-first.https.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/byte-length-queuing-strategy.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/count-queuing-strategy.html [ Failure Timeout ]
-crbug.com/626703 external/wpt/streams/count-queuing-strategy.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/close-propagation-backward.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/close-propagation-forward.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/error-propagation-backward.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/error-propagation-forward.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/flow-control.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/general.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/multiple-propagation.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/pipe-through.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/piping/transform-streams.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/bad-strategies.html [ Failure Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/bad-strategies.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/bad-underlying-sources.html [ Failure Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/bad-underlying-sources.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/brand-checks.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/cancel.html [ Failure Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/cancel.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/count-queuing-strategy-integration.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/default-reader.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/garbage-collection.sharedworker.html [ Timeout ]
 crbug.com/626703 external/wpt/streams/readable-streams/general.html [ Failure Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/general.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/pipe-through.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/tee.sharedworker.html [ Timeout ]
-crbug.com/626703 external/wpt/streams/readable-streams/templated.sharedworker.html [ Timeout ]
 crbug.com/626703 external/wpt/svg/linking/reftests/href-filter-element.html [ Failure ]
 crbug.com/626703 external/wpt/uievents/interface/click-event-manual.htm [ Timeout ]
 crbug.com/626703 external/wpt/uievents/interface/dblclick-event-manual.htm [ Timeout ]
@@ -2660,7 +2611,6 @@
 crbug.com/657748 external/wpt/css/css-display-3/display-contents-dynamic-table-001-inline.html [ Failure ]
 crbug.com/657748 external/wpt/css/css-display-3/display-contents-flex-002.html [ Failure ]
 crbug.com/657748 external/wpt/css/css-display-3/display-contents-flex-003.html [ Failure ]
-crbug.com/657748 external/wpt/css/css-display-3/display-contents-computed-style.html [ Failure ]
 
 # ====== End of display: contents tests ======
 
diff --git a/third_party/WebKit/LayoutTests/external/PRESUBMIT.py b/third_party/WebKit/LayoutTests/external/PRESUBMIT.py
index bf6c0a8..e1c225a 100644
--- a/third_party/WebKit/LayoutTests/external/PRESUBMIT.py
+++ b/third_party/WebKit/LayoutTests/external/PRESUBMIT.py
@@ -2,46 +2,53 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Lint functionality duplicated from WPT upstream.
+"""Lint functionality duplicated from web-platform-tests upstream.
 
 This is to catch lint errors that would otherwise be caught in WPT CI.
+See http://web-platform-tests.org/writing-tests/lint-tool.html for more
+information about the lint tool.
 """
 
+
 def _LintWPT(input_api, output_api):
     wpt_path = input_api.os_path.join(input_api.PresubmitLocalPath(), 'wpt')
-    linter_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
-        '..', '..', 'Tools', 'Scripts', 'webkitpy', 'thirdparty', 'wpt', 'wpt', 'lint')
+    linter_path = input_api.os_path.join(
+        input_api.PresubmitLocalPath(), '..', '..', 'Tools', 'Scripts',
+        'webkitpy', 'thirdparty', 'wpt', 'wpt', 'lint')
 
     paths_in_wpt = []
-    for file in input_api.AffectedFiles():
-        abs_path = file.AbsoluteLocalPath()
+    for f in input_api.AffectedFiles():
+        abs_path = f.AbsoluteLocalPath()
         if abs_path.startswith(wpt_path):
             paths_in_wpt.append(abs_path[len(wpt_path) + 1:])
 
+    # If there are changes in LayoutTests/external that aren't in wpt, e.g.
+    # changes to wpt_automation or this presubmit script, then we can return
+    # to avoid running the linter on all files in wpt (which is slow).
+    if not paths_in_wpt:
+        return []
+
     args = [
         input_api.python_executable,
         linter_path,
-        '--repo-root', wpt_path,
-        '-ignore-glob' '"*-expected.txt"'
+        '--repo-root=%s' % wpt_path,
+        '--ignore-glob=*-expected.txt',
     ] + paths_in_wpt
 
-    stdout_data, _ = input_api.subprocess.Popen(
+    proc = input_api.subprocess.Popen(
         args,
         stdout=input_api.subprocess.PIPE,
-        stderr=input_api.subprocess.PIPE).communicate()
+        stderr=input_api.subprocess.PIPE)
+    stdout, _ = proc.communicate()
 
-    if stdout_data:
-        return [output_api.PresubmitError(stdout_data)]
+    if stdout:
+        return [output_api.PresubmitError(stdout)]
     return []
 
 
 def CheckChangeOnUpload(input_api, output_api):
-    results = []
-    results.extend(_LintWPT(input_api, output_api))
-    return results
+    return _LintWPT(input_api, output_api)
 
 
 def CheckChangeOnCommit(input_api, output_api):
-    results = []
-    results.extend(_LintWPT(input_api, output_api))
-    return results
+    return _LintWPT(input_api, output_api)
diff --git a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt
index e1e19f09..182d8a9 100644
--- a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt
@@ -241,11 +241,6 @@
 ry: auto
 scale: none
 scroll-behavior: auto
-scroll-snap-coordinate: none
-scroll-snap-destination: 0px 0px
-scroll-snap-points-x: none
-scroll-snap-points-y: none
-scroll-snap-type: none
 shape-image-threshold: 0
 shape-margin: 0px
 shape-outside: none
diff --git a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
index 95a01f87..9cd914c 100644
--- a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
@@ -241,11 +241,6 @@
 ry: auto
 scale: none
 scroll-behavior: auto
-scroll-snap-coordinate: none
-scroll-snap-destination: 0px 0px
-scroll-snap-points-x: none
-scroll-snap-points-y: none
-scroll-snap-type: none
 shape-image-threshold: 0
 shape-margin: 0px
 shape-outside: none
diff --git a/third_party/WebKit/LayoutTests/fast/css/scroll-snap-parsing-expected.txt b/third_party/WebKit/LayoutTests/fast/css/scroll-snap-parsing-expected.txt
deleted file mode 100644
index 914ef142..0000000
--- a/third_party/WebKit/LayoutTests/fast/css/scroll-snap-parsing-expected.txt
+++ /dev/null
@@ -1,126 +0,0 @@
-Test the parsing and application of the scroll-snap-* properties.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-Test case: inherited type
-PASS style.scrollSnapType is "mandatory"
-
-Test case: initial type
-PASS style.scrollSnapType is "none"
-
-Test case: inherited points-x
-PASS style.scrollSnapPointsX is "repeat(20%)"
-
-Test case: initial points-x
-PASS style.scrollSnapPointsX is "none"
-
-Test case: inherited points-y
-PASS style.scrollSnapPointsY is "repeat(20%)"
-
-Test case: initial points-y
-PASS style.scrollSnapPointsY is "none"
-
-Test case: inherited destination
-PASS style.scrollSnapDestination is "20px 20px"
-
-Test case: initial destination
-PASS style.scrollSnapDestination is "0px 0px"
-
-Test case: inherited coordinate
-PASS style.scrollSnapCoordinate is "10px 10px"
-
-Test case: initial coordinate
-PASS style.scrollSnapCoordinate is "none"
-
-Test case: mandatory type
-PASS style.scrollSnapType is "mandatory"
-
-Test case: proximity type
-PASS style.scrollSnapType is "proximity"
-
-Test case: none type
-PASS style.scrollSnapType is "none"
-
-Test case: percentage points repeat along x axis
-PASS style.scrollSnapPointsX is "repeat(100%)"
-
-Test case: pixel points repeat along x axis
-PASS style.scrollSnapPointsX is "repeat(25px)"
-
-Test case: percentage points repeat along y axis
-PASS style.scrollSnapPointsY is "repeat(100%)"
-
-Test case: pixel points repeat along y axis
-PASS style.scrollSnapPointsY is "repeat(25px)"
-
-Test case: calc repeat along y axis
-PASS style.scrollSnapPointsY is "repeat(calc(25px + 1%))"
-
-Test case: reject zero
-PASS style.scrollSnapPointsY is "none"
-
-Test case: reject negative
-PASS style.scrollSnapPointsY is "none"
-
-Test case: pixel/pixel destination
-PASS style.scrollSnapDestination is "10px 50px"
-
-Test case: pixel/percentage destination
-PASS style.scrollSnapDestination is "20px 40%"
-
-Test case: unitless/pixel destination
-PASS style.scrollSnapDestination is "0px 10px"
-
-Test case: percentage/pixel destination
-PASS style.scrollSnapDestination is "0% 0px"
-
-Test case: percentage/percentage destination
-PASS style.scrollSnapDestination is "5% 100%"
-
-Test case: calc/percentage destination
-PASS style.scrollSnapDestination is "calc(20px + 10%) 40%"
-
-Test case: 3 piece percentage destination
-PASS style.scrollSnapDestination is "0px 0px"
-
-Test case: 1 piece destination with implied center
-PASS style.scrollSnapDestination is "50% 0%"
-
-Test case: single pixel coordinate
-PASS style.scrollSnapCoordinate is "50px 100px"
-
-Test case: single percentage coordinate
-PASS style.scrollSnapCoordinate is "50% 100%"
-
-Test case: 3 piece percentage coordinate
-PASS style.scrollSnapCoordinate is "none"
-
-Test case: 4 piece pixel coordinate
-PASS style.scrollSnapCoordinate is "10px 15px"
-
-Test case: 1 piece coordinate with implied center
-PASS style.scrollSnapCoordinate is "0% 50%"
-
-Test case: multiple pixel coordinates
-PASS style.scrollSnapCoordinate is "50px 100px, 150px 100px, 200px 100px"
-
-Test case: multiple percentage coordinates
-PASS style.scrollSnapCoordinate is "50% 100%, 0% 100%, 200% 100%"
-
-Test case: multiple mixed pixel/percentage/calc coordinates
-PASS style.scrollSnapCoordinate is "calc(100px + 10%) 100%, 150% 50%, 200px calc(10px + 5%)"
-
-Test case: reject invalid position list
-PASS style.scrollSnapCoordinate is "none"
-
-Test case: reject invalid position separator
-PASS style.scrollSnapCoordinate is "none"
-
-Test case: reject invalid position with terminating comma
-PASS style.scrollSnapCoordinate is "none"
-
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/fast/css/scroll-snap-parsing.html b/third_party/WebKit/LayoutTests/fast/css/scroll-snap-parsing.html
deleted file mode 100644
index c88349b..0000000
--- a/third_party/WebKit/LayoutTests/fast/css/scroll-snap-parsing.html
+++ /dev/null
@@ -1,83 +0,0 @@
-<!DOCTYPE html>
-<script src="../../resources/js-test.js"></script>
-<style>
-div#container {
-	scroll-snap-type: mandatory;
-	scroll-snap-points-x: repeat(20%);
-	scroll-snap-points-y: repeat(20%);
-	scroll-snap-destination: 20px 20px;
-	scroll-snap-coordinate: 10px 10px;
-}
-</style>
-
-<!-- test initial and inherited values first -->
-<div id="container">
-	<div class="test" property="scroll-snap-type" style="scroll-snap-type: inherit;" expected="mandatory" desc="inherited type" ></div>
-	<div class="test" property="scroll-snap-type" style="scroll-snap-type: initial;" expected="none" desc="initial type" ></div>
-	<div class="test" property="scroll-snap-points-x" style="scroll-snap-points-x: inherit;" expected="repeat(20%)" desc="inherited points-x" ></div>
-	<div class="test" property="scroll-snap-points-x" style="scroll-snap-points-x: initial;" expected="none" desc="initial points-x" ></div>
-	<div class="test" property="scroll-snap-points-y" style="scroll-snap-points-y: inherit;" expected="repeat(20%)" desc="inherited points-y" ></div>
-	<div class="test" property="scroll-snap-points-y" style="scroll-snap-points-y: initial;" expected="none" desc="initial points-y" ></div>
-	<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: inherit;" expected="20px 20px" desc="inherited destination" ></div>
-	<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: initial;" expected="0px 0px" desc="initial destination" ></div>
-	<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: inherit;" expected="10px 10px" desc="inherited coordinate" ></div>
-	<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: initial;" expected="none" desc="initial coordinate" ></div>
-</div>
-
-<div class="test" property="scroll-snap-type" style="scroll-snap-type: mandatory;" expected="mandatory" desc="mandatory type" ></div>
-<div class="test" property="scroll-snap-type" style="scroll-snap-type: proximity;" expected="proximity" desc="proximity type" ></div>
-<div class="test" property="scroll-snap-type" style="scroll-snap-type: none;" expected="none" desc="none type" ></div>
-
-<div class="test" property="scroll-snap-points-x" style="scroll-snap-points-x: repeat(100%);" expected="repeat(100%)" desc="percentage points repeat along x axis" ></div>
-<div class="test" property="scroll-snap-points-x" style="scroll-snap-points-x: repeat(25px);" expected="repeat(25px)" desc="pixel points repeat along x axis" ></div>
-<div class="test" property="scroll-snap-points-y" style="scroll-snap-points-y: repeat(100%);" expected="repeat(100%)" desc="percentage points repeat along y axis" ></div>
-<div class="test" property="scroll-snap-points-y" style="scroll-snap-points-y: repeat(25px);" expected="repeat(25px)" desc="pixel points repeat along y axis" ></div>
-<div class="test" property="scroll-snap-points-y" style="scroll-snap-points-y: repeat(calc(25px + 1%));" expected="repeat(calc(25px + 1%))" desc="calc repeat along y axis" ></div>
-<div class="test" property="scroll-snap-points-y" style="scroll-snap-points-x: repeat(0px);" expected="none" desc="reject zero" ></div>
-<div class="test" property="scroll-snap-points-y" style="scroll-snap-points-y: repeat(-1px);" expected="none" desc="reject negative" ></div>
-
-
-
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: 10px 50px;" expected="10px 50px" desc="pixel/pixel destination" ></div>
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: 20px 40%;" expected="20px 40%" desc="pixel/percentage destination" ></div>
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: 0 10px;" expected="0px 10px" desc="unitless/pixel destination" ></div>
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: 0% 0px;" expected="0% 0px" desc="percentage/pixel destination" ></div>
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: 5% 100%;" expected="5% 100%" desc="percentage/percentage destination" ></div>
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: calc(10% + 20px) 40%;" expected="calc(20px + 10%) 40%" desc="calc/percentage destination" ></div>
-
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: left top 50%;" expected="0px 0px" desc="3 piece percentage destination" ></div>
-<div class="test" property="scroll-snap-destination" style="scroll-snap-destination: top;" expected="50% 0%" desc="1 piece destination with implied center" ></div>
-
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: 50px 100px;" expected="50px 100px" desc="single pixel coordinate" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: 50% 100%;" expected="50% 100%" desc="single percentage coordinate" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: left top 50%;" expected="none" desc="3 piece percentage coordinate" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: left 10px top 15px;" expected="10px 15px" desc="4 piece pixel coordinate" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: left;" expected="0% 50%" desc="1 piece coordinate with implied center" ></div>
-
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: 50px 100px, 150px 100px, left 200px top 100px;" expected="50px 100px, 150px 100px, 200px 100px" desc="multiple pixel coordinates" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: 50% 100%, left 0% top 100%, 200% 100%;" expected="50% 100%, 0% 100%, 200% 100%" desc="multiple percentage coordinates" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: calc(10% + 100px) 100%, 150%, 200px calc(5% + 10px);" expected="calc(100px + 10%) 100%, 150% 50%, 200px calc(10px + 5%)" desc="multiple mixed pixel/percentage/calc coordinates" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: 50px 100px, junk;" expected="none" desc="reject invalid position list" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: 50px 100px / 1px 1px;" expected="none" desc="reject invalid position separator" ></div>
-<div class="test" property="scroll-snap-coordinate" style="scroll-snap-coordinate: 50px 100px,;" expected="none" desc="reject invalid position with terminating comma" ></div>
-
-
-<script>
-description("Test the parsing and application of the scroll-snap-* properties.");
-
-var tests = document.querySelectorAll('.test');
-var style;
-for (var i = 0; i < tests.length; i++) {
-  debug('Test case: ' + tests[i].attributes.desc.value);
-  var property = camelCase(tests[i].attributes.property.value);
-  style = window.getComputedStyle(tests[i]);
-  shouldBeEqualToString('style.' + property, tests[i].attributes.expected.value);
-  debug('');
-}
-
-function camelCase(str) {
-  return str.replace(/-([a-z])/g, function (m, w) {
-    return w.toUpperCase();
-  });
-}
-</script>
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-modal-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-modal-expected.txt
index 4164b90..ae6128f5 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-modal-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-modal-expected.txt
@@ -165,34 +165,6 @@
   },
   "properties": [
     {
-      "name": "live",
-      "value": {
-        "type": "token",
-        "value": "assertive"
-      }
-    },
-    {
-      "name": "atomic",
-      "value": {
-        "type": "boolean",
-        "value": false
-      }
-    },
-    {
-      "name": "relevant",
-      "value": {
-        "type": "tokenList",
-        "value": "additions text"
-      }
-    },
-    {
-      "name": "busy",
-      "value": {
-        "type": "boolean",
-        "value": false
-      }
-    },
-    {
       "name": "modal",
       "value": {
         "type": "boolean",
diff --git a/third_party/WebKit/LayoutTests/paint/tables/stacking-context-row-background-clipped-with-offset-expected.html b/third_party/WebKit/LayoutTests/paint/tables/stacking-context-row-background-clipped-with-offset-expected.html
new file mode 100644
index 0000000..ebe656b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/tables/stacking-context-row-background-clipped-with-offset-expected.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<style>
+table { border-spacing: 0; }
+</style>
+<table style="margin-left: 100px; margin-top: 100px; width: 200px; height: 200px">
+  <tr style="background: green"><td></td><td></td><td></td><td></td></tr>
+  <tr style="background: blue"><td></td><td></td><td></td><td></td></tr>
+  <tr style="background: magenta"><td></td><td></td><td></td><td></td></tr>
+  <tr style="background: maroon"><td></td><td></td><td></td><td></td></tr>
+</table>
+<table style="margin-left: 100px; margin-top: 20px; width: 100px; height: 100px">
+  <tr style="background: green"><td></td><td></td></tr>
+  <tr style="background: blue"><td></td><td></td></tr>
+</table>
+<table style="margin-left: 100px; margin-top: 20px; width: 100px; height: 100px">
+  <tr style="background: magenta"><td></td><td></td></tr>
+  <tr style="background: maroon"><td></td><td></td></tr>
+</table>
diff --git a/third_party/WebKit/LayoutTests/paint/tables/stacking-context-row-background-clipped-with-offset.html b/third_party/WebKit/LayoutTests/paint/tables/stacking-context-row-background-clipped-with-offset.html
new file mode 100644
index 0000000..ef7a3d6f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/tables/stacking-context-row-background-clipped-with-offset.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<style>
+div { overflow: hidden; margin-left: 100px }
+table { border-spacing: 0; width: 200px; height: 200px }
+tr { will-change: transform }
+tr:nth-child(1) { background: green }
+tr:nth-child(2) { background: blue }
+tr:nth-child(3) { background: magenta }
+tr:nth-child(4) { background: maroon }
+</style>
+<div style="margin-top: 100px">
+  <table>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+  </table>
+</div>
+<div style="margin-top: 20px; width: 100px; height: 100px">
+  <table>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+  </table>
+</div>
+<div id="scrolled" style="margin-top: 20px; width: 100px; height: 100px">
+  <table>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+    <tr><td></td><td></td><td></td><td></td></tr>
+  </table>
+</div>
+<script>
+scrolled.scrollTop = 100;
+scrolled.scrollLeft = 100;
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/plugins/navigator-pluginarray-deleteframe.html b/third_party/WebKit/LayoutTests/plugins/navigator-pluginarray-deleteframe.html
new file mode 100644
index 0000000..09e68fc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/plugins/navigator-pluginarray-deleteframe.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+<body>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script type="text/javascript">
+test(function () {
+  var iframe = document.createElement('iframe');
+  document.body.appendChild(iframe);
+  var iframe_navigator = iframe.contentWindow.navigator;
+  assert_greater_than_equal(iframe_navigator.plugins.length, 1, "At least one plugin must be available.");
+  assert_greater_than_equal(iframe_navigator.mimeTypes.length, 1, "At least one mime type must be available.");
+  iframe.remove();
+  for (var i = 0; i < iframe_navigator.plugins.length; i++) {
+    assert_equals(null, iframe_navigator.plugins[i]);
+  }
+  for (var i = 0; i < iframe_navigator.mimeTypes.length; i++) {
+    assert_equals(null, iframe_navigator.mimeTypes[i]);
+  }
+}, "Tests that navigator.plugins returns null once the iframe is removed.");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt b/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt
index 3f261c2..b4ae6eb 100644
--- a/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt
@@ -241,11 +241,6 @@
 ry: auto
 scale: none
 scroll-behavior: auto
-scroll-snap-coordinate: none
-scroll-snap-destination: 0px 0px
-scroll-snap-points-x: none
-scroll-snap-points-y: none
-scroll-snap-type: none
 shape-image-threshold: 0
 shape-margin: 0px
 shape-outside: none
diff --git a/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt b/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt
index fe295ea..8f7aadd 100644
--- a/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt
@@ -248,10 +248,30 @@
 ry
 scale
 scrollBehavior
-scrollSnapCoordinate
-scrollSnapDestination
-scrollSnapPointsX
-scrollSnapPointsY
+scrollPadding
+scrollPaddingBlock
+scrollPaddingBlockEnd
+scrollPaddingBlockStart
+scrollPaddingBottom
+scrollPaddingInline
+scrollPaddingInlineEnd
+scrollPaddingInlineStart
+scrollPaddingLeft
+scrollPaddingRight
+scrollPaddingTop
+scrollSnapAlign
+scrollSnapMargin
+scrollSnapMarginBlock
+scrollSnapMarginBlockEnd
+scrollSnapMarginBlockStart
+scrollSnapMarginBottom
+scrollSnapMarginInline
+scrollSnapMarginInlineEnd
+scrollSnapMarginInlineStart
+scrollSnapMarginLeft
+scrollSnapMarginRight
+scrollSnapMarginTop
+scrollSnapStop
 scrollSnapType
 setProperty
 shapeImageThreshold
diff --git a/third_party/WebKit/Source/core/BUILD.gn b/third_party/WebKit/Source/core/BUILD.gn
index b9f2ff0..0925f7e9 100644
--- a/third_party/WebKit/Source/core/BUILD.gn
+++ b/third_party/WebKit/Source/core/BUILD.gn
@@ -525,7 +525,10 @@
     "$blink_core_output_dir/css/properties/CSSPropertyAPIRadius.h",
     "$blink_core_output_dir/css/properties/CSSPropertyAPIRotate.h",
     "$blink_core_output_dir/css/properties/CSSPropertyAPIScale.h",
-    "$blink_core_output_dir/css/properties/CSSPropertyAPIScrollSnapCoordinate.h",
+    "$blink_core_output_dir/css/properties/CSSPropertyAPIScrollSnapAlign.h",
+    "$blink_core_output_dir/css/properties/CSSPropertyAPIScrollPadding.h",
+    "$blink_core_output_dir/css/properties/CSSPropertyAPIScrollSnapMargin.h",
+    "$blink_core_output_dir/css/properties/CSSPropertyAPIScrollSnapType.h",
     "$blink_core_output_dir/css/properties/CSSPropertyAPIShapeImageThreshold.h",
     "$blink_core_output_dir/css/properties/CSSPropertyAPIShapeMargin.h",
     "$blink_core_output_dir/css/properties/CSSPropertyAPIShapeOutside.h",
diff --git a/third_party/WebKit/Source/core/css/BUILD.gn b/third_party/WebKit/Source/core/css/BUILD.gn
index aa1d635..d6b7ddf 100644
--- a/third_party/WebKit/Source/core/css/BUILD.gn
+++ b/third_party/WebKit/Source/core/css/BUILD.gn
@@ -443,7 +443,10 @@
     "properties/CSSPropertyAPIRadius.cpp",
     "properties/CSSPropertyAPIRotate.cpp",
     "properties/CSSPropertyAPIScale.cpp",
-    "properties/CSSPropertyAPIScrollSnapCoordinate.cpp",
+    "properties/CSSPropertyAPIScrollPadding.cpp",
+    "properties/CSSPropertyAPIScrollSnapAlign.cpp",
+    "properties/CSSPropertyAPIScrollSnapMargin.cpp",
+    "properties/CSSPropertyAPIScrollSnapType.cpp",
     "properties/CSSPropertyAPIShapeImageThreshold.cpp",
     "properties/CSSPropertyAPIShapeMargin.cpp",
     "properties/CSSPropertyAPIShapeOutside.cpp",
diff --git a/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp b/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp
index 42cf16d..b3d9c8a5 100644
--- a/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp
+++ b/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp
@@ -173,10 +173,8 @@
     CSSPropertyDominantBaseline, CSSPropertyTextAnchor, CSSPropertyWritingMode,
     CSSPropertyVectorEffect, CSSPropertyPaintOrder, CSSPropertyD, CSSPropertyCx,
     CSSPropertyCy, CSSPropertyX, CSSPropertyY, CSSPropertyR, CSSPropertyRx,
-    CSSPropertyRy, CSSPropertyScrollSnapType, CSSPropertyScrollSnapPointsX,
-    CSSPropertyScrollSnapPointsY, CSSPropertyScrollSnapCoordinate,
-    CSSPropertyScrollSnapDestination, CSSPropertyTranslate, CSSPropertyRotate,
-    CSSPropertyScale, CSSPropertyCaretColor};
+    CSSPropertyRy, CSSPropertyTranslate, CSSPropertyRotate, CSSPropertyScale,
+    CSSPropertyCaretColor};
 
 CSSValueID CssIdentifierForFontSizeKeyword(int keyword_size) {
   DCHECK_NE(keyword_size, 0);
diff --git a/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h b/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h
index 383e1e8..41e6f5a 100644
--- a/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h
+++ b/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h
@@ -2583,35 +2583,109 @@
 }
 
 template <>
-inline CSSIdentifierValue::CSSIdentifierValue(ScrollSnapType snap_type)
+inline CSSIdentifierValue::CSSIdentifierValue(SnapAxis axis)
     : CSSValue(kIdentifierClass) {
-  switch (snap_type) {
-    case kScrollSnapTypeNone:
-      value_id_ = CSSValueNone;
+  switch (axis) {
+    case kSnapAxisX:
+      value_id_ = CSSValueX;
       break;
-    case kScrollSnapTypeMandatory:
-      value_id_ = CSSValueMandatory;
+    case kSnapAxisY:
+      value_id_ = CSSValueY;
       break;
-    case kScrollSnapTypeProximity:
-      value_id_ = CSSValueProximity;
+    case kSnapAxisBlock:
+      value_id_ = CSSValueBlock;
+      break;
+    case kSnapAxisInline:
+      value_id_ = CSSValueInline;
+      break;
+    case kSnapAxisBoth:
+      value_id_ = CSSValueBoth;
       break;
   }
 }
 
 template <>
-inline ScrollSnapType CSSIdentifierValue::ConvertTo() const {
+inline SnapAxis CSSIdentifierValue::ConvertTo() const {
   switch (GetValueID()) {
-    case CSSValueNone:
-      return kScrollSnapTypeNone;
-    case CSSValueMandatory:
-      return kScrollSnapTypeMandatory;
-    case CSSValueProximity:
-      return kScrollSnapTypeProximity;
+    case CSSValueX:
+      return kSnapAxisX;
+    case CSSValueY:
+      return kSnapAxisY;
+    case CSSValueBlock:
+      return kSnapAxisBlock;
+    case CSSValueInline:
+      return kSnapAxisInline;
+    case CSSValueBoth:
+      return kSnapAxisBoth;
     default:
       break;
   }
   NOTREACHED();
-  return kScrollSnapTypeNone;
+  return kSnapAxisBoth;
+}
+
+template <>
+inline CSSIdentifierValue::CSSIdentifierValue(SnapStrictness strictness)
+    : CSSValue(kIdentifierClass) {
+  switch (strictness) {
+    case kSnapStrictnessProximity:
+      value_id_ = CSSValueProximity;
+      break;
+    case kSnapStrictnessMandatory:
+      value_id_ = CSSValueMandatory;
+      break;
+  }
+}
+
+template <>
+inline SnapStrictness CSSIdentifierValue::ConvertTo() const {
+  switch (GetValueID()) {
+    case CSSValueProximity:
+      return kSnapStrictnessProximity;
+    case CSSValueMandatory:
+      return kSnapStrictnessMandatory;
+    default:
+      break;
+  }
+  NOTREACHED();
+  return kSnapStrictnessProximity;
+}
+
+template <>
+inline CSSIdentifierValue::CSSIdentifierValue(SnapAlignment alignment)
+    : CSSValue(kIdentifierClass) {
+  switch (alignment) {
+    case kSnapAlignmentNone:
+      value_id_ = CSSValueNone;
+      break;
+    case kSnapAlignmentStart:
+      value_id_ = CSSValueStart;
+      break;
+    case kSnapAlignmentEnd:
+      value_id_ = CSSValueEnd;
+      break;
+    case kSnapAlignmentCenter:
+      value_id_ = CSSValueCenter;
+      break;
+  }
+}
+
+template <>
+inline SnapAlignment CSSIdentifierValue::ConvertTo() const {
+  switch (GetValueID()) {
+    case CSSValueNone:
+      return kSnapAlignmentNone;
+    case CSSValueStart:
+      return kSnapAlignmentStart;
+    case CSSValueEnd:
+      return kSnapAlignmentEnd;
+    case CSSValueCenter:
+      return kSnapAlignmentCenter;
+    default:
+      break;
+  }
+  NOTREACHED();
+  return kSnapAlignmentNone;
 }
 
 template <>
diff --git a/third_party/WebKit/Source/core/css/CSSProperties.json5 b/third_party/WebKit/Source/core/css/CSSProperties.json5
index 85e28b5..02e281e0 100644
--- a/third_party/WebKit/Source/core/css/CSSProperties.json5
+++ b/third_party/WebKit/Source/core/css/CSSProperties.json5
@@ -1973,33 +1973,201 @@
     },
     {
       name: "scroll-snap-type",
-      runtime_flag: "CSSScrollSnapPoints",
-      type_name: "ScrollSnapType",
-      field_template: "storage_only",
-      field_group: "rare-non-inherited",
-      field_size: 2,
-      default_value: "kScrollSnapTypeNone",
-    },
-    {
-      name: "scroll-snap-points-x",
-      converter: "ConvertSnapPoints",
-      runtime_flag: "CSSScrollSnapPoints",
-    },
-    {
-      name: "scroll-snap-points-y",
-      converter: "ConvertSnapPoints",
-      runtime_flag: "CSSScrollSnapPoints",
-    },
-    {
-      name: "scroll-snap-destination",
-      converter: "ConvertPosition",
-      runtime_flag: "CSSScrollSnapPoints",
-    },
-    {
-      name: "scroll-snap-coordinate",
       api_class: true,
       api_methods: ["parseSingleValue"],
-      converter: "ConvertSnapCoordinates",
+      converter: "ConvertSnapType",
+      getter: "GetScrollSnapType",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-align",
+      api_class: true,
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertSnapAlign",
+      getter: "GetScrollSnapAlign",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-stop",
+      default_value: "normal",
+      field_template: "keyword",
+      keywords: ["normal", "always"],
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-top",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-bottom",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-left",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-right",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-block-start",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      direction_aware: true,
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-block-end",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      direction_aware: true,
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-inline-start",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      direction_aware: true,
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-inline-end",
+      api_class: "CSSPropertyAPIScrollPadding",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollPadding",
+      include_paths: ["platform/Length.h"],
+      typedom_types: ["Length", "Percent"],
+      type_name: "Length",
+      direction_aware: true,
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-top",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-bottom",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-left",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-right",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-block-start",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      direction_aware: true,
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-block-end",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      direction_aware: true,
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-inline-start",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      direction_aware: true,
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-inline-end",
+      api_class: "CSSPropertyAPIScrollSnapMargin",
+      api_methods: ["parseSingleValue"],
+      converter: "ConvertLength",
+      initial: "InitialScrollSnapMargin",
+      direction_aware: true,
+      include_paths: ["platform/Length.h"],
+      type_name: "Length",
       runtime_flag: "CSSScrollSnapPoints",
     },
     {
@@ -3476,6 +3644,36 @@
       longhands: "break-inside",
     },
     {
+      name: "scroll-padding",
+      longhands: "scroll-padding-top;scroll-padding-right;scroll-padding-bottom;scroll-padding-left",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-block",
+      longhands: "scroll-padding-block-start;scroll-padding-block-end",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-padding-inline",
+      longhands: "scroll-padding-inline-start;scroll-padding-inline-end",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin",
+      longhands: "scroll-snap-margin-top;scroll-snap-margin-right;scroll-snap-margin-bottom;scroll-snap-margin-left",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-block",
+      longhands: "scroll-snap-margin-block-start;scroll-snap-margin-block-end",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
+      name: "scroll-snap-margin-inline",
+      longhands: "scroll-snap-margin-inline-start;scroll-snap-margin-inline-end",
+      runtime_flag: "CSSScrollSnapPoints",
+    },
+    {
       name: "transition",
       longhands: "transition-property;transition-duration;transition-timing-function;transition-delay",
     },
diff --git a/third_party/WebKit/Source/core/css/CSSProperty.cpp b/third_party/WebKit/Source/core/css/CSSProperty.cpp
index 1d21939f..3b7a354 100644
--- a/third_party/WebKit/Source/core/css/CSSProperty.cpp
+++ b/third_party/WebKit/Source/core/css/CSSProperty.cpp
@@ -227,6 +227,30 @@
     case CSSPropertyWebkitBorderAfterWidth:
       return ResolveToPhysicalProperty(direction, writing_mode, kAfterSide,
                                        borderWidthShorthand());
+    case CSSPropertyScrollPaddingInlineStart:
+      return ResolveToPhysicalProperty(direction, writing_mode, kStartSide,
+                                       scrollPaddingShorthand());
+    case CSSPropertyScrollPaddingInlineEnd:
+      return ResolveToPhysicalProperty(direction, writing_mode, kEndSide,
+                                       scrollPaddingShorthand());
+    case CSSPropertyScrollPaddingBlockStart:
+      return ResolveToPhysicalProperty(direction, writing_mode, kBeforeSide,
+                                       scrollPaddingShorthand());
+    case CSSPropertyScrollPaddingBlockEnd:
+      return ResolveToPhysicalProperty(direction, writing_mode, kAfterSide,
+                                       scrollPaddingShorthand());
+    case CSSPropertyScrollSnapMarginInlineStart:
+      return ResolveToPhysicalProperty(direction, writing_mode, kStartSide,
+                                       scrollSnapMarginShorthand());
+    case CSSPropertyScrollSnapMarginInlineEnd:
+      return ResolveToPhysicalProperty(direction, writing_mode, kEndSide,
+                                       scrollSnapMarginShorthand());
+    case CSSPropertyScrollSnapMarginBlockStart:
+      return ResolveToPhysicalProperty(direction, writing_mode, kBeforeSide,
+                                       scrollSnapMarginShorthand());
+    case CSSPropertyScrollSnapMarginBlockEnd:
+      return ResolveToPhysicalProperty(direction, writing_mode, kAfterSide,
+                                       scrollSnapMarginShorthand());
     case CSSPropertyInlineSize:
     case CSSPropertyWebkitLogicalWidth: {
       const CSSPropertyID kProperties[2] = {CSSPropertyWidth,
diff --git a/third_party/WebKit/Source/core/css/CSSValueKeywords.json5 b/third_party/WebKit/Source/core/css/CSSValueKeywords.json5
index 39ff4fa..966692a 100644
--- a/third_party/WebKit/Source/core/css/CSSValueKeywords.json5
+++ b/third_party/WebKit/Source/core/css/CSSValueKeywords.json5
@@ -885,6 +885,9 @@
     "pixelated",
     "-webkit-optimize-contrast",
 
+    // image-orientation
+    "from-image",
+
     // shape-outside
     "nonzero",
     "evenodd",
@@ -1110,9 +1113,24 @@
 
     // scroll-snap-type
     // none
+    "x",
+    "y",
+    // block
+    // inline
+    // both
     "mandatory",
     "proximity",
-    "from-image",
+
+    // scroll-snap-align
+    // none
+    // start
+    // end
+    // center
+
+    // scroll-snap-stop
+    // normal
+    // always
+
 
     // containment
     // paint
diff --git a/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp b/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp
index 3713d852..6758297 100644
--- a/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp
+++ b/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp
@@ -1653,6 +1653,27 @@
   return list;
 }
 
+static CSSValuePair* ValuesForInlineBlockShorthand(
+    const StylePropertyShorthand& shorthand,
+    const ComputedStyle& style,
+    const LayoutObject* layout_object,
+    Node* styled_node,
+    bool allow_visited_style) {
+  const CSSValue* start_value = ComputedStyleCSSValueMapping::Get(
+      shorthand.properties()[0], style, layout_object, styled_node,
+      allow_visited_style);
+  const CSSValue* end_value = ComputedStyleCSSValueMapping::Get(
+      shorthand.properties()[1], style, layout_object, styled_node,
+      allow_visited_style);
+  // Both properties must be specified.
+  if (!start_value || !end_value)
+    return nullptr;
+
+  CSSValuePair* pair = CSSValuePair::Create(start_value, end_value,
+                                            CSSValuePair::kDropIdenticalValues);
+  return pair;
+}
+
 static CSSValueList* ValueForBorderRadiusShorthand(const ComputedStyle& style) {
   CSSValueList* list = CSSValueList::CreateSlashSeparated();
 
@@ -1968,42 +1989,21 @@
   return list;
 }
 
-static CSSValue* ValueForScrollSnapDestination(const LengthPoint& destination,
-                                               const ComputedStyle& style) {
-  CSSValueList* list = CSSValueList::CreateSpaceSeparated();
-  list->Append(*ZoomAdjustedPixelValueForLength(destination.X(), style));
-  list->Append(*ZoomAdjustedPixelValueForLength(destination.Y(), style));
-  return list;
-}
-
-static CSSValue* ValueForScrollSnapPoints(const ScrollSnapPoints& points,
-                                          const ComputedStyle& style) {
-  if (points.has_repeat) {
-    CSSFunctionValue* repeat = CSSFunctionValue::Create(CSSValueRepeat);
-    repeat->Append(
-        *ZoomAdjustedPixelValueForLength(points.repeat_offset, style));
-    return repeat;
+static CSSValue* ValueForScrollSnapType(const ScrollSnapType& type,
+                                        const ComputedStyle& style) {
+  if (!type.is_none) {
+    return CSSValuePair::Create(CSSIdentifierValue::Create(type.axis),
+                                CSSIdentifierValue::Create(type.strictness),
+                                CSSValuePair::kDropIdenticalValues);
   }
-
   return CSSIdentifierValue::Create(CSSValueNone);
 }
 
-static CSSValue* ValueForScrollSnapCoordinate(
-    const Vector<LengthPoint>& coordinates,
-    const ComputedStyle& style) {
-  if (coordinates.IsEmpty())
-    return CSSIdentifierValue::Create(CSSValueNone);
-
-  CSSValueList* list = CSSValueList::CreateCommaSeparated();
-
-  for (auto& coordinate : coordinates) {
-    auto pair = CSSValueList::CreateSpaceSeparated();
-    pair->Append(*ZoomAdjustedPixelValueForLength(coordinate.X(), style));
-    pair->Append(*ZoomAdjustedPixelValueForLength(coordinate.Y(), style));
-    list->Append(*pair);
-  }
-
-  return list;
+static CSSValue* ValueForScrollSnapAlign(const ScrollSnapAlign& align,
+                                         const ComputedStyle& style) {
+  return CSSValuePair::Create(CSSIdentifierValue::Create(align.alignmentX),
+                              CSSIdentifierValue::Create(align.alignmentY),
+                              CSSValuePair::kDropIdenticalValues);
 }
 
 // Returns a suitable value for the page-break-(before|after) property, given
@@ -3460,6 +3460,30 @@
     case CSSPropertyPadding:
       return ValuesForSidesShorthand(paddingShorthand(), style, layout_object,
                                      styled_node, allow_visited_style);
+    case CSSPropertyScrollPadding:
+      return ValuesForSidesShorthand(scrollPaddingShorthand(), style,
+                                     layout_object, styled_node,
+                                     allow_visited_style);
+    case CSSPropertyScrollPaddingBlock:
+      return ValuesForInlineBlockShorthand(scrollPaddingBlockShorthand(), style,
+                                           layout_object, styled_node,
+                                           allow_visited_style);
+    case CSSPropertyScrollPaddingInline:
+      return ValuesForInlineBlockShorthand(scrollPaddingInlineShorthand(),
+                                           style, layout_object, styled_node,
+                                           allow_visited_style);
+    case CSSPropertyScrollSnapMargin:
+      return ValuesForSidesShorthand(scrollSnapMarginShorthand(), style,
+                                     layout_object, styled_node,
+                                     allow_visited_style);
+    case CSSPropertyScrollSnapMarginBlock:
+      return ValuesForInlineBlockShorthand(scrollSnapMarginBlockShorthand(),
+                                           style, layout_object, styled_node,
+                                           allow_visited_style);
+    case CSSPropertyScrollSnapMarginInline:
+      return ValuesForInlineBlockShorthand(scrollSnapMarginInlineShorthand(),
+                                           style, layout_object, styled_node,
+                                           allow_visited_style);
     // Individual properties not part of the spec.
     case CSSPropertyBackgroundRepeatX:
     case CSSPropertyBackgroundRepeatY:
@@ -3690,16 +3714,56 @@
     case CSSPropertyRy:
       return ZoomAdjustedPixelValueForLength(svg_style.Ry(), style);
     case CSSPropertyScrollSnapType:
-      return CSSIdentifierValue::Create(style.GetScrollSnapType());
-    case CSSPropertyScrollSnapPointsX:
-      return ValueForScrollSnapPoints(style.ScrollSnapPointsX(), style);
-    case CSSPropertyScrollSnapPointsY:
-      return ValueForScrollSnapPoints(style.ScrollSnapPointsY(), style);
-    case CSSPropertyScrollSnapCoordinate:
-      return ValueForScrollSnapCoordinate(style.ScrollSnapCoordinate(), style);
-    case CSSPropertyScrollSnapDestination:
-      return ValueForScrollSnapDestination(style.ScrollSnapDestination(),
-                                           style);
+      return ValueForScrollSnapType(style.GetScrollSnapType(), style);
+    case CSSPropertyScrollSnapAlign:
+      return ValueForScrollSnapAlign(style.GetScrollSnapAlign(), style);
+    case CSSPropertyScrollSnapStop:
+      return CSSIdentifierValue::Create(style.ScrollSnapStop());
+    case CSSPropertyScrollPaddingTop:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingTop(), style);
+    case CSSPropertyScrollPaddingRight:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingRight(), style);
+    case CSSPropertyScrollPaddingBottom:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingBottom(),
+                                             style);
+    case CSSPropertyScrollPaddingLeft:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingLeft(), style);
+    case CSSPropertyScrollPaddingBlockStart:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingBlockStart(),
+                                             style);
+    case CSSPropertyScrollPaddingBlockEnd:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingBlockEnd(),
+                                             style);
+    case CSSPropertyScrollPaddingInlineStart:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingInlineStart(),
+                                             style);
+    case CSSPropertyScrollPaddingInlineEnd:
+      return ZoomAdjustedPixelValueForLength(style.ScrollPaddingInlineEnd(),
+                                             style);
+    case CSSPropertyScrollSnapMarginTop:
+      return ZoomAdjustedPixelValueForLength(style.ScrollSnapMarginTop(),
+                                             style);
+    case CSSPropertyScrollSnapMarginRight:
+      return ZoomAdjustedPixelValueForLength(style.ScrollSnapMarginRight(),
+                                             style);
+    case CSSPropertyScrollSnapMarginBottom:
+      return ZoomAdjustedPixelValueForLength(style.ScrollSnapMarginBottom(),
+                                             style);
+    case CSSPropertyScrollSnapMarginLeft:
+      return ZoomAdjustedPixelValueForLength(style.ScrollSnapMarginLeft(),
+                                             style);
+    case CSSPropertyScrollSnapMarginBlockStart:
+      return ZoomAdjustedPixelValueForLength(style.ScrollSnapMarginBlockStart(),
+                                             style);
+    case CSSPropertyScrollSnapMarginBlockEnd:
+      return ZoomAdjustedPixelValueForLength(style.ScrollSnapMarginBlockEnd(),
+                                             style);
+    case CSSPropertyScrollSnapMarginInlineStart:
+      return ZoomAdjustedPixelValueForLength(
+          style.ScrollSnapMarginInlineStart(), style);
+    case CSSPropertyScrollSnapMarginInlineEnd:
+      return ZoomAdjustedPixelValueForLength(style.ScrollSnapMarginInlineEnd(),
+                                             style);
     case CSSPropertyTranslate: {
       if (!style.Translate())
         return CSSIdentifierValue::Create(CSSValueNone);
diff --git a/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp b/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp
index 21c3734..3e42083 100644
--- a/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp
+++ b/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp
@@ -506,6 +506,18 @@
     }
     case CSSPropertyBorderRadius:
       return Get4Values(borderRadiusShorthand());
+    case CSSPropertyScrollPadding:
+      return Get4Values(scrollPaddingShorthand());
+    case CSSPropertyScrollPaddingBlock:
+      return Get2Values(scrollPaddingBlockShorthand());
+    case CSSPropertyScrollPaddingInline:
+      return Get2Values(scrollPaddingInlineShorthand());
+    case CSSPropertyScrollSnapMargin:
+      return Get4Values(scrollSnapMarginShorthand());
+    case CSSPropertyScrollSnapMarginBlock:
+      return Get2Values(scrollSnapMarginBlockShorthand());
+    case CSSPropertyScrollSnapMarginInline:
+      return Get2Values(scrollSnapMarginInlineShorthand());
     default:
       return String();
   }
@@ -695,6 +707,32 @@
   return result.ToString();
 }
 
+String StylePropertySerializer::Get2Values(
+    const StylePropertyShorthand& shorthand) const {
+  // Assume the properties are in the usual order start, end.
+  int start_value_index =
+      property_set_.FindPropertyIndex(shorthand.properties()[0]);
+  int end_value_index =
+      property_set_.FindPropertyIndex(shorthand.properties()[1]);
+
+  if (start_value_index == -1 || end_value_index == -1)
+    return String();
+
+  PropertyValueForSerializer start =
+      property_set_.PropertyAt(start_value_index);
+  PropertyValueForSerializer end = property_set_.PropertyAt(end_value_index);
+
+  bool show_end = !DataEquivalent(start.Value(), end.Value());
+
+  StringBuilder result;
+  result.Append(start.Value()->CssText());
+  if (show_end) {
+    result.Append(' ');
+    result.Append(end.Value()->CssText());
+  }
+  return result.ToString();
+}
+
 String StylePropertySerializer::Get4Values(
     const StylePropertyShorthand& shorthand) const {
   // Assume the properties are in the usual order top, right, bottom, left.
diff --git a/third_party/WebKit/Source/core/css/StylePropertySerializer.h b/third_party/WebKit/Source/core/css/StylePropertySerializer.h
index cddd0193..4045910 100644
--- a/third_party/WebKit/Source/core/css/StylePropertySerializer.h
+++ b/third_party/WebKit/Source/core/css/StylePropertySerializer.h
@@ -47,6 +47,7 @@
   String GetAlignmentShorthandValue(const StylePropertyShorthand&) const;
   String BorderPropertyValue() const;
   String GetLayeredShorthandValue(const StylePropertyShorthand&) const;
+  String Get2Values(const StylePropertyShorthand&) const;
   String Get4Values(const StylePropertyShorthand&) const;
   String BorderSpacingValue(const StylePropertyShorthand&) const;
   String GetShorthandValue(const StylePropertyShorthand&,
diff --git a/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp b/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp
index d756114..4588ef58 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp
@@ -40,6 +40,22 @@
     case CSSPropertyPaddingLeft:
     case CSSPropertyPaddingRight:
     case CSSPropertyPaddingTop:
+    case CSSPropertyScrollPaddingTop:
+    case CSSPropertyScrollPaddingRight:
+    case CSSPropertyScrollPaddingBottom:
+    case CSSPropertyScrollPaddingLeft:
+    case CSSPropertyScrollPaddingBlockStart:
+    case CSSPropertyScrollPaddingBlockEnd:
+    case CSSPropertyScrollPaddingInlineStart:
+    case CSSPropertyScrollPaddingInlineEnd:
+    case CSSPropertyScrollSnapMarginTop:
+    case CSSPropertyScrollSnapMarginRight:
+    case CSSPropertyScrollSnapMarginBottom:
+    case CSSPropertyScrollSnapMarginLeft:
+    case CSSPropertyScrollSnapMarginBlockStart:
+    case CSSPropertyScrollSnapMarginBlockEnd:
+    case CSSPropertyScrollSnapMarginInlineStart:
+    case CSSPropertyScrollSnapMarginInlineEnd:
     case CSSPropertyWebkitLogicalWidth:
     case CSSPropertyWebkitLogicalHeight:
     case CSSPropertyWebkitMinLogicalWidth:
@@ -841,10 +857,9 @@
     case CSSPropertyWordBreak:
       return value_id == CSSValueNormal || value_id == CSSValueBreakAll ||
              value_id == CSSValueKeepAll || value_id == CSSValueBreakWord;
-    case CSSPropertyScrollSnapType:
+    case CSSPropertyScrollSnapStop:
       DCHECK(RuntimeEnabledFeatures::CSSScrollSnapPointsEnabled());
-      return value_id == CSSValueNone || value_id == CSSValueMandatory ||
-             value_id == CSSValueProximity;
+      return value_id == CSSValueNormal || value_id == CSSValueAlways;
     default:
       NOTREACHED();
       return false;
@@ -957,7 +972,7 @@
     case CSSPropertyWordBreak:
     case CSSPropertyWordWrap:
     case CSSPropertyWritingMode:
-    case CSSPropertyScrollSnapType:
+    case CSSPropertyScrollSnapStop:
       return true;
     case CSSPropertyJustifyContent:
     case CSSPropertyAlignContent:
diff --git a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
index cfefdb0..4ef06b9 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
@@ -807,24 +807,6 @@
   return nullptr;
 }
 
-static CSSValue* ConsumeScrollSnapPoints(CSSParserTokenRange& range,
-                                         CSSParserMode css_parser_mode) {
-  if (range.Peek().Id() == CSSValueNone)
-    return ConsumeIdent(range);
-  if (range.Peek().FunctionId() == CSSValueRepeat) {
-    CSSParserTokenRange args = ConsumeFunction(range);
-    CSSPrimitiveValue* parsed_value =
-        ConsumeLengthOrPercent(args, css_parser_mode, kValueRangeNonNegative);
-    if (args.AtEnd() && parsed_value &&
-        (parsed_value->IsCalculated() || parsed_value->GetDoubleValue() > 0)) {
-      CSSFunctionValue* result = CSSFunctionValue::Create(CSSValueRepeat);
-      result->Append(*parsed_value);
-      return result;
-    }
-  }
-  return nullptr;
-}
-
 static CSSValue* ConsumeReflect(CSSParserTokenRange& range,
                                 const CSSParserContext* context) {
   CSSIdentifierValue* direction =
@@ -1534,10 +1516,6 @@
     case CSSPropertyWebkitLogicalWidth:
     case CSSPropertyWebkitLogicalHeight:
       return CSSPropertyLengthUtils::ConsumeWidthOrHeight(range_, *context_);
-    case CSSPropertyScrollSnapDestination:
-      // TODO(crbug.com/724912): Retire scroll-snap-destination
-      return ConsumePosition(range_, *context_, UnitlessQuirk::kForbid,
-                             Optional<WebFeature>());
     case CSSPropertyObjectPosition:
       return ConsumePosition(range_, *context_, UnitlessQuirk::kForbid,
                              WebFeature::kThreeValuedPositionObjectPosition);
@@ -1654,9 +1632,6 @@
       return ConsumePerspective(
           range_, context_,
           unresolved_property == CSSPropertyAliasWebkitPerspective);
-    case CSSPropertyScrollSnapPointsX:
-    case CSSPropertyScrollSnapPointsY:
-      return ConsumeScrollSnapPoints(range_, context_->Mode());
     case CSSPropertyBorderImageRepeat:
     case CSSPropertyWebkitMaskBoxImageRepeat:
       return CSSPropertyBorderImageUtils::ConsumeBorderImageRepeat(range_);
@@ -2343,6 +2318,23 @@
   return range_.AtEnd();
 }
 
+bool CSSPropertyParser::Consume2Values(const StylePropertyShorthand& shorthand,
+                                       bool important) {
+  DCHECK_EQ(shorthand.length(), 2u);
+  const CSSPropertyID* longhands = shorthand.properties();
+  const CSSValue* start = ParseSingleValue(longhands[0], shorthand.id());
+  if (!start)
+    return false;
+
+  const CSSValue* end = ParseSingleValue(longhands[1], shorthand.id());
+  if (!end)
+    end = start;
+  AddParsedProperty(longhands[0], shorthand.id(), *start, important);
+  AddParsedProperty(longhands[1], shorthand.id(), *end, important);
+
+  return range_.AtEnd();
+}
+
 bool CSSPropertyParser::Consume4Values(const StylePropertyShorthand& shorthand,
                                        bool important) {
   DCHECK_EQ(shorthand.length(), 4u);
@@ -3286,6 +3278,18 @@
       return ConsumePlaceItemsShorthand(important);
     case CSSPropertyPlaceSelf:
       return ConsumePlaceSelfShorthand(important);
+    case CSSPropertyScrollPadding:
+      return Consume4Values(scrollPaddingShorthand(), important);
+    case CSSPropertyScrollPaddingBlock:
+      return Consume2Values(scrollPaddingBlockShorthand(), important);
+    case CSSPropertyScrollPaddingInline:
+      return Consume2Values(scrollPaddingInlineShorthand(), important);
+    case CSSPropertyScrollSnapMargin:
+      return Consume4Values(scrollSnapMarginShorthand(), important);
+    case CSSPropertyScrollSnapMarginBlock:
+      return Consume2Values(scrollSnapMarginBlockShorthand(), important);
+    case CSSPropertyScrollSnapMarginInline:
+      return Consume2Values(scrollSnapMarginInlineShorthand(), important);
     default:
       return false;
   }
diff --git a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.h b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.h
index e6d338d..7e9a362 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.h
+++ b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.h
@@ -85,6 +85,7 @@
 
   bool ParseShorthand(CSSPropertyID, bool important);
   bool ConsumeShorthandGreedily(const StylePropertyShorthand&, bool important);
+  bool Consume2Values(const StylePropertyShorthand&, bool important);
   bool Consume4Values(const StylePropertyShorthand&, bool important);
 
   // Legacy parsing allows <string>s for animation-name
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollPadding.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollPadding.cpp
new file mode 100644
index 0000000..1e911fd
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollPadding.cpp
@@ -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.
+
+#include "core/css/properties/CSSPropertyAPIScrollPadding.h"
+
+#include "core/css/parser/CSSParserContext.h"
+#include "core/css/parser/CSSPropertyParserHelpers.h"
+
+namespace blink {
+
+const CSSValue* CSSPropertyAPIScrollPadding::parseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) {
+  return ConsumeLengthOrPercent(
+      range, context.Mode(), kValueRangeNonNegative,
+      CSSPropertyParserHelpers::UnitlessQuirk::kAllow);
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapAlign.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapAlign.cpp
new file mode 100644
index 0000000..8aa1dae
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapAlign.cpp
@@ -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.
+
+#include "core/css/properties/CSSPropertyAPIScrollSnapAlign.h"
+
+#include "core/css/CSSValuePair.h"
+#include "core/css/parser/CSSPropertyParserHelpers.h"
+
+namespace blink {
+
+const CSSValue* CSSPropertyAPIScrollSnapAlign::parseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) {
+  CSSValueID x_id = range.Peek().Id();
+  if (x_id != CSSValueNone && x_id != CSSValueStart && x_id != CSSValueEnd &&
+      x_id != CSSValueCenter)
+    return nullptr;
+  CSSValue* x_value = CSSPropertyParserHelpers::ConsumeIdent(range);
+  if (range.AtEnd())
+    return x_value;
+
+  CSSValueID y_id = range.Peek().Id();
+  if (y_id != CSSValueNone && y_id != CSSValueStart && y_id != CSSValueEnd &&
+      y_id != CSSValueCenter)
+    return x_value;
+  CSSValue* y_value = CSSPropertyParserHelpers::ConsumeIdent(range);
+  CSSValuePair* pair = CSSValuePair::Create(x_value, y_value,
+                                            CSSValuePair::kDropIdenticalValues);
+  return pair;
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapCoordinate.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapCoordinate.cpp
deleted file mode 100644
index 95a03eb3..0000000
--- a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapCoordinate.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "core/css/properties/CSSPropertyAPIScrollSnapCoordinate.h"
-
-#include "core/css/CSSValueList.h"
-#include "core/css/CSSValuePair.h"
-#include "core/css/parser/CSSParserContext.h"
-#include "core/css/parser/CSSPropertyParserHelpers.h"
-
-// TODO(crbug.com/724912): Retire scroll-snap-coordinate
-
-class CSSParserLocalContext;
-namespace blink {
-
-using namespace CSSPropertyParserHelpers;
-
-static CSSValueList* ConsumePositionList(CSSParserTokenRange& range,
-                                         const CSSParserContext& context) {
-  CSSValueList* positions = CSSValueList::CreateCommaSeparated();
-  do {
-    CSSValue* position = ConsumePosition(range, context, UnitlessQuirk::kForbid,
-                                         Optional<WebFeature>());
-    if (!position)
-      return nullptr;
-    positions->Append(*position);
-  } while (ConsumeCommaIncludingWhitespace(range));
-  return positions;
-}
-
-const CSSValue* CSSPropertyAPIScrollSnapCoordinate::parseSingleValue(
-    CSSParserTokenRange& range,
-    const CSSParserContext& context,
-    const CSSParserLocalContext&) {
-  if (range.Peek().Id() == CSSValueNone)
-    return ConsumeIdent(range);
-  return ConsumePositionList(range, context);
-}
-
-}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapMargin.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapMargin.cpp
new file mode 100644
index 0000000..ee0e8e45
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapMargin.cpp
@@ -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.
+
+#include "core/css/properties/CSSPropertyAPIScrollSnapMargin.h"
+
+#include "core/css/parser/CSSParserContext.h"
+#include "core/css/parser/CSSPropertyParserHelpers.h"
+
+namespace blink {
+
+const CSSValue* CSSPropertyAPIScrollSnapMargin::parseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) {
+  return ConsumeLengthOrPercent(
+      range, context.Mode(), kValueRangeNonNegative,
+      CSSPropertyParserHelpers::UnitlessQuirk::kAllow);
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapType.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapType.cpp
new file mode 100644
index 0000000..fa30bb9
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIScrollSnapType.cpp
@@ -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.
+
+#include "core/css/properties/CSSPropertyAPIScrollSnapType.h"
+
+#include "core/css/CSSValuePair.h"
+#include "core/css/parser/CSSPropertyParserHelpers.h"
+
+namespace blink {
+
+const CSSValue* CSSPropertyAPIScrollSnapType::parseSingleValue(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) {
+  CSSValueID axis_id = range.Peek().Id();
+  if (axis_id != CSSValueNone && axis_id != CSSValueX && axis_id != CSSValueY &&
+      axis_id != CSSValueBlock && axis_id != CSSValueInline &&
+      axis_id != CSSValueBoth)
+    return nullptr;
+  CSSValue* axis_value = CSSPropertyParserHelpers::ConsumeIdent(range);
+  if (range.AtEnd() || axis_id == CSSValueNone)
+    return axis_value;
+
+  CSSValueID strictness_id = range.Peek().Id();
+  if (strictness_id != CSSValueProximity && strictness_id != CSSValueMandatory)
+    return axis_value;
+  CSSValue* strictness_value = CSSPropertyParserHelpers::ConsumeIdent(range);
+  CSSValuePair* pair = CSSValuePair::Create(axis_value, strictness_value,
+                                            CSSValuePair::kDropIdenticalValues);
+  return pair;
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp b/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp
index 2c6c1ef..be66909 100644
--- a/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp
+++ b/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp
@@ -1247,41 +1247,43 @@
       StyleBuilderConverter::ConvertComputedLength<float>(state, list.Item(2)));
 }
 
-ScrollSnapPoints StyleBuilderConverter::ConvertSnapPoints(
-    StyleResolverState& state,
-    const CSSValue& value) {
-  // Handles: none | repeat(<length>)
-  ScrollSnapPoints points;
-  points.has_repeat = false;
-
-  if (!value.IsFunctionValue())
-    return points;
-
-  const CSSFunctionValue& repeat_function = ToCSSFunctionValue(value);
-  SECURITY_DCHECK(repeat_function.length() == 1);
-  points.repeat_offset =
-      ConvertLength(state, ToCSSPrimitiveValue(repeat_function.Item(0)));
-  points.has_repeat = true;
-
-  return points;
-}
-
-Vector<LengthPoint> StyleBuilderConverter::ConvertSnapCoordinates(
-    StyleResolverState& state,
-    const CSSValue& value) {
-  // Handles: none | <position>#
-  Vector<LengthPoint> coordinates;
-
-  if (!value.IsValueList())
-    return coordinates;
-
-  const CSSValueList& value_list = ToCSSValueList(value);
-  coordinates.ReserveInitialCapacity(value_list.length());
-  for (auto& snap_coordinate : value_list) {
-    coordinates.UncheckedAppend(ConvertPosition(state, *snap_coordinate));
+ScrollSnapType StyleBuilderConverter::ConvertSnapType(StyleResolverState&,
+                                                      const CSSValue& value) {
+  ScrollSnapType snapType = ComputedStyle::InitialScrollSnapType();
+  if (value.IsValuePair()) {
+    const CSSValuePair& pair = ToCSSValuePair(value);
+    snapType.is_none = false;
+    snapType.axis = ToCSSIdentifierValue(pair.First()).ConvertTo<SnapAxis>();
+    snapType.strictness =
+        ToCSSIdentifierValue(pair.Second()).ConvertTo<SnapStrictness>();
+    return snapType;
   }
 
-  return coordinates;
+  if (ToCSSIdentifierValue(value).GetValueID() == CSSValueNone) {
+    snapType.is_none = true;
+    return snapType;
+  }
+
+  snapType.is_none = false;
+  snapType.axis = ToCSSIdentifierValue(value).ConvertTo<SnapAxis>();
+  return snapType;
+}
+
+ScrollSnapAlign StyleBuilderConverter::ConvertSnapAlign(StyleResolverState&,
+                                                        const CSSValue& value) {
+  ScrollSnapAlign snapAlign = ComputedStyle::InitialScrollSnapAlign();
+  if (value.IsValuePair()) {
+    const CSSValuePair& pair = ToCSSValuePair(value);
+    snapAlign.alignmentX =
+        ToCSSIdentifierValue(pair.First()).ConvertTo<SnapAlignment>();
+    snapAlign.alignmentY =
+        ToCSSIdentifierValue(pair.Second()).ConvertTo<SnapAlignment>();
+  } else {
+    snapAlign.alignmentX =
+        ToCSSIdentifierValue(value).ConvertTo<SnapAlignment>();
+    snapAlign.alignmentY = snapAlign.alignmentX;
+  }
+  return snapAlign;
 }
 
 PassRefPtr<TranslateTransformOperation> StyleBuilderConverter::ConvertTranslate(
diff --git a/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.h b/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.h
index 7311c164..eb69d8a9 100644
--- a/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.h
+++ b/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.h
@@ -178,12 +178,8 @@
       const OrderedNamedGridLines&,
       NamedGridLinesMap&);
 
-  static ScrollSnapPoints ConvertSnapPoints(StyleResolverState&,
-                                            const CSSValue&);
-  static Vector<LengthPoint> ConvertSnapCoordinates(StyleResolverState&,
-                                                    const CSSValue&);
-  static LengthPoint ConvertSnapDestination(StyleResolverState&,
-                                            const CSSValue&);
+  static ScrollSnapType ConvertSnapType(StyleResolverState&, const CSSValue&);
+  static ScrollSnapAlign ConvertSnapAlign(StyleResolverState&, const CSSValue&);
   static PassRefPtr<TranslateTransformOperation> ConvertTranslate(
       StyleResolverState&,
       const CSSValue&);
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index 61e9ade..fd0f51c 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -1949,7 +1949,6 @@
   }
 
   ScrollSnapType snap_type = overflow_style->GetScrollSnapType();
-  const LengthPoint& snap_destination = overflow_style->ScrollSnapDestination();
 
   RefPtr<ComputedStyle> document_style = GetLayoutViewItem().MutableStyle();
   if (document_style->GetWritingMode() != root_writing_mode ||
@@ -1962,8 +1961,7 @@
       document_style->OverflowX() != overflow_x ||
       document_style->OverflowY() != overflow_y ||
       document_style->ColumnGap() != column_gap ||
-      document_style->GetScrollSnapType() != snap_type ||
-      document_style->ScrollSnapDestination() != snap_destination) {
+      document_style->GetScrollSnapType() != snap_type) {
     RefPtr<ComputedStyle> new_style = ComputedStyle::Clone(*document_style);
     new_style->SetWritingMode(root_writing_mode);
     new_style->SetDirection(root_direction);
@@ -1975,7 +1973,6 @@
     new_style->SetOverflowY(overflow_y);
     new_style->SetColumnGap(column_gap);
     new_style->SetScrollSnapType(snap_type);
-    new_style->SetScrollSnapDestination(snap_destination);
     GetLayoutViewItem().SetStyle(new_style);
     SetupFontBuilder(*new_style);
   }
diff --git a/third_party/WebKit/Source/core/frame/UseCounter.cpp b/third_party/WebKit/Source/core/frame/UseCounter.cpp
index e803c85..f801238 100644
--- a/third_party/WebKit/Source/core/frame/UseCounter.cpp
+++ b/third_party/WebKit/Source/core/frame/UseCounter.cpp
@@ -46,7 +46,7 @@
 }
 
 // Make sure update_use_counter_css.py was run which updates histograms.xml.
-constexpr int kMaximumCSSSampleId = 560;
+constexpr int kMaximumCSSSampleId = 584;
 
 }  // namespace
 
@@ -963,14 +963,10 @@
       return 498;
     case CSSPropertyScrollSnapType:
       return 499;
-    case CSSPropertyScrollSnapPointsX:
-      return 500;
-    case CSSPropertyScrollSnapPointsY:
-      return 501;
-    case CSSPropertyScrollSnapCoordinate:
-      return 502;
-    case CSSPropertyScrollSnapDestination:
-      return 503;
+    // CSSPropertyScrollSnapPointsX was 500.
+    // CSSPropertyScrollSnapPointsY was 501.
+    // CSSPropertyScrollSnapCoordinate was 502.
+    // CSSPropertyScrollSnapDestination was 503.
     case CSSPropertyTranslate:
       return 504;
     case CSSPropertyRotate:
@@ -1084,6 +1080,54 @@
       return 559;
     case CSSPropertyPlaceSelf:
       return 560;
+    case CSSPropertyScrollSnapAlign:
+      return 561;
+    case CSSPropertyScrollPadding:
+      return 562;
+    case CSSPropertyScrollPaddingTop:
+      return 563;
+    case CSSPropertyScrollPaddingRight:
+      return 564;
+    case CSSPropertyScrollPaddingBottom:
+      return 565;
+    case CSSPropertyScrollPaddingLeft:
+      return 566;
+    case CSSPropertyScrollPaddingBlock:
+      return 567;
+    case CSSPropertyScrollPaddingBlockStart:
+      return 568;
+    case CSSPropertyScrollPaddingBlockEnd:
+      return 569;
+    case CSSPropertyScrollPaddingInline:
+      return 570;
+    case CSSPropertyScrollPaddingInlineStart:
+      return 571;
+    case CSSPropertyScrollPaddingInlineEnd:
+      return 572;
+    case CSSPropertyScrollSnapMargin:
+      return 573;
+    case CSSPropertyScrollSnapMarginTop:
+      return 574;
+    case CSSPropertyScrollSnapMarginRight:
+      return 575;
+    case CSSPropertyScrollSnapMarginBottom:
+      return 576;
+    case CSSPropertyScrollSnapMarginLeft:
+      return 577;
+    case CSSPropertyScrollSnapMarginBlock:
+      return 578;
+    case CSSPropertyScrollSnapMarginBlockStart:
+      return 579;
+    case CSSPropertyScrollSnapMarginBlockEnd:
+      return 580;
+    case CSSPropertyScrollSnapMarginInline:
+      return 581;
+    case CSSPropertyScrollSnapMarginInlineStart:
+      return 582;
+    case CSSPropertyScrollSnapMarginInlineEnd:
+      return 583;
+    case CSSPropertyScrollSnapStop:
+      return 584;
     // 1. Add new features above this line (don't change the assigned numbers of
     // the existing items).
     // 2. Update kMaximumCSSSampleId with the new maximum value.
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.cpp b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
index 8c1b379..21f1a31 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
@@ -451,20 +451,20 @@
       GetNode() != GetDocument().ViewportDefiningElement();
 
   ScrollSnapType old_snap_type =
-      old_style ? old_style->GetScrollSnapType() : kScrollSnapTypeNone;
+      old_style ? old_style->GetScrollSnapType() : ScrollSnapType();
   ScrollSnapType new_snap_type = new_style && allows_snap_container
                                      ? new_style->GetScrollSnapType()
-                                     : kScrollSnapTypeNone;
+                                     : ScrollSnapType();
   if (old_snap_type != new_snap_type)
     snap_coordinator->SnapContainerDidChange(*this, new_snap_type);
 
-  Vector<LengthPoint> empty_vector;
-  const Vector<LengthPoint>& old_snap_coordinate =
-      old_style ? old_style->ScrollSnapCoordinate() : empty_vector;
-  const Vector<LengthPoint>& new_snap_coordinate =
-      new_style ? new_style->ScrollSnapCoordinate() : empty_vector;
-  if (old_snap_coordinate != new_snap_coordinate)
-    snap_coordinator->SnapAreaDidChange(*this, new_snap_coordinate);
+  ScrollSnapAlign old_snap_align =
+      old_style ? old_style->GetScrollSnapAlign() : ScrollSnapAlign();
+  ScrollSnapAlign new_snap_align = new_style && allows_snap_container
+                                       ? new_style->GetScrollSnapAlign()
+                                       : ScrollSnapAlign();
+  if (old_snap_align != new_snap_align)
+    snap_coordinator->SnapAreaDidChange(*this, new_snap_align);
 }
 
 void LayoutBox::AddScrollSnapMapping() {
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h
index cd43aa2..d2c10a3 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h
@@ -297,6 +297,12 @@
                              -BorderLeft());
   }
 
+  LayoutRectOutsets BorderPaddingInsets() const {
+    return LayoutRectOutsets(
+        -(PaddingTop() + BorderTop()), -(PaddingRight() + BorderRight()),
+        -(PaddingBottom() + BorderBottom()), -(PaddingLeft() + BorderLeft()));
+  }
+
   bool HasBorderOrPadding() const {
     return Style()->HasBorder() || Style()->HasPadding();
   }
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_item_result.h b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_item_result.h
index 6a312e3..ea695dd8 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_item_result.h
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_item_result.h
@@ -93,6 +93,7 @@
 
   // NGInlineItemResults for this line.
   NGInlineItemResults& Results() { return results_; }
+  const NGInlineItemResults& Results() const { return results_; }
 
  private:
   const ComputedStyle* line_style_ = nullptr;
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index 38a28a8..d387bb9 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -255,7 +255,8 @@
   container_builder_.AddChild(line_box.ToLineBoxFragment(), offset);
 
   max_inline_size_ = std::max(max_inline_size_, inline_size);
-  content_size_ = line_bottom;
+  content_size_ = ComputeContentSize(*line_info, line_bottom);
+
   return true;
 }
 
@@ -332,6 +333,35 @@
   }
 }
 
+LayoutUnit NGInlineLayoutAlgorithm::ComputeContentSize(
+    const NGLineInfo& line_info,
+    LayoutUnit line_bottom) {
+  LayoutUnit content_size = line_bottom;
+
+  const Vector<NGInlineItem>& items = Node().Items();
+  const NGInlineItemResults& line_items = line_info.Results();
+  DCHECK(!line_items.IsEmpty());
+
+  // If the last item was a <br> we need to adjust the content_size to clear
+  // floats if specified. The <br> element must be at the back of the item
+  // result list as it forces a line to break.
+  const NGInlineItemResult& item_result = line_items.back();
+  const NGInlineItem& item = items[item_result.item_index];
+  const LayoutObject* layout_object = item.GetLayoutObject();
+
+  // layout_object may be null in certain cases, e.g. if it's a kBidiControl.
+  if (layout_object && layout_object->IsBR()) {
+    NGLogicalOffset bfc_offset =
+        ContainerBfcOffset() + NGLogicalOffset(LayoutUnit(), content_size);
+    AdjustToClearance(GetClearanceOffset(ConstraintSpace().Exclusions(),
+                                         item.Style()->Clear()),
+                      &bfc_offset);
+    content_size = bfc_offset.block_offset - ContainerBfcOffset().block_offset;
+  }
+
+  return content_size;
+}
+
 NGLayoutOpportunity NGInlineLayoutAlgorithm::FindLayoutOpportunityForLine() {
   // TODO(ikilpatrick): Using the constraint space BFC offset here seems wrong.
   // Logically we shouldn't hit this codepath when placing the items as we
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h
index 42dd4c4..fe8b26d 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h
@@ -68,6 +68,8 @@
                       LayoutUnit inline_size,
                       LayoutUnit available_width);
 
+  LayoutUnit ComputeContentSize(const NGLineInfo&, LayoutUnit line_bottom);
+
   NGLayoutOpportunity FindLayoutOpportunityForLine();
 
   NGInlineLayoutStateStack box_states_;
diff --git a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp
index 5762342d..b39e900 100644
--- a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp
+++ b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp
@@ -43,10 +43,10 @@
   return box;
 }
 
-void SnapCoordinator::SnapAreaDidChange(
-    LayoutBox& snap_area,
-    const Vector<LengthPoint>& snap_coordinates) {
-  if (snap_coordinates.IsEmpty()) {
+void SnapCoordinator::SnapAreaDidChange(LayoutBox& snap_area,
+                                        ScrollSnapAlign scroll_snap_align) {
+  if (scroll_snap_align.alignmentX == kSnapAlignmentNone &&
+      scroll_snap_align.alignmentY == kSnapAlignmentNone) {
     snap_area.SetSnapContainer(nullptr);
     return;
   }
@@ -62,7 +62,7 @@
 
 void SnapCoordinator::SnapContainerDidChange(LayoutBox& snap_container,
                                              ScrollSnapType scroll_snap_type) {
-  if (scroll_snap_type == kScrollSnapTypeNone) {
+  if (scroll_snap_type.is_none) {
     // TODO(majidvp): Track and report these removals to CompositorWorker
     // instance responsible for snapping
     snap_containers_.erase(&snap_container);
@@ -79,88 +79,6 @@
   // container or from existing areas in orphan pool.
 }
 
-// Translate local snap coordinates into snap container's scrolling content
-// coordinate space.
-static Vector<FloatPoint> LocalToContainerSnapCoordinates(
-    const LayoutBox& container_box,
-    const LayoutBox& snap_area) {
-  Vector<FloatPoint> result;
-  LayoutPoint scroll_offset(container_box.ScrollLeft(),
-                            container_box.ScrollTop());
-
-  const Vector<LengthPoint>& snap_coordinates =
-      snap_area.Style()->ScrollSnapCoordinate();
-  for (auto& coordinate : snap_coordinates) {
-    FloatPoint local_point =
-        FloatPointForLengthPoint(coordinate, FloatSize(snap_area.Size()));
-    FloatPoint container_point =
-        snap_area.LocalToAncestorPoint(local_point, &container_box);
-    container_point.MoveBy(scroll_offset);
-    result.push_back(container_point);
-  }
-  return result;
-}
-
-Vector<double> SnapCoordinator::SnapOffsets(const ContainerNode& element,
-                                            ScrollbarOrientation orientation) {
-  const ComputedStyle* style = element.GetComputedStyle();
-  const LayoutBox* snap_container = element.GetLayoutBox();
-  DCHECK(style);
-  DCHECK(snap_container);
-
-  Vector<double> result;
-
-  if (style->GetScrollSnapType() == kScrollSnapTypeNone)
-    return result;
-
-  const ScrollSnapPoints& snap_points = (orientation == kHorizontalScrollbar)
-                                            ? style->ScrollSnapPointsX()
-                                            : style->ScrollSnapPointsY();
-
-  LayoutUnit client_size = (orientation == kHorizontalScrollbar)
-                               ? snap_container->ClientWidth()
-                               : snap_container->ClientHeight();
-  LayoutUnit scroll_size = (orientation == kHorizontalScrollbar)
-                               ? snap_container->ScrollWidth()
-                               : snap_container->ScrollHeight();
-
-  if (snap_points.has_repeat) {
-    LayoutUnit repeat = ValueForLength(snap_points.repeat_offset, client_size);
-
-    // calc() values may be negative or zero in which case we clamp them to 1px.
-    // See: https://lists.w3.org/Archives/Public/www-style/2015Jul/0075.html
-    repeat = std::max<LayoutUnit>(repeat, LayoutUnit(1));
-    for (LayoutUnit offset = repeat; offset <= (scroll_size - client_size);
-         offset += repeat) {
-      result.push_back(offset.ToFloat());
-    }
-  }
-
-  // Compute element-based snap points by mapping the snap coordinates from
-  // snap areas to snap container.
-  bool did_add_snap_area_offset = false;
-  if (SnapAreaSet* snap_areas = snap_container->SnapAreas()) {
-    for (auto& snap_area : *snap_areas) {
-      Vector<FloatPoint> snap_coordinates =
-          LocalToContainerSnapCoordinates(*snap_container, *snap_area);
-      for (const FloatPoint& snap_coordinate : snap_coordinates) {
-        float snap_offset = (orientation == kHorizontalScrollbar)
-                                ? snap_coordinate.X()
-                                : snap_coordinate.Y();
-        if (snap_offset > scroll_size - client_size)
-          continue;
-        result.push_back(snap_offset);
-        did_add_snap_area_offset = true;
-      }
-    }
-  }
-
-  if (did_add_snap_area_offset)
-    std::sort(result.begin(), result.end());
-
-  return result;
-}
-
 #ifndef NDEBUG
 
 void SnapCoordinator::ShowSnapAreaMap() {
diff --git a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.h b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.h
index c9f5558..7f1b645 100644
--- a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.h
+++ b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.h
@@ -12,9 +12,9 @@
 
 namespace blink {
 
-class ContainerNode;
 class LayoutBox;
-struct LengthPoint;
+struct ScrollSnapType;
+struct ScrollSnapAlign;
 
 // Snap Coordinator keeps track of snap containers and all of their associated
 // snap areas. It also contains the logic to generate the list of valid snap
@@ -39,8 +39,7 @@
   DEFINE_INLINE_TRACE() {}
 
   void SnapContainerDidChange(LayoutBox&, ScrollSnapType);
-  void SnapAreaDidChange(LayoutBox&,
-                         const Vector<LengthPoint>& snap_coordinates);
+  void SnapAreaDidChange(LayoutBox&, ScrollSnapAlign);
 
 #ifndef NDEBUG
   void ShowSnapAreaMap();
@@ -51,8 +50,6 @@
   friend class SnapCoordinatorTest;
   explicit SnapCoordinator();
 
-  Vector<double> SnapOffsets(const ContainerNode&, ScrollbarOrientation);
-
   HashSet<const LayoutBox*> snap_containers_;
 };
 
diff --git a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinatorTest.cpp b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinatorTest.cpp
index fd5486f6..4f7c9ef 100644
--- a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinatorTest.cpp
+++ b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinatorTest.cpp
@@ -10,6 +10,7 @@
 #include "core/dom/Element.h"
 #include "core/frame/LocalFrameView.h"
 #include "core/html/HTMLElement.h"
+#include "core/layout/LayoutBox.h"
 #include "core/style/ComputedStyle.h"
 #include "core/testing/DummyPageHolder.h"
 #include "platform/scroll/ScrollTypes.h"
@@ -35,7 +36,7 @@
         "        height: 1000px;"
         "        width: 1000px;"
         "        overflow: scroll;"
-        "        scroll-snap-type: mandatory;"
+        "        scroll-snap-type: both mandatory;"
         "    }"
         "    #snap-element-fixed-position {"
         "         position: fixed;"
@@ -66,11 +67,10 @@
     return *GetDocument().getElementById("snap-container");
   }
 
-  SnapCoordinator& Coordinator() { return *GetDocument().GetSnapCoordinator(); }
-
-  Vector<double> SnapOffsets(const ContainerNode& node,
-                             ScrollbarOrientation orientation) {
-    return Coordinator().SnapOffsets(node, orientation);
+  unsigned SizeOfSnapAreas(const ContainerNode& node) {
+    if (node.GetLayoutBox()->SnapAreas())
+      return node.GetLayoutBox()->SnapAreas()->size();
+    return 0U;
   }
 
   std::unique_ptr<DummyPageHolder> page_holder_;
@@ -78,100 +78,25 @@
 
 INSTANTIATE_TEST_CASE_P(All, SnapCoordinatorTest, ::testing::Bool());
 
-TEST_P(SnapCoordinatorTest, ValidRepeat) {
-  SnapContainer().setAttribute(styleAttr,
-                               "scroll-snap-points-x: repeat(20%); "
-                               "scroll-snap-points-y: repeat(400px);");
-  GetDocument().UpdateStyleAndLayout();
-  {
-    const int expected_step_size = SnapContainer().clientWidth() * 0.2;
-    Vector<double> actual = SnapOffsets(SnapContainer(), kHorizontalScrollbar);
-    EXPECT_EQ(5U, actual.size());
-    for (size_t i = 0; i < actual.size(); ++i)
-      EXPECT_EQ((i + 1) * expected_step_size, actual[i]);
-  }
-  {
-    Vector<double> actual = SnapOffsets(SnapContainer(), kVerticalScrollbar);
-    EXPECT_EQ(2U, actual.size());
-    EXPECT_EQ(400, actual[0]);
-    EXPECT_EQ(800, actual[1]);
-  }
-}
-
-TEST_P(SnapCoordinatorTest, EmptyRepeat) {
-  SnapContainer().setAttribute(
-      styleAttr, "scroll-snap-points-x: none; scroll-snap-points-y: none;");
-  GetDocument().UpdateStyleAndLayout();
-
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kHorizontalScrollbar).size());
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kVerticalScrollbar).size());
-}
-
-TEST_P(SnapCoordinatorTest, ZeroAndNegativeRepeat) {
-  // These be rejected as an invalid repeat values thus no snap offset is
-  // created.
-  SnapContainer().setAttribute(
-      styleAttr,
-      "scroll-snap-points-x: repeat(-1px); scroll-snap-points-y: repeat(0px);");
-  GetDocument().UpdateStyleAndLayout();
-
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kHorizontalScrollbar).size());
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kVerticalScrollbar).size());
-
-  // Calc values are not be rejected outright but instead clamped to 1px min
-  // repeat value.
-  SnapContainer().setAttribute(styleAttr,
-                               "scroll-snap-points-x: repeat(calc(10px - "
-                               "100%)); scroll-snap-points-y: "
-                               "repeat(calc(0px));");
-  GetDocument().UpdateStyleAndLayout();
-
-  // A repeat value of 1px should give us |(scroll size - client size) / 1| snap
-  // offsets.
-  unsigned expected_horizontal_snap_offsets =
-      SnapContainer().scrollWidth() - SnapContainer().clientWidth();
-  EXPECT_EQ(expected_horizontal_snap_offsets,
-            SnapOffsets(SnapContainer(), kHorizontalScrollbar).size());
-  unsigned expected_vertical_snap_offsets =
-      SnapContainer().scrollHeight() - SnapContainer().clientHeight();
-  EXPECT_EQ(expected_vertical_snap_offsets,
-            SnapOffsets(SnapContainer(), kVerticalScrollbar).size());
-}
-
 TEST_P(SnapCoordinatorTest, SimpleSnapElement) {
   Element& snap_element = *GetDocument().getElementById("snap-element");
-  snap_element.setAttribute(styleAttr, "scroll-snap-coordinate: 10px 11px;");
+  snap_element.setAttribute(styleAttr, "scroll-snap-align: start;");
   GetDocument().UpdateStyleAndLayout();
 
-  EXPECT_EQ(10, SnapOffsets(SnapContainer(), kHorizontalScrollbar)[0]);
-  EXPECT_EQ(11, SnapOffsets(SnapContainer(), kVerticalScrollbar)[0]);
-
-  // Multiple coordinate and translates
-  snap_element.setAttribute(styleAttr,
-                            "scroll-snap-coordinate: 20px 21px, 40px 41px; "
-                            "transform: translate(10px, 10px);");
-  GetDocument().UpdateStyleAndLayout();
-
-  Vector<double> result = SnapOffsets(SnapContainer(), kHorizontalScrollbar);
-  EXPECT_EQ(30, result[0]);
-  EXPECT_EQ(50, result[1]);
-  result = SnapOffsets(SnapContainer(), kVerticalScrollbar);
-  EXPECT_EQ(31, result[0]);
-  EXPECT_EQ(51, result[1]);
+  EXPECT_EQ(1U, SizeOfSnapAreas(SnapContainer()));
 }
 
 TEST_P(SnapCoordinatorTest, NestedSnapElement) {
   Element& snap_element = *GetDocument().getElementById("nested-snap-element");
-  snap_element.setAttribute(styleAttr, "scroll-snap-coordinate: 20px 25px;");
+  snap_element.setAttribute(styleAttr, "scroll-snap-align: start;");
   GetDocument().UpdateStyleAndLayout();
 
-  EXPECT_EQ(20, SnapOffsets(SnapContainer(), kHorizontalScrollbar)[0]);
-  EXPECT_EQ(25, SnapOffsets(SnapContainer(), kVerticalScrollbar)[0]);
+  EXPECT_EQ(1U, SizeOfSnapAreas(SnapContainer()));
 }
 
 TEST_P(SnapCoordinatorTest, NestedSnapElementCaptured) {
   Element& snap_element = *GetDocument().getElementById("nested-snap-element");
-  snap_element.setAttribute(styleAttr, "scroll-snap-coordinate: 20px 25px;");
+  snap_element.setAttribute(styleAttr, "scroll-snap-align: start;");
 
   Element* intermediate = GetDocument().getElementById("intermediate");
   intermediate->setAttribute(styleAttr, "overflow: scroll;");
@@ -180,92 +105,47 @@
 
   // Intermediate scroller captures nested snap elements first so ancestor
   // does not get them.
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kHorizontalScrollbar).size());
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kVerticalScrollbar).size());
+  EXPECT_EQ(0U, SizeOfSnapAreas(SnapContainer()));
+  EXPECT_EQ(1U, SizeOfSnapAreas(*intermediate));
 }
 
 TEST_P(SnapCoordinatorTest, PositionFixedSnapElement) {
   Element& snap_element =
       *GetDocument().getElementById("snap-element-fixed-position");
-  snap_element.setAttribute(styleAttr, "scroll-snap-coordinate: 1px 1px;");
+  snap_element.setAttribute(styleAttr, "scroll-snap-align: start;");
   GetDocument().UpdateStyleAndLayout();
 
   // Position fixed elements are contained in document and not its immediate
   // ancestor scroller. They cannot be a valid snap destination so they should
   // not contribute snap points to their immediate snap container or document
   // See: https://lists.w3.org/Archives/Public/www-style/2015Jun/0376.html
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kHorizontalScrollbar).size());
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kVerticalScrollbar).size());
+  EXPECT_EQ(0U, SizeOfSnapAreas(SnapContainer()));
 
   Element* body = GetDocument().ViewportDefiningElement();
-  EXPECT_EQ(0U, SnapOffsets(*body, kHorizontalScrollbar).size());
-  EXPECT_EQ(0U, SnapOffsets(*body, kVerticalScrollbar).size());
-}
-
-TEST_P(SnapCoordinatorTest, RepeatAndSnapElementTogether) {
-  GetDocument()
-      .getElementById("snap-element")
-      ->setAttribute(styleAttr, "scroll-snap-coordinate: 5px 10px;");
-  GetDocument()
-      .getElementById("nested-snap-element")
-      ->setAttribute(styleAttr, "scroll-snap-coordinate: 250px 450px;");
-
-  SnapContainer().setAttribute(styleAttr,
-                               "scroll-snap-points-x: repeat(200px); "
-                               "scroll-snap-points-y: repeat(400px);");
-  GetDocument().UpdateStyleAndLayout();
-
-  {
-    Vector<double> result = SnapOffsets(SnapContainer(), kHorizontalScrollbar);
-    EXPECT_EQ(7U, result.size());
-    EXPECT_EQ(5, result[0]);
-    EXPECT_EQ(200, result[1]);
-    EXPECT_EQ(250, result[2]);
-  }
-  {
-    Vector<double> result = SnapOffsets(SnapContainer(), kVerticalScrollbar);
-    EXPECT_EQ(4U, result.size());
-    EXPECT_EQ(10, result[0]);
-    EXPECT_EQ(400, result[1]);
-    EXPECT_EQ(450, result[2]);
-  }
-}
-
-TEST_P(SnapCoordinatorTest, SnapPointsAreScrollOffsetIndependent) {
-  Element& snap_element = *GetDocument().getElementById("snap-element");
-  snap_element.setAttribute(styleAttr, "scroll-snap-coordinate: 10px 11px;");
-  SnapContainer().scrollBy(100, 100);
-  GetDocument().UpdateStyleAndLayout();
-
-  EXPECT_EQ(SnapContainer().scrollLeft(), 100);
-  EXPECT_EQ(SnapContainer().scrollTop(), 100);
-  EXPECT_EQ(10, SnapOffsets(SnapContainer(), kHorizontalScrollbar)[0]);
-  EXPECT_EQ(11, SnapOffsets(SnapContainer(), kVerticalScrollbar)[0]);
+  EXPECT_EQ(0U, SizeOfSnapAreas(*body));
 }
 
 TEST_P(SnapCoordinatorTest, UpdateStyleForSnapElement) {
   Element& snap_element = *GetDocument().getElementById("snap-element");
-  snap_element.setAttribute(styleAttr, "scroll-snap-coordinate: 10px 11px;");
+  snap_element.setAttribute(styleAttr, "scroll-snap-align: start;");
   GetDocument().UpdateStyleAndLayout();
 
-  EXPECT_EQ(10, SnapOffsets(SnapContainer(), kHorizontalScrollbar)[0]);
-  EXPECT_EQ(11, SnapOffsets(SnapContainer(), kVerticalScrollbar)[0]);
+  EXPECT_EQ(1U, SizeOfSnapAreas(SnapContainer()));
 
   snap_element.remove();
   GetDocument().UpdateStyleAndLayout();
 
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kHorizontalScrollbar).size());
-  EXPECT_EQ(0U, SnapOffsets(SnapContainer(), kVerticalScrollbar).size());
+  EXPECT_EQ(0U, SizeOfSnapAreas(SnapContainer()));
 
   // Add a new snap element
   Element& container = *GetDocument().getElementById("snap-container");
   container.setInnerHTML(
-      "<div style='scroll-snap-coordinate: 20px 22px;'><div "
-      "style='width:2000px; height:2000px;'></div></div>");
+      "<div style='scroll-snap-align: start;'>"
+      "    <div style='width:2000px; height:2000px;'></div>"
+      "</div>");
   GetDocument().UpdateStyleAndLayout();
 
-  EXPECT_EQ(20, SnapOffsets(SnapContainer(), kHorizontalScrollbar)[0]);
-  EXPECT_EQ(22, SnapOffsets(SnapContainer(), kVerticalScrollbar)[0]);
+  EXPECT_EQ(1U, SizeOfSnapAreas(SnapContainer()));
 }
 
 TEST_P(SnapCoordinatorTest, LayoutViewCapturesWhenBodyElementViewportDefining) {
@@ -273,18 +153,17 @@
       "<style>"
       "body {"
       "    overflow: scroll;"
-      "    scroll-snap-type: mandatory;"
+      "    scroll-snap-type: both mandatory;"
       "    height: 1000px;"
       "    width: 1000px;"
       "    margin: 5px;"
       "}"
       "</style>"
       "<body>"
-      "    <div id='snap-element' style='scroll-snap-coordinate: 5px "
-      "6px;'></div>"
-      "    <div>"
-      "       <div id='nested-snap-element' style='scroll-snap-coordinate: "
-      "10px 11px;'></div>"
+      "    <div id='snap-element' style='scroll-snap-align: start;></div>"
+      "    <div id='intermediate'>"
+      "        <div id='nested-snap-element'"
+      "            style='scroll-snap-align: start;'></div>"
       "    </div>"
       "    <div style='width:2000px; height:2000px;'></div>"
       "</body>");
@@ -296,12 +175,9 @@
 
   // When body is viewport defining and overflows then any snap points on the
   // body element will be captured by layout view as the snap container.
-  Vector<double> result = SnapOffsets(GetDocument(), kHorizontalScrollbar);
-  EXPECT_EQ(10, result[0]);
-  EXPECT_EQ(15, result[1]);
-  result = SnapOffsets(GetDocument(), kVerticalScrollbar);
-  EXPECT_EQ(11, result[0]);
-  EXPECT_EQ(16, result[1]);
+  EXPECT_EQ(2U, SizeOfSnapAreas(GetDocument()));
+  EXPECT_EQ(0U, SizeOfSnapAreas(*(GetDocument().body())));
+  EXPECT_EQ(0U, SizeOfSnapAreas(*(GetDocument().documentElement())));
 }
 
 TEST_P(SnapCoordinatorTest,
@@ -310,7 +186,7 @@
       "<style>"
       ":root {"
       "    overflow: scroll;"
-      "    scroll-snap-type: mandatory;"
+      "    scroll-snap-type: both mandatory;"
       "    height: 500px;"
       "    width: 500px;"
       "}"
@@ -320,11 +196,10 @@
       "</style>"
       "<html>"
       "   <body>"
-      "       <div id='snap-element' style='scroll-snap-coordinate: 5px "
-      "6px;'></div>"
-      "       <div>"
-      "         <div id='nested-snap-element' style='scroll-snap-coordinate: "
-      "10px 11px;'></div>"
+      "       <div id='snap-element' style='scroll-snap-align: start;></div>"
+      "       <div id='intermediate'>"
+      "         <div id='nested-snap-element'"
+      "             style='scroll-snap-align: start;'></div>"
       "      </div>"
       "      <div style='width:2000px; height:2000px;'></div>"
       "   </body>"
@@ -339,12 +214,9 @@
   // When body is viewport defining and overflows then any snap points on the
   // the document element will be captured by layout view as the snap
   // container.
-  Vector<double> result = SnapOffsets(GetDocument(), kHorizontalScrollbar);
-  EXPECT_EQ(10, result[0]);
-  EXPECT_EQ(15, result[1]);
-  result = SnapOffsets(GetDocument(), kVerticalScrollbar);
-  EXPECT_EQ(11, result[0]);
-  EXPECT_EQ(16, result[1]);
+  EXPECT_EQ(2U, SizeOfSnapAreas(GetDocument()));
+  EXPECT_EQ(0U, SizeOfSnapAreas(*(GetDocument().body())));
+  EXPECT_EQ(0U, SizeOfSnapAreas(*(GetDocument().documentElement())));
 }
 
 TEST_P(SnapCoordinatorTest,
@@ -353,26 +225,25 @@
       "<style>"
       ":root {"
       "    overflow: scroll;"
-      "    scroll-snap-type: mandatory;"
+      "    scroll-snap-type: both mandatory;"
       "    height: 500px;"
       "    width: 500px;"
       "}"
       "body {"
       "    overflow: scroll;"
-      "    scroll-snap-type: mandatory;"
+      "    scroll-snap-type: both mandatory;"
       "    height: 1000px;"
       "    width: 1000px;"
       "    margin: 5px;"
       "}"
       "</style>"
       "<html>"
-      "   <body style='overflow: scroll; scroll-snap-type: mandatory; "
+      "   <body style='overflow: scroll; scroll-snap-type: both mandatory; "
       "height:1000px; width:1000px;'>"
-      "       <div id='snap-element' style='scroll-snap-coordinate: 5px "
-      "6px;'></div>"
-      "       <div>"
-      "         <div id='nested-snap-element' style='scroll-snap-coordinate: "
-      "10px 11px;'></div>"
+      "       <div id='snap-element' style='scroll-snap-align: start;></div>"
+      "       <div id='intermediate'>"
+      "         <div id='nested-snap-element'"
+      "             style='scroll-snap-align: start;'></div>"
       "      </div>"
       "      <div style='width:2000px; height:2000px;'></div>"
       "   </body>"
@@ -387,12 +258,7 @@
   // When body and document elements are both scrollable then body element
   // should capture snap points defined on it as opposed to layout view.
   Element& body = *GetDocument().body();
-  Vector<double> result = SnapOffsets(body, kHorizontalScrollbar);
-  EXPECT_EQ(5, result[0]);
-  EXPECT_EQ(10, result[1]);
-  result = SnapOffsets(body, kVerticalScrollbar);
-  EXPECT_EQ(6, result[0]);
-  EXPECT_EQ(11, result[1]);
+  EXPECT_EQ(2U, SizeOfSnapAreas(body));
 }
 
 }  // namespace
diff --git a/third_party/WebKit/Source/core/paint/BoxPainter.cpp b/third_party/WebKit/Source/core/paint/BoxPainter.cpp
index 2e1d851..55b61d7 100644
--- a/third_party/WebKit/Source/core/paint/BoxPainter.cpp
+++ b/third_party/WebKit/Source/core/paint/BoxPainter.cpp
@@ -24,16 +24,13 @@
 #include "core/paint/ObjectPainter.h"
 #include "core/paint/PaintInfo.h"
 #include "core/paint/PaintLayer.h"
-#include "core/paint/RoundedInnerRectClipper.h"
 #include "core/paint/ScrollRecorder.h"
 #include "core/paint/ThemePainter.h"
 #include "core/style/ShadowList.h"
 #include "platform/LengthFunctions.h"
 #include "platform/geometry/LayoutPoint.h"
-#include "platform/geometry/LayoutRectOutsets.h"
 #include "platform/graphics/GraphicsContextStateSaver.h"
 #include "platform/graphics/paint/CompositingDisplayItem.h"
-#include "platform/wtf/Optional.h"
 
 namespace blink {
 
@@ -433,30 +430,9 @@
 
   Optional<RoundedInnerRectClipper> clip_to_border;
   if (info.is_rounded_fill) {
-    FloatRoundedRect border =
-        info.is_border_fill
-            ? BackgroundRoundedRectAdjustedForBleedAvoidance(
-                  obj.StyleRef(), rect, bleed_avoidance, has_line_box_sibling,
-                  box_size, info.include_left_edge, info.include_right_edge)
-            : GetBackgroundRoundedRect(
-                  obj.StyleRef(), rect, has_line_box_sibling, box_size,
-                  info.include_left_edge, info.include_right_edge);
-
-    // Clip to the padding or content boxes as necessary.
-    if (bg_layer.Clip() == kContentFillBox) {
-      border = obj.Style()->GetRoundedInnerBorderFor(
-          LayoutRect(border.Rect()),
-          LayoutRectOutsets(-(obj.PaddingTop() + obj.BorderTop()),
-                            -(obj.PaddingRight() + obj.BorderRight()),
-                            -(obj.PaddingBottom() + obj.BorderBottom()),
-                            -(obj.PaddingLeft() + obj.BorderLeft())),
-          info.include_left_edge, info.include_right_edge);
-    } else if (bg_layer.Clip() == kPaddingFillBox) {
-      border = obj.Style()->GetRoundedInnerBorderFor(LayoutRect(border.Rect()),
-                                                     info.include_left_edge,
-                                                     info.include_right_edge);
-    }
-
+    FloatRoundedRect border = RoundedBorderRectForClip(
+        obj.StyleRef(), info, bg_layer, rect, bleed_avoidance,
+        has_line_box_sibling, box_size, obj.BorderPaddingInsets());
     clip_to_border.emplace(obj, paint_info, rect, border, kApplyToContext);
   }
 
diff --git a/third_party/WebKit/Source/core/paint/BoxPainter.h b/third_party/WebKit/Source/core/paint/BoxPainter.h
index 66e10e3b6d..bb6dbf9 100644
--- a/third_party/WebKit/Source/core/paint/BoxPainter.h
+++ b/third_party/WebKit/Source/core/paint/BoxPainter.h
@@ -7,9 +7,11 @@
 
 #include "core/layout/BackgroundBleedAvoidance.h"
 #include "core/paint/BoxPainterBase.h"
+#include "core/paint/RoundedInnerRectClipper.h"
 #include "platform/geometry/LayoutSize.h"
 #include "platform/graphics/GraphicsTypes.h"
 #include "platform/wtf/Allocator.h"
+#include "platform/wtf/Optional.h"
 #include "third_party/skia/include/core/SkBlendMode.h"
 
 namespace blink {
diff --git a/third_party/WebKit/Source/core/paint/BoxPainterBase.cpp b/third_party/WebKit/Source/core/paint/BoxPainterBase.cpp
index 816dc5c..9b69143 100644
--- a/third_party/WebKit/Source/core/paint/BoxPainterBase.cpp
+++ b/third_party/WebKit/Source/core/paint/BoxPainterBase.cpp
@@ -12,6 +12,7 @@
 #include "core/style/ShadowList.h"
 #include "platform/LengthFunctions.h"
 #include "platform/geometry/LayoutRect.h"
+#include "platform/geometry/LayoutRectOutsets.h"
 #include "platform/graphics/GraphicsContextStateSaver.h"
 
 namespace blink {
@@ -354,4 +355,35 @@
       (!should_paint_image || !layer.ImageOccludesNextLayers(doc, style));
 }
 
+FloatRoundedRect BoxPainterBase::RoundedBorderRectForClip(
+    const ComputedStyle& style,
+    const BoxPainterBase::FillLayerInfo info,
+    const FillLayer& bg_layer,
+    const LayoutRect& rect,
+    BackgroundBleedAvoidance bleed_avoidance,
+    bool has_line_box_sibling,
+    const LayoutSize& box_size,
+    LayoutRectOutsets border_padding_insets) {
+  FloatRoundedRect border =
+      info.is_border_fill
+          ? BackgroundRoundedRectAdjustedForBleedAvoidance(
+                style, rect, bleed_avoidance, has_line_box_sibling, box_size,
+                info.include_left_edge, info.include_right_edge)
+          : GetBackgroundRoundedRect(style, rect, has_line_box_sibling,
+                                     box_size, info.include_left_edge,
+                                     info.include_right_edge);
+
+  // Clip to the padding or content boxes as necessary.
+  if (bg_layer.Clip() == kContentFillBox) {
+    border = style.GetRoundedInnerBorderFor(
+        LayoutRect(border.Rect()), border_padding_insets,
+        info.include_left_edge, info.include_right_edge);
+  } else if (bg_layer.Clip() == kPaddingFillBox) {
+    border = style.GetRoundedInnerBorderFor(LayoutRect(border.Rect()),
+                                            info.include_left_edge,
+                                            info.include_right_edge);
+  }
+  return border;
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/BoxPainterBase.h b/third_party/WebKit/Source/core/paint/BoxPainterBase.h
index db613b3..f4131d0 100644
--- a/third_party/WebKit/Source/core/paint/BoxPainterBase.h
+++ b/third_party/WebKit/Source/core/paint/BoxPainterBase.h
@@ -19,6 +19,7 @@
 class LayoutPoint;
 class LayoutRect;
 class FillLayer;
+class LayoutRectOutsets;
 struct PaintInfo;
 
 // Base class for box painting. Has no dependencies on the layout tree and thus
@@ -114,6 +115,15 @@
       const LayoutSize& box_size,
       bool include_logical_left_edge,
       bool include_logical_right_edge);
+  static FloatRoundedRect RoundedBorderRectForClip(
+      const ComputedStyle&,
+      const FillLayerInfo,
+      const FillLayer&,
+      const LayoutRect&,
+      BackgroundBleedAvoidance,
+      bool has_line_box_sibling,
+      const LayoutSize&,
+      LayoutRectOutsets border_padding_insets);
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.cpp b/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.cpp
index 9203531..028cbbd 100644
--- a/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.cpp
+++ b/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.cpp
@@ -4,15 +4,15 @@
 
 #include "core/paint/RoundedInnerRectClipper.h"
 
-#include "core/layout/LayoutObject.h"
 #include "core/paint/PaintInfo.h"
 #include "platform/graphics/paint/ClipDisplayItem.h"
+#include "platform/graphics/paint/DisplayItemClient.h"
 #include "platform/graphics/paint/PaintController.h"
 
 namespace blink {
 
 RoundedInnerRectClipper::RoundedInnerRectClipper(
-    const LayoutObject& layout_object,
+    const DisplayItemClient& layout_object,
     const PaintInfo& paint_info,
     const LayoutRect& rect,
     const FloatRoundedRect& clip_rect,
diff --git a/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.h b/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.h
index fb82d54..2b1f5fe 100644
--- a/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.h
+++ b/third_party/WebKit/Source/core/paint/RoundedInnerRectClipper.h
@@ -12,7 +12,7 @@
 
 class FloatRoundedRect;
 class LayoutRect;
-class LayoutObject;
+class DisplayItemClient;
 struct PaintInfo;
 
 enum RoundedInnerRectClipperBehavior { kApplyToDisplayList, kApplyToContext };
@@ -21,7 +21,7 @@
   DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
 
  public:
-  RoundedInnerRectClipper(const LayoutObject&,
+  RoundedInnerRectClipper(const DisplayItemClient&,
                           const PaintInfo&,
                           const LayoutRect&,
                           const FloatRoundedRect& clip_rect,
@@ -29,7 +29,7 @@
   ~RoundedInnerRectClipper();
 
  private:
-  const LayoutObject& layout_object_;
+  const DisplayItemClient& layout_object_;
   const PaintInfo& paint_info_;
   bool use_paint_controller_;
   DisplayItem::Type clip_type_;
diff --git a/third_party/WebKit/Source/core/paint/TableRowPainter.cpp b/third_party/WebKit/Source/core/paint/TableRowPainter.cpp
index 3edfcc56..a34d901 100644
--- a/third_party/WebKit/Source/core/paint/TableRowPainter.cpp
+++ b/third_party/WebKit/Source/core/paint/TableRowPainter.cpp
@@ -27,16 +27,9 @@
     return;
 
   if (ShouldPaintSelfBlockBackground(paint_info.phase)) {
-    const auto* section = layout_table_row_.Section();
-    LayoutRect cull_rect = LayoutRect(paint_info.GetCullRect().rect_);
-    cull_rect.MoveBy(layout_table_row_.PhysicalLocation(section));
-    LayoutRect logical_rect_in_section =
-        section->LogicalRectForWritingModeAndDirection(cull_rect);
-    CellSpan dirtied_rows;
-    CellSpan dirtied_columns;
-    section->DirtiedRowsAndEffectiveColumns(logical_rect_in_section,
-                                            dirtied_rows, dirtied_columns);
-    PaintBoxDecorationBackground(paint_info, paint_offset, dirtied_columns);
+    PaintBoxDecorationBackground(
+        paint_info, paint_offset,
+        layout_table_row_.Section()->FullTableEffectiveColumnSpan());
   }
 
   if (paint_info.phase == kPaintPhaseSelfBlockBackgroundOnly)
diff --git a/third_party/WebKit/Source/core/style/BUILD.gn b/third_party/WebKit/Source/core/style/BUILD.gn
index 1c6f6e95..9964caf 100644
--- a/third_party/WebKit/Source/core/style/BUILD.gn
+++ b/third_party/WebKit/Source/core/style/BUILD.gn
@@ -55,9 +55,9 @@
     "OrderedNamedGridLines.h",
     "OutlineValue.h",
     "PaintImages.h",
+    "QuadLengthValue.h",
     "QuotesData.cpp",
     "QuotesData.h",
-    "ScrollSnapPoints.h",
     "ShadowData.cpp",
     "ShadowData.h",
     "ShadowList.cpp",
diff --git a/third_party/WebKit/Source/core/style/ComputedStyle.h b/third_party/WebKit/Source/core/style/ComputedStyle.h
index a131cf65..f27fa12 100644
--- a/third_party/WebKit/Source/core/style/ComputedStyle.h
+++ b/third_party/WebKit/Source/core/style/ComputedStyle.h
@@ -1311,60 +1311,193 @@
     SET_VAR(rare_non_inherited_data_, scroll_behavior_, b);
   }
 
-  // scroll-snap-coordinate
-  static Vector<LengthPoint> InitialScrollSnapCoordinate() {
-    return Vector<LengthPoint>();
-  }
-  const Vector<LengthPoint>& ScrollSnapCoordinate() const {
-    return rare_non_inherited_data_->scroll_snap_data_->coordinates_;
-  }
-  void SetScrollSnapCoordinate(const Vector<LengthPoint>& b) {
-    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, coordinates_,
-                   b);
-  }
-
-  // scroll-snap-destination
-  static LengthPoint InitialScrollSnapDestination() {
-    return LengthPoint(Length(0, kFixed), Length(0, kFixed));
-  }
-  const LengthPoint& ScrollSnapDestination() const {
-    return rare_non_inherited_data_->scroll_snap_data_->destination_;
-  }
-  void SetScrollSnapDestination(const LengthPoint& b) {
-    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, destination_,
-                   b);
-  }
-
-  // scroll-snap-points-x
-  static ScrollSnapPoints InitialScrollSnapPointsX() {
-    return ScrollSnapPoints();
-  }
-  const ScrollSnapPoints& ScrollSnapPointsX() const {
-    return rare_non_inherited_data_->scroll_snap_data_->x_points_;
-  }
-  void SetScrollSnapPointsX(const ScrollSnapPoints& b) {
-    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, x_points_, b);
-  }
-
-  // scroll-snap-points-y
-  static ScrollSnapPoints InitialScrollSnapPointsY() {
-    return ScrollSnapPoints();
-  }
-  const ScrollSnapPoints& ScrollSnapPointsY() const {
-    return rare_non_inherited_data_->scroll_snap_data_->y_points_;
-  }
-  void SetScrollSnapPointsY(const ScrollSnapPoints& b) {
-    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, y_points_, b);
-  }
-
   // scroll-snap-type
-  static ScrollSnapType InitialScrollSnapType() { return kScrollSnapTypeNone; }
+  static ScrollSnapType InitialScrollSnapType() { return ScrollSnapType(); }
   ScrollSnapType GetScrollSnapType() const {
-    return static_cast<ScrollSnapType>(
-        rare_non_inherited_data_->scroll_snap_type_);
+    return rare_non_inherited_data_->scroll_snap_data_->type_;
   }
-  void SetScrollSnapType(ScrollSnapType b) {
-    SET_VAR(rare_non_inherited_data_, scroll_snap_type_, b);
+  void SetScrollSnapType(const ScrollSnapType& b) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, type_, b);
+  }
+
+  // Scroll Padding properties
+  static Length InitialScrollPadding() { return Length(); }
+
+  // scroll-padding-top
+  const Length& ScrollPaddingTop() const {
+    return rare_non_inherited_data_->scroll_snap_data_->padding_.top;
+  }
+  void SetScrollPaddingTop(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, padding_.top,
+                   v);
+  }
+
+  // scroll-padding-bottom
+  const Length& ScrollPaddingBottom() const {
+    return rare_non_inherited_data_->scroll_snap_data_->padding_.bottom;
+  }
+  void SetScrollPaddingBottom(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, padding_.bottom,
+                   v);
+  }
+
+  // scroll-padding-left
+  const Length& ScrollPaddingLeft() const {
+    return rare_non_inherited_data_->scroll_snap_data_->padding_.left;
+  }
+  void SetScrollPaddingLeft(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, padding_.left,
+                   v);
+  }
+
+  // scroll-padding-right
+  const Length& ScrollPaddingRight() const {
+    return rare_non_inherited_data_->scroll_snap_data_->padding_.right;
+  }
+  void SetScrollPaddingRight(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, padding_.right,
+                   v);
+  }
+
+  // scroll-padding-block-start
+  const Length& ScrollPaddingBlockStart() const {
+    return IsHorizontalWritingMode() ? ScrollPaddingTop() : ScrollPaddingLeft();
+  }
+  void SetScrollPaddingBlockStart(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollPaddingTop(v);
+    else
+      SetScrollPaddingLeft(v);
+  }
+
+  // scroll-padding-block-end
+  const Length& ScrollPaddingBlockEnd() const {
+    return IsHorizontalWritingMode() ? ScrollPaddingBottom()
+                                     : ScrollPaddingRight();
+  }
+  void SetScrollPaddingBlockEnd(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollPaddingBottom(v);
+    else
+      SetScrollPaddingRight(v);
+  }
+
+  // scroll-padding-inline-start
+  const Length& ScrollPaddingInlineStart() const {
+    return IsHorizontalWritingMode() ? ScrollPaddingLeft() : ScrollPaddingTop();
+  }
+  void SetScrollPaddingInlineStart(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollPaddingLeft(v);
+    else
+      SetScrollPaddingTop(v);
+  }
+
+  // scroll-padding-inline-end
+  const Length& ScrollPaddingInlineEnd() const {
+    return IsHorizontalWritingMode() ? ScrollPaddingRight()
+                                     : ScrollPaddingBottom();
+  }
+  void SetScrollPaddingInlineEnd(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollPaddingRight(v);
+    else
+      SetScrollPaddingBottom(v);
+  }
+
+  // scroll-snap-margin
+  static Length InitialScrollSnapMargin() { return Length(); }
+
+  // scroll-snap-margin-top
+  const Length& ScrollSnapMarginTop() const {
+    return rare_non_inherited_data_->scroll_snap_data_->margin_.top;
+  }
+  void SetScrollSnapMarginTop(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, margin_.top, v);
+  }
+
+  // scroll-snap-margin-bottom
+  const Length& ScrollSnapMarginBottom() const {
+    return rare_non_inherited_data_->scroll_snap_data_->margin_.bottom;
+  }
+  void SetScrollSnapMarginBottom(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, margin_.bottom,
+                   v);
+  }
+
+  // scroll-snap-margin-left
+  const Length& ScrollSnapMarginLeft() const {
+    return rare_non_inherited_data_->scroll_snap_data_->margin_.left;
+  }
+  void SetScrollSnapMarginLeft(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, margin_.left,
+                   v);
+  }
+
+  // scroll-snap-margin-right
+  const Length& ScrollSnapMarginRight() const {
+    return rare_non_inherited_data_->scroll_snap_data_->margin_.right;
+  }
+  void SetScrollSnapMarginRight(const Length& v) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, margin_.right,
+                   v);
+  }
+
+  // scroll-snap-margin-block-start
+  const Length& ScrollSnapMarginBlockStart() const {
+    return IsHorizontalWritingMode() ? ScrollSnapMarginTop()
+                                     : ScrollSnapMarginLeft();
+  }
+  void SetScrollSnapMarginBlockStart(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollSnapMarginTop(v);
+    else
+      SetScrollSnapMarginLeft(v);
+  }
+
+  // scroll-snap-margin-block-end
+  const Length& ScrollSnapMarginBlockEnd() const {
+    return IsHorizontalWritingMode() ? ScrollSnapMarginBottom()
+                                     : ScrollSnapMarginRight();
+  }
+  void SetScrollSnapMarginBlockEnd(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollSnapMarginBottom(v);
+    else
+      SetScrollSnapMarginRight(v);
+  }
+
+  // scroll-snap-margin-inline-start
+  const Length& ScrollSnapMarginInlineStart() const {
+    return IsHorizontalWritingMode() ? ScrollSnapMarginLeft()
+                                     : ScrollSnapMarginTop();
+  }
+  void SetScrollSnapMarginInlineStart(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollSnapMarginLeft(v);
+    else
+      SetScrollSnapMarginTop(v);
+  }
+
+  // scroll-snap-margin-inline-end
+  const Length& ScrollSnapMarginInlineEnd() const {
+    return IsHorizontalWritingMode() ? ScrollSnapMarginRight()
+                                     : ScrollSnapMarginBottom();
+  }
+  void SetScrollSnapMarginInlineEnd(const Length& v) {
+    if (IsHorizontalWritingMode())
+      SetScrollSnapMarginRight(v);
+    else
+      SetScrollSnapMarginBottom(v);
+  }
+
+  // scroll-snap-align
+  static ScrollSnapAlign InitialScrollSnapAlign() { return ScrollSnapAlign(); }
+  ScrollSnapAlign GetScrollSnapAlign() const {
+    return rare_non_inherited_data_->scroll_snap_data_->align_;
+  }
+  void SetScrollSnapAlign(const ScrollSnapAlign& b) {
+    SET_NESTED_VAR(rare_non_inherited_data_, scroll_snap_data_, align_, b);
   }
 
   // shape-image-threshold (aka -webkit-shape-image-threshold)
diff --git a/third_party/WebKit/Source/core/style/ComputedStyleConstants.h b/third_party/WebKit/Source/core/style/ComputedStyleConstants.h
index 243afcd4..c381ef9 100644
--- a/third_party/WebKit/Source/core/style/ComputedStyleConstants.h
+++ b/third_party/WebKit/Source/core/style/ComputedStyleConstants.h
@@ -350,10 +350,21 @@
   kContentBox
 };
 
-enum ScrollSnapType {
-  kScrollSnapTypeNone,
-  kScrollSnapTypeMandatory,
-  kScrollSnapTypeProximity
+enum SnapAxis {
+  kSnapAxisBoth,
+  kSnapAxisX,
+  kSnapAxisY,
+  kSnapAxisBlock,
+  kSnapAxisInline,
+};
+
+enum SnapStrictness { kSnapStrictnessProximity, kSnapStrictnessMandatory };
+
+enum SnapAlignment {
+  kSnapAlignmentNone,
+  kSnapAlignmentStart,
+  kSnapAlignmentEnd,
+  kSnapAlignmentCenter
 };
 
 enum AutoRepeatType { kNoAutoRepeat, kAutoFill, kAutoFit };
diff --git a/third_party/WebKit/Source/core/style/QuadLengthValue.h b/third_party/WebKit/Source/core/style/QuadLengthValue.h
new file mode 100644
index 0000000..b3cb447
--- /dev/null
+++ b/third_party/WebKit/Source/core/style/QuadLengthValue.h
@@ -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.
+
+#ifndef QuadLengthValue_h
+#define QuadLengthValue_h
+
+#include <memory>
+#include "platform/Length.h"
+
+namespace blink {
+
+struct QuadLengthValue {
+  DISALLOW_NEW();
+
+  QuadLengthValue() {}
+
+  explicit QuadLengthValue(Length length)
+      : top(length), right(length), bottom(length), left(length) {}
+
+  QuadLengthValue(const QuadLengthValue& other)
+      : top(other.top),
+        right(other.right),
+        bottom(other.bottom),
+        left(other.left) {}
+
+  bool operator==(const QuadLengthValue& other) const {
+    return top == other.top && right == other.right && bottom == other.bottom &&
+           left == other.left;
+  }
+
+  bool operator!=(const QuadLengthValue& other) const {
+    return !(*this == other);
+  }
+
+  Length top;
+  Length right;
+  Length bottom;
+  Length left;
+};
+
+}  // namespace blink
+
+#endif  // QuadLengthValue_h
diff --git a/third_party/WebKit/Source/core/style/ScrollSnapPoints.h b/third_party/WebKit/Source/core/style/ScrollSnapPoints.h
deleted file mode 100644
index 473265b6..0000000
--- a/third_party/WebKit/Source/core/style/ScrollSnapPoints.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ScrollSnapPoints_h
-#define ScrollSnapPoints_h
-
-#include <memory>
-#include "platform/Length.h"
-
-namespace blink {
-
-struct ScrollSnapPoints {
-  DISALLOW_NEW();
-
-  ScrollSnapPoints()
-      : repeat_offset(100, kPercent), has_repeat(false), uses_elements(false) {}
-
-  bool operator==(const ScrollSnapPoints& other) const {
-    return repeat_offset == other.repeat_offset &&
-           has_repeat == other.has_repeat &&
-           uses_elements == other.uses_elements;
-  }
-  bool operator!=(const ScrollSnapPoints& other) const {
-    return !(*this == other);
-  }
-
-  Length repeat_offset;
-  bool has_repeat;
-  bool uses_elements;
-};
-
-}  // namespace blink
-
-#endif  // ScrollSnapPoints_h
diff --git a/third_party/WebKit/Source/core/style/StyleScrollSnapData.cpp b/third_party/WebKit/Source/core/style/StyleScrollSnapData.cpp
index b33d1b2..e6841a2 100644
--- a/third_party/WebKit/Source/core/style/StyleScrollSnapData.cpp
+++ b/third_party/WebKit/Source/core/style/StyleScrollSnapData.cpp
@@ -30,20 +30,20 @@
 namespace blink {
 
 StyleScrollSnapData::StyleScrollSnapData()
-    : x_points_(ComputedStyle::InitialScrollSnapPointsX()),
-      y_points_(ComputedStyle::InitialScrollSnapPointsY()),
-      destination_(ComputedStyle::InitialScrollSnapDestination()),
-      coordinates_(ComputedStyle::InitialScrollSnapCoordinate()) {}
+    : type_(ComputedStyle::InitialScrollSnapType()),
+      align_(ComputedStyle::InitialScrollSnapAlign()),
+      padding_(ComputedStyle::InitialScrollPadding()),
+      margin_(ComputedStyle::InitialScrollSnapMargin()) {}
 
 StyleScrollSnapData::StyleScrollSnapData(const StyleScrollSnapData& other)
-    : x_points_(other.x_points_),
-      y_points_(other.y_points_),
-      destination_(other.destination_),
-      coordinates_(other.coordinates_) {}
+    : type_(other.type_),
+      align_(other.align_),
+      padding_(other.padding_),
+      margin_(other.margin_) {}
 
 bool operator==(const StyleScrollSnapData& a, const StyleScrollSnapData& b) {
-  return a.x_points_ == b.x_points_ && a.y_points_ == b.y_points_ &&
-         a.destination_ == b.destination_ && a.coordinates_ == b.coordinates_;
+  return a.type_ == b.type_ && a.align_ == b.align_ &&
+         a.padding_ == b.padding_ && a.margin_ == b.margin_;
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/style/StyleScrollSnapData.h b/third_party/WebKit/Source/core/style/StyleScrollSnapData.h
index 517f57b..ea02b97 100644
--- a/third_party/WebKit/Source/core/style/StyleScrollSnapData.h
+++ b/third_party/WebKit/Source/core/style/StyleScrollSnapData.h
@@ -26,7 +26,8 @@
 #ifndef StyleScrollSnapData_h
 #define StyleScrollSnapData_h
 
-#include "core/style/ScrollSnapPoints.h"
+#include "core/style/ComputedStyleConstants.h"
+#include "core/style/QuadLengthValue.h"
 #include "platform/LengthPoint.h"
 #include "platform/wtf/Allocator.h"
 #include "platform/wtf/RefCounted.h"
@@ -34,6 +35,57 @@
 
 namespace blink {
 
+using ScrollPadding = QuadLengthValue;
+using ScrollSnapMargin = QuadLengthValue;
+
+struct ScrollSnapType {
+  DISALLOW_NEW();
+
+  ScrollSnapType()
+      : is_none(true),
+        axis(kSnapAxisBoth),
+        strictness(kSnapStrictnessProximity) {}
+
+  ScrollSnapType(const ScrollSnapType& other)
+      : is_none(other.is_none),
+        axis(other.axis),
+        strictness(other.strictness) {}
+
+  bool operator==(const ScrollSnapType& other) const {
+    return is_none == other.is_none && axis == other.axis &&
+           strictness == other.strictness;
+  }
+
+  bool operator!=(const ScrollSnapType& other) const {
+    return !(*this == other);
+  }
+
+  bool is_none;
+  SnapAxis axis;
+  SnapStrictness strictness;
+};
+
+struct ScrollSnapAlign {
+  DISALLOW_NEW();
+
+  ScrollSnapAlign()
+      : alignmentX(kSnapAlignmentNone), alignmentY(kSnapAlignmentNone) {}
+
+  ScrollSnapAlign(const ScrollSnapAlign& other)
+      : alignmentX(other.alignmentX), alignmentY(other.alignmentY) {}
+
+  bool operator==(const ScrollSnapAlign& other) const {
+    return alignmentX == other.alignmentX && alignmentY == other.alignmentY;
+  }
+
+  bool operator!=(const ScrollSnapAlign& other) const {
+    return !(*this == other);
+  }
+
+  SnapAlignment alignmentX;
+  SnapAlignment alignmentY;
+};
+
 class StyleScrollSnapData : public RefCounted<StyleScrollSnapData> {
  public:
   static PassRefPtr<StyleScrollSnapData> Create() {
@@ -43,14 +95,14 @@
     return AdoptRef(new StyleScrollSnapData(*this));
   }
 
-  ScrollSnapPoints x_points_;
-  ScrollSnapPoints y_points_;
-  LengthPoint destination_;
-  Vector<LengthPoint> coordinates_;
+  ScrollSnapType type_;
+  ScrollSnapAlign align_;
+  ScrollPadding padding_;
+  ScrollSnapMargin margin_;
 
  private:
   StyleScrollSnapData();
-  StyleScrollSnapData(const StyleScrollSnapData&);
+  StyleScrollSnapData(const StyleScrollSnapData& other);
 };
 
 bool operator==(const StyleScrollSnapData&, const StyleScrollSnapData&);
diff --git a/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp b/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp
index db6ef3fa..b5aec8e 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp
@@ -1347,7 +1347,6 @@
   // These roles have implicit live region status.
   if (live_region_status.IsEmpty()) {
     switch (RoleValue()) {
-      case kAlertDialogRole:
       case kAlertRole:
         return live_region_status_assertive;
       case kLogRole:
diff --git a/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp b/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp
index 3f6f763f..0141a1d8 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp
@@ -1206,7 +1206,7 @@
 bool AXObjectImpl::SupportsARIAAttributes() const {
   return IsLiveRegion() || SupportsARIADragging() || SupportsARIADropping() ||
          SupportsARIAFlowTo() || SupportsARIAOwns() ||
-         HasAttribute(aria_labelAttr);
+         HasAttribute(aria_labelAttr) || HasAttribute(aria_currentAttr);
 }
 
 bool AXObjectImpl::SupportsRangeValue() const {
diff --git a/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.cpp b/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.cpp
index 66d6a66cf..a46663d 100644
--- a/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.cpp
+++ b/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.cpp
@@ -20,6 +20,7 @@
 
 #include "modules/plugins/DOMMimeTypeArray.h"
 
+#include "core/dom/Document.h"
 #include "core/frame/LocalFrame.h"
 #include "core/page/Page.h"
 #include "platform/plugins/PluginData.h"
@@ -28,12 +29,13 @@
 
 namespace blink {
 
-DOMMimeTypeArray::DOMMimeTypeArray(LocalFrame* frame) : ContextClient(frame) {
+DOMMimeTypeArray::DOMMimeTypeArray(LocalFrame* frame)
+    : ContextLifecycleObserver(frame ? frame->GetDocument() : nullptr) {
   UpdatePluginData();
 }
 
 DEFINE_TRACE(DOMMimeTypeArray) {
-  ContextClient::Trace(visitor);
+  ContextLifecycleObserver::Trace(visitor);
   visitor->Trace(dom_mime_types_);
 }
 
@@ -96,4 +98,8 @@
   }
 }
 
+void DOMMimeTypeArray::ContextDestroyed(ExecutionContext*) {
+  dom_mime_types_.clear();
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.h b/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.h
index 62a63a1..a935f90 100644
--- a/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.h
+++ b/third_party/WebKit/Source/modules/plugins/DOMMimeTypeArray.h
@@ -34,7 +34,7 @@
 
 class DOMMimeTypeArray final : public GarbageCollected<DOMMimeTypeArray>,
                                public ScriptWrappable,
-                               public ContextClient {
+                               public ContextLifecycleObserver {
   DEFINE_WRAPPERTYPEINFO();
   USING_GARBAGE_COLLECTED_MIXIN(DOMMimeTypeArray);
 
@@ -53,6 +53,7 @@
  private:
   explicit DOMMimeTypeArray(LocalFrame*);
   PluginData* GetPluginData() const;
+  void ContextDestroyed(ExecutionContext*) override;
 
   HeapVector<Member<DOMMimeType>> dom_mime_types_;
 };
diff --git a/third_party/WebKit/Source/modules/plugins/DOMPluginArray.cpp b/third_party/WebKit/Source/modules/plugins/DOMPluginArray.cpp
index 9527ae103f..829bf44 100644
--- a/third_party/WebKit/Source/modules/plugins/DOMPluginArray.cpp
+++ b/third_party/WebKit/Source/modules/plugins/DOMPluginArray.cpp
@@ -20,6 +20,7 @@
 
 #include "modules/plugins/DOMPluginArray.h"
 
+#include "core/dom/Document.h"
 #include "core/frame/LocalDOMWindow.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/Navigator.h"
@@ -33,12 +34,13 @@
 
 namespace blink {
 
-DOMPluginArray::DOMPluginArray(LocalFrame* frame) : ContextClient(frame) {
+DOMPluginArray::DOMPluginArray(LocalFrame* frame)
+    : ContextLifecycleObserver(frame ? frame->GetDocument() : nullptr) {
   UpdatePluginData();
 }
 
 DEFINE_TRACE(DOMPluginArray) {
-  ContextClient::Trace(visitor);
+  ContextLifecycleObserver::Trace(visitor);
   visitor->Trace(dom_plugins_);
 }
 
@@ -47,11 +49,12 @@
 }
 
 DOMPlugin* DOMPluginArray::item(unsigned index) {
+  if (index >= dom_plugins_.size())
+    return nullptr;
+
   // TODO(lfg): Temporary to track down https://crbug.com/731239.
   CHECK(main_frame_origin_->IsSameSchemeHostPort(GetPluginData()->Origin()));
 
-  if (index >= dom_plugins_.size())
-    return nullptr;
   if (!dom_plugins_[index]) {
     dom_plugins_[index] =
         DOMPlugin::Create(GetFrame(), *GetPluginData()->Plugins()[index]);
@@ -128,4 +131,8 @@
   }
 }
 
+void DOMPluginArray::ContextDestroyed(ExecutionContext*) {
+  dom_plugins_.clear();
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/plugins/DOMPluginArray.h b/third_party/WebKit/Source/modules/plugins/DOMPluginArray.h
index c5ea54b8..197092c 100644
--- a/third_party/WebKit/Source/modules/plugins/DOMPluginArray.h
+++ b/third_party/WebKit/Source/modules/plugins/DOMPluginArray.h
@@ -35,7 +35,7 @@
 
 class DOMPluginArray final : public GarbageCollectedFinalized<DOMPluginArray>,
                              public ScriptWrappable,
-                             public ContextClient {
+                             public ContextLifecycleObserver {
   DEFINE_WRAPPERTYPEINFO();
   USING_GARBAGE_COLLECTED_MIXIN(DOMPluginArray);
 
@@ -56,6 +56,7 @@
  private:
   explicit DOMPluginArray(LocalFrame*);
   PluginData* GetPluginData() const;
+  void ContextDestroyed(ExecutionContext*) override;
 
   HeapVector<Member<DOMPlugin>> dom_plugins_;
 
diff --git a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp
index c1fb7fc..75d7fbf3a 100644
--- a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp
+++ b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp
@@ -30,6 +30,7 @@
 
 #include "modules/websockets/DocumentWebSocketChannel.h"
 
+#include <memory>
 #include "core/dom/DOMArrayBuffer.h"
 #include "core/dom/ExecutionContext.h"
 #include "core/dom/TaskRunnerHelper.h"
@@ -37,15 +38,12 @@
 #include "core/fileapi/FileReaderLoaderClient.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/LocalFrameClient.h"
-#include "core/frame/WebLocalFrameBase.h"
 #include "core/inspector/ConsoleMessage.h"
 #include "core/loader/DocumentLoader.h"
 #include "core/loader/FrameLoader.h"
 #include "core/loader/MixedContentChecker.h"
 #include "core/loader/SubresourceFilter.h"
 #include "core/loader/ThreadableLoadingContext.h"
-#include "core/page/ChromeClient.h"
-#include "core/page/Page.h"
 #include "core/probe/CoreProbes.h"
 #include "modules/websockets/InspectorWebSocketEvents.h"
 #include "modules/websockets/WebSocketChannelClient.h"
@@ -61,9 +59,7 @@
 #include "platform/wtf/PtrUtil.h"
 #include "public/platform/InterfaceProvider.h"
 #include "public/platform/Platform.h"
-#include "public/platform/WebSocketHandshakeThrottle.h"
 #include "public/platform/WebTraceLocation.h"
-#include "public/platform/WebURL.h"
 
 namespace blink {
 
@@ -139,44 +135,12 @@
   // |this| is deleted here.
 }
 
-struct DocumentWebSocketChannel::ConnectInfo {
-  ConnectInfo(const String& selected_protocol, const String& extensions)
-      : selected_protocol(selected_protocol), extensions(extensions) {}
-
-  const String selected_protocol;
-  const String extensions;
-};
-
-// static
-DocumentWebSocketChannel* DocumentWebSocketChannel::CreateForTesting(
-    Document* document,
-    WebSocketChannelClient* client,
-    std::unique_ptr<SourceLocation> location,
-    WebSocketHandle* handle,
-    std::unique_ptr<WebSocketHandshakeThrottle> handshake_throttle) {
-  return new DocumentWebSocketChannel(
-      ThreadableLoadingContext::Create(*document), client, std::move(location),
-      WTF::WrapUnique(handle), std::move(handshake_throttle));
-}
-
-// static
-DocumentWebSocketChannel* DocumentWebSocketChannel::Create(
-    ThreadableLoadingContext* loading_context,
-    WebSocketChannelClient* client,
-    std::unique_ptr<SourceLocation> location) {
-  return new DocumentWebSocketChannel(
-      loading_context, client, std::move(location),
-      WTF::MakeUnique<WebSocketHandleImpl>(),
-      Platform::Current()->CreateWebSocketHandshakeThrottle());
-}
-
 DocumentWebSocketChannel::DocumentWebSocketChannel(
     ThreadableLoadingContext* loading_context,
     WebSocketChannelClient* client,
     std::unique_ptr<SourceLocation> location,
-    std::unique_ptr<WebSocketHandle> handle,
-    std::unique_ptr<WebSocketHandshakeThrottle> handshake_throttle)
-    : handle_(std::move(handle)),
+    WebSocketHandle* handle)
+    : handle_(WTF::WrapUnique(handle ? handle : new WebSocketHandleImpl())),
       client_(client),
       identifier_(CreateUniqueIdentifier()),
       loading_context_(loading_context),
@@ -184,9 +148,7 @@
       received_data_size_for_flow_control_(
           kReceivedDataSizeForFlowControlHighWaterMark * 2),  // initial quota
       sent_size_of_top_message_(0),
-      location_at_construction_(std::move(location)),
-      handshake_throttle_(std::move(handshake_throttle)),
-      throttle_passed_(false) {}
+      location_at_construction_(std::move(location)) {}
 
 DocumentWebSocketChannel::~DocumentWebSocketChannel() {
   DCHECK(!blob_loader_);
@@ -260,22 +222,6 @@
                    loading_context_->FirstPartyForCookies(),
                    loading_context_->UserAgent(), this);
 
-  // TODO(ricea): Maybe lookup GetDocument()->GetFrame() less often?
-  if (handshake_throttle_ && GetDocument() && GetDocument()->GetFrame() &&
-      GetDocument()->GetFrame()->GetPage()) {
-    // TODO(ricea): We may need to do something special here for SharedWorkers
-    // and ServiceWorkers
-    // TODO(ricea): Figure out who owns this WebFrame object and how long it can
-    // be expected to live.
-    LocalFrame* frame = GetDocument()->GetFrame();
-    WebLocalFrame* web_frame =
-        frame->GetPage()->GetChromeClient().GetWebLocalFrameBase(frame);
-    handshake_throttle_->ThrottleHandshake(url, web_frame, this);
-  } else {
-    // Treat no throttle as success.
-    throttle_passed_ = true;
-  }
-
   FlowControlIfNecessary();
   TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketCreate",
                        TRACE_EVENT_SCOPE_THREAD, "data",
@@ -397,7 +343,6 @@
   }
   connection_handle_for_scheduler_.reset();
   AbortAsyncOperations();
-  handshake_throttle_.reset();
   handle_.reset();
   client_ = nullptr;
   identifier_ = 0;
@@ -490,7 +435,6 @@
         // No message should be sent from now on.
         DCHECK_EQ(messages_.size(), 1u);
         DCHECK_EQ(sent_size_of_top_message_, 0u);
-        handshake_throttle_.reset();
         handle_->Close(message->code, message->reason);
         messages_.pop_front();
         break;
@@ -520,7 +464,6 @@
 void DocumentWebSocketChannel::HandleDidClose(bool was_clean,
                                               unsigned short code,
                                               const String& reason) {
-  handshake_throttle_.reset();
   handle_.reset();
   AbortAsyncOperations();
   if (!client_) {
@@ -554,13 +497,6 @@
   DCHECK_EQ(handle, handle_.get());
   DCHECK(client_);
 
-  if (!throttle_passed_) {
-    connect_info_ = WTF::MakeUnique<ConnectInfo>(selected_protocol, extensions);
-    return;
-  }
-
-  handshake_throttle_.reset();
-
   client_->DidConnect(selected_protocol, extensions);
 }
 
@@ -736,25 +672,6 @@
     client_->DidStartClosingHandshake();
 }
 
-void DocumentWebSocketChannel::OnSuccess() {
-  DCHECK(!throttle_passed_);
-  DCHECK(handshake_throttle_);
-  throttle_passed_ = true;
-  handshake_throttle_ = nullptr;
-  if (connect_info_) {
-    client_->DidConnect(std::move(connect_info_->selected_protocol),
-                        std::move(connect_info_->extensions));
-    connect_info_.reset();
-  }
-}
-
-void DocumentWebSocketChannel::OnError(const WebString& console_message) {
-  DCHECK(!throttle_passed_);
-  DCHECK(handshake_throttle_);
-  handshake_throttle_ = nullptr;
-  FailAsError(console_message);
-}
-
 void DocumentWebSocketChannel::DidFinishLoadingBlob(DOMArrayBuffer* buffer) {
   blob_loader_.Clear();
   DCHECK(handle_);
@@ -782,7 +699,6 @@
 void DocumentWebSocketChannel::TearDownFailedConnection() {
   // m_handle and m_client can be null here.
   connection_handle_for_scheduler_.reset();
-  handshake_throttle_.reset();
 
   if (client_)
     client_->DidError();
diff --git a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h
index 8e05203c..d779151e 100644
--- a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h
+++ b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h
@@ -33,7 +33,6 @@
 
 #include <stdint.h>
 #include <memory>
-#include <utility>
 #include "bindings/core/v8/SourceLocation.h"
 #include "core/fileapi/Blob.h"
 #include "core/fileapi/FileError.h"
@@ -51,43 +50,40 @@
 #include "platform/wtf/Vector.h"
 #include "platform/wtf/text/CString.h"
 #include "platform/wtf/text/WTFString.h"
-#include "public/platform/WebCallbacks.h"
 
 namespace blink {
 
 class ThreadableLoadingContext;
 class WebSocketHandshakeRequest;
-class WebSocketHandshakeThrottle;
 
 // This class is a WebSocketChannel subclass that works with a Document in a
 // DOMWindow (i.e. works in the main thread).
 class MODULES_EXPORT DocumentWebSocketChannel final
     : public WebSocketChannel,
-      public WebSocketHandleClient,
-      public WebCallbacks<void, const WebString&> {
+      public WebSocketHandleClient {
  public:
   // You can specify the source file and the line number information
   // explicitly by passing the last parameter.
   // In the usual case, they are set automatically and you don't have to
   // pass it.
+  // Specify handle explicitly only in tests.
   static DocumentWebSocketChannel* Create(
       Document* document,
       WebSocketChannelClient* client,
-      std::unique_ptr<SourceLocation> location) {
+      std::unique_ptr<SourceLocation> location,
+      WebSocketHandle* handle = 0) {
     DCHECK(document);
     return Create(ThreadableLoadingContext::Create(*document), client,
-                  std::move(location));
+                  std::move(location), handle);
   }
-  static DocumentWebSocketChannel* Create(ThreadableLoadingContext*,
-                                          WebSocketChannelClient*,
-                                          std::unique_ptr<SourceLocation>);
-  static DocumentWebSocketChannel* CreateForTesting(
-      Document*,
-      WebSocketChannelClient*,
-      std::unique_ptr<SourceLocation>,
-      WebSocketHandle*,
-      std::unique_ptr<WebSocketHandshakeThrottle>);
-
+  static DocumentWebSocketChannel* Create(
+      ThreadableLoadingContext* loading_context,
+      WebSocketChannelClient* client,
+      std::unique_ptr<SourceLocation> location,
+      WebSocketHandle* handle = 0) {
+    return new DocumentWebSocketChannel(loading_context, client,
+                                        std::move(location), handle);
+  }
   ~DocumentWebSocketChannel() override;
 
   // WebSocketChannel functions.
@@ -112,7 +108,6 @@
  private:
   class BlobLoader;
   class Message;
-  struct ConnectInfo;
 
   enum MessageType {
     kMessageTypeText,
@@ -131,9 +126,7 @@
   DocumentWebSocketChannel(ThreadableLoadingContext*,
                            WebSocketChannelClient*,
                            std::unique_ptr<SourceLocation>,
-                           std::unique_ptr<WebSocketHandle>,
-                           std::unique_ptr<WebSocketHandshakeThrottle>);
-
+                           WebSocketHandle*);
   void SendInternal(WebSocketHandle::MessageType,
                     const char* data,
                     size_t total_size,
@@ -174,11 +167,6 @@
   void DidReceiveFlowControl(WebSocketHandle*, int64_t quota) override;
   void DidStartClosingHandshake(WebSocketHandle*) override;
 
-  // WebCallbacks<void, const WebString&> functions. These are called with the
-  // results of throttling.
-  void OnSuccess() override;
-  void OnError(const WebString& console_message) override;
-
   // Methods for BlobLoader.
   void DidFinishLoadingBlob(DOMArrayBuffer*);
   void DidFailLoadingBlob(FileError::ErrorCode);
@@ -210,11 +198,6 @@
 
   std::unique_ptr<SourceLocation> location_at_construction_;
   RefPtr<WebSocketHandshakeRequest> handshake_request_;
-  std::unique_ptr<WebSocketHandshakeThrottle> handshake_throttle_;
-  // This field is only initialised if the object is still waiting for a
-  // throttle response when DidConnect is called.
-  std::unique_ptr<ConnectInfo> connect_info_;
-  bool throttle_passed_;
 
   static const uint64_t kReceivedDataSizeForFlowControlHighWaterMark = 1 << 15;
 };
diff --git a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp
index 079360f..a17e615 100644
--- a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp
+++ b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "modules/websockets/DocumentWebSocketChannel.h"
+#include "modules/websockets/WebSocketChannel.h"
 
 #include <stdint.h>
 #include <memory>
@@ -10,7 +10,7 @@
 #include "core/dom/Document.h"
 #include "core/fileapi/Blob.h"
 #include "core/testing/DummyPageHolder.h"
-#include "modules/websockets/WebSocketChannel.h"
+#include "modules/websockets/DocumentWebSocketChannel.h"
 #include "modules/websockets/WebSocketChannelClient.h"
 #include "modules/websockets/WebSocketHandle.h"
 #include "modules/websockets/WebSocketHandleClient.h"
@@ -19,10 +19,6 @@
 #include "platform/wtf/PtrUtil.h"
 #include "platform/wtf/Vector.h"
 #include "platform/wtf/text/WTFString.h"
-#include "public/platform/WebCallbacks.h"
-#include "public/platform/WebSocketHandshakeThrottle.h"
-#include "public/platform/WebURL.h"
-#include "public/web/WebLocalFrame.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -93,31 +89,16 @@
   MOCK_METHOD2(Close, void(unsigned short, const String&));
 };
 
-class MockWebSocketHandshakeThrottle : public WebSocketHandshakeThrottle {
- public:
-  static MockWebSocketHandshakeThrottle* Create() {
-    return new testing::StrictMock<MockWebSocketHandshakeThrottle>();
-  }
-  MockWebSocketHandshakeThrottle() {}
-  ~MockWebSocketHandshakeThrottle() override { Destructor(); }
-
-  MOCK_METHOD3(ThrottleHandshake,
-               void(const WebURL&,
-                    WebLocalFrame*,
-                    WebCallbacks<void, const WebString&>*));
-
-  // This method is used to allow us to require that the destructor is called at
-  // a particular time.
-  MOCK_METHOD0(Destructor, void());
-};
-
 class DocumentWebSocketChannelTest : public ::testing::Test {
  public:
   DocumentWebSocketChannelTest()
       : page_holder_(DummyPageHolder::Create()),
         channel_client_(MockWebSocketChannelClient::Create()),
         handle_(MockWebSocketHandle::Create()),
-        handshake_throttle_(nullptr),
+        channel_(DocumentWebSocketChannel::Create(&page_holder_->GetDocument(),
+                                                  channel_client_.Get(),
+                                                  SourceLocation::Capture(),
+                                                  Handle())),
         sum_of_consumed_buffered_amount_(0) {
     ON_CALL(*ChannelClient(), DidConsumeBufferedAmount(_))
         .WillByDefault(Invoke(
@@ -126,13 +107,6 @@
 
   ~DocumentWebSocketChannelTest() { Channel()->Disconnect(); }
 
-  void SetUp() override {
-    channel_ = DocumentWebSocketChannel::CreateForTesting(
-        &page_holder_->GetDocument(), channel_client_.Get(),
-        SourceLocation::Capture(), Handle(),
-        WTF::WrapUnique(handshake_throttle_));
-  }
-
   MockWebSocketChannelClient* ChannelClient() { return channel_client_.Get(); }
 
   WebSocketChannel* Channel() {
@@ -143,10 +117,6 @@
     return static_cast<WebSocketHandleClient*>(channel_.Get());
   }
 
-  WebCallbacks<void, const WebString&>* WebCallbacks() {
-    return channel_.Get();
-  }
-
   MockWebSocketHandle* Handle() { return handle_; }
 
   void DidConsumeBufferedAmount(unsigned long a) {
@@ -170,8 +140,6 @@
   std::unique_ptr<DummyPageHolder> page_holder_;
   Persistent<MockWebSocketChannelClient> channel_client_;
   MockWebSocketHandle* handle_;
-  // |handshake_throttle_| is owned by |channel_| once SetUp() has been called.
-  MockWebSocketHandshakeThrottle* handshake_throttle_;
   Persistent<DocumentWebSocketChannel> channel_;
   unsigned long sum_of_consumed_buffered_amount_;
 };
@@ -807,221 +775,6 @@
                   SourceLocation::Create(String(), 0, 0, nullptr));
 }
 
-class DocumentWebSocketChannelHandshakeThrottleTest
-    : public DocumentWebSocketChannelTest {
- public:
-  DocumentWebSocketChannelHandshakeThrottleTest() {
-    handshake_throttle_ = MockWebSocketHandshakeThrottle::Create();
-  }
-
-  // Expectations for the normal result of calling Channel()->Connect() with a
-  // non-null throttle.
-  void NormalHandshakeExpectations() {
-    EXPECT_CALL(*Handle(), Initialize(_));
-    EXPECT_CALL(*Handle(), Connect(_, _, _, _, _, _));
-    EXPECT_CALL(*Handle(), FlowControl(_));
-    EXPECT_CALL(*handshake_throttle_, ThrottleHandshake(_, _, _));
-  }
-
-  static KURL url() { return KURL(KURL(), "ws://localhost/"); }
-};
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest, ThrottleArguments) {
-  EXPECT_CALL(*Handle(), Initialize(_));
-  EXPECT_CALL(*Handle(), Connect(_, _, _, _, _, _));
-  EXPECT_CALL(*Handle(), FlowControl(_));
-  EXPECT_CALL(*handshake_throttle_,
-              ThrottleHandshake(WebURL(url()), _, WebCallbacks()));
-  EXPECT_CALL(*handshake_throttle_, Destructor());
-  Channel()->Connect(url(), "");
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest, ThrottleSucceedsFirst) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(checkpoint, Call(1));
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(checkpoint, Call(2));
-    EXPECT_CALL(*ChannelClient(), DidConnect(String("a"), String("b")));
-  }
-  Channel()->Connect(url(), "");
-  checkpoint.Call(1);
-  WebCallbacks()->OnSuccess();
-  checkpoint.Call(2);
-  HandleClient()->DidConnect(Handle(), String("a"), String("b"));
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest, HandshakeSucceedsFirst) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(checkpoint, Call(1));
-    EXPECT_CALL(checkpoint, Call(2));
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*ChannelClient(), DidConnect(String("a"), String("b")));
-  }
-  Channel()->Connect(url(), "");
-  checkpoint.Call(1);
-  HandleClient()->DidConnect(Handle(), String("a"), String("b"));
-  checkpoint.Call(2);
-  WebCallbacks()->OnSuccess();
-}
-
-// This happens if JS code calls close() during the handshake.
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest, FailDuringThrottle) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*ChannelClient(), DidError());
-    EXPECT_CALL(*ChannelClient(), DidClose(_, _, _));
-    EXPECT_CALL(checkpoint, Call(1));
-  }
-  Channel()->Connect(url(), "");
-  Channel()->Fail("close during handshake", kWarningMessageLevel,
-                  SourceLocation::Create(String(), 0, 0, nullptr));
-  checkpoint.Call(1);
-}
-
-// It makes no difference to the behaviour if the WebSocketHandle has actually
-// connected.
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       FailDuringThrottleAfterConnect) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*ChannelClient(), DidError());
-    EXPECT_CALL(*ChannelClient(), DidClose(_, _, _));
-    EXPECT_CALL(checkpoint, Call(1));
-  }
-  Channel()->Connect(url(), "");
-  HandleClient()->DidConnect(Handle(), String("a"), String("b"));
-  Channel()->Fail("close during handshake", kWarningMessageLevel,
-                  SourceLocation::Create(String(), 0, 0, nullptr));
-  checkpoint.Call(1);
-}
-
-// This happens if the JS context is destroyed during the handshake.
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest, CloseDuringThrottle) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*Handle(), Close(_, _));
-    EXPECT_CALL(checkpoint, Call(1));
-  }
-  Channel()->Connect(url(), "");
-  Channel()->Close(DocumentWebSocketChannel::kCloseEventCodeGoingAway, "");
-  checkpoint.Call(1);
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       CloseDuringThrottleAfterConnect) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*Handle(), Close(_, _));
-    EXPECT_CALL(checkpoint, Call(1));
-  }
-  Channel()->Connect(url(), "");
-  HandleClient()->DidConnect(Handle(), String("a"), String("b"));
-  Channel()->Close(DocumentWebSocketChannel::kCloseEventCodeGoingAway, "");
-  checkpoint.Call(1);
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       DisconnectDuringThrottle) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(checkpoint, Call(1));
-  }
-  Channel()->Connect(url(), "");
-  Channel()->Disconnect();
-  checkpoint.Call(1);
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       DisconnectDuringThrottleAfterConnect) {
-  Checkpoint checkpoint;
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(checkpoint, Call(1));
-  }
-  Channel()->Connect(url(), "");
-  HandleClient()->DidConnect(Handle(), String("a"), String("b"));
-  Channel()->Disconnect();
-  checkpoint.Call(1);
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       ThrottleReportsErrorBeforeConnect) {
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*ChannelClient(), DidError());
-    EXPECT_CALL(*ChannelClient(), DidClose(_, _, _));
-  }
-  Channel()->Connect(url(), "");
-  WebCallbacks()->OnError("Connection blocked by throttle");
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       ThrottleReportsErrorAfterConnect) {
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*ChannelClient(), DidError());
-    EXPECT_CALL(*ChannelClient(), DidClose(_, _, _));
-  }
-  Channel()->Connect(url(), "");
-  HandleClient()->DidConnect(Handle(), String("a"), String("b"));
-  WebCallbacks()->OnError("Connection blocked by throttle");
-}
-
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       ConnectFailBeforeThrottle) {
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*ChannelClient(), DidError());
-    EXPECT_CALL(*ChannelClient(), DidClose(_, _, _));
-  }
-  Channel()->Connect(url(), "");
-  HandleClient()->DidFail(Handle(), "connect failed");
-}
-
-// TODO(ricea): Can this actually happen?
-TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
-       ConnectCloseBeforeThrottle) {
-  NormalHandshakeExpectations();
-  {
-    InSequence s;
-    EXPECT_CALL(*handshake_throttle_, Destructor());
-    EXPECT_CALL(*ChannelClient(), DidClose(_, _, _));
-  }
-  Channel()->Connect(url(), "");
-  HandleClient()->DidClose(Handle(), false,
-                           WebSocketChannel::kCloseEventCodeProtocolError,
-                           "connect error");
-}
-
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/exported/Platform.cpp b/third_party/WebKit/Source/platform/exported/Platform.cpp
index c28506a..d8a57f7 100644
--- a/third_party/WebKit/Source/platform/exported/Platform.cpp
+++ b/third_party/WebKit/Source/platform/exported/Platform.cpp
@@ -53,7 +53,6 @@
 #include "public/platform/WebPrerenderingSupport.h"
 #include "public/platform/WebRTCCertificateGenerator.h"
 #include "public/platform/WebRTCPeerConnectionHandler.h"
-#include "public/platform/WebSocketHandshakeThrottle.h"
 #include "public/platform/WebStorageNamespace.h"
 #include "public/platform/WebThread.h"
 #include "public/platform/modules/serviceworker/WebServiceWorkerCacheStorage.h"
@@ -228,11 +227,6 @@
   return nullptr;
 }
 
-std::unique_ptr<WebSocketHandshakeThrottle>
-Platform::CreateWebSocketHandshakeThrottle() {
-  return nullptr;
-}
-
 std::unique_ptr<WebImageCaptureFrameGrabber>
 Platform::CreateImageCaptureFrameGrabber() {
   return nullptr;
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp
index 0a37e356..0fab5bcd 100644
--- a/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp
+++ b/third_party/WebKit/Source/platform/fonts/shaping/HarfBuzzShaperTest.cpp
@@ -454,4 +454,20 @@
             composite_result->SnappedStartPositionForOffset(8));
 }
 
+TEST_F(HarfBuzzShaperTest, ShapeResultCopyRangeAcrossRuns) {
+  // Create 3 runs:
+  // [0]: 1 character.
+  // [1]: 5 characters.
+  // [2]: 2 character.
+  String mixed_string(u"\u65E5Hello\u65E5\u65E5");
+  TextDirection direction = TextDirection::kLtr;
+  HarfBuzzShaper shaper(mixed_string.Characters16(), mixed_string.length());
+  RefPtr<ShapeResult> result = shaper.Shape(&font, direction);
+
+  // CopyRange(5, 7) should copy 1 character from [1] and 1 from [2].
+  RefPtr<ShapeResult> target = ShapeResult::Create(&font, 0, direction);
+  result->CopyRange(5, 7, target.Get());
+  EXPECT_EQ(2u, target->NumCharacters());
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/ShapeResult.cpp b/third_party/WebKit/Source/platform/fonts/shaping/ShapeResult.cpp
index ce2a8c34..c5734e0 100644
--- a/third_party/WebKit/Source/platform/fonts/shaping/ShapeResult.cpp
+++ b/third_party/WebKit/Source/platform/fonts/shaping/ShapeResult.cpp
@@ -466,7 +466,7 @@
 
     if (start_offset < run_end && end_offset > run_start) {
       unsigned start = start_offset > run_start ? start_offset - run_start : 0;
-      unsigned end = std::min(end_offset - run_start, run_end);
+      unsigned end = std::min(end_offset, run_end) - run_start;
       DCHECK(end > start);
 
       auto sub_run = (*run).CreateSubRun(start, end);
@@ -477,6 +477,9 @@
     }
   }
 
+  DCHECK_EQ(index - target->num_characters_,
+            std::min(end_offset, EndIndexForResult()) -
+                std::max(start_offset, StartIndexForResult()));
   target->num_characters_ = index;
 }
 
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp b/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp
index 33da1532..d2674f18 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp
@@ -280,7 +280,7 @@
     // use cc_list because it is not finalized yet.
     auto list_clone =
         Convert(paint_chunks, layer_state, layer_offset, display_items);
-    recorder.getRecordingCanvas()->drawDisplayItemList(list_clone);
+    recorder.getRecordingCanvas()->drawPicture(list_clone->ReleaseAsRecord());
     params.tracking.CheckUnderInvalidations(params.debug_name,
                                             recorder.finishRecordingAsPicture(),
                                             params.interest_rect);
diff --git a/third_party/WebKit/Source/platform/graphics/filters/SkiaImageFilterBuilder.cpp b/third_party/WebKit/Source/platform/graphics/filters/SkiaImageFilterBuilder.cpp
index 2325bb9..e554812 100644
--- a/third_party/WebKit/Source/platform/graphics/filters/SkiaImageFilterBuilder.cpp
+++ b/third_party/WebKit/Source/platform/graphics/filters/SkiaImageFilterBuilder.cpp
@@ -132,8 +132,8 @@
     // can be serialized.
     SkBitmap bitmap;
     const SkRect mask_record_bounds = reflection.MaskBounds();
-    if (mask_record_bounds.width() * mask_record_bounds.height() <
-        kMaxMaskBufferSize) {
+    SkScalar mask_buffer_size = mask_record_bounds.width() * mask_record_bounds.height();
+    if (mask_buffer_size < kMaxMaskBufferSize && mask_buffer_size > 0.0f) {
       bitmap.allocPixels(SkImageInfo::MakeN32Premul(
           mask_record_bounds.width(), mask_record_bounds.height()));
       SkiaPaintCanvas canvas(bitmap);
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PaintArtifact.cpp b/third_party/WebKit/Source/platform/graphics/paint/PaintArtifact.cpp
index 2754e91..8b90a505 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/PaintArtifact.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/PaintArtifact.cpp
@@ -103,7 +103,7 @@
   scoped_refptr<cc::DisplayItemList> display_item_list =
       PaintChunksToCcLayer::Convert(pointer_paint_chunks, replay_state,
                                     gfx::Vector2dF(), GetDisplayItemList());
-  canvas.drawDisplayItemList(display_item_list);
+  canvas.drawPicture(display_item_list->ReleaseAsRecord());
 }
 
 DISABLE_CFI_PERF
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
index 4cd61c3..542a75f 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
@@ -121,10 +121,6 @@
   end_renderer_hidden_idle_period_closure_.Reset(base::Bind(
       &RendererSchedulerImpl::EndIdlePeriod, weak_factory_.GetWeakPtr()));
 
-  suspend_timers_when_backgrounded_closure_.Reset(
-      base::Bind(&RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded,
-                 weak_factory_.GetWeakPtr()));
-
   default_loading_task_queue_ =
       NewLoadingTaskQueue(TaskQueue::QueueType::DEFAULT_LOADING);
   default_timer_task_queue_ =
@@ -234,7 +230,13 @@
           "RendererScheduler.TaskDurationPerQueueType2.Background.FirstMinute"),
       background_after_first_minute_task_duration_reporter(
           "RendererScheduler.TaskDurationPerQueueType2.Background."
-          "AfterFirstMinute") {
+          "AfterFirstMinute"),
+      hidden_task_duration_reporter(
+          "RendererScheduler.TaskDurationPerQueueType2.Hidden"),
+      visible_task_duration_reporter(
+          "RendererScheduler.TaskDurationPerQueueType.Visible"),
+      hidden_music_task_duration_reporter(
+          "RendererScheduler.TaskDurationPerQueueType.HiddenMusic") {
   foreground_main_thread_load_tracker.Resume(now);
 }
 
@@ -559,51 +561,47 @@
   UpdatePolicyLocked(UpdateType::FORCE_UPDATE);
 }
 
-void RendererSchedulerImpl::OnRendererBackgrounded() {
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
-               "RendererSchedulerImpl::OnRendererBackgrounded");
+void RendererSchedulerImpl::SetRendererHidden(bool hidden) {
+  if (hidden) {
+    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
+                 "RendererSchedulerImpl::OnRendererHidden");
+  } else {
+    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
+                 "RendererSchedulerImpl::OnRendererVisible");
+  }
   helper_.CheckOnValidThread();
-  if (helper_.IsShutdown() || GetMainThreadOnly().renderer_backgrounded)
-    return;
-
-  GetMainThreadOnly().renderer_backgrounded = true;
-
-  UpdatePolicy();
-
-  base::TimeTicks now = tick_clock()->NowTicks();
-  GetMainThreadOnly().foreground_main_thread_load_tracker.Pause(now);
-  GetMainThreadOnly().background_main_thread_load_tracker.Resume(now);
-
-  if (!GetMainThreadOnly().timer_queue_suspension_when_backgrounded_enabled)
-    return;
-
-  suspend_timers_when_backgrounded_closure_.Cancel();
-  base::TimeDelta suspend_timers_when_backgrounded_delay =
-      base::TimeDelta::FromMilliseconds(
-          kSuspendTimersWhenBackgroundedDelayMillis);
-  control_task_queue_->PostDelayedTask(
-      FROM_HERE, suspend_timers_when_backgrounded_closure_.GetCallback(),
-      suspend_timers_when_backgrounded_delay);
+  GetMainThreadOnly().renderer_hidden = hidden;
 }
 
-void RendererSchedulerImpl::OnRendererForegrounded() {
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
-               "RendererSchedulerImpl::OnRendererForegrounded");
+void RendererSchedulerImpl::SetRendererBackgrounded(bool backgrounded) {
+  if (backgrounded) {
+    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
+                 "RendererSchedulerImpl::OnRendererBackgrounded");
+  } else {
+    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
+                 "RendererSchedulerImpl::OnRendererForegrounded");
+  }
   helper_.CheckOnValidThread();
-  if (helper_.IsShutdown() || !GetMainThreadOnly().renderer_backgrounded)
+  if (helper_.IsShutdown() ||
+      GetMainThreadOnly().renderer_backgrounded == backgrounded)
     return;
 
-  GetMainThreadOnly().renderer_backgrounded = false;
-  GetMainThreadOnly().renderer_suspended = false;
+  GetMainThreadOnly().renderer_backgrounded = backgrounded;
+  if (!backgrounded)
+    GetMainThreadOnly().renderer_suspended = false;
+
+  GetMainThreadOnly().background_status_changed_at = tick_clock()->NowTicks();
 
   UpdatePolicy();
 
   base::TimeTicks now = tick_clock()->NowTicks();
-  GetMainThreadOnly().foreground_main_thread_load_tracker.Resume(now);
-  GetMainThreadOnly().background_main_thread_load_tracker.Pause(now);
-
-  suspend_timers_when_backgrounded_closure_.Cancel();
-  ResumeTimerQueueWhenForegroundedOrResumed();
+  if (backgrounded) {
+    GetMainThreadOnly().foreground_main_thread_load_tracker.Pause(now);
+    GetMainThreadOnly().background_main_thread_load_tracker.Resume(now);
+  } else {
+    GetMainThreadOnly().foreground_main_thread_load_tracker.Resume(now);
+    GetMainThreadOnly().background_main_thread_load_tracker.Pause(now);
+  }
 }
 
 void RendererSchedulerImpl::OnAudioStateChanged() {
@@ -629,7 +627,6 @@
     return;
   if (!GetMainThreadOnly().renderer_backgrounded)
     return;
-  suspend_timers_when_backgrounded_closure_.Cancel();
 
   UMA_HISTOGRAM_COUNTS("PurgeAndSuspend.PendingTaskCount",
                        helper_.GetNumberOfPendingTasks());
@@ -637,7 +634,7 @@
   // TODO(hajimehoshi): We might need to suspend not only timer queue but also
   // e.g. loading tasks or postMessage.
   GetMainThreadOnly().renderer_suspended = true;
-  SuspendTimerQueueWhenBackgrounded();
+  UpdatePolicy();
 }
 
 void RendererSchedulerImpl::ResumeRenderer() {
@@ -646,9 +643,8 @@
     return;
   if (!GetMainThreadOnly().renderer_backgrounded)
     return;
-  suspend_timers_when_backgrounded_closure_.Cancel();
   GetMainThreadOnly().renderer_suspended = false;
-  ResumeTimerQueueWhenForegroundedOrResumed();
+  UpdatePolicy();
 }
 
 void RendererSchedulerImpl::EndIdlePeriod() {
@@ -947,6 +943,24 @@
   UpdatePolicyLocked(UpdateType::FORCE_UPDATE);
 }
 
+namespace {
+
+void UpdatePolicyDuration(base::TimeTicks now,
+                          base::TimeTicks policy_expiration,
+                          base::TimeDelta* policy_duration) {
+  if (policy_expiration <= now)
+    return;
+
+  if (policy_duration->is_zero()) {
+    *policy_duration = policy_expiration - now;
+    return;
+  }
+
+  *policy_duration = std::min(*policy_duration, policy_expiration - now);
+}
+
+}  // namespace
+
 void RendererSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) {
   helper_.CheckOnValidThread();
   any_thread_lock_.AssertAcquired();
@@ -1002,20 +1016,31 @@
   // work.
   if (GetMainThreadOnly().last_audio_state_change &&
       !GetMainThreadOnly().is_audio_playing) {
-    base::TimeTicks audio_will_expire =
-        GetMainThreadOnly().last_audio_state_change.value() +
-        kThrottlingDelayAfterAudioIsPlayed;
+    UpdatePolicyDuration(now,
+                         GetMainThreadOnly().last_audio_state_change.value() +
+                             kThrottlingDelayAfterAudioIsPlayed,
+                         &new_policy_duration);
+  }
 
-    base::TimeDelta audio_will_expire_after = audio_will_expire - now;
+  bool timer_queue_newly_suspended = false;
+  if (GetMainThreadOnly().renderer_backgrounded &&
+      GetMainThreadOnly().timer_queue_suspension_when_backgrounded_enabled) {
+    base::TimeTicks suspend_timers_at =
+        GetMainThreadOnly().background_status_changed_at +
+        base::TimeDelta::FromMilliseconds(
+            kSuspendTimersWhenBackgroundedDelayMillis);
 
-    if (audio_will_expire_after > base::TimeDelta()) {
-      if (new_policy_duration.is_zero()) {
-        new_policy_duration = audio_will_expire_after;
-      } else {
-        new_policy_duration =
-            std::min(new_policy_duration, audio_will_expire_after);
-      }
-    }
+    timer_queue_newly_suspended =
+        !GetMainThreadOnly().timer_queue_suspended_when_backgrounded;
+    GetMainThreadOnly().timer_queue_suspended_when_backgrounded =
+        now >= suspend_timers_at || GetMainThreadOnly().renderer_suspended;
+    timer_queue_newly_suspended &=
+        GetMainThreadOnly().timer_queue_suspended_when_backgrounded;
+
+    if (!GetMainThreadOnly().timer_queue_suspended_when_backgrounded)
+      UpdatePolicyDuration(now, suspend_timers_at, &new_policy_duration);
+  } else {
+    GetMainThreadOnly().timer_queue_suspended_when_backgrounded = false;
   }
 
   if (new_policy_duration > base::TimeDelta()) {
@@ -1162,7 +1187,7 @@
 
   if (GetMainThreadOnly().renderer_suspended) {
     new_policy.loading_queue_policy.is_enabled = false;
-    DCHECK(!new_policy.timer_queue_policy.is_enabled);
+    new_policy.timer_queue_policy.is_enabled = false;
   }
 
   if (GetMainThreadOnly().use_virtual_time) {
@@ -1245,6 +1270,9 @@
 
   DCHECK(compositor_task_queue_->IsQueueEnabled());
   GetMainThreadOnly().current_policy = new_policy;
+
+  if (timer_queue_newly_suspended)
+    Platform::Current()->RequestPurgeMemory();
 }
 
 void RendererSchedulerImpl::ApplyTaskQueuePolicy(
@@ -1719,27 +1747,6 @@
   UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED);
 }
 
-void RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded() {
-  DCHECK(GetMainThreadOnly().renderer_backgrounded);
-  if (GetMainThreadOnly().timer_queue_suspended_when_backgrounded)
-    return;
-
-  GetMainThreadOnly().timer_queue_suspended_when_backgrounded = true;
-  ForceUpdatePolicy();
-  Platform::Current()->RequestPurgeMemory();
-}
-
-void RendererSchedulerImpl::ResumeTimerQueueWhenForegroundedOrResumed() {
-  DCHECK(!GetMainThreadOnly().renderer_backgrounded ||
-         (GetMainThreadOnly().renderer_backgrounded &&
-          !GetMainThreadOnly().renderer_suspended));
-  if (!GetMainThreadOnly().timer_queue_suspended_when_backgrounded)
-    return;
-
-  GetMainThreadOnly().timer_queue_suspended_when_backgrounded = false;
-  ForceUpdatePolicy();
-}
-
 void RendererSchedulerImpl::ResetForNavigationLocked() {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                "RendererSchedulerImpl::ResetForNavigationLocked");
@@ -1989,6 +1996,19 @@
     GetMainThreadOnly().foreground_task_duration_reporter.RecordTask(queue_type,
                                                                      duration);
   }
+
+  if (GetMainThreadOnly().renderer_hidden) {
+    GetMainThreadOnly().hidden_task_duration_reporter.RecordTask(queue_type,
+                                                                 duration);
+
+    if (ShouldDisableThrottlingBecauseOfAudio(start_time)) {
+      GetMainThreadOnly().hidden_music_task_duration_reporter.RecordTask(
+          queue_type, duration);
+    }
+  } else {
+    GetMainThreadOnly().visible_task_duration_reporter.RecordTask(queue_type,
+                                                                  duration);
+  }
 }
 
 void RendererSchedulerImpl::OnBeginNestedRunLoop() {
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
index 41ee62b..8336f29a 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
@@ -102,8 +102,8 @@
   void DidHandleInputEventOnMainThread(const WebInputEvent& web_input_event,
                                        WebInputEventResult result) override;
   void DidAnimateForInputOnCompositorThread() override;
-  void OnRendererBackgrounded() override;
-  void OnRendererForegrounded() override;
+  void SetRendererHidden(bool hidden) override;
+  void SetRendererBackgrounded(bool backgrounded) override;
   void SuspendRenderer() override;
   void ResumeRenderer() override;
   void AddPendingNavigation(NavigatingFrameType type) override;
@@ -521,6 +521,9 @@
     TaskDurationMetricReporter background_first_minute_task_duration_reporter;
     TaskDurationMetricReporter
         background_after_first_minute_task_duration_reporter;
+    TaskDurationMetricReporter hidden_task_duration_reporter;
+    TaskDurationMetricReporter visible_task_duration_reporter;
+    TaskDurationMetricReporter hidden_music_task_duration_reporter;
   };
 
   struct AnyThread {
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
index ab19544..3ef917d 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
@@ -1192,7 +1192,7 @@
       FakeInputEvent(blink::WebInputEvent::kGestureTapDown),
       RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
   // Action events like ScrollBegin will kick us back into compositor priority,
   // allowing service of the timer, loading and idle queues.
@@ -1240,7 +1240,7 @@
       FakeInputEvent(blink::WebInputEvent::kGestureTapDown),
       WebInputEventResult::kHandledSystem);
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
   // Action events like ScrollBegin will kick us back into compositor priority,
   // allowing service of the timer, loading and idle queues.
@@ -1670,7 +1670,7 @@
       FakeInputEvent(blink::WebInputEvent::kTouchMove),
       RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
   // Receiving the second touchmove will kick us back into compositor priority.
   run_order.clear();
@@ -2360,7 +2360,7 @@
 
   scheduler_->SuspendTimerQueue();
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
   scheduler_->ResumeTimerQueue();
   RunUntilIdle();
@@ -2377,7 +2377,7 @@
   scheduler_->task_queue_throttler()->IncreaseThrottleRefCount(
       static_cast<TaskQueue*>(timer_task_runner_.get()));
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 }
 
 TEST_F(RendererSchedulerImplTest, ThrottleAndSuspendTimerQueue) {
@@ -2389,7 +2389,7 @@
   RunUntilIdle();
   scheduler_->SuspendTimerQueue();
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 }
 
 TEST_F(RendererSchedulerImplTest, MultipleSuspendsNeedMultipleResumes) {
@@ -2400,15 +2400,15 @@
   scheduler_->SuspendTimerQueue();
   scheduler_->SuspendTimerQueue();
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
   scheduler_->ResumeTimerQueue();
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
   scheduler_->ResumeTimerQueue();
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
   scheduler_->ResumeTimerQueue();
   RunUntilIdle();
@@ -2418,7 +2418,7 @@
 
 TEST_F(RendererSchedulerImplTest, SuspendRenderer) {
   // Assume that the renderer is backgrounded.
-  scheduler_->OnRendererBackgrounded();
+  scheduler_->SetRendererBackgrounded(true);
 
   // Tasks in some queues don't fire when the renderer is suspended.
   std::vector<std::string> run_order;
@@ -2432,7 +2432,7 @@
 
   // The rest queued tasks fire when the tab goes foregrounded.
   run_order.clear();
-  scheduler_->OnRendererForegrounded();
+  scheduler_->SetRendererBackgrounded(false);
   RunUntilIdle();
   EXPECT_THAT(run_order,
               testing::ElementsAre(std::string("L1"), std::string("T1")));
@@ -2450,7 +2450,7 @@
   ScopedAutoAdvanceNowEnabler enable_auto_advance_now(mock_task_runner_);
 
   // Assume that the renderer is backgrounded.
-  scheduler_->OnRendererBackgrounded();
+  scheduler_->SetRendererBackgrounded(true);
 
   // Tasks in some queues don't fire when the renderer is suspended.
   std::vector<std::string> run_order;
@@ -2491,7 +2491,7 @@
   PostTestTasks(&run_order, "D3 T3");
   // No crash occurs when the resumed renderer goes foregrounded.
   // Posted tasks while the renderer is resumed fire.
-  scheduler_->OnRendererForegrounded();
+  scheduler_->SetRendererBackgrounded(false);
   RunUntilIdle();
   EXPECT_THAT(run_order,
               testing::ElementsAre(std::string("D3"), std::string("T3")));
@@ -2539,7 +2539,7 @@
   std::vector<std::string> run_order;
   PostTestTasks(&run_order, "D1 C1");
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 }
 
 TEST_F(RendererSchedulerImplTest, TestRendererBackgroundedTimerSuspension) {
@@ -2551,7 +2551,7 @@
   base::TimeTicks now;
 
   // The background signal will not immediately suspend the timer queue.
-  scheduler_->OnRendererBackgrounded();
+  scheduler_->SetRendererBackgrounded(true);
   now += base::TimeDelta::FromMilliseconds(1100);
   clock_->SetNowTicks(now);
   RunUntilIdle();
@@ -2577,10 +2577,11 @@
   // Timer tasks should be suspended until the foregrounded signal.
   PostTestTasks(&run_order, "T4 T5");
   now += base::TimeDelta::FromSeconds(10);
+  clock_->SetNowTicks(now);
   RunUntilIdle();
-  EXPECT_TRUE(run_order.empty());
+  EXPECT_THAT(run_order, testing::ElementsAre());
 
-  scheduler_->OnRendererForegrounded();
+  scheduler_->SetRendererBackgrounded(false);
   RunUntilIdle();
   EXPECT_THAT(run_order,
               testing::ElementsAre(std::string("T4"), std::string("T5")));
@@ -3853,7 +3854,7 @@
        DISABLED_DefaultTimerTasksAreThrottledWhenBackgrounded) {
   ScopedAutoAdvanceNowEnabler enable_auto_advance_now(mock_task_runner_);
 
-  scheduler_->OnRendererBackgrounded();
+  scheduler_->SetRendererBackgrounded(true);
 
   std::vector<base::TimeTicks> run_times;
 
@@ -3871,7 +3872,7 @@
       FROM_HERE, base::Bind(&RecordingTimeTestTask, &run_times, clock_.get()),
       base::TimeDelta::FromMilliseconds(200));
 
-  scheduler_->OnRendererForegrounded();
+  scheduler_->SetRendererBackgrounded(false);
 
   mock_task_runner_->RunUntilTime(base::TimeTicks() +
                                   base::TimeDelta::FromMilliseconds(1500));
diff --git a/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc b/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc
index e2027fc..6b0e10c 100644
--- a/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc
+++ b/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc
@@ -72,9 +72,9 @@
   return false;
 }
 
-void FakeRendererScheduler::OnRendererBackgrounded() {}
+void FakeRendererScheduler::SetRendererHidden(bool hidden) {}
 
-void FakeRendererScheduler::OnRendererForegrounded() {}
+void FakeRendererScheduler::SetRendererBackgrounded(bool backgrounded) {}
 
 void FakeRendererScheduler::SuspendRenderer() {}
 
diff --git a/third_party/WebKit/Source/web/tests/scheduler/ThrottlingTest.cpp b/third_party/WebKit/Source/web/tests/scheduler/ThrottlingTest.cpp
index a36b99b..e31600f 100644
--- a/third_party/WebKit/Source/web/tests/scheduler/ThrottlingTest.cpp
+++ b/third_party/WebKit/Source/web/tests/scheduler/ThrottlingTest.cpp
@@ -56,7 +56,7 @@
       ->CurrentThread()
       ->Scheduler()
       ->GetRendererSchedulerForTest()
-      ->OnRendererBackgrounded();
+      ->SetRendererBackgrounded(true);
 
   // Run delayed tasks for 1 second. All tasks should be completed
   // with throttling disabled.
@@ -88,7 +88,7 @@
       ->CurrentThread()
       ->Scheduler()
       ->GetRendererSchedulerForTest()
-      ->OnRendererBackgrounded();
+      ->SetRendererBackgrounded(true);
 
   // Make sure that we run a task once a second.
   for (int i = 0; i < 3; ++i) {
diff --git a/third_party/WebKit/public/platform/Platform.h b/third_party/WebKit/public/platform/Platform.h
index 2fdcc52a..f65d73f 100644
--- a/third_party/WebKit/public/platform/Platform.h
+++ b/third_party/WebKit/public/platform/Platform.h
@@ -35,8 +35,6 @@
 #include <windows.h>
 #endif
 
-#include <memory>
-
 #include "BlameContext.h"
 #include "UserMetricsAction.h"
 #include "WebAudioDevice.h"
@@ -120,7 +118,6 @@
 class WebScrollbarBehavior;
 class WebSecurityOrigin;
 class WebServiceWorkerCacheStorage;
-class WebSocketHandshakeThrottle;
 class WebSpeechSynthesizer;
 class WebSpeechSynthesizerClient;
 class WebStorageNamespace;
@@ -573,13 +570,6 @@
   virtual std::unique_ptr<WebImageCaptureFrameGrabber>
   CreateImageCaptureFrameGrabber();
 
-  // WebSocket ----------------------------------------------------------
-
-  // If this method returns non-null the returned object will be used to
-  // determine if/when a new WebSocket connection can be exposed to Javascript.
-  virtual std::unique_ptr<WebSocketHandshakeThrottle>
-  CreateWebSocketHandshakeThrottle();
-
   // WebWorker ----------------------------------------------------------
 
   virtual void DidStartWorkerThread() {}
diff --git a/third_party/WebKit/public/platform/WebSocketHandshakeThrottle.h b/third_party/WebKit/public/platform/WebSocketHandshakeThrottle.h
deleted file mode 100644
index dfe09e1..0000000
--- a/third_party/WebKit/public/platform/WebSocketHandshakeThrottle.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// WebSocketHandshakeThrottle provides a facility for embedders to delay
-// WebSocket connection establishment. Specifically, at the same time as the
-// handshake is started blink::Platform::CreateWebSocketHandshakeThrottle() will
-// be called. If a non-null WebSocketHandshakeThrottle is returned then
-// ThrottleHandshake() will be called on it. If the result is error then the
-// handshake will be aborted, and a connection error will be reported to
-// Javascript. If the throttle hasn't reported a result when the WebSocket
-// handshake succeeds then Blink will wait for the throttle result before
-// reporting the connection is open to Javascript.
-
-#ifndef WebSocketHandshakeThrottle_h
-#define WebSocketHandshakeThrottle_h
-
-#include "public/platform/WebCallbacks.h"
-
-namespace blink {
-
-class WebURL;
-class WebLocalFrame;
-class WebString;
-
-// Embedders can implement this class to delay WebSocket connections.
-class WebSocketHandshakeThrottle {
- public:
-  // Destruction implies that the handshake has been aborted. Any ongoing work
-  // should be cleaned up if possible.
-  virtual ~WebSocketHandshakeThrottle() {}
-
-  // The WebCallbacks OnSuccess or OnError should be called asychronously to
-  // permit Javascript to use the connection or not. OnError should be passed
-  // a message to be displayed on the console indicating why the handshake was
-  // blocked. This object will be destroyed synchronously inside the
-  // callbacks. Callbacks must not be called after this object has been
-  // destroyed.
-  virtual void ThrottleHandshake(const WebURL&,
-                                 WebLocalFrame*,
-                                 WebCallbacks<void, const WebString&>*) = 0;
-};
-
-}  // namespace blink
-
-#endif  // WebSocketHandshakeThrottle_h
diff --git a/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h b/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h
index 656c3fa..17fadc04 100644
--- a/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h
+++ b/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h
@@ -113,17 +113,17 @@
   // a fling). Called by the compositor (impl) thread.
   virtual void DidAnimateForInputOnCompositorThread() = 0;
 
-  // Tells the scheduler that the renderer process has been backgrounded, i.e.,
-  // there are no critical, user facing activities (visual, audio, etc...)
-  // driven by this process. A stricter condition than |OnRendererHidden()|, the
-  // process is assumed to be foregrounded when the scheduler is constructed.
+  // Tells the scheduler about the change of renderer visibility status (e.g.
+  // "all widgets are hidden" condition). Used mostly for metric purposes.
   // Must be called on the main thread.
-  virtual void OnRendererBackgrounded() = 0;
+  virtual void SetRendererHidden(bool hidden) = 0;
 
-  // Tells the scheduler that the renderer process has been foregrounded.
-  // This is the assumed state when the scheduler is constructed.
-  // Must be called on the main thread.
-  virtual void OnRendererForegrounded() = 0;
+  // Tells the scheduler about the change of renderer background status, i.e.,
+  // there are no critical, user facing activities (visual, audio, etc...)
+  // driven by this process. A stricter condition than |SetRendererHidden()|,
+  // the process is assumed to be foregrounded when the scheduler is
+  // constructed. Must be called on the main thread.
+  virtual void SetRendererBackgrounded(bool backgrounded) = 0;
 
   // Tells the scheduler that the render process should be suspended. This can
   // only be done when the renderer is backgrounded. The renderer will be
@@ -180,7 +180,7 @@
   virtual void VirtualTimeResumed() = 0;
 
   // Sets whether to allow suspension of timers after the backgrounded signal is
-  // received via OnRendererBackgrounded. Defaults to disabled.
+  // received via SetRendererBackgrounded(true). Defaults to disabled.
   virtual void SetTimerQueueSuspensionWhenBackgroundedEnabled(bool enabled) = 0;
 
   // Sets the default blame context to which top level work should be
diff --git a/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h b/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h
index b06b8e6..aa89af4 100644
--- a/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h
+++ b/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h
@@ -37,8 +37,8 @@
   void DidHandleInputEventOnMainThread(const WebInputEvent& web_input_event,
                                        WebInputEventResult result) override;
   void DidAnimateForInputOnCompositorThread() override;
-  void OnRendererBackgrounded() override;
-  void OnRendererForegrounded() override;
+  void SetRendererHidden(bool hidden) override;
+  void SetRendererBackgrounded(bool backgrounded) override;
   void SuspendRenderer() override;
   void ResumeRenderer() override;
   void AddPendingNavigation(NavigatingFrameType type) override;
diff --git a/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h b/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h
index 74e531a..14ba4dbe 100644
--- a/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h
+++ b/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h
@@ -42,8 +42,8 @@
   MOCK_METHOD2(DidHandleInputEventOnMainThread,
                void(const WebInputEvent&, WebInputEventResult));
   MOCK_METHOD0(DidAnimateForInputOnCompositorThread, void());
-  MOCK_METHOD0(OnRendererBackgrounded, void());
-  MOCK_METHOD0(OnRendererForegrounded, void());
+  MOCK_METHOD1(SetRendererHidden, void(bool));
+  MOCK_METHOD1(SetRendererBackgrounded, void(bool));
   MOCK_METHOD0(SuspendRenderer, void());
   MOCK_METHOD0(ResumeRenderer, void());
   MOCK_METHOD1(AddPendingNavigation, void(NavigatingFrameType));
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 6bfd8528..7a6cf3a 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -23979,6 +23979,31 @@
   <int value="557" label="place-content"/>
   <int value="558" label="place-items"/>
   <int value="559" label="transform-box"/>
+  <int value="560" label="place-self"/>
+  <int value="561" label="scroll-snap-align"/>
+  <int value="562" label="scroll-padding"/>
+  <int value="563" label="scroll-padding-top"/>
+  <int value="564" label="scroll-padding-right"/>
+  <int value="565" label="scroll-padding-bottom"/>
+  <int value="566" label="scroll-padding-left"/>
+  <int value="567" label="scroll-padding-block"/>
+  <int value="568" label="scroll-padding-block-start"/>
+  <int value="569" label="scroll-padding-block-end"/>
+  <int value="570" label="scroll-padding-inline"/>
+  <int value="571" label="scroll-padding-inline-start"/>
+  <int value="572" label="scroll-padding-inline-end"/>
+  <int value="573" label="scroll-snap-margin"/>
+  <int value="574" label="scroll-snap-margin-top"/>
+  <int value="575" label="scroll-snap-margin-right"/>
+  <int value="576" label="scroll-snap-margin-bottom"/>
+  <int value="577" label="scroll-snap-margin-left"/>
+  <int value="578" label="scroll-snap-margin-block"/>
+  <int value="579" label="scroll-snap-margin-block-start"/>
+  <int value="580" label="scroll-snap-margin-block-end"/>
+  <int value="581" label="scroll-snap-margin-inline"/>
+  <int value="582" label="scroll-snap-margin-inline-start"/>
+  <int value="583" label="scroll-snap-margin-inline-end"/>
+  <int value="584" label="scroll-snap-stop"/>
 </enum>
 
 <enum name="MappedEditingCommands" type="int">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 51751a9..d379da6 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -94155,6 +94155,15 @@
       label="Time spent in tasks of a particular type starting from the first
              minute after backgrounding the renderer. The renderer is
              expected to be mostly idle during this period."/>
+  <suffix name="Hidden"
+      label="Time spent in tasks of a particular type when the renderer is
+             hidden."/>
+  <suffix name="Visible"
+      label="Time spent in tasks of a particular type when the renderer is
+             visible."/>
+  <suffix name="HiddenMusic"
+      label="Time spent in tasks of a particular type when the renderer is
+             hidden and is playing audible sound."/>
   <affected-histogram name="RendererScheduler.TaskDurationPerQueueType2"/>
 </histogram_suffixes>
 
diff --git a/tools/perf/benchmarks/battor.py b/tools/perf/benchmarks/battor.py
index 092b13c..3480b0c 100644
--- a/tools/perf/benchmarks/battor.py
+++ b/tools/perf/benchmarks/battor.py
@@ -48,6 +48,10 @@
   def Name(cls):
     return 'battor.trivial_pages'
 
+  def GetExpectations(self):
+    return page_sets.TrivialStoryExpectations()
+
+
 @benchmark.Enabled('mac')
 @benchmark.Owner(emails=['charliea@chromium.org'])
 class BattOrSteadyStatePages(_BattOrBenchmark):
@@ -59,3 +63,6 @@
   @classmethod
   def Name(cls):
     return 'battor.steady_state'
+
+  def GetExpectations(self):
+    return page_sets.IdleAfterLoadingStoryExpectations()
diff --git a/tools/perf/page_sets/idle_after_loading_stories.py b/tools/perf/page_sets/idle_after_loading_stories.py
index fc2248c..7f1a8a7 100644
--- a/tools/perf/page_sets/idle_after_loading_stories.py
+++ b/tools/perf/page_sets/idle_after_loading_stories.py
@@ -7,9 +7,7 @@
 # Chrome has high idle CPU usage on these sites, even after they have quiesced.
 SITES = [
   # https://bugs.chromium.org/p/chromium/issues/detail?id=505990
-  # TODO(charliea): Temporarily disable this site, since it causes tracing to
-  # explode from too much data. https://crbug.com/647398
-  # 'http://abcnews.go.com/',
+  'http://abcnews.go.com/',
 
   # https://bugs.chromium.org/p/chromium/issues/detail?id=505601
   'http://www.slideshare.net/patrickmeenan',
@@ -53,3 +51,9 @@
     # https://crbug.com/638365.
     for url in SITES:
       self.AddStory(_BasePage(self, url, wait_in_seconds, url))
+
+
+class IdleAfterLoadingStoryExpectations(story.expectations.StoryExpectations):
+  def SetExpectations(self):
+    self.DisableStory(
+        'http://abcnews.go.com/', [story.expectations.ALL], 'crbug.com/505990')
diff --git a/tools/perf/page_sets/trivial_sites.py b/tools/perf/page_sets/trivial_sites.py
index ae951d7..409c26d3 100644
--- a/tools/perf/page_sets/trivial_sites.py
+++ b/tools/perf/page_sets/trivial_sites.py
@@ -153,3 +153,8 @@
         self, shared_state, wait_in_seconds, measure_memory))
     self.AddStory(TrivialAnimationPage(
         self, shared_state, wait_in_seconds, measure_memory))
+
+
+class TrivialStoryExpectations(story.expectations.StoryExpectations):
+  def SetExpectations(self):
+    pass # No tests are disabled.
diff --git a/ui/app_list/views/search_box_view.cc b/ui/app_list/views/search_box_view.cc
index c5899f2aa0..77236d3 100644
--- a/ui/app_list/views/search_box_view.cc
+++ b/ui/app_list/views/search_box_view.cc
@@ -44,7 +44,7 @@
 
 namespace {
 
-constexpr int kPadding = 16;
+constexpr int kPadding = 12;
 constexpr int kInnerPadding = 24;
 constexpr int kPreferredWidth = 360;
 constexpr int kPreferredWidthFullscreen = 544;
@@ -53,7 +53,7 @@
 constexpr SkColor kHintTextColor = SkColorSetARGBMacro(0xFF, 0xA0, 0xA0, 0xA0);
 
 constexpr int kBackgroundBorderCornerRadius = 2;
-constexpr int kBackgroundBorderCornerRadiusFullscreen = 20;
+constexpr int kBackgroundBorderCornerRadiusFullscreen = 24;
 
 constexpr int kGoogleIconSize = 24;
 constexpr int kMicIconSize = 24;
@@ -180,6 +180,9 @@
     content_container_->AddChildView(google_icon_);
 
     search_box_->set_placeholder_text_color(kDefaultSearchboxColor);
+    search_box_->set_placeholder_text_draw_flags(
+        gfx::Canvas::TEXT_ALIGN_CENTER);
+    search_box_->SetFontList(search_box_->GetFontList().DeriveWithSizeDelta(2));
   } else {
     back_button_ = new SearchBoxImageButton(this);
     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
@@ -230,6 +233,9 @@
 }
 
 void SearchBoxView::SetShadow(const gfx::ShadowValue& shadow) {
+  if (is_fullscreen_app_list_enabled_)
+    return;
+
   SetBorder(base::MakeUnique<views::ShadowBorder>(shadow));
   Layout();
 }
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index 7b36a34b..7e9dbae11 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -272,6 +272,7 @@
       selection_text_color_(SK_ColorWHITE),
       selection_background_color_(SK_ColorBLUE),
       placeholder_text_color_(kDefaultPlaceholderTextColor),
+      placeholder_text_draw_flags_(gfx::Canvas::DefaultCanvasTextAlignment()),
       invalid_(false),
       text_input_type_(ui::TEXT_INPUT_TYPE_TEXT),
       text_input_flags_(0),
@@ -1970,11 +1971,12 @@
   // Draw placeholder text if needed.
   gfx::RenderText* render_text = GetRenderText();
   if (text().empty() && !GetPlaceholderText().empty()) {
-    canvas->DrawStringRect(GetPlaceholderText(), GetFontList(),
-                           ui::MaterialDesignController::IsSecondaryUiMaterial()
-                               ? SkColorSetA(GetTextColor(), 0x83)
-                               : placeholder_text_color_,
-                           render_text->display_rect());
+    canvas->DrawStringRectWithFlags(
+        GetPlaceholderText(), GetFontList(),
+        ui::MaterialDesignController::IsSecondaryUiMaterial()
+            ? SkColorSetA(GetTextColor(), 0x83)
+            : placeholder_text_color_,
+        render_text->display_rect(), placeholder_text_draw_flags_);
   }
 
   render_text->Draw(canvas);
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h
index 27639d4..bc57114 100644
--- a/ui/views/controls/textfield/textfield.h
+++ b/ui/views/controls/textfield/textfield.h
@@ -162,6 +162,10 @@
     placeholder_text_color_ = color;
   }
 
+  void set_placeholder_text_draw_flags(int flags) {
+    placeholder_text_draw_flags_ = flags;
+  }
+
   // Sets whether to indicate the textfield has invalid content.
   void SetInvalid(bool invalid);
   bool invalid() const { return invalid_; }
@@ -472,6 +476,9 @@
   // TODO(estade): remove this when Harmony/MD is default.
   SkColor placeholder_text_color_;
 
+  // The draw flags specified for |placeholder_text_|.
+  int placeholder_text_draw_flags_;
+
   // True when the contents are deemed unacceptable and should be indicated as
   // such.
   bool invalid_;