diff --git a/BUILD.gn b/BUILD.gn
index 3c4534f..4ffa9a8ce 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -758,7 +758,6 @@
       ":chrome_official_builder_no_unittests",
       "//base:base_unittests",
       "//chrome/test:browser_tests",
-      "//chrome/test:sync_integration_tests",
       "//ipc:ipc_tests",
       "//media:media_unittests",
       "//media/midi:midi_unittests",
diff --git a/DEPS b/DEPS
index b057a45..9544515 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '227fee38565ad52dff45c6619c28c6b6990cea5d',
+  'v8_revision': '71c1795aea7573672dd264568357c0f49afc7321',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -64,7 +64,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '827db14d7f3d0085253b686587717361ffbcad1b',
+  'pdfium_revision': '66568bcd683dd7b18672cb3aebca4487e9203519',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -96,7 +96,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '67894d267da3cd26af31413ec51764f5baabee26',
+  'catapult_revision': '9c9ac13a2b028b9aa24257dc9e669eda29a301ea',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -199,7 +199,7 @@
     Var('chromium_git') + '/external/bidichecker/lib.git' + '@' + '97f2aa645b74c28c57eca56992235c79850fa9e0',
 
   'src/third_party/webgl/src':
-    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '40d75ac0e565191d93ff3c4e8fd19f8b2df7f617',
+    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'f7157c2751220a8f0def9f3f7f6ff37392ac83f0',
 
   'src/third_party/webdriver/pylib':
     Var('chromium_git') + '/external/selenium/py.git' + '@' + '5fd78261a75fe08d27ca4835fb6c5ce4b42275bd',
@@ -484,10 +484,10 @@
       Var('chromium_git') + '/external/mockito/mockito.git' + '@' + 'de83ad4598ad4cf5ea53c69a8a8053780b04b850',
 
     'src/third_party/netty-tcnative/src':
-      Var('chromium_git') + '/external/netty-tcnative.git' + '@' + '2a25ec75d6889d32594a6f8b4d42962c15255d76',
+      Var('chromium_git') + '/external/netty-tcnative.git' + '@' + '5b46a8ef4a39c39c576fcdaaf718b585d75df463',
 
     'src/third_party/netty4/src':
-      Var('chromium_git') + '/external/netty4.git' + '@' + 'e0f26303b4ce635365be19414d0ac81f2ef6ba3c',
+      Var('chromium_git') + '/external/netty4.git' + '@' + 'cc4420b13bb4eeea5b1cf4f93b2755644cd3b120',
 
     'src/third_party/robolectric/robolectric':
       Var('chromium_git') + '/external/robolectric.git' + '@' + '2a0b6ba221c14f3371813a676ce06143353e448d',
@@ -505,7 +505,7 @@
       Var('chromium_git') + '/external/github.com/kennethreitz/requests.git' + '@' + 'f172b30356d821d180fa4ecfa3e71c7274a32de4',
 
     'src/third_party/custom_tabs_client/src':
-      Var('chromium_git') + '/external/github.com/GoogleChrome/custom-tabs-client.git' + '@' + '3c95e2dda9b292653ff13454f093e5ff136814d2',
+      Var('chromium_git') + '/external/github.com/GoogleChrome/custom-tabs-client.git' + '@' + '4889dd9d552d24f08584dde29f639c0da4ea0f12',
 
     'src/third_party/gvr-android-sdk/src':
       Var('chromium_git') + '/external/github.com/googlevr/gvr-android-sdk.git' + '@' + '8d1395957283ee13ebe2bc672ba24e5ca4ec343f',
diff --git a/ash/mus/move_event_handler.cc b/ash/mus/move_event_handler.cc
index 6c89fa9f..0051d49 100644
--- a/ash/mus/move_event_handler.cc
+++ b/ash/mus/move_event_handler.cc
@@ -10,6 +10,7 @@
 #include "ui/aura/mus/window_manager_delegate.h"
 #include "ui/aura/window.h"
 #include "ui/base/class_property.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/base/hit_test.h"
 #include "ui/events/event.h"
 
@@ -28,26 +29,26 @@
 namespace mus {
 namespace {
 
-ui::mojom::CursorType CursorForWindowComponent(int window_component) {
+ui::CursorType CursorForWindowComponent(int window_component) {
   switch (window_component) {
     case HTBOTTOM:
-      return ui::mojom::CursorType::kSouthResize;
+      return ui::CursorType::kSouthResize;
     case HTBOTTOMLEFT:
-      return ui::mojom::CursorType::kSouthWestResize;
+      return ui::CursorType::kSouthWestResize;
     case HTBOTTOMRIGHT:
-      return ui::mojom::CursorType::kSouthEastResize;
+      return ui::CursorType::kSouthEastResize;
     case HTLEFT:
-      return ui::mojom::CursorType::kWestResize;
+      return ui::CursorType::kWestResize;
     case HTRIGHT:
-      return ui::mojom::CursorType::kEastResize;
+      return ui::CursorType::kEastResize;
     case HTTOP:
-      return ui::mojom::CursorType::kNorthResize;
+      return ui::CursorType::kNorthResize;
     case HTTOPLEFT:
-      return ui::mojom::CursorType::kNorthWestResize;
+      return ui::CursorType::kNorthWestResize;
     case HTTOPRIGHT:
-      return ui::mojom::CursorType::kNorthEastResize;
+      return ui::CursorType::kNorthEastResize;
     default:
-      return ui::mojom::CursorType::kNull;
+      return ui::CursorType::kNull;
   }
 }
 
@@ -122,7 +123,8 @@
     const int hit_test_location =
         wm_window_->GetNonClientComponent(event->location());
     window_manager_client_->SetNonClientCursor(
-        wm_window_->aura_window(), CursorForWindowComponent(hit_test_location));
+        wm_window_->aura_window(),
+        ui::CursorData(CursorForWindowComponent(hit_test_location)));
   }
 
   WorkspaceEventHandlerMus* workspace_event_handler =
diff --git a/ash/system/brightness/tray_brightness_unittest.cc b/ash/system/brightness/tray_brightness_unittest.cc
index 4819859..e1f4e08 100644
--- a/ash/system/brightness/tray_brightness_unittest.cc
+++ b/ash/system/brightness/tray_brightness_unittest.cc
@@ -9,13 +9,13 @@
 #include "ash/shell.h"
 #include "ash/system/tray/system_tray_delegate.h"
 #include "ash/system/tray/system_tray_item.h"
-#include "ash/test/ash_test.h"
+#include "ash/test/ash_test_base.h"
 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
 #include "ui/views/view.h"
 
 namespace ash {
 
-class TrayBrightnessTest : public AshTest {
+class TrayBrightnessTest : public test::AshTestBase {
  public:
   TrayBrightnessTest() {}
   ~TrayBrightnessTest() override {}
diff --git a/ash/system/date/date_view_unittest.cc b/ash/system/date/date_view_unittest.cc
index 7d5d510e..ea6df969 100644
--- a/ash/system/date/date_view_unittest.cc
+++ b/ash/system/date/date_view_unittest.cc
@@ -4,13 +4,13 @@
 
 #include "ash/system/date/date_view.h"
 
-#include "ash/test/ash_test.h"
+#include "ash/test/ash_test_base.h"
 #include "ui/views/controls/label.h"
 
 namespace ash {
 namespace tray {
 
-class TimeViewTest : public AshTest {
+class TimeViewTest : public test::AshTestBase {
  public:
   TimeViewTest() {}
   ~TimeViewTest() override {}
diff --git a/ash/system/screen_security/screen_tray_item_unittest.cc b/ash/system/screen_security/screen_tray_item_unittest.cc
index 5c7d9d2..122c478 100644
--- a/ash/system/screen_security/screen_tray_item_unittest.cc
+++ b/ash/system/screen_security/screen_tray_item_unittest.cc
@@ -10,7 +10,7 @@
 #include "ash/system/tray/system_tray.h"
 #include "ash/system/tray/system_tray_notifier.h"
 #include "ash/system/tray/tray_item_view.h"
-#include "ash/test/ash_test.h"
+#include "ash/test/ash_test_base.h"
 #include "base/callback.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -36,7 +36,7 @@
       ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE));
 }
 
-class ScreenTrayItemTest : public AshTest {
+class ScreenTrayItemTest : public test::AshTestBase {
  public:
   ScreenTrayItemTest() : tray_item_(NULL), stop_callback_hit_count_(0) {}
   ~ScreenTrayItemTest() override {}
@@ -47,7 +47,7 @@
   int stop_callback_hit_count() const { return stop_callback_hit_count_; }
 
   void SetUp() override {
-    AshTest::SetUp();
+    AshTestBase::SetUp();
     TrayItemView::DisableAnimationsForTest();
   }
 
@@ -192,7 +192,7 @@
   EXPECT_FALSE(tray_item->tray_view()->visible());
 
   std::vector<SystemTrayItem*> tray_items =
-      AshTest::GetPrimarySystemTray()->GetTrayItems();
+      test::AshTestBase::GetPrimarySystemTray()->GetTrayItems();
   EXPECT_NE(std::find(tray_items.begin(), tray_items.end(), tray_item),
             tray_items.end());
 
@@ -200,16 +200,16 @@
   EXPECT_TRUE(tray_item->tray_view()->visible());
 
   // The default view should be created in a new bubble.
-  AshTest::GetPrimarySystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+  test::AshTestBase::GetPrimarySystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
   EXPECT_TRUE(tray_item->default_view());
-  AshTest::GetPrimarySystemTray()->CloseSystemBubble();
+  test::AshTestBase::GetPrimarySystemTray()->CloseSystemBubble();
   EXPECT_FALSE(tray_item->default_view());
 
   test->StopSession();
   EXPECT_FALSE(tray_item->tray_view()->visible());
 
   // The default view should not be visible because session is stopped.
-  AshTest::GetPrimarySystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+  test::AshTestBase::GetPrimarySystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
   EXPECT_FALSE(tray_item->default_view()->visible());
 }
 
diff --git a/ash/system/tray/hover_highlight_view.cc b/ash/system/tray/hover_highlight_view.cc
index e96272b6..9df8b65 100644
--- a/ash/system/tray/hover_highlight_view.cc
+++ b/ash/system/tray/hover_highlight_view.cc
@@ -57,11 +57,13 @@
 
 void HoverHighlightView::SetSubText(const base::string16& sub_text) {
   DCHECK(text_label_);
+  DCHECK(!sub_text.empty());
 
   if (!sub_text_label_) {
     sub_text_label_ = TrayPopupUtils::CreateDefaultLabel();
     tri_view_->AddView(TriView::Container::CENTER, sub_text_label_);
   }
+
   TrayPopupItemStyle sub_style(TrayPopupItemStyle::FontStyle::CAPTION);
   sub_style.set_color_style(TrayPopupItemStyle::ColorStyle::INACTIVE);
   sub_style.SetupLabel(sub_text_label_);
@@ -117,7 +119,8 @@
   style.SetupLabel(text_label_);
   tri_view_->AddView(TriView::Container::CENTER, text_label_);
 
-  SetSubText(sub_text);
+  if (!sub_text.empty())
+    SetSubText(sub_text);
 
   tri_view_->SetContainerVisible(TriView::Container::END, false);
 
diff --git a/ash/system/tray/hover_highlight_view.h b/ash/system/tray/hover_highlight_view.h
index 4283c53..6c60bfc 100644
--- a/ash/system/tray/hover_highlight_view.h
+++ b/ash/system/tray/hover_highlight_view.h
@@ -69,8 +69,8 @@
   // Hide or show the right view.
   void SetRightViewVisible(bool visible);
 
-  // Sets text for the sub label. Precondition for this function is that
-  // |text_label_| is non-null.
+  // Sets the text of |sub_text_label_| to |sub_text|. Prior to calling this
+  // function, |text_label_| must not be null and |sub_text| must not be empty.
   void SetSubText(const base::string16& sub_text);
 
   // Allows view to expand its height. Size of unexapandable view is fixed and
diff --git a/ash/wm/ash_focus_rules.cc b/ash/wm/ash_focus_rules.cc
index ac4d9a8..21f7f023 100644
--- a/ash/wm/ash_focus_rules.cc
+++ b/ash/wm/ash_focus_rules.cc
@@ -13,7 +13,9 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_state_aura.h"
 #include "ash/wm_window.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
+#include "ui/events/event.h"
 
 namespace ash {
 namespace wm {
@@ -33,13 +35,9 @@
 ////////////////////////////////////////////////////////////////////////////////
 // AshFocusRules, public:
 
-AshFocusRules::AshFocusRules() {}
+AshFocusRules::AshFocusRules() = default;
 
-AshFocusRules::~AshFocusRules() {}
-
-bool AshFocusRules::IsWindowConsideredActivatable(aura::Window* window) const {
-  return ash::IsWindowConsideredActivatable(WmWindow::Get(window));
-}
+AshFocusRules::~AshFocusRules() = default;
 
 ////////////////////////////////////////////////////////////////////////////////
 // AshFocusRules, ::wm::FocusRules:
@@ -73,6 +71,19 @@
   return true;
 }
 
+bool AshFocusRules::CanFocusWindow(aura::Window* window,
+                                   const ui::Event* event) const {
+  if (!window)
+    return true;
+
+  if (event && (event->IsMouseEvent() || event->IsGestureEvent()) &&
+      !window->GetProperty(aura::client::kActivateOnPointerKey)) {
+    return false;
+  }
+
+  return BaseFocusRules::CanFocusWindow(window, event);
+}
+
 aura::Window* AshFocusRules::GetNextActivatableWindow(
     aura::Window* ignore) const {
   DCHECK(ignore);
@@ -141,7 +152,7 @@
         !window_state->IsMinimized())
       return *i;
   }
-  return NULL;
+  return nullptr;
 }
 
 }  // namespace wm
diff --git a/ash/wm/ash_focus_rules.h b/ash/wm/ash_focus_rules.h
index 97546cd..75f5f4f 100644
--- a/ash/wm/ash_focus_rules.h
+++ b/ash/wm/ash_focus_rules.h
@@ -18,10 +18,6 @@
   AshFocusRules();
   ~AshFocusRules() override;
 
-  // Tests if the given |window| can be activated, ignoring the system modal
-  // dialog state.
-  bool IsWindowConsideredActivatable(aura::Window* window) const;
-
  private:
   // ::wm::BaseFocusRules:
   bool IsToplevelWindow(aura::Window* window) const override;
@@ -29,6 +25,8 @@
   bool IsWindowConsideredVisibleForActivation(
       aura::Window* window) const override;
   bool CanActivateWindow(aura::Window* window) const override;
+  bool CanFocusWindow(aura::Window* window,
+                      const ui::Event* event) const override;
   aura::Window* GetNextActivatableWindow(aura::Window* ignore) const override;
 
   aura::Window* GetTopmostWindowToActivateForContainerIndex(
diff --git a/ash/wm/mru_window_tracker_unittest.cc b/ash/wm/mru_window_tracker_unittest.cc
index 9463941f..97364ca 100644
--- a/ash/wm/mru_window_tracker_unittest.cc
+++ b/ash/wm/mru_window_tracker_unittest.cc
@@ -6,20 +6,22 @@
 
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
-#include "ash/test/ash_test.h"
+#include "ash/test/ash_test_base.h"
 #include "ash/wm/window_state.h"
+#include "ash/wm/window_state_aura.h"
+#include "ash/wm/window_util.h"
 #include "ash/wm_window.h"
 #include "ui/base/hit_test.h"
 
 namespace ash {
 
-class MruWindowTrackerTest : public AshTest {
+class MruWindowTrackerTest : public test::AshTestBase {
  public:
   MruWindowTrackerTest() {}
   ~MruWindowTrackerTest() override {}
 
-  std::unique_ptr<WindowOwner> CreateTestWindow() {
-    return AshTest::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
+  std::unique_ptr<aura::Window> CreateTestWindow() {
+    return AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
   }
 
   MruWindowTracker* mru_window_tracker() {
@@ -32,75 +34,66 @@
 
 // Basic test that the activation order is tracked.
 TEST_F(MruWindowTrackerTest, Basic) {
-  std::unique_ptr<WindowOwner> w1_owner(CreateTestWindow());
-  WmWindow* w1 = w1_owner->window();
-  std::unique_ptr<WindowOwner> w2_owner(CreateTestWindow());
-  WmWindow* w2 = w2_owner->window();
-  std::unique_ptr<WindowOwner> w3_owner(CreateTestWindow());
-  WmWindow* w3 = w3_owner->window();
-  w3->Activate();
-  w2->Activate();
-  w1->Activate();
+  std::unique_ptr<aura::Window> w1(CreateTestWindow());
+  std::unique_ptr<aura::Window> w2(CreateTestWindow());
+  std::unique_ptr<aura::Window> w3(CreateTestWindow());
+  wm::ActivateWindow(w3.get());
+  wm::ActivateWindow(w2.get());
+  wm::ActivateWindow(w1.get());
 
   WmWindow::Windows window_list = mru_window_tracker()->BuildMruWindowList();
   ASSERT_EQ(3u, window_list.size());
-  EXPECT_EQ(w1, window_list[0]);
-  EXPECT_EQ(w2, window_list[1]);
-  EXPECT_EQ(w3, window_list[2]);
+  EXPECT_EQ(w1.get(), window_list[0]->aura_window());
+  EXPECT_EQ(w2.get(), window_list[1]->aura_window());
+  EXPECT_EQ(w3.get(), window_list[2]->aura_window());
 }
 
 // Test that minimized windows are not treated specially.
 TEST_F(MruWindowTrackerTest, MinimizedWindowsAreLru) {
-  std::unique_ptr<WindowOwner> w1_owner(CreateTestWindow());
-  WmWindow* w1 = w1_owner->window();
-  std::unique_ptr<WindowOwner> w2_owner(CreateTestWindow());
-  WmWindow* w2 = w2_owner->window();
-  std::unique_ptr<WindowOwner> w3_owner(CreateTestWindow());
-  WmWindow* w3 = w3_owner->window();
-  std::unique_ptr<WindowOwner> w4_owner(CreateTestWindow());
-  WmWindow* w4 = w4_owner->window();
-  std::unique_ptr<WindowOwner> w5_owner(CreateTestWindow());
-  WmWindow* w5 = w5_owner->window();
-  std::unique_ptr<WindowOwner> w6_owner(CreateTestWindow());
-  WmWindow* w6 = w6_owner->window();
-  w6->Activate();
-  w5->Activate();
-  w4->Activate();
-  w3->Activate();
-  w2->Activate();
-  w1->Activate();
+  std::unique_ptr<aura::Window> w1(CreateTestWindow());
+  std::unique_ptr<aura::Window> w2(CreateTestWindow());
+  std::unique_ptr<aura::Window> w3(CreateTestWindow());
+  std::unique_ptr<aura::Window> w4(CreateTestWindow());
+  std::unique_ptr<aura::Window> w5(CreateTestWindow());
+  std::unique_ptr<aura::Window> w6(CreateTestWindow());
+  wm::ActivateWindow(w6.get());
+  wm::ActivateWindow(w5.get());
+  wm::ActivateWindow(w4.get());
+  wm::ActivateWindow(w3.get());
+  wm::ActivateWindow(w2.get());
+  wm::ActivateWindow(w1.get());
 
-  w1->GetWindowState()->Minimize();
-  w4->GetWindowState()->Minimize();
-  w5->GetWindowState()->Minimize();
+  wm::GetWindowState(w1.get())->Minimize();
+  wm::GetWindowState(w4.get())->Minimize();
+  wm::GetWindowState(w5.get())->Minimize();
 
   // By minimizing the first window, we activate w2 which will move it to the
   // front of the MRU queue.
-  EXPECT_TRUE(w2->IsActive());
+  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
 
   WmWindow::Windows window_list = mru_window_tracker()->BuildMruWindowList();
-  EXPECT_EQ(w2, window_list[0]);
-  EXPECT_EQ(w1, window_list[1]);
-  EXPECT_EQ(w3, window_list[2]);
-  EXPECT_EQ(w4, window_list[3]);
-  EXPECT_EQ(w5, window_list[4]);
-  EXPECT_EQ(w6, window_list[5]);
+  EXPECT_EQ(w2.get(), window_list[0]->aura_window());
+  EXPECT_EQ(w1.get(), window_list[1]->aura_window());
+  EXPECT_EQ(w3.get(), window_list[2]->aura_window());
+  EXPECT_EQ(w4.get(), window_list[3]->aura_window());
+  EXPECT_EQ(w5.get(), window_list[4]->aura_window());
+  EXPECT_EQ(w6.get(), window_list[5]->aura_window());
 }
 
 // Tests that windows being dragged are only in the WindowList once.
 TEST_F(MruWindowTrackerTest, DraggedWindowsInListOnlyOnce) {
-  std::unique_ptr<WindowOwner> w1_owner(CreateTestWindow());
-  WmWindow* w1 = w1_owner->window();
-  w1->Activate();
+  std::unique_ptr<aura::Window> w1(CreateTestWindow());
+  wm::ActivateWindow(w1.get());
 
   // Start dragging the window.
-  w1->GetWindowState()->CreateDragDetails(
+  wm::GetWindowState(w1.get())->CreateDragDetails(
       gfx::Point(), HTRIGHT, aura::client::WINDOW_MOVE_SOURCE_TOUCH);
 
   // The dragged window should only be in the list once.
   WmWindow::Windows window_list =
       mru_window_tracker()->BuildWindowListIgnoreModal();
-  EXPECT_EQ(1, std::count(window_list.begin(), window_list.end(), w1));
+  EXPECT_EQ(1, std::count(window_list.begin(), window_list.end(),
+                          WmWindow::Get(w1.get())));
 }
 
 }  // namespace ash
diff --git a/ash/wm/window_manager_unittest.cc b/ash/wm/window_manager_unittest.cc
index 38667af..4183276 100644
--- a/ash/wm/window_manager_unittest.cc
+++ b/ash/wm/window_manager_unittest.cc
@@ -8,6 +8,7 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/test/test_activation_delegate.h"
 #include "ash/wm/window_util.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/cursor_client_observer.h"
 #include "ui/aura/client/focus_client.h"
 #include "ui/aura/env.h"
@@ -349,6 +350,45 @@
   }
 }
 
+// Tests that Set window property |kActivateOnPointerKey| to false could
+// properly ignore pointer window activation.
+TEST_F(WindowManagerTest, ActivateOnPointerWindowProperty) {
+  // Create two test windows, window1 and window2.
+  aura::test::TestWindowDelegate wd;
+  std::unique_ptr<aura::Window> w1(
+      CreateTestWindowInShellWithDelegate(&wd, -1, gfx::Rect(10, 10, 50, 50)));
+  std::unique_ptr<aura::Window> w2(
+      CreateTestWindowInShellWithDelegate(&wd, -2, gfx::Rect(70, 70, 50, 50)));
+
+  // Activate window1.
+  wm::ActivateWindow(w1.get());
+  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+  EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+
+  // Set window2 not pointer activatable.
+  w2->SetProperty(aura::client::kActivateOnPointerKey, false);
+  // Mouse click on window2.
+  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), w2.get());
+  generator.ClickLeftButton();
+  // Window2 should not become active.
+  EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+  // Gesture a tap at window2.
+  generator.GestureTapAt(w2->bounds().CenterPoint());
+  // Window2 should not become active.
+  EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+  // Set window2 now pointer activatable.
+  w2->SetProperty(aura::client::kActivateOnPointerKey, true);
+  // Mouse click on window2.
+  generator.ClickLeftButton();
+  // Window2 should become active.
+  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+  EXPECT_FALSE(wm::IsActiveWindow(w1.get()));
+}
+
 TEST_F(WindowManagerTest, PanelActivation) {
   aura::test::TestWindowDelegate wd;
   std::unique_ptr<aura::Window> w1(
diff --git a/build/android/pylib/results/presentation/test_results_presentation.py b/build/android/pylib/results/presentation/test_results_presentation.py
index d7e2b5a..07f30c2 100755
--- a/build/android/pylib/results/presentation/test_results_presentation.py
+++ b/build/android/pylib/results/presentation/test_results_presentation.py
@@ -306,6 +306,7 @@
 
   return '%s/%s/%s' % (server_url, bucket, dest)
 
+
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument('--json-file', help='Path of json file.')
@@ -347,16 +348,21 @@
   if ((args.build_properties is None) ==
          (args.build_number is None or args.builder_name is None)):
     raise parser.error('Exactly one of build_perperties or '
-                    '(build_number or builder_names) should be given.')
+                       '(build_number or builder_name) should be given.')
 
   if (args.build_number is None) != (args.builder_name is None):
     raise parser.error('args.build_number and args.builder_name '
-                    'has to be be given together'
-                    'or not given at all.')
+                       'has to be be given together'
+                       'or not given at all.')
 
-  if (len(args.positional) == 0) == (args.json_file is None):
+  if len(args.positional) == 0 and args.json_file is None:
+    if args.output_json:
+        with open(args.output_json, 'w') as f:
+          json.dump({}, f)
+    return
+  elif len(args.positional) != 0 and args.json_file:
     raise parser.error('Exactly one of args.positional and '
-                    'args.json_file should be given.')
+                       'args.json_file should be given.')
 
   if args.build_properties:
     build_properties = json.loads(args.build_properties)
diff --git a/build/check_gn_headers.py b/build/check_gn_headers.py
index be1e797a..73f1593 100755
--- a/build/check_gn_headers.py
+++ b/build/check_gn_headers.py
@@ -76,7 +76,12 @@
   all_headers = set()
 
   for _target, properties in gn['targets'].iteritems():
-    for f in properties.get('sources', []):
+    sources = properties.get('sources', [])
+    public = properties.get('public', [])
+    # Exclude '"public": "*"'.
+    if type(public) is list:
+      sources += public
+    for f in sources:
       if f.endswith('.h') or f.endswith('.hh'):
         if f.startswith('//'):
           f = f[2:]  # Strip the '//' prefix.
diff --git a/build/check_gn_headers_unittest.py b/build/check_gn_headers_unittest.py
index f62fe62..892fa66 100755
--- a/build/check_gn_headers_unittest.py
+++ b/build/check_gn_headers_unittest.py
@@ -38,8 +38,14 @@
       "//:All": {
       },
       "//:base": {
+         "public": [ "//base/p.h" ],
          "sources": [ "//base/a.cc", "//base/a.h", "//base/b.hh" ],
          "visibility": [ "*" ]
+      },
+      "//:star_public": {
+         "public": "*",
+         "sources": [ "//base/c.h" ],
+         "visibility": [ "*" ]
       }
     }
 }
@@ -88,6 +94,8 @@
     expected = set([
         'base/a.h',
         'base/b.hh',
+        'base/c.h',
+        'base/p.h',
     ])
     self.assertEquals(headers, expected)
 
diff --git a/cc/layers/scrollbar_layer_unittest.cc b/cc/layers/scrollbar_layer_unittest.cc
index 6e8cf704..177b6b2 100644
--- a/cc/layers/scrollbar_layer_unittest.cc
+++ b/cc/layers/scrollbar_layer_unittest.cc
@@ -753,6 +753,7 @@
       scroll_layer->element_id()));
 
   scroll_layer->SetBounds(gfx::Size(20, 20));
+  scroll_layer->ShowScrollbars();
   scroll_layer->SetForceRenderSurfaceForTesting(true);
   layer_tree_host_->UpdateLayers();
   host_impl->CreatePendingTree();
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 5d38bf5..1e4d4fd 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -340,6 +340,8 @@
       tile_manager_.TakeImagesToInvalidateOnSyncTree());
 
   if (CommitToActiveTree()) {
+    active_tree_->HandleScrollbarShowRequestsFromMain();
+
     // We have to activate animations here or "IsActive()" is true on the layers
     // but the animations aren't activated yet so they get ignored by
     // UpdateDrawProperties.
@@ -3283,8 +3285,9 @@
 
   DistributeScrollDelta(scroll_state);
 
-  active_tree_->SetCurrentlyScrollingNode(
-      scroll_state->current_native_scrolling_node());
+  ScrollNode* current_scrolling_node =
+      scroll_state->current_native_scrolling_node();
+  active_tree_->SetCurrentlyScrollingNode(current_scrolling_node);
   did_lock_scrolling_layer_ =
       scroll_state->delta_consumed_for_scroll_sequence();
 
@@ -3292,6 +3295,8 @@
   bool did_scroll_y = scroll_state->caused_scroll_y();
   bool did_scroll_content = did_scroll_x || did_scroll_y;
   if (did_scroll_content) {
+    ShowScrollbarsForImplScroll(current_scrolling_node->element_id);
+
     // If we are scrolling with an active scroll handler, forward latency
     // tracking information to the main thread so the delay introduced by the
     // handler is accounted for.
@@ -3352,6 +3357,7 @@
   if (!changed)
     return;
 
+  ShowScrollbarsForImplScroll(OuterViewportScrollLayer()->element_id());
   client_->SetNeedsCommitOnImplThread();
   // After applying the synchronous input handler's scroll offset, tell it what
   // we ended up with.
@@ -4153,6 +4159,7 @@
     const gfx::ScrollOffset& scroll_offset) {
   if (list_type == ElementListType::ACTIVE) {
     SetTreeLayerScrollOffsetMutated(element_id, active_tree(), scroll_offset);
+    ShowScrollbarsForImplScroll(element_id);
   } else {
     SetTreeLayerScrollOffsetMutated(element_id, pending_tree(), scroll_offset);
     SetTreeLayerScrollOffsetMutated(element_id, recycle_tree(), scroll_offset);
@@ -4305,4 +4312,12 @@
     has_scrolled_by_touch_ = true;
 }
 
+void LayerTreeHostImpl::ShowScrollbarsForImplScroll(ElementId element_id) {
+  if (!element_id)
+    return;
+  if (ScrollbarAnimationController* animation_controller =
+          ScrollbarAnimationControllerForElementId(element_id))
+    animation_controller->DidScrollUpdate();
+}
+
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index e7b3f61..b6a6be40 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -709,6 +709,7 @@
   void UpdateScrollSourceInfo(bool is_wheel_scroll);
 
   bool IsScrolledBy(LayerImpl* child, ScrollNode* ancestor);
+  void ShowScrollbarsForImplScroll(ElementId element_id);
 
   using UIResourceMap = std::unordered_map<UIResourceId, UIResourceData>;
   UIResourceMap ui_resource_map_;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 995afae..f4ba45c 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -2919,8 +2919,10 @@
     LayerImpl* root = host_impl_->active_tree()->InnerViewportContainerLayer();
     scrollbar->SetScrollElementId(scroll->element_id());
     root->test_properties()->AddChild(std::move(scrollbar));
+    scroll->set_needs_show_scrollbars(true);
     host_impl_->active_tree()->BuildPropertyTreesForTesting();
     host_impl_->active_tree()->DidBecomeActive();
+    host_impl_->active_tree()->HandleScrollbarShowRequestsFromMain();
     DrawFrame();
 
     // SetScrollElementId will initialize the scrollbar which will cause it to
@@ -3056,8 +3058,8 @@
       host_impl_->DidFinishImplFrame();
     }
 
-    // Setting the scroll offset outside a scroll should also cause the
-    // scrollbar to appear and to schedule a scrollbar animation.
+    // Setting the scroll offset outside a scroll should not cause the
+    // scrollbar to appear or schedule a scrollbar animation.
     if (host_impl_->active_tree()
             ->property_trees()
             ->scroll_tree.UpdateScrollOffsetBaseForTesting(
@@ -3067,57 +3069,9 @@
           host_impl_->InnerViewportScrollLayer()->id());
     EXPECT_FALSE(did_request_next_frame_);
     EXPECT_FALSE(did_request_redraw_);
-    if (expecting_animations) {
-      EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
-                requested_animation_delay_);
-      EXPECT_FALSE(animation_task_.Equals(base::Closure()));
-      requested_animation_delay_ = base::TimeDelta();
-      animation_task_ = base::Closure();
-    } else {
-      EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
-      EXPECT_TRUE(animation_task_.Equals(base::Closure()));
-    }
-
-    if (expecting_animations) {
-      // Scrolling should have stopped the animation, so we should not be
-      // getting redraws.
-      begin_frame_args =
-          CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 5, fake_now);
-      host_impl_->WillBeginImplFrame(begin_frame_args);
-      host_impl_->Animate();
-      EXPECT_FALSE(did_request_next_frame_);
-      did_request_next_frame_ = false;
-      EXPECT_FALSE(did_request_redraw_);
-      did_request_redraw_ = false;
-      host_impl_->DidFinishImplFrame();
-    }
-
-    // For Andrdoid, scrollbar animation is not triggered unnecessarily.
-    // For Aura Overlay Scrollbar, scrollbar appears even if scroll offset did
-    // not change.
-    host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
-                            InputHandler::WHEEL);
-    host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2dF(5, 0)).get());
-    EXPECT_FALSE(did_request_next_frame_);
-    EXPECT_TRUE(did_request_redraw_);
-    did_request_redraw_ = false;
     EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
     EXPECT_TRUE(animation_task_.Equals(base::Closure()));
 
-    host_impl_->ScrollEnd(EndState().get());
-    EXPECT_FALSE(did_request_next_frame_);
-    EXPECT_FALSE(did_request_redraw_);
-    if (animator == LayerTreeSettings::AURA_OVERLAY) {
-      EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
-                requested_animation_delay_);
-      EXPECT_FALSE(animation_task_.Equals(base::Closure()));
-      requested_animation_delay_ = base::TimeDelta();
-      animation_task_ = base::Closure();
-    } else {
-      EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
-      EXPECT_TRUE(animation_task_.Equals(base::Closure()));
-    }
-
     // Changing page scale triggers scrollbar animation.
     host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 4.f);
     host_impl_->active_tree()->SetPageScaleOnActiveTree(1.1f);
@@ -3267,6 +3221,7 @@
   scrollbar->SetScrollElementId(scroll->element_id());
   container->test_properties()->AddChild(std::move(scrollbar));
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
+  scroll->set_needs_show_scrollbars(true);
   host_impl_->pending_tree()->BuildPropertyTreesForTesting();
   host_impl_->ActivateSyncTree();
 
@@ -3407,20 +3362,11 @@
   EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForElementId(
       root_scroll->element_id()));
 
-  // Changing one of the viewport layers should result in a scrollbar animation
-  // update.
+  // Scrolling the viewport should result in a scrollbar animation update.
   animation_task_ = base::Closure();
-  host_impl_->active_tree()
-      ->InnerViewportContainerLayer()
-      ->SetViewportBoundsDelta(gfx::Vector2dF(10, 10));
-  EXPECT_FALSE(animation_task_.Equals(base::Closure()));
-  animation_task_ = base::Closure();
-  host_impl_->active_tree()->OuterViewportScrollLayer()->SetCurrentScrollOffset(
-      gfx::ScrollOffset(10, 10));
-  EXPECT_FALSE(animation_task_.Equals(base::Closure()));
-  animation_task_ = base::Closure();
-  host_impl_->active_tree()->InnerViewportScrollLayer()->SetCurrentScrollOffset(
-      gfx::ScrollOffset(10, 10));
+  host_impl_->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL);
+  host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(10, 10)).get());
+  host_impl_->ScrollEnd(EndState().get());
   EXPECT_FALSE(animation_task_.Equals(base::Closure()));
   animation_task_ = base::Closure();
 
@@ -3446,9 +3392,8 @@
   // update.
   animation_task_ = base::Closure();
   child_clip_ptr->SetBounds(gfx::Size(200, 200));
-  EXPECT_FALSE(animation_task_.Equals(base::Closure()));
-  animation_task_ = base::Closure();
-  child_ptr->SetCurrentScrollOffset(gfx::ScrollOffset(10, 10));
+  child_ptr->set_needs_show_scrollbars(true);
+  host_impl_->active_tree()->HandleScrollbarShowRequestsFromMain();
   EXPECT_FALSE(animation_task_.Equals(base::Closure()));
   animation_task_ = base::Closure();
 
@@ -12242,6 +12187,7 @@
 
   // Capture scrollbar_1, then move mouse to scrollbar_2's layer, should post an
   // event to fade out scrollbar_1.
+  scrollbar_1_animation_controller->DidScrollUpdate();
   animation_task_ = base::Closure();
 
   host_impl_->MouseDown();
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 0461219ab..4082462 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -242,51 +242,23 @@
     clip_size.Scale(1 / current_page_scale_factor());
   }
 
-  bool scrollbar_needs_animation = false;
-  bool clip_layer_size_did_change = false;
-  bool scroll_layer_size_did_change = false;
   bool y_offset_did_change = false;
   for (auto* scrollbar : ScrollbarsFor(scroll_layer->element_id())) {
     if (scrollbar->orientation() == HORIZONTAL) {
-      scrollbar_needs_animation |= scrollbar->SetCurrentPos(current_offset.x());
-      clip_layer_size_did_change |=
-          scrollbar->SetClipLayerLength(clip_size.width());
-      scroll_layer_size_did_change |=
-          scrollbar->SetScrollLayerLength(scroll_size.width());
+      scrollbar->SetCurrentPos(current_offset.x());
+      scrollbar->SetClipLayerLength(clip_size.width());
+      scrollbar->SetScrollLayerLength(scroll_size.width());
     } else {
-      scrollbar_needs_animation |= y_offset_did_change |=
-          scrollbar->SetCurrentPos(current_offset.y());
-      clip_layer_size_did_change |=
-          scrollbar->SetClipLayerLength(clip_size.height());
-      scroll_layer_size_did_change |=
-          scrollbar->SetScrollLayerLength(scroll_size.height());
+      y_offset_did_change = scrollbar->SetCurrentPos(current_offset.y());
+      scrollbar->SetClipLayerLength(clip_size.height());
+      scrollbar->SetScrollLayerLength(scroll_size.height());
     }
-    scrollbar_needs_animation |=
-        scrollbar->SetVerticalAdjust(clip_layer->ViewportBoundsDelta().y());
+    scrollbar->SetVerticalAdjust(clip_layer->ViewportBoundsDelta().y());
   }
 
-  scrollbar_needs_animation |=
-      (clip_layer_size_did_change || scroll_layer_size_did_change);
-
   if (y_offset_did_change && IsViewportLayerId(scroll_layer_id))
     TRACE_COUNTER_ID1("cc", "scroll_offset_y", scroll_layer->id(),
                       current_offset.y());
-
-  if (scrollbar_needs_animation) {
-    ScrollbarAnimationController* controller =
-        layer_tree_host_impl_->ScrollbarAnimationControllerForElementId(
-            scroll_layer->element_id());
-    if (!controller)
-      return;
-
-    // TODO(chaopeng) clip_layer_size_did_change should call DidResize after
-    // crbug.com/701810 got fixed.
-    if (scroll_layer_size_did_change) {
-      controller->DidResize();
-    } else {
-      controller->DidScrollUpdate();
-    }
-  }
 }
 
 RenderSurfaceImpl* LayerTreeImpl::RootRenderSurface() const {
@@ -494,10 +466,10 @@
   target_tree->has_ever_been_drawn_ = false;
 
   // Note: this needs to happen after SetPropertyTrees.
-  target_tree->ShowScrollbars();
+  target_tree->HandleScrollbarShowRequestsFromMain();
 }
 
-void LayerTreeImpl::ShowScrollbars() {
+void LayerTreeImpl::HandleScrollbarShowRequestsFromMain() {
   LayerTreeHostCommon::CallFunctionForEveryLayer(this, [this](
                                                            LayerImpl* layer) {
     if (!layer->needs_show_scrollbars())
@@ -930,6 +902,13 @@
 
   set_needs_update_draw_properties();
   DidUpdateScrollState(inner_viewport_scroll_layer_id_);
+
+  if (IsActiveTree() && layer_tree_host_impl_->ViewportMainScrollLayer()) {
+    if (ScrollbarAnimationController* controller =
+            layer_tree_host_impl_->ScrollbarAnimationControllerForElementId(
+                OuterViewportScrollLayer()->element_id()))
+      controller->DidScrollUpdate();
+  }
 }
 
 void LayerTreeImpl::SetDeviceScaleFactor(float device_scale_factor) {
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index 9dbaa2c..4a3f3ab9 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -464,6 +464,7 @@
   void ClearLayerList();
 
   void BuildLayerListForTesting();
+  void HandleScrollbarShowRequestsFromMain();
 
   void InvalidateRegionForImages(const ImageIdFlatSet& images_to_invalidate);
 
@@ -476,7 +477,6 @@
                                 float max_page_scale_factor);
   bool IsViewportLayerId(int id) const;
   void UpdateScrollbars(int scroll_layer_id, int clip_layer_id);
-  void ShowScrollbars();
   void DidUpdatePageScale();
   void PushBrowserControls(const float* top_controls_shown_ratio);
   bool ClampBrowserControlsShownRatio();
diff --git a/chrome/VERSION b/chrome/VERSION
index 92ea6e4..7570bad 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=60
 MINOR=0
-BUILD=3083
+BUILD=3084
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 7f384aa..f3b72f5 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -372,6 +372,7 @@
     "//third_party/android_tools:android_support_v7_mediarouter_java",
     "//third_party/android_tools:android_support_v7_recyclerview_java",
     "//third_party/cacheinvalidation:cacheinvalidation_javalib",
+    "//third_party/custom_tabs_client:custom_tabs_support_java",
     "//third_party/hamcrest:hamcrest_java",
     "//ui/android:ui_java",
     "//url/mojo:url_mojom_gurl_java",
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 96a72c3..5f50da2f 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -605,6 +605,21 @@
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize" >
         </activity>
 
+        <!-- Activities for Browser Actions -->
+        {% if channel in ['default'] %}
+        <activity android:name="org.chromium.chrome.browser.browseractions.BrowserActionActivity"
+            android:theme="@style/FullscreenTransparentActivityTheme"
+            android:exported="true"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize">
+            <intent-filter>
+                <action android:name="android.support.customtabs.browseractions.browser_action_open" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+            </intent-filter>
+        </activity>
+        {% endif %}
+
         <!-- Service for Broadcasting a Physical Web URL -->
         <service
             android:name="org.chromium.chrome.browser.physicalweb.PhysicalWebBroadcastService"
diff --git a/chrome/android/java/DEPS b/chrome/android/java/DEPS
index 9ffefe4..ec8e03e 100644
--- a/chrome/android/java/DEPS
+++ b/chrome/android/java/DEPS
@@ -17,6 +17,7 @@
   "+components/web_contents_delegate_android",
   "+components/web_restrictions",
   "+content/public/android/java",
+  "+device/geolocation/android/java",
   "+services/service_manager/public/java",
 ]
 
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index e21804f00..02c215b 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -59,6 +59,15 @@
         <item name="windowNoTitle">true</item>
     </style>
 
+    <style name="FullscreenTransparentActivityTheme" parent="Theme.AppCompat.Light.NoActionBar" >
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
     <style name="FullscreenWhiteActivityTheme" parent="FullscreenWhite">
         <item name="windowActionBar">false</item>
     </style>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java
new file mode 100644
index 0000000..2ba8475
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java
@@ -0,0 +1,128 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.browseractions;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.customtabs.browseractions.BrowserActionItem;
+import android.support.customtabs.browseractions.BrowserActionsIntent;
+
+import org.chromium.base.Log;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.UrlConstants;
+import org.chromium.chrome.browser.init.AsyncInitializationActivity;
+import org.chromium.chrome.browser.util.IntentUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A transparent {@link AsyncInitializationActivity} that displays the browser action context menu.
+ */
+public class BrowserActionActivity extends AsyncInitializationActivity {
+    private static final String TAG = "BrowserActions";
+
+    private int mType;
+    private Uri mUri;
+    private String mCreatorPackageName;
+    private List<BrowserActionItem> mActions = new ArrayList<>();
+
+    @Override
+    protected void setContentView() {
+        openContextMenu();
+    }
+
+    @Override
+    @SuppressFBWarnings("URF_UNREAD_FIELD")
+    protected boolean isStartedUpCorrectly(Intent intent) {
+        if (intent == null
+                || !BrowserActionsIntent.ACTION_BROWSER_ACTIONS_OPEN.equals(intent.getAction())) {
+            return false;
+        }
+        mUri = Uri.parse(IntentHandler.getUrlFromIntent(intent));
+        mType = IntentUtils.safeGetIntExtra(
+                intent, BrowserActionsIntent.EXTRA_TYPE, BrowserActionsIntent.URL_TYPE_NONE);
+        mCreatorPackageName = BrowserActionsIntent.getCreatorPackageName(intent);
+        ArrayList<Bundle> bundles = IntentUtils.getParcelableArrayListExtra(
+                intent, BrowserActionsIntent.EXTRA_MENU_ITEMS);
+        if (bundles != null) {
+            parseBrowserActionItems(bundles);
+        }
+        if (mUri == null) {
+            Log.e(TAG, "Missing url");
+            return false;
+        } else if (!UrlConstants.HTTP_SCHEME.equals(mUri.getScheme())
+                && !UrlConstants.HTTPS_SCHEME.equals(mUri.getScheme())) {
+            Log.e(TAG, "Url should only be HTTP or HTTPS scheme");
+            return false;
+        } else if (mCreatorPackageName == null) {
+            Log.e(TAG, "Missing creator's pacakge name");
+            return false;
+        } else if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+            Log.e(TAG, "Intent should not be started with FLAG_ACTIVITY_NEW_TASK");
+            return false;
+        } else if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0) {
+            Log.e(TAG, "Intent should not be started with FLAG_ACTIVITY_NEW_DOCUMENT");
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Opens a Browser Actions context menu based on the parsed data.
+     */
+    public void openContextMenu() {
+        return;
+    }
+
+    @Override
+    protected boolean shouldDelayBrowserStartup() {
+        return true;
+    }
+
+    @Override
+    public boolean shouldStartGpuProcess() {
+        return true;
+    }
+
+    /**
+     * Gets custom item list for browser action menu.
+     * @param bundles Data for custom items from {@link BrowserActionsIntent}.
+     */
+    private void parseBrowserActionItems(ArrayList<Bundle> bundles) {
+        for (int i = 0; i < bundles.size(); i++) {
+            Bundle bundle = bundles.get(i);
+            String title = IntentUtils.safeGetString(bundle, BrowserActionsIntent.KEY_TITLE);
+            PendingIntent action =
+                    IntentUtils.safeGetParcelable(bundle, BrowserActionsIntent.KEY_ACTION);
+            Bitmap icon = IntentUtils.safeGetParcelable(bundle, BrowserActionsIntent.KEY_ICON);
+            if (title != null && action != null) {
+                BrowserActionItem item = new BrowserActionItem(title, action);
+                if (icon != null) {
+                    item.setIcon(icon);
+                }
+                mActions.add(item);
+            } else if (title != null) {
+                Log.e(TAG, "Missing action for item: " + i);
+            } else if (action != null) {
+                Log.e(TAG, "Missing title for item: " + i);
+            } else {
+                Log.e(TAG, "Missing title and action for item: " + i);
+            }
+        }
+    }
+
+    /**
+     * Callback when Browser Actions menu dialog is shown.
+     */
+    private void onMenuShown() {
+        beginLoadingLibrary();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
index 0cf871e3..673908f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -40,6 +40,7 @@
 import org.chromium.chrome.browser.sync.SyncController;
 import org.chromium.components.signin.AccountManagerHelper;
 import org.chromium.content.common.ContentSwitches;
+import org.chromium.device.geolocation.LocationProviderFactory;
 import org.chromium.printing.PrintDocumentAdapterWrapper;
 import org.chromium.printing.PrintingControllerImpl;
 import org.chromium.ui.PhotoPickerListener;
@@ -131,6 +132,9 @@
         UniqueIdentificationGeneratorFactory.registerGenerator(SyncController.GENERATOR_ID,
                 new UuidBasedUniqueIdentificationGenerator(
                         application, SESSIONS_UUID_PREF_KEY), false);
+
+        // Indicate that we can use the GMS location provider.
+        LocationProviderFactory.useGmsCoreLocationProvider();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java
index d432ee3f..bda7caa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java
@@ -232,6 +232,12 @@
 
     private void configureSettingsButton(RemoteViews bigView) {
         if (mSettingsAction == null) {
+            bigView.setViewVisibility(R.id.origin_settings_icon, View.GONE);
+            int rightPadding =
+                    dpToPx(BUTTON_ICON_PADDING_DP, mContext.getResources().getDisplayMetrics());
+            int leftPadding =
+                    Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ? rightPadding : 0;
+            bigView.setViewPadding(R.id.origin, leftPadding, 0, rightPadding, 0);
             return;
         }
         bigView.setOnClickPendingIntent(R.id.origin, mSettingsAction.intent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java
index 6035b0cb..2eb51fec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java
@@ -504,16 +504,6 @@
                 NotificationSystemStatusUtil.determineAppNotificationStatus(context),
                 NotificationSystemStatusUtil.APP_NOTIFICATIONS_STATUS_BOUNDARY);
 
-        // Set up a pending intent for going to the settings screen for |origin|.
-        Intent settingsIntent = PreferencesLauncher.createIntentForSettingsPage(
-                context, SingleWebsitePreferences.class.getName());
-        settingsIntent.setData(makeIntentData(notificationId, origin, -1 /* actionIndex */));
-        settingsIntent.putExtra(Preferences.EXTRA_SHOW_FRAGMENT_ARGUMENTS,
-                SingleWebsitePreferences.createFragmentArgsForSite(origin));
-
-        PendingIntent pendingSettingsIntent = PendingIntent.getActivity(context,
-                PENDING_INTENT_REQUEST_CODE, settingsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
         PendingIntent clickIntent = makePendingIntent(context,
                 NotificationConstants.ACTION_CLICK_NOTIFICATION, notificationId, origin, profileId,
                 incognito, tag, webApkPackage, -1 /* actionIndex */);
@@ -555,19 +545,6 @@
             }
         }
 
-        // If action buttons are displayed, there isn't room for the full Site Settings button
-        // label and icon, so abbreviate it. This has the unfortunate side-effect of unnecessarily
-        // abbreviating it on Android Wear also (crbug.com/576656). If custom layouts are enabled,
-        // the label and icon provided here only affect Android Wear, so don't abbreviate them.
-        boolean abbreviateSiteSettings = actions.length > 0 && !useCustomLayouts(hasImage);
-        int settingsIconId = abbreviateSiteSettings ? 0 : R.drawable.settings_cog;
-        CharSequence settingsTitle = abbreviateSiteSettings
-                                     ? res.getString(R.string.notification_site_settings_button)
-                                     : res.getString(R.string.page_info_site_settings_button);
-        // If the settings button is displayed together with the other buttons it has to be the last
-        // one, so add it after the other actions.
-        notificationBuilder.addSettingsAction(settingsIconId, settingsTitle, pendingSettingsIntent);
-
         // The Android framework applies a fallback vibration pattern for the sound when the device
         // is in vibrate mode, there is no custom pattern, and the vibration default has been
         // disabled. To truly prevent vibration, provide a custom empty pattern.
@@ -581,6 +558,31 @@
 
         String platformTag = makePlatformTag(notificationId, origin, tag);
         if (webApkPackage.isEmpty()) {
+            // Set up a pending intent for going to the settings screen for |origin|.
+            Intent settingsIntent = PreferencesLauncher.createIntentForSettingsPage(
+                    context, SingleWebsitePreferences.class.getName());
+            settingsIntent.setData(makeIntentData(notificationId, origin, -1 /* actionIndex */));
+            settingsIntent.putExtra(Preferences.EXTRA_SHOW_FRAGMENT_ARGUMENTS,
+                    SingleWebsitePreferences.createFragmentArgsForSite(origin));
+
+            PendingIntent pendingSettingsIntent = PendingIntent.getActivity(context,
+                    PENDING_INTENT_REQUEST_CODE, settingsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+            // If action buttons are displayed, there isn't room for the full Site Settings button
+            // label and icon, so abbreviate it. This has the unfortunate side-effect of
+            // unnecessarily abbreviating it on Android Wear also (crbug.com/576656). If custom
+            // layouts are enabled, the label and icon provided here only affect Android Wear, so
+            // don't abbreviate them.
+            boolean abbreviateSiteSettings = actions.length > 0 && !useCustomLayouts(hasImage);
+            int settingsIconId = abbreviateSiteSettings ? 0 : R.drawable.settings_cog;
+            CharSequence settingsTitle = abbreviateSiteSettings
+                    ? res.getString(R.string.notification_site_settings_button)
+                    : res.getString(R.string.page_info_site_settings_button);
+            // If the settings button is displayed together with the other buttons it has to be the
+            // last one, so add it after the other actions.
+            notificationBuilder.addSettingsAction(
+                    settingsIconId, settingsTitle, pendingSettingsIntent);
+
             mNotificationManager.notify(platformTag, PLATFORM_ID, notificationBuilder.build());
             NotificationUmaTracker.getInstance().onNotificationShown(
                     NotificationUmaTracker.SITES, ChannelDefinitions.CHANNEL_ID_SITES);
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 6254f67a..1f42cf90 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
@@ -38,7 +38,6 @@
     @Nullable
     final LayoutManagerChrome mLayoutManager;
     final BottomSheet mBottomSheet;
-    final View mFadingBackgroundView;
     final String mTitle;
 
     private boolean mShowOverviewOnClose;
@@ -59,7 +58,6 @@
         mTab = tab;
         mTabModelSelector = tabModelSelector;
         mLayoutManager = layoutManager;
-        mFadingBackgroundView = mTab.getActivity().getFadingBackgroundView();
         mBottomSheet = mTab.getActivity().getBottomSheet();
         mTitle = context.getResources().getString(R.string.button_new_tab);
 
@@ -89,7 +87,7 @@
 
             @Override
             public void onHidden(Tab tab) {
-                mFadingBackgroundView.setEnabled(true);
+                mTab.getActivity().getFadingBackgroundView().setEnabled(true);
                 if (!mTab.isClosing()) mShowOverviewOnClose = false;
             }
 
@@ -142,7 +140,7 @@
     public void destroy() {
         // The next tab will be selected before this one is destroyed. If the currently selected
         // tab is a Chrome Home new tab page, the FadingBackgroundView should not be enabled.
-        mFadingBackgroundView.setEnabled(
+        mTab.getActivity().getFadingBackgroundView().setEnabled(
                 !isTabChromeHomeNewTabPage(mTabModelSelector.getCurrentTab()));
 
         if (mLayoutManager != null) {
@@ -152,7 +150,9 @@
     }
 
     private void onNewTabPageShown() {
-        mFadingBackgroundView.setEnabled(false);
+        if (mTab.getActivity().getFadingBackgroundView() != null) {
+            mTab.getActivity().getFadingBackgroundView().setEnabled(false);
+        }
 
         // This method may be called when an NTP is selected due to the user switching tab models.
         // In this case, we do not want the bottom sheet to open. Unfortunately, without observing
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentApp.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentApp.java
index 1b1bf9a9..78c296f6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentApp.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentApp.java
@@ -56,6 +56,7 @@
     /** The maximum number of milliseconds to wait for a connection to READY_TO_PAY service. */
     private static final long SERVICE_CONNECTION_TIMEOUT_MS = 1000;
 
+    private static final String EXTRA_ID = "id";
     private static final String EXTRA_MERCHANT_NAME = "merchantName";
     private static final String EXTRA_METHOD_NAME = "methodName";
     private static final String EXTRA_METHOD_NAMES = "methodNames";
@@ -146,8 +147,8 @@
             public void onServiceDisconnected(ComponentName name) {}
         };
 
-        mIsReadyToPayIntent.putExtras(buildExtras(
-                null, origin, iframeOrigin, certificateChain, methodDataMap, null, null, null));
+        mIsReadyToPayIntent.putExtras(buildExtras(null, null, origin, iframeOrigin,
+                certificateChain, methodDataMap, null, null, null));
         try {
             if (!ContextUtils.getApplicationContext().bindService(
                         mIsReadyToPayIntent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
@@ -243,7 +244,7 @@
     }
 
     @Override
-    public void invokePaymentApp(final String merchantName, final String origin,
+    public void invokePaymentApp(final String id, final String merchantName, final String origin,
             final String iframeOrigin, final byte[][] certificateChain,
             final Map<String, PaymentMethodData> methodDataMap, final PaymentItem total,
             final List<PaymentItem> displayItems,
@@ -252,8 +253,8 @@
         mInstrumentDetailsCallback = callback;
 
         if (!mIsIncognito) {
-            launchPaymentApp(merchantName, origin, iframeOrigin, certificateChain, methodDataMap,
-                    total, displayItems, modifiers);
+            launchPaymentApp(id, merchantName, origin, iframeOrigin, certificateChain,
+                    methodDataMap, total, displayItems, modifiers);
             return;
         }
 
@@ -270,7 +271,7 @@
                         new OnClickListener() {
                             @Override
                             public void onClick(DialogInterface dialog, int which) {
-                                launchPaymentApp(merchantName, origin, iframeOrigin,
+                                launchPaymentApp(id, merchantName, origin, iframeOrigin,
                                         certificateChain, methodDataMap, total, displayItems,
                                         modifiers);
                             }
@@ -291,10 +292,10 @@
                 .show();
     }
 
-    private void launchPaymentApp(String merchantName, String origin, String iframeOrigin,
-            byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap,
-            PaymentItem total, List<PaymentItem> displayItems,
-            Map<String, PaymentDetailsModifier> modifiers) {
+    private void launchPaymentApp(String id, String merchantName, String origin,
+            String iframeOrigin, byte[][] certificateChain,
+            Map<String, PaymentMethodData> methodDataMap, PaymentItem total,
+            List<PaymentItem> displayItems, Map<String, PaymentDetailsModifier> modifiers) {
         assert mMethodNames.containsAll(methodDataMap.keySet());
         assert mInstrumentDetailsCallback != null;
 
@@ -309,20 +310,21 @@
             return;
         }
 
-        mPayIntent.putExtras(buildExtras(merchantName, origin, iframeOrigin, certificateChain,
+        mPayIntent.putExtras(buildExtras(id, merchantName, origin, iframeOrigin, certificateChain,
                 methodDataMap, total, displayItems, modifiers));
         if (!window.showIntent(mPayIntent, this, R.string.payments_android_app_error)) {
             notifyErrorInvokingPaymentApp();
         }
     }
 
-    private static Bundle buildExtras(@Nullable String merchantName, String origin,
-            String iframeOrigin, @Nullable byte[][] certificateChain,
+    private static Bundle buildExtras(@Nullable String id, @Nullable String merchantName,
+            String origin, String iframeOrigin, @Nullable byte[][] certificateChain,
             Map<String, PaymentMethodData> methodDataMap, @Nullable PaymentItem total,
             @Nullable List<PaymentItem> displayItems,
             @Nullable Map<String, PaymentDetailsModifier> modifiers) {
         Bundle extras = new Bundle();
 
+        if (id != null) extras.putString(EXTRA_ID, id);
         if (merchantName != null) extras.putString(EXTRA_MERCHANT_NAME, merchantName);
         extras.putString(EXTRA_ORIGIN, origin);
         extras.putString(EXTRA_IFRAME_ORIGIN, iframeOrigin);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java
index 8557299c..f4510ea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java
@@ -81,8 +81,8 @@
     }
 
     @Override
-    public void invokePaymentApp(String unusedMerchantName, String unusedOrigin,
-            String unusedIFrameOrigin, byte[][] unusedCertificateChain,
+    public void invokePaymentApp(String unusedRequestId, String unusedMerchantName,
+            String unusedOrigin, String unusedIFrameOrigin, byte[][] unusedCertificateChain,
             Map<String, PaymentMethodData> unusedMethodDataMap, PaymentItem unusedTotal,
             List<PaymentItem> unusedDisplayItems,
             Map<String, PaymentDetailsModifier> unusedModifiers,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java
index 952a98e3..2bb0092 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java
@@ -73,6 +73,7 @@
      *
      * The callback will be invoked with the resulting payment details or error.
      *
+     * @param id               The unique identifier of the PaymentRequest.
      * @param merchantName     The name of the merchant.
      * @param origin           The origin of this merchant.
      * @param iframeOrigin     The origin of the iframe that invoked PaymentRequest.
@@ -86,10 +87,11 @@
      * @param modifiers        The relevant payment details modifiers.
      * @param callback         The object that will receive the instrument details.
      */
-    public abstract void invokePaymentApp(String merchantName, String origin, String iframeOrigin,
-            @Nullable byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap,
-            PaymentItem total, List<PaymentItem> displayItems,
-            Map<String, PaymentDetailsModifier> modifiers, InstrumentDetailsCallback callback);
+    public abstract void invokePaymentApp(String id, String merchantName, String origin,
+            String iframeOrigin, @Nullable byte[][] certificateChain,
+            Map<String, PaymentMethodData> methodDataMap, PaymentItem total,
+            List<PaymentItem> displayItems, Map<String, PaymentDetailsModifier> modifiers,
+            InstrumentDetailsCallback callback);
 
     /**
      * Cleans up any resources held by the payment instrument. For example, closes server
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 954e4c9..8cb5b57 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -300,6 +300,7 @@
      */
     private SectionInformation mUiShippingOptions;
 
+    private String mId;
     private Map<String, PaymentMethodData> mMethodData;
     private boolean mRequestShipping;
     private boolean mRequestPayerName;
@@ -457,6 +458,7 @@
             disconnectFromClientWithDebugMessage("Missing total");
             return;
         }
+        mId = details.id;
 
         PaymentAppFactory.getInstance().create(mWebContents,
                 Collections.unmodifiableSet(mMethodData.keySet()), this /* callback */);
@@ -1225,7 +1227,7 @@
             }
         }
 
-        instrument.invokePaymentApp(mMerchantName, mSchemelessOriginForPaymentApp,
+        instrument.invokePaymentApp(mId, mMerchantName, mSchemelessOriginForPaymentApp,
                 mSchemelessIFrameOriginForPaymentApp, mCertificateChain,
                 Collections.unmodifiableMap(methodData), mRawTotal, mRawLineItems,
                 Collections.unmodifiableMap(modifiers), this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentInstrument.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentInstrument.java
index 0d698bd4..bdd2c2f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentInstrument.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentInstrument.java
@@ -56,7 +56,7 @@
     }
 
     @Override
-    public void invokePaymentApp(String merchantName, String origin, String iframeOrigin,
+    public void invokePaymentApp(String id, String merchantName, String origin, String iframeOrigin,
             byte[][] unusedCertificateChain, Map<String, PaymentMethodData> methodData,
             PaymentItem total, List<PaymentItem> displayItems,
             Map<String, PaymentDetailsModifier> modifiers, InstrumentDetailsCallback callback) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java
index 9d6131f..6875659 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBottomSheetContent.java
@@ -89,7 +89,7 @@
         activity.getBottomSheet().addObserver(new EmptyBottomSheetObserver() {
             @Override
             public void onSheetOpened() {
-                mRecyclerView.scrollTo(0, 0);
+                mRecyclerView.scrollToPosition(0);
 
                 // TODO(https://crbug.com/689962) Ensure this call does not discard all suggestions
                 // every time the sheet is opened.
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 12248dc0..05ed6d9 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
@@ -350,6 +350,8 @@
     private VrShellDelegate(ChromeActivity activity, VrClassesWrapper wrapper) {
         mActivity = activity;
         mVrClassesWrapper = wrapper;
+        // If an activity isn't resumed at the point, it must have been paused.
+        mPaused = ApplicationStatus.getStateForActivity(activity) != ActivityState.RESUMED;
         updateVrSupportLevel();
         mNativeVrShellDelegate = nativeInit();
         Choreographer choreographer = Choreographer.getInstance();
@@ -375,6 +377,8 @@
                 break;
             case ActivityState.PAUSED:
                 if (activity == mActivity) pauseVr();
+                // Other activities should only pause while we're paused due to Android lifecycle.
+                assert mPaused;
                 break;
             case ActivityState.RESUMED:
                 assert !mInVr;
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index ba6966a..fcd4ce20 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -120,6 +120,7 @@
   "java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProvider.java",
   "java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProxy.java",
   "java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetService.java",
+  "java/src/org/chromium/chrome/browser/browseractions/BrowserActionActivity.java",
   "java/src/org/chromium/chrome/browser/browsing_data/UrlFilters.java",
   "java/src/org/chromium/chrome/browser/childaccounts/ChildAccountFeedbackReporter.java",
   "java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java",
@@ -1489,6 +1490,7 @@
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFailCompleteTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFieldTrialTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFreeShippingTest.java",
+  "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteContactDetailsAndFreeShippingTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIncompleteEmailTest.java",
@@ -1635,6 +1637,7 @@
   "junit/src/org/chromium/chrome/browser/DisableHistogramsRule.java",
   "junit/src/org/chromium/chrome/browser/ShortcutHelperTest.java",
   "junit/src/org/chromium/chrome/browser/SSLClientCertificateRequestTest.java",
+  "junit/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerTest.java",
   "junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java
new file mode 100644
index 0000000..fe63e2f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestIdTest.java
@@ -0,0 +1,54 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.payments;
+
+import android.content.DialogInterface;
+import android.support.test.filters.MediumTest;
+
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.autofill.AutofillTestHelper;
+import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A payment integration test for verifying the PaymentResponse contains the
+ * free-form identifier specified in PaymentDetailsInit.
+ */
+public class PaymentRequestIdTest extends PaymentRequestTestBase {
+    public PaymentRequestIdTest() {
+        super("payment_request_id_test.html");
+    }
+
+    @Override
+    public void onMainActivityStarted()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        AutofillTestHelper helper = new AutofillTestHelper();
+        String billingAddressId = helper.setProfile(new AutofillProfile("", "https://example.com",
+                true, "Jon Doe", "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "",
+                "US", "555-555-5555", "", "en-US"));
+        helper.setCreditCard(new CreditCard("", "https://example.com", true, true, "Jon Doe",
+                "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
+                billingAddressId, "" /* serverId */));
+    }
+
+    /**
+     * Submit the payment information without shipping address or shipping options to the merchant
+     * when the user clicks "Pay."
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testPaymentResponse()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickAndWait(R.id.button_primary, mReadyForUnmaskInput);
+        setTextInCardUnmaskDialogAndWait(R.id.card_unmask_input, "123", mReadyToUnmask);
+        clickCardUnmaskButtonAndWait(DialogInterface.BUTTON_POSITIVE, mDismissed);
+        expectResultContains(new String[] {"my_payment_id"});
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
index c0100504..02bae87 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
@@ -1013,10 +1013,10 @@
         }
 
         @Override
-        public void invokePaymentApp(String merchantName, String origin, String iframeOrigin,
-                byte[][] certificateChain, Map<String, PaymentMethodData> methodData,
-                PaymentItem total, List<PaymentItem> displayItems,
-                Map<String, PaymentDetailsModifier> modifiers,
+        public void invokePaymentApp(String id, String merchantName, String origin,
+                String iframeOrigin, byte[][] certificateChain,
+                Map<String, PaymentMethodData> methodData, PaymentItem total,
+                List<PaymentItem> displayItems, Map<String, PaymentDetailsModifier> modifiers,
                 InstrumentDetailsCallback detailsCallback) {
             detailsCallback.onInstrumentDetailsReady(
                     mMethodName, "{\"transaction\": 1337}");
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java
new file mode 100644
index 0000000..6d71290
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browseractions/BrowserActionActivityTest.java
@@ -0,0 +1,103 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.browseractions;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.customtabs.browseractions.BrowserActionsIntent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.util.Feature;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+/**
+ * Unit tests for BrowserActionActivity.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class BrowserActionActivityTest {
+    private static final String HTTP_SCHEME_TEST_URL = "http://www.example.com";
+    private static final String HTTPS_SCHEME_TEST_URL = "https://www.example.com";
+    private static final String CHROME_SCHEME_TEST_URL = "chrome://example";
+    private static final String CONTENT_SCHEME_TEST_URL = "content://example";
+
+    private BrowserActionActivity mActivity = new BrowserActionActivity();
+    private Context mContext;
+
+    @Mock
+    private PendingIntent mPendingIntent;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        Answer<String> answer = new Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocation) {
+                return "some.other.app.package.name";
+            }
+        };
+        doAnswer(answer).when(mPendingIntent).getCreatorPackage();
+    }
+
+    @Test
+    @Feature({"BrowserActions"})
+    public void testStartedUpCorrectly() {
+        assertFalse(mActivity.isStartedUpCorrectly(null));
+        assertFalse(mActivity.isStartedUpCorrectly(new Intent()));
+
+        Intent mIntent = createBaseBrowserActionsIntent(HTTP_SCHEME_TEST_URL);
+        assertTrue(mActivity.isStartedUpCorrectly(mIntent));
+
+        mIntent = createBaseBrowserActionsIntent(HTTP_SCHEME_TEST_URL);
+        mIntent.removeExtra(BrowserActionsIntent.EXTRA_APP_ID);
+        assertFalse(mActivity.isStartedUpCorrectly(mIntent));
+
+        mIntent = createBaseBrowserActionsIntent(HTTPS_SCHEME_TEST_URL);
+        assertTrue(mActivity.isStartedUpCorrectly(mIntent));
+
+        mIntent = createBaseBrowserActionsIntent(CHROME_SCHEME_TEST_URL);
+        assertFalse(mActivity.isStartedUpCorrectly(mIntent));
+
+        mIntent = createBaseBrowserActionsIntent(CONTENT_SCHEME_TEST_URL);
+        assertFalse(mActivity.isStartedUpCorrectly(mIntent));
+
+        mIntent = createBaseBrowserActionsIntent(HTTP_SCHEME_TEST_URL);
+        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        assertFalse(mActivity.isStartedUpCorrectly(mIntent));
+
+        mIntent = createBaseBrowserActionsIntent(HTTP_SCHEME_TEST_URL);
+        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        assertFalse(mActivity.isStartedUpCorrectly(mIntent));
+    }
+
+    /**
+     * Creates a simple Intent for Browser Actions which contains a url, the {@link
+     * BrowserActionsIntent.ACTION_BROWSER_ACTIONS_OPEN} action and source package name.
+     * @param url The url for the data of the Intent.
+     * @return The simple Intent for Browser Actions.
+     */
+    private Intent createBaseBrowserActionsIntent(String url) {
+        return new BrowserActionsIntent.Builder(mContext, Uri.parse(url))
+                .build()
+                .getIntent()
+                .putExtra(BrowserActionsIntent.EXTRA_APP_ID, mPendingIntent);
+    }
+}
diff --git a/chrome/android/webapk/shell_apk/AndroidManifest.xml b/chrome/android/webapk/shell_apk/AndroidManifest.xml
index 5854c1e..a9dc831a 100644
--- a/chrome/android/webapk/shell_apk/AndroidManifest.xml
+++ b/chrome/android/webapk/shell_apk/AndroidManifest.xml
@@ -31,12 +31,6 @@
                 <data android:scheme="{{{scope_url_scheme}}}" android:host="{{{scope_url_host}}}" android:pathPrefix="{{{scope_url_path}}}"></data>
             </intent-filter>
         </activity>
-        <activity android:name="org.chromium.webapk.shell_apk.NotificationSettingsLauncherActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.NOTIFICATION_PREFERENCES"/>
-            </intent-filter>
-        </activity>
         <meta-data android:name="org.chromium.webapk.shell_apk.shellApkVersion" android:value="{{{shell_apk_version}}}" />
         <meta-data android:name="org.chromium.webapk.shell_apk.runtimeHost" android:value="{{{runtime_host}}}" />
         <meta-data android:name="org.chromium.webapk.shell_apk.startUrl" android:value="{{{start_url}}}" />
diff --git a/chrome/android/webapk/shell_apk/BUILD.gn b/chrome/android/webapk/shell_apk/BUILD.gn
index 5958254b..2a9adc89 100644
--- a/chrome/android/webapk/shell_apk/BUILD.gn
+++ b/chrome/android/webapk/shell_apk/BUILD.gn
@@ -112,7 +112,6 @@
     "src/org/chromium/webapk/shell_apk/DexLoader.java",
     "src/org/chromium/webapk/shell_apk/HostBrowserClassLoader.java",
     "src/org/chromium/webapk/shell_apk/MainActivity.java",
-    "src/org/chromium/webapk/shell_apk/NotificationSettingsLauncherActivity.java",
     "src/org/chromium/webapk/shell_apk/WebApkSandboxedProcessService.java",
     "src/org/chromium/webapk/shell_apk/WebApkSandboxedProcessService0.java",
     "src/org/chromium/webapk/shell_apk/WebApkSandboxedProcessService1.java",
diff --git a/chrome/android/webapk/shell_apk/shell_apk_version.gni b/chrome/android/webapk/shell_apk/shell_apk_version.gni
index 718adb4..c4d66ff 100644
--- a/chrome/android/webapk/shell_apk/shell_apk_version.gni
+++ b/chrome/android/webapk/shell_apk/shell_apk_version.gni
@@ -6,7 +6,7 @@
 # (including AndroidManifest.xml) is updated. This version should be incremented
 # prior to uploading a new ShellAPK to the WebAPK Minting Server.
 # Does not affect Chrome.apk
-template_shell_apk_version = 3
+template_shell_apk_version = 4
 
 # The ShellAPK version expected by Chrome. Chrome will try to update the WebAPK
 # if the WebAPK's ShellAPK version is less than |expected_shell_apk_version|.
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/NotificationSettingsLauncherActivity.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/NotificationSettingsLauncherActivity.java
deleted file mode 100644
index fd405f2..0000000
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/NotificationSettingsLauncherActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.webapk.shell_apk;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * Forwards NOTIFICATION_PREFERENCES intent to host browser.
- */
-public class NotificationSettingsLauncherActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstance) {
-        super.onCreate(savedInstance);
-
-        String hostPackage = WebApkUtils.getHostBrowserPackageName(this);
-        Intent intent = getIntent();
-        intent.setPackage(hostPackage);
-        intent.setComponent(null);
-        startActivity(intent);
-        finish();
-    }
-}
diff --git a/chrome/browser/android/offline_pages/offline_page_request_job.cc b/chrome/browser/android/offline_pages/offline_page_request_job.cc
index 40edfd4f..570e255 100644
--- a/chrome/browser/android/offline_pages/offline_page_request_job.cc
+++ b/chrome/browser/android/offline_pages/offline_page_request_job.cc
@@ -639,13 +639,6 @@
   return false;
 }
 
-int OfflinePageRequestJob::GetResponseCode() const {
-  if (!fake_headers_for_redirect_)
-    return URLRequestFileJob::GetResponseCode();
-
-  return net::URLRequestRedirectJob::REDIRECT_302_FOUND;
-}
-
 void OfflinePageRequestJob::OnOpenComplete(int result) {
   UMA_HISTOGRAM_SPARSE_SLOWLY("OfflinePages.RequestJob.OpenFileErrorCode",
                               -result);
diff --git a/chrome/browser/android/offline_pages/offline_page_request_job.h b/chrome/browser/android/offline_pages/offline_page_request_job.h
index 15aa31c4..500af977 100644
--- a/chrome/browser/android/offline_pages/offline_page_request_job.h
+++ b/chrome/browser/android/offline_pages/offline_page_request_job.h
@@ -91,7 +91,6 @@
   void GetResponseInfo(net::HttpResponseInfo* info) override;
   void GetLoadTimingInfo(net::LoadTimingInfo* load_timing_info) const override;
   bool CopyFragmentOnRedirect(const GURL& location) const override;
-  int GetResponseCode() const override;
 
   // net::URLRequestFileJob overrides:
   void OnOpenComplete(int result) override;
diff --git a/chrome/browser/android/vr_shell/ui_elements/ui_element.h b/chrome/browser/android/vr_shell/ui_elements/ui_element.h
index 5d1d6eea..e46ebf2 100644
--- a/chrome/browser/android/vr_shell/ui_elements/ui_element.h
+++ b/chrome/browser/android/vr_shell/ui_elements/ui_element.h
@@ -113,9 +113,6 @@
   // Valid IDs are non-negative.
   int id = -1;
 
-  // Name string for debugging and testing purposes.
-  std::string name;
-
   // If a non-negative parent ID is specified, applicable transformations
   // are applied relative to the parent, rather than absolutely.
   int parent_id = -1;
diff --git a/chrome/browser/android/vr_shell/ui_scene.cc b/chrome/browser/android/vr_shell/ui_scene.cc
index 6744fe6..82984bb 100644
--- a/chrome/browser/android/vr_shell/ui_scene.cc
+++ b/chrome/browser/android/vr_shell/ui_scene.cc
@@ -146,10 +146,18 @@
   return !GetHeadLockedElements().empty();
 }
 
+void UiScene::SetBackgroundColor(const vr::Colorf& color) {
+  background_color_ = color;
+}
+
 const vr::Colorf& UiScene::GetBackgroundColor() const {
   return background_color_;
 }
 
+void UiScene::SetBackgroundDistance(float distance) {
+  background_distance_ = distance;
+}
+
 float UiScene::GetBackgroundDistance() const {
   return background_distance_;
 }
diff --git a/chrome/browser/android/vr_shell/ui_scene.h b/chrome/browser/android/vr_shell/ui_scene.h
index a70a3c0..56077e05 100644
--- a/chrome/browser/android/vr_shell/ui_scene.h
+++ b/chrome/browser/android/vr_shell/ui_scene.h
@@ -62,8 +62,11 @@
   std::vector<const UiElement*> GetHeadLockedElements() const;
   bool HasVisibleHeadLockedElements() const;
 
+  void SetBackgroundColor(const vr::Colorf& color);
   const vr::Colorf& GetBackgroundColor() const;
+  void SetBackgroundDistance(float distance);
   float GetBackgroundDistance() const;
+
   bool GetWebVrRenderingEnabled() const;
 
   void OnGLInitialized();
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager.cc b/chrome/browser/android/vr_shell/ui_scene_manager.cc
index 29434a3..4cbeae4 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager.cc
+++ b/chrome/browser/android/vr_shell/ui_scene_manager.cc
@@ -19,16 +19,26 @@
 static constexpr int kWarningTimeoutSeconds = 30;
 static constexpr float kWarningDistance = 0.7;
 static constexpr float kWarningAngleRadians = 16.3 * M_PI / 180.0;
-
 static constexpr float kPermanentWarningHeight = 0.226;
 static constexpr float kPermanentWarningWidth = 0.078;
 static constexpr float kTransientWarningHeight = 0.512;
 static constexpr float kTransientWarningWidth = 0.160;
 
-static constexpr float kContentHeight = 2.4;
-static constexpr float kContentWidth = 1.6;
+static constexpr float kContentWidth = 2.4;
+static constexpr float kContentHeight = 1.6;
 static constexpr float kContentDistance = 2.5;
 static constexpr float kContentVerticalOffset = -0.26;
+static constexpr float kBackplaneSize = 1000.0;
+static constexpr float kBackgroundDistanceMultiplier = 1.414;
+
+static constexpr float kSceneSize = 25.0;
+static constexpr float kSceneHeight = 4.0;
+static constexpr int kFloorGridlineCount = 40;
+static constexpr vr::Colorf kBackgroundHorizonColor = {0.57, 0.57, 0.57, 1.0};
+static constexpr vr::Colorf kBackgroundCenterColor = {0.48, 0.48, 0.48, 1.0};
+
+// Tiny distance to offset textures that should appear in the same plane.
+static constexpr float kTextureOffset = 0.01;
 
 }  // namespace
 
@@ -36,15 +46,20 @@
     : scene_(scene), weak_ptr_factory_(this) {
   std::unique_ptr<UiElement> element;
 
-  // For now, use an ID range that does not conflict with the HTML UI.
-  int id = 1000;
+  CreateBackground();
+  CreateContentQuad();
+  CreateSecurityWarnings();
+}
 
-  // Permanent WebVR security warning.
+UiSceneManager::~UiSceneManager() {}
+
+void UiSceneManager::CreateSecurityWarnings() {
+  std::unique_ptr<UiElement> element;
+
   // TODO(mthiesse): Programatically compute the proper texture size for these
   // textured UI elements.
   element = base::MakeUnique<PermanentSecurityWarning>(512);
-  element->id = id++;
-  element->name = "Permanent security warning";
+  element->id = AllocateId();
   element->fill = vr_shell::Fill::NONE;
   element->size = {kPermanentWarningWidth, kPermanentWarningHeight, 1};
   element->scale = {kWarningDistance, kWarningDistance, 1};
@@ -57,10 +72,8 @@
   permanent_security_warning_ = element.get();
   scene_->AddUiElement(std::move(element));
 
-  // Transient WebVR security warning.
   element = base::MakeUnique<TransientSecurityWarning>(1024);
-  element->id = id++;
-  element->name = "Transient security warning";
+  element->id = AllocateId();
   element->fill = vr_shell::Fill::NONE;
   element->size = {kTransientWarningWidth, kTransientWarningHeight, 1};
   element->scale = {kWarningDistance, kWarningDistance, 1};
@@ -70,20 +83,86 @@
   element->lock_to_fov = true;
   transient_security_warning_ = element.get();
   scene_->AddUiElement(std::move(element));
+}
 
-  // Main web content quad.
+void UiSceneManager::CreateContentQuad() {
+  std::unique_ptr<UiElement> element;
+
   element = base::MakeUnique<UiElement>();
-  element->id = id++;
-  element->name = "Content";
+  element->id = AllocateId();
   element->fill = vr_shell::Fill::CONTENT;
   element->size = {kContentWidth, kContentHeight, 1};
   element->translation = {0, kContentVerticalOffset, -kContentDistance};
   element->visible = false;
   main_content_ = element.get();
+  browser_ui_elements_.push_back(element.get());
   scene_->AddUiElement(std::move(element));
+
+  // Place an invisible but hittable plane behind the content quad, to keep the
+  // reticle roughly planar with the content if near content.
+  element = base::MakeUnique<UiElement>();
+  element->id = AllocateId();
+  element->fill = vr_shell::Fill::NONE;
+  element->size = {kBackplaneSize, kBackplaneSize, 1.0};
+  element->translation = {0.0, 0.0, -kTextureOffset};
+  element->parent_id = main_content_->id;
+  browser_ui_elements_.push_back(element.get());
+  scene_->AddUiElement(std::move(element));
+
+  // Limit reticle distance to a sphere based on content distance.
+  scene_->SetBackgroundDistance(main_content_->translation.z() *
+                                -kBackgroundDistanceMultiplier);
 }
 
-UiSceneManager::~UiSceneManager() {}
+void UiSceneManager::CreateBackground() {
+  std::unique_ptr<UiElement> element;
+
+  // Floor.
+  element = base::MakeUnique<UiElement>();
+  element->id = AllocateId();
+  element->size = {kSceneSize, kSceneSize, 1.0};
+  element->translation = {0.0, -kSceneHeight / 2, 0.0};
+  element->rotation = {1.0, 0.0, 0.0, -M_PI / 2.0};
+  element->fill = vr_shell::Fill::OPAQUE_GRADIENT;
+  element->edge_color = kBackgroundHorizonColor;
+  element->center_color = kBackgroundCenterColor;
+  element->draw_phase = 0;
+  browser_ui_elements_.push_back(element.get());
+  scene_->AddUiElement(std::move(element));
+
+  // Ceiling.
+  element = base::MakeUnique<UiElement>();
+  element->id = AllocateId();
+  element->fill = vr_shell::Fill::OPAQUE_GRADIENT;
+  element->size = {kSceneSize, kSceneSize, 1.0};
+  element->translation = {0.0, kSceneHeight / 2, 0.0};
+  element->rotation = {1.0, 0.0, 0.0, M_PI / 2};
+  element->fill = vr_shell::Fill::OPAQUE_GRADIENT;
+  element->edge_color = kBackgroundHorizonColor;
+  element->center_color = kBackgroundCenterColor;
+  element->draw_phase = 0;
+  browser_ui_elements_.push_back(element.get());
+  scene_->AddUiElement(std::move(element));
+
+  // Floor grid.
+  element = base::MakeUnique<UiElement>();
+  element->id = AllocateId();
+  element->fill = vr_shell::Fill::GRID_GRADIENT;
+  element->size = {kSceneSize, kSceneSize, 1.0};
+  element->translation = {0.0, -kSceneHeight / 2 + kTextureOffset, 0.0};
+  element->rotation = {1.0, 0.0, 0.0, -M_PI / 2};
+  element->fill = vr_shell::Fill::GRID_GRADIENT;
+  element->center_color = kBackgroundHorizonColor;
+  vr::Colorf edge_color = kBackgroundHorizonColor;
+  edge_color.a = 0.0;
+  element->edge_color = edge_color;
+  element->gridline_count = kFloorGridlineCount;
+  element->draw_phase = 0;
+  browser_ui_elements_.push_back(element.get());
+  scene_->AddUiElement(std::move(element));
+
+  scene_->SetBackgroundColor(kBackgroundHorizonColor);
+}
 
 base::WeakPtr<UiSceneManager> UiSceneManager::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
@@ -91,7 +170,12 @@
 
 void UiSceneManager::SetWebVRMode(bool web_vr) {
   web_vr_mode_ = web_vr;
-  main_content_->visible = !web_vr_mode_;
+
+  // Make all VR scene UI elements visible if not in WebVR.
+  for (UiElement* element : browser_ui_elements_) {
+    element->visible = !web_vr_mode_;
+  }
+
   ConfigureSecurityWarnings();
 }
 
@@ -117,4 +201,8 @@
   transient_security_warning_->visible = false;
 }
 
+int UiSceneManager::AllocateId() {
+  return next_available_id_++;
+}
+
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager.h b/chrome/browser/android/vr_shell/ui_scene_manager.h
index 62f2f98..72a8ab3 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager.h
+++ b/chrome/browser/android/vr_shell/ui_scene_manager.h
@@ -26,9 +26,13 @@
   void SetWebVRMode(bool web_vr);
 
  private:
+  void CreateSecurityWarnings();
+  void CreateContentQuad();
+  void CreateBackground();
+
   void ConfigureSecurityWarnings();
   void OnSecurityWarningTimer();
-  void OnGLInitialized();
+  int AllocateId();
 
   UiScene* scene_;
 
@@ -40,6 +44,10 @@
   bool web_vr_mode_ = false;
   bool secure_origin_ = false;
 
+  int next_available_id_ = 1;
+
+  std::vector<UiElement*> browser_ui_elements_;
+
   base::OneShotTimer security_warning_timer_;
 
   base::WeakPtrFactory<UiSceneManager> weak_ptr_factory_;
diff --git a/chrome/browser/autofill/autofill_save_card_infobar_delegate_mobile_unittest.cc b/chrome/browser/autofill/autofill_save_card_infobar_delegate_mobile_unittest.cc
index 7a560a276..b9281da 100644
--- a/chrome/browser/autofill/autofill_save_card_infobar_delegate_mobile_unittest.cc
+++ b/chrome/browser/autofill/autofill_save_card_infobar_delegate_mobile_unittest.cc
@@ -14,7 +14,9 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
 #include "components/infobars/core/confirm_infobar_delegate.h"
+#include "components/prefs/pref_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -54,7 +56,10 @@
   void TearDown() override;
 
  protected:
-  std::unique_ptr<ConfirmInfoBarDelegate> CreateDelegate(bool is_uploading);
+  std::unique_ptr<ConfirmInfoBarDelegate> CreateDelegate(
+      bool is_uploading,
+      prefs::PreviousSaveCreditCardPromptUserDecision
+          previous_save_credit_card_prompt_user_decision);
 
   std::unique_ptr<TestPersonalDataManager> personal_data_;
 
@@ -80,6 +85,10 @@
   personal_data_.reset(new TestPersonalDataManager());
   personal_data_->set_database(autofill_client->GetDatabase());
   personal_data_->SetPrefService(profile()->GetPrefs());
+
+  profile()->GetPrefs()->SetInteger(
+      prefs::kAutofillAcceptSaveCreditCardPromptState,
+      prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE);
 }
 
 void AutofillSaveCardInfoBarDelegateMobileTest::TearDown() {
@@ -88,7 +97,10 @@
 }
 
 std::unique_ptr<ConfirmInfoBarDelegate>
-AutofillSaveCardInfoBarDelegateMobileTest::CreateDelegate(bool is_uploading) {
+AutofillSaveCardInfoBarDelegateMobileTest::CreateDelegate(
+    bool is_uploading,
+    prefs::PreviousSaveCreditCardPromptUserDecision
+        previous_save_credit_card_prompt_user_decision) {
   base::HistogramTester histogram_tester;
   CreditCard credit_card;
   std::unique_ptr<base::DictionaryValue> legal_message;
@@ -97,11 +109,25 @@
           is_uploading, credit_card, std::move(legal_message),
           base::Bind(base::IgnoreResult(
                          &TestPersonalDataManager::SaveImportedCreditCard),
-                     base::Unretained(personal_data_.get()), credit_card)));
+                     base::Unretained(personal_data_.get()), credit_card),
+          profile()->GetPrefs()));
   std::string destination = is_uploading ? ".Server" : ".Local";
-  histogram_tester.ExpectUniqueSample("Autofill.CreditCardInfoBar"
-                                          + destination,
-                                      AutofillMetrics::INFOBAR_SHOWN, 1);
+  std::string previous_response;
+  switch (previous_save_credit_card_prompt_user_decision) {
+    case prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED:
+      previous_response = ".PreviouslyAccepted";
+      break;
+    case prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED:
+      previous_response = ".PreviouslyDenied";
+      break;
+    default:
+      EXPECT_EQ(previous_save_credit_card_prompt_user_decision,
+                prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE);
+      break;
+  }
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.CreditCardInfoBar" + destination + previous_response,
+      AutofillMetrics::INFOBAR_SHOWN, 1);
   return delegate;
 }
 
@@ -111,8 +137,9 @@
 
   // Accept the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ false));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ false,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE));
     EXPECT_CALL(*personal_data_, SaveImportedCreditCard(_));
 
     base::HistogramTester histogram_tester;
@@ -123,35 +150,41 @@
 
   // Cancel the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ false));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ false,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED));
 
     base::HistogramTester histogram_tester;
     EXPECT_TRUE(infobar->Cancel());
-    histogram_tester.ExpectUniqueSample("Autofill.CreditCardInfoBar.Local",
-                                        AutofillMetrics::INFOBAR_DENIED, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.CreditCardInfoBar.Local.PreviouslyAccepted",
+        AutofillMetrics::INFOBAR_DENIED, 1);
   }
 
   // Dismiss the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ false));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ false,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED));
 
     base::HistogramTester histogram_tester;
     infobar->InfoBarDismissed();
-    histogram_tester.ExpectUniqueSample("Autofill.CreditCardInfoBar.Local",
-                                        AutofillMetrics::INFOBAR_DENIED, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.CreditCardInfoBar.Local.PreviouslyDenied",
+        AutofillMetrics::INFOBAR_DENIED, 1);
   }
 
   // Ignore the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ false));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ false,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED));
 
     base::HistogramTester histogram_tester;
     infobar.reset();
-    histogram_tester.ExpectUniqueSample("Autofill.CreditCardInfoBar.Local",
-                                        AutofillMetrics::INFOBAR_IGNORED, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.CreditCardInfoBar.Local.PreviouslyDenied",
+        AutofillMetrics::INFOBAR_IGNORED, 1);
   }
 }
 
@@ -161,8 +194,9 @@
 
   // Accept the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ true));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ true,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE));
     EXPECT_CALL(*personal_data_, SaveImportedCreditCard(_));
 
     base::HistogramTester histogram_tester;
@@ -173,35 +207,41 @@
 
   // Cancel the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ true));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ true,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED));
 
     base::HistogramTester histogram_tester;
     EXPECT_TRUE(infobar->Cancel());
-    histogram_tester.ExpectUniqueSample("Autofill.CreditCardInfoBar.Server",
-                                        AutofillMetrics::INFOBAR_DENIED, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.CreditCardInfoBar.Server.PreviouslyAccepted",
+        AutofillMetrics::INFOBAR_DENIED, 1);
   }
 
   // Dismiss the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ true));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ true,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED));
 
     base::HistogramTester histogram_tester;
     infobar->InfoBarDismissed();
-    histogram_tester.ExpectUniqueSample("Autofill.CreditCardInfoBar.Server",
-                                        AutofillMetrics::INFOBAR_DENIED, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.CreditCardInfoBar.Server.PreviouslyDenied",
+        AutofillMetrics::INFOBAR_DENIED, 1);
   }
 
   // Ignore the infobar.
   {
-    std::unique_ptr<ConfirmInfoBarDelegate> infobar(
-        CreateDelegate(/* is_uploading= */ true));
+    std::unique_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(
+        /* is_uploading= */ true,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED));
 
     base::HistogramTester histogram_tester;
     infobar.reset();
-    histogram_tester.ExpectUniqueSample("Autofill.CreditCardInfoBar.Server",
-                                        AutofillMetrics::INFOBAR_IGNORED, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.CreditCardInfoBar.Server.PreviouslyDenied",
+        AutofillMetrics::INFOBAR_IGNORED, 1);
   }
 }
 
diff --git a/chrome/browser/chromeos/arc/arc_session_manager.cc b/chrome/browser/chromeos/arc/arc_session_manager.cc
index 6a46751a..f3ab8b7a2 100644
--- a/chrome/browser/chromeos/arc/arc_session_manager.cc
+++ b/chrome/browser/chromeos/arc/arc_session_manager.cc
@@ -1004,7 +1004,7 @@
 
 void ArcSessionManager::OnSendFeedbackClicked() {
   DCHECK(support_host_);
-  chrome::OpenFeedbackDialog(nullptr);
+  chrome::OpenFeedbackDialog(nullptr, chrome::kFeedbackSourceArcApp);
 }
 
 void ArcSessionManager::SetArcSessionRunnerForTesting(
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index 00c1c9e..fbe85e25 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -300,6 +300,19 @@
     content::RunAllPendingInMessageLoop();
   }
 
+  void SetupTether() {
+    chromeos::NetworkStateHandler* network_state_handler =
+        chromeos::NetworkHandler::Get()->network_state_handler();
+    network_state_handler->SetTetherTechnologyState(
+        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+    network_state_handler->AddTetherNetworkState(
+        "tetherGuid1", "tetherName1", "tetherCarrier1",
+        50 /* battery_percentage */, 75 /* signal_strength */);
+    network_state_handler->AddTetherNetworkState(
+        "tetherGuid2", "tetherName2", "tetherCarrier2",
+        75 /* battery_percentage */, 100 /* signal_strength */);
+  }
+
   void AddService(const std::string& service_path,
                   const std::string& name,
                   const std::string& type,
@@ -851,6 +864,31 @@
   EXPECT_TRUE(RunNetworkingSubtest("getGlobalPolicy")) << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest,
+                       Tether_GetTetherNetworks) {
+  SetupTether();
+  EXPECT_TRUE(RunNetworkingSubtest("getTetherNetworks")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest,
+                       Tether_GetTetherNetworkProperties) {
+  SetupTether();
+  EXPECT_TRUE(RunNetworkingSubtest("getTetherNetworkProperties")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest,
+                       Tether_GetTetherNetworkManagedProperties) {
+  SetupTether();
+  EXPECT_TRUE(RunNetworkingSubtest("getTetherNetworkManagedProperties"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(NetworkingPrivateChromeOSApiTest,
+                       Tether_GetTetherNetworkState) {
+  SetupTether();
+  EXPECT_TRUE(RunNetworkingSubtest("getTetherNetworkState")) << message_;
+}
+
 // Tests subset of networking API for the networking API alias - to verify that
 // using API methods and event does not cause access exceptions (due to
 // missing permissions).
diff --git a/chrome/browser/feedback/show_feedback_page.cc b/chrome/browser/feedback/show_feedback_page.cc
index d3e0cd8..d3091ba 100644
--- a/chrome/browser/feedback/show_feedback_page.cc
+++ b/chrome/browser/feedback/show_feedback_page.cc
@@ -4,10 +4,12 @@
 
 #include <string>
 
+#include "base/metrics/histogram_macros.h"
 #include "chrome/browser/extensions/api/feedback_private/feedback_private_api.h"
 #include "chrome/browser/feedback/feedback_dialog_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/webui/md_feedback/md_feedback_dialog_controller.h"
 #include "chrome/common/chrome_switches.h"
@@ -15,6 +17,7 @@
 namespace chrome {
 
 void ShowFeedbackPage(Browser* browser,
+                      FeedbackSource source,
                       const std::string& description_template,
                       const std::string& category_tag) {
   GURL page_url;
@@ -29,6 +32,10 @@
     return;
   }
 
+  // Record an UMA histogram to know the most frequent feedback request source.
+  UMA_HISTOGRAM_ENUMERATION("Feedback.RequestSource", source,
+                            kFeedbackSourceCount);
+
   if (::switches::MdFeedbackEnabled()) {
     MdFeedbackDialogController::GetInstance()->Show(profile);
     return;
diff --git a/chrome/browser/resources/settings/device_page/display.html b/chrome/browser/resources/settings/device_page/display.html
index a3d4928..b0dea45 100644
--- a/chrome/browser/resources/settings/device_page/display.html
+++ b/chrome/browser/resources/settings/device_page/display.html
@@ -40,10 +40,6 @@
        margin: 10px 0;
      }
 
-     .title-text {
-       margin-top: 10px;
-     }
-
      .settings-box > paper-button:first-child {
        -webkit-padding-start: 0
      }
@@ -57,9 +53,9 @@
      } 
     </style>
     <div class="settings-box first layout vertical self-stretch">
-      <div class="title-text layout self-start">
+      <h2 class="layout self-start">
         $i18n{displayArrangementTitle}
-      </div>
+      </h2>
       <div class="secondary layout self-start"
           hidden="[[!hasMultipleDisplays_(displays)]]">
         $i18n{displayArrangementText}
diff --git a/chrome/browser/resources/settings/device_page/pointers.html b/chrome/browser/resources/settings/device_page/pointers.html
index ef0c121..97743b2 100644
--- a/chrome/browser/resources/settings/device_page/pointers.html
+++ b/chrome/browser/resources/settings/device_page/pointers.html
@@ -53,12 +53,14 @@
       <!-- Subsection title only appears if both mouse and touchpad exist. -->
       <h2 hidden$="[[!hasMouse]]">$i18n{touchpadTitle}</h2>
       <div class$="[[getSubsectionClass_(hasMouse, hasTouchpad)]]">
-        <div class="settings-box block first">
-          <settings-toggle-button id="enableTapToClick"
+        <div class="settings-box first">
+          <settings-toggle-button id="enableTapToClick" class="start"
               pref="{{prefs.settings.touchpad.enable_tap_to_click}}"
               label="$i18n{touchpadTapToClickEnabledLabel}">
           </settings-toggle-button>
-          <settings-toggle-button id="enableTapDragging"
+        </div>
+        <div class="settings-box">
+          <settings-toggle-button id="enableTapDragging" class="start"
               pref="{{prefs.settings.touchpad.enable_tap_dragging}}"
               label="$i18n{tapDraggingLabel}">
           </settings-toggle-button>
diff --git a/chrome/browser/resources/settings/settings_page/settings_subpage_search.html b/chrome/browser/resources/settings/settings_page/settings_subpage_search.html
index c3aacac..7b88464a 100644
--- a/chrome/browser/resources/settings/settings_page/settings_subpage_search.html
+++ b/chrome/browser/resources/settings/settings_page/settings_subpage_search.html
@@ -1,4 +1,5 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field_behavior.html">
+<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="chrome://resources/html/polymer.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
@@ -7,7 +8,7 @@
 
 <dom-module id="settings-subpage-search">
   <template>
-    <style>
+    <style include="cr-hidden-style">
       :host {
         --subpage-search-underline: {
           margin-bottom: -2px;
diff --git a/chrome/browser/resources/settings/site_settings/site_list.html b/chrome/browser/resources/settings/site_settings/site_list.html
index 0564325..d962b62f 100644
--- a/chrome/browser/resources/settings/site_settings/site_list.html
+++ b/chrome/browser/resources/settings/site_settings/site_list.html
@@ -3,6 +3,7 @@
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
 <link rel="import" href="../i18n_setup.html">
@@ -17,7 +18,7 @@
 
 <dom-module id="site-list">
   <template>
-    <style include="settings-shared">
+    <style include="settings-shared iron-flex">
       .selectable {
         -webkit-user-select: text;
       }
diff --git a/chrome/browser/safe_browsing/srt_prompt_controller.cc b/chrome/browser/safe_browsing/srt_prompt_controller.cc
index 75bdfa0..02727a78 100644
--- a/chrome/browser/safe_browsing/srt_prompt_controller.cc
+++ b/chrome/browser/safe_browsing/srt_prompt_controller.cc
@@ -4,9 +4,6 @@
 
 #include "chrome/browser/safe_browsing/srt_prompt_controller.h"
 
-#include <initializer_list>
-
-#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 
 namespace safe_browsing {
@@ -15,49 +12,16 @@
 
 // Some dummy strings to be displayed in the Cleaner dialog while iterating on
 // the dialog's UX design and work on the Chrome<->Cleaner IPC is ongoing.
-constexpr char kMainTextIntroduction[] =
-    "Have you seen unusual startup pages or strange search results? Chrome has "
-    "detected the following kinds of programs on your computer that may be "
-    "causing the problem:";
-constexpr char kMainTextActionExplanation[] =
-    "Chrome can remove the detected programs, which should stop the strange "
-    "behavior.";
-constexpr char kUnwantedSoftwareCategory1[] = "1 Browser hijacker";
-constexpr char kUnwantedSoftwareCategory2[] = "2 Ad Injectors";
-
-constexpr char kDetailsSectionSettingsResetExplanation[] =
-    "Chrome will reset the following settings:";
-constexpr char kDetailsSectionSetting1[] = "Default search engine";
-constexpr char kDetailsSectionSetting2[] = "Startup pages";
-constexpr char kDetailsSectionSetting3[] = "Homepage";
-constexpr char kDetailsSectionSetting4[] = "Shortcuts";
-constexpr char kDetailsSectionSetting5[] =
-    "All extensions (these can be enabled again later)";
-constexpr char kDetailsSectionActionExplanation[] =
-    "The following files will be removed:";
-constexpr char kDetailsSectionPoweredBy[] = "Powered by: <ESET logo>";
-
-constexpr char kDummyDirectory[] =
-    "C:\\Documents and Settings\\JohnDoe\\Local Settings\\Application Data"
-    "\\IAmNotWanted\\application\\";
-constexpr char kDummyFilename1[] = "somefile.dll";
-constexpr char kDummyFilename2[] = "another_file.dll";
-constexpr char kDummyFilename3[] = "more_stuff.dll";
-constexpr char kDummyFilename4[] = "run_me.exe";
-
-constexpr char kShowDetails[] = "Learn more";
-constexpr char kHideDetails[] = "Close";
-constexpr char kWindowTitle[] = "Chrome detected unusual behavior";
-constexpr char kAcceptButtonLabel[] = "Start cleanup";
+constexpr char kWindowTitle[] = "Clean up your computer?";
+constexpr char kMainText[] =
+    "Chrome found software that harms your browsing experience. Remove related "
+    "files from your computer and restore browser settings, including your "
+    "search engine and home page.";
+constexpr char kAcceptButtonLabel[] = "Cleanup";
+constexpr char kAdvancedButtonLabel[] = "Advanced";
 
 }  // namespace
 
-SRTPromptController::LabelInfo::LabelInfo(LabelType type,
-                                          const base::string16& text)
-    : type(type), text(text) {}
-
-SRTPromptController::LabelInfo::~LabelInfo() = default;
-
 SRTPromptController::SRTPromptController() {}
 
 SRTPromptController::~SRTPromptController() = default;
@@ -66,65 +30,18 @@
   return base::UTF8ToUTF16(kWindowTitle);
 }
 
-std::vector<SRTPromptController::LabelInfo> SRTPromptController::GetMainText()
-    const {
-  return {
-      LabelInfo(LabelInfo::PARAGRAPH, base::UTF8ToUTF16(kMainTextIntroduction)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(kUnwantedSoftwareCategory1)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(kUnwantedSoftwareCategory2)),
-      LabelInfo(LabelInfo::PARAGRAPH,
-                base::UTF8ToUTF16(kMainTextActionExplanation)),
-  };
-}
-
-std::vector<SRTPromptController::LabelInfo>
-SRTPromptController::GetDetailsText() const {
-  return {
-      LabelInfo(LabelInfo::PARAGRAPH,
-                base::UTF8ToUTF16(kDetailsSectionSettingsResetExplanation)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(kDetailsSectionSetting1)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(kDetailsSectionSetting2)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(kDetailsSectionSetting3)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(kDetailsSectionSetting4)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(kDetailsSectionSetting5)),
-      LabelInfo(LabelInfo::PARAGRAPH,
-                base::UTF8ToUTF16(kDetailsSectionActionExplanation)),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(base::JoinString(
-                    {kDummyDirectory, kDummyFilename1}, nullptr))),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(base::JoinString(
-                    {kDummyDirectory, kDummyFilename2}, nullptr))),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(base::JoinString(
-                    {kDummyDirectory, kDummyFilename3}, nullptr))),
-      LabelInfo(LabelInfo::BULLET_ITEM,
-                base::UTF8ToUTF16(base::JoinString(
-                    {kDummyDirectory, kDummyFilename4}, nullptr))),
-      LabelInfo(LabelInfo::PARAGRAPH,
-                base::UTF8ToUTF16(kDetailsSectionPoweredBy)),
-  };
-}
-
-base::string16 SRTPromptController::GetShowDetailsLabel() const {
-  return base::UTF8ToUTF16(kShowDetails);
-}
-
-base::string16 SRTPromptController::GetHideDetailsLabel() const {
-  return base::UTF8ToUTF16(kHideDetails);
+base::string16 SRTPromptController::GetMainText() const {
+  return base::UTF8ToUTF16(kMainText);
 }
 
 base::string16 SRTPromptController::GetAcceptButtonLabel() const {
   return base::UTF8ToUTF16(kAcceptButtonLabel);
 }
 
+base::string16 SRTPromptController::GetAdvancedButtonLabel() const {
+  return base::UTF8ToUTF16(kAdvancedButtonLabel);
+}
+
 void SRTPromptController::DialogShown() {}
 
 void SRTPromptController::Accept() {
@@ -135,6 +52,14 @@
   OnInteractionDone();
 }
 
+void SRTPromptController::Close() {
+  OnInteractionDone();
+}
+
+void SRTPromptController::AdvancedButtonClicked() {
+  OnInteractionDone();
+}
+
 void SRTPromptController::OnInteractionDone() {
   delete this;
 }
diff --git a/chrome/browser/safe_browsing/srt_prompt_controller.h b/chrome/browser/safe_browsing/srt_prompt_controller.h
index 3546491..81dca5b 100644
--- a/chrome/browser/safe_browsing/srt_prompt_controller.h
+++ b/chrome/browser/safe_browsing/srt_prompt_controller.h
@@ -16,57 +16,40 @@
 // UI. Also provides functions, such as |Accept()| and |Cancel()|, that should
 // be called by the UI in response to user actions.
 //
-// Objects of this class are typically created by the SwReporterProcess class,
-// which will pass in information the controller needs to be able to create
-// strings to be displayed in the prompt dialog.
-//
 // This class manages its own lifetime and will delete itself once the Cleaner
 // dialog has been dismissed and either of |Accept()| or |Cancel()| have been
 // called.
 class SRTPromptController {
  public:
-  struct LabelInfo {
-    enum LabelType {
-      // Indicates that |text| should be displayed as a multi-line label.
-      PARAGRAPH,
-      // Indicates that |text| should be displayed as a multi-line label that is
-      // indented and starts with a bullet point.
-      BULLET_ITEM,
-    };
-
-    LabelInfo(LabelType type, const base::string16& text);
-    ~LabelInfo();
-
-    LabelType type;
-    base::string16 text;
-  };
-
   SRTPromptController();
 
   base::string16 GetWindowTitle() const;
-  // The text to be shown in the Cleaner dialog's main section and will
-  // always be visible while the dialog is displayed.
-  std::vector<LabelInfo> GetMainText() const;
-  // The text to be shown in the expandable details section of the
-  // Cleaner dialog.
-  std::vector<LabelInfo> GetDetailsText() const;
-  // The text on the button that expands the details section.
-  base::string16 GetShowDetailsLabel() const;
-  // The text on the button that folds the details section.
-  base::string16 GetHideDetailsLabel() const;
+  base::string16 GetMainText() const;
   base::string16 GetAcceptButtonLabel() const;
+  base::string16 GetAdvancedButtonLabel() const;
 
   // Called by the Cleaner dialog when the dialog has been shown. Used for
   // reporting metrics.
   void DialogShown();
   // Called by the Cleaner dialog when user accepts the prompt. Once |Accept()|
-  // has been called, the controller will eventually delete itself and so no
-  // member functions should be called after that.
+  // has been called, the controller will eventually delete itself and no member
+  // functions should be called after that.
   void Accept();
-  // Called by the Cleaner dialog when the dialog is dismissed or canceled. Once
-  // |Cancel()| has been called, the controller will eventually delete itself
-  // and so no member functions should be called after that.
+  // Called by the Cleaner dialog when the dialog is closed via the cancel
+  // button. Once |Cancel()| has been called, the controller will eventually
+  // delete itself and no member functions should be called after that.
   void Cancel();
+  // Called by the Cleaner dialog when the dialog is closed by some other means
+  // than the cancel button (for example, by pressing Esc or clicking the 'x' on
+  // the top of the dialog). After a call to |Dismiss()|, the controller will
+  // eventually delete itself and no member functions should be called after
+  // that.
+  void Close();
+  // Called when the advanced button is clicked, after which the dialog will
+  // close. After a call to |AdvancedButtonClicked()|, the controller will
+  // eventually delete itself and no member functions should be called after
+  // that.
+  void AdvancedButtonClicked();
 
  protected:
   ~SRTPromptController();
diff --git a/chrome/browser/supervised_user/supervised_user_interstitial.cc b/chrome/browser/supervised_user/supervised_user_interstitial.cc
index 0e25c7c..311e725 100644
--- a/chrome/browser/supervised_user/supervised_user_interstitial.cc
+++ b/chrome/browser/supervised_user/supervised_user_interstitial.cc
@@ -271,7 +271,8 @@
     ReportChildAccountFeedback(web_contents_, message, url_);
 #else
     chrome::ShowFeedbackPage(chrome::FindBrowserWithWebContents(web_contents_),
-                             message, std::string());
+                             chrome::kFeedbackSourceSupervisedUserInterstitial,
+                             message, std::string() /* category_tag */);
 #endif
     return;
   }
diff --git a/chrome/browser/ui/ash/chrome_new_window_client.cc b/chrome/browser/ui/ash/chrome_new_window_client.cc
index 90f4deed..e9a75ec 100644
--- a/chrome/browser/ui/ash/chrome_new_window_client.cc
+++ b/chrome/browser/ui/ash/chrome_new_window_client.cc
@@ -218,5 +218,5 @@
 }
 
 void ChromeNewWindowClient::OpenFeedbackPage() {
-  chrome::OpenFeedbackDialog(GetActiveBrowser());
+  chrome::OpenFeedbackDialog(GetActiveBrowser(), chrome::kFeedbackSourceAsh);
 }
diff --git a/chrome/browser/ui/ash/ime_controller_chromeos.cc b/chrome/browser/ui/ash/ime_controller_chromeos.cc
index f9637bb..d252e368 100644
--- a/chrome/browser/ui/ash/ime_controller_chromeos.cc
+++ b/chrome/browser/ui/ash/ime_controller_chromeos.cc
@@ -10,6 +10,12 @@
 bool ImeController::CanCycleIme() {
   chromeos::input_method::InputMethodManager* manager =
       chromeos::input_method::InputMethodManager::Get();
+  DCHECK(manager);
+  if (!manager->GetActiveIMEState()) {
+    LOG(WARNING) << "Cannot cycle through input methods as they are not "
+                    "initialized yet.";
+    return false;
+  }
   return manager->GetActiveIMEState()->CanCycleInputMethod();
 }
 
@@ -28,6 +34,12 @@
 bool ImeController::CanSwitchIme(const ui::Accelerator& accelerator) {
   chromeos::input_method::InputMethodManager* manager =
       chromeos::input_method::InputMethodManager::Get();
+  DCHECK(manager);
+  if (!manager->GetActiveIMEState()) {
+    LOG(WARNING) << "Cannot switch input methods as they are not "
+                    "initialized yet.";
+    return false;
+  }
   return manager->GetActiveIMEState()->CanSwitchInputMethod(accelerator);
 }
 
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index a3da129..9c9e27f 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -76,8 +76,6 @@
           user_prefs::UserPrefs::Get(web_contents->GetBrowserContext()),
           Profile::FromBrowserContext(web_contents->GetBrowserContext())
               ->IsOffTheRecord()) {
-  DCHECK(web_contents);
-
 #if !defined(OS_ANDROID)
   // Since ZoomController is also a WebContentsObserver, we need to be careful
   // about disconnecting from it since the relative order of destruction of
@@ -192,7 +190,7 @@
       ->AddInfoBar(CreateSaveCardInfoBarMobile(
           base::MakeUnique<AutofillSaveCardInfoBarDelegateMobile>(
               false, card, std::unique_ptr<base::DictionaryValue>(nullptr),
-              callback)));
+              callback, GetPrefs())));
 #else
   // Do lazy initialization of SaveCardBubbleControllerImpl.
   autofill::SaveCardBubbleControllerImpl::CreateForWebContents(
@@ -212,7 +210,7 @@
   InfoBarService::FromWebContents(web_contents())
       ->AddInfoBar(CreateSaveCardInfoBarMobile(
           base::MakeUnique<AutofillSaveCardInfoBarDelegateMobile>(
-              true, card, std::move(legal_message), callback)));
+              true, card, std::move(legal_message), callback, GetPrefs())));
 #else
   // Do lazy initialization of SaveCardBubbleControllerImpl.
   autofill::SaveCardBubbleControllerImpl::CreateForWebContents(web_contents());
diff --git a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc
index 78ad6a8..4aaaf03 100644
--- a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc
@@ -14,8 +14,11 @@
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/validation.h"
 #include "components/autofill/core/common/autofill_constants.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
 #include "components/grit/components_scaled_resources.h"
+#include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/navigation_handle.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -35,9 +38,9 @@
 SaveCardBubbleControllerImpl::SaveCardBubbleControllerImpl(
     content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
-      save_card_bubble_view_(nullptr) {
-  DCHECK(web_contents);
-}
+      save_card_bubble_view_(nullptr),
+      pref_service_(
+          user_prefs::UserPrefs::Get(web_contents->GetBrowserContext())) {}
 
 SaveCardBubbleControllerImpl::~SaveCardBubbleControllerImpl() {
   if (save_card_bubble_view_)
@@ -54,7 +57,9 @@
 
   AutofillMetrics::LogSaveCardPromptMetric(
       AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_uploading_,
-      is_reshow_);
+      is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
 
   card_ = card;
   save_card_callback_ = save_card_callback;
@@ -71,12 +76,16 @@
   should_cvc_be_requested_ = should_cvc_be_requested;
   AutofillMetrics::LogSaveCardPromptMetric(
       AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_uploading_,
-      is_reshow_);
+      is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
 
   if (!LegalMessageLine::Parse(*legal_message, &legal_message_lines_)) {
     AutofillMetrics::LogSaveCardPromptMetric(
         AutofillMetrics::SAVE_CARD_PROMPT_END_INVALID_LEGAL_MESSAGE,
-        is_uploading_, is_reshow_);
+        is_uploading_, is_reshow_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState));
     return;
   }
 
@@ -96,7 +105,9 @@
   is_reshow_ = true;
   AutofillMetrics::LogSaveCardPromptMetric(
       AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_uploading_,
-      is_reshow_);
+      is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
 
   ShowBubble();
 }
@@ -151,28 +162,41 @@
   save_card_callback_.Run();
   save_card_callback_.Reset();
   AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, is_uploading_,
-      is_reshow_);
+      AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, is_uploading_, is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
+  pref_service_->SetInteger(
+      prefs::kAutofillAcceptSaveCreditCardPromptState,
+      prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED);
 }
 
 void SaveCardBubbleControllerImpl::OnCancelButton() {
   save_card_callback_.Reset();
   AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, is_uploading_, is_reshow_);
+      AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, is_uploading_, is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
+  pref_service_->SetInteger(
+      prefs::kAutofillAcceptSaveCreditCardPromptState,
+      prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED);
 }
 
 void SaveCardBubbleControllerImpl::OnLearnMoreClicked() {
   OpenUrl(GURL(kHelpURL));
   AutofillMetrics::LogSaveCardPromptMetric(
       AutofillMetrics::SAVE_CARD_PROMPT_DISMISS_CLICK_LEARN_MORE, is_uploading_,
-      is_reshow_);
+      is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
 }
 
 void SaveCardBubbleControllerImpl::OnLegalMessageLinkClicked(const GURL& url) {
   OpenUrl(url);
   AutofillMetrics::LogSaveCardPromptMetric(
       AutofillMetrics::SAVE_CARD_PROMPT_DISMISS_CLICK_LEGAL_MESSAGE,
-      is_uploading_, is_reshow_);
+      is_uploading_, is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
 }
 
 void SaveCardBubbleControllerImpl::OnBubbleClosed() {
@@ -222,13 +246,17 @@
 
     AutofillMetrics::LogSaveCardPromptMetric(
         AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_SHOWING, is_uploading_,
-        is_reshow_);
+        is_reshow_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState));
   } else {
     UpdateIcon();
 
     AutofillMetrics::LogSaveCardPromptMetric(
         AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_HIDDEN, is_uploading_,
-        is_reshow_);
+        is_reshow_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState));
   }
 }
 
@@ -252,7 +280,9 @@
   timer_.reset(new base::ElapsedTimer());
 
   AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, is_uploading_, is_reshow_);
+      AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, is_uploading_, is_reshow_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
 }
 
 void SaveCardBubbleControllerImpl::UpdateIcon() {
diff --git a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h
index 1f5382f..2b924ab8 100644
--- a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h
+++ b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h
@@ -14,6 +14,8 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
+class PrefService;
+
 namespace autofill {
 
 // Implementation of per-tab class to control the save credit card bubble and
@@ -93,6 +95,9 @@
   // Weak reference. Will be nullptr if no bubble is currently shown.
   SaveCardBubbleView* save_card_bubble_view_;
 
+  // Weak reference to read & write |kAutofillAcceptSaveCreditCardPromptState|.
+  PrefService* pref_service_;
+
   // Callback to run if user presses Save button in the bubble.
   // If save_card_callback_.is_null() is true then no bubble is available to
   // show and the icon is not visible.
diff --git a/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc b/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc
index a1121ec0e..fc31bc0 100644
--- a/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc
+++ b/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc
@@ -18,6 +18,8 @@
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/credit_card.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
+#include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/navigation_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -61,8 +63,13 @@
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
     AddTab(browser(), GURL("about:blank"));
-    TestSaveCardBubbleControllerImpl::CreateForTesting(
-        browser()->tab_strip_model()->GetActiveWebContents());
+    content::WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+    TestSaveCardBubbleControllerImpl::CreateForTesting(web_contents);
+    user_prefs::UserPrefs::Get(web_contents->GetBrowserContext())
+        ->SetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState,
+            prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE);
   }
 
   BrowserWindow* CreateBrowserWindow() override {
@@ -267,6 +274,63 @@
 }
 
 TEST_F(SaveCardBubbleControllerImplTest,
+       Metrics_Local_FirstShow_CancelButton_FirstShow) {
+  base::HistogramTester histogram_tester;
+  ShowLocalBubble();
+  controller()->OnCancelButton();
+  controller()->OnBubbleClosed();
+
+  ShowLocalBubble();
+  controller()->OnCancelButton();
+  controller()->OnBubbleClosed();
+
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.SaveCreditCardPrompt.Local.FirstShow"),
+      ElementsAre(Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, 1)));
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.SaveCreditCardPrompt.Local.FirstShow.PreviouslyDenied"),
+      ElementsAre(Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, 1)));
+}
+
+TEST_F(SaveCardBubbleControllerImplTest,
+       Metrics_Local_FirstShow_CancelButton_FirstShow_SaveButton_FirstShow) {
+  base::HistogramTester histogram_tester;
+  ShowLocalBubble();
+  controller()->OnCancelButton();
+  controller()->OnBubbleClosed();
+
+  ShowLocalBubble();
+  controller()->OnSaveButton();
+  controller()->OnBubbleClosed();
+
+  ShowLocalBubble();
+
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.SaveCreditCardPrompt.Local.FirstShow"),
+      ElementsAre(Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, 1)));
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.SaveCreditCardPrompt.Local.FirstShow.PreviouslyAccepted"),
+      ElementsAre(Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1)));
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.SaveCreditCardPrompt.Local.FirstShow.PreviouslyDenied"),
+      ElementsAre(Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1),
+                  Bucket(AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, 1)));
+}
+
+TEST_F(SaveCardBubbleControllerImplTest,
        Metrics_Local_FirstShow_NavigateWhileShowing) {
   ShowLocalBubble();
 
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index a8f59027..e917e171 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -559,7 +559,7 @@
 #endif
 #if defined(GOOGLE_CHROME_BUILD)
     case IDC_FEEDBACK:
-      OpenFeedbackDialog(browser_);
+      OpenFeedbackDialog(browser_, kFeedbackSourceBrowserCommand);
       break;
 #endif
     case IDC_SHOW_BOOKMARK_BAR:
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index b8c0b13..713c98e 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browsing_data/browsing_data_helper.h"
-
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/devtools/devtools_window.h"
@@ -1103,9 +1102,11 @@
 #endif
 }
 
-void OpenFeedbackDialog(Browser* browser) {
+void OpenFeedbackDialog(Browser* browser, FeedbackSource source) {
   base::RecordAction(UserMetricsAction("Feedback"));
-  chrome::ShowFeedbackPage(browser, std::string(), std::string());
+  chrome::ShowFeedbackPage(browser, source,
+                           std::string() /* description_template */,
+                           std::string() /* category_tag */);
 }
 
 void ToggleBookmarkBar(Browser* browser) {
diff --git a/chrome/browser/ui/browser_commands.h b/chrome/browser/ui/browser_commands.h
index 1c5d567..235a46e 100644
--- a/chrome/browser/ui/browser_commands.h
+++ b/chrome/browser/ui/browser_commands.h
@@ -9,6 +9,7 @@
 
 #include "build/build_config.h"
 #include "chrome/browser/devtools/devtools_toggle_action.h"
+#include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
 #include "content/public/common/page_zoom.h"
 #include "printing/features/features.h"
@@ -130,7 +131,7 @@
 void ToggleDevToolsWindow(Browser* browser, DevToolsToggleAction action);
 bool CanOpenTaskManager();
 void OpenTaskManager(Browser* browser);
-void OpenFeedbackDialog(Browser* browser);
+void OpenFeedbackDialog(Browser* browser, FeedbackSource source);
 void ToggleBookmarkBar(Browser* browser);
 void ShowAppMenu(Browser* browser);
 void ShowAvatarMenu(Browser* browser);
diff --git a/chrome/browser/ui/chrome_pages.h b/chrome/browser/ui/chrome_pages.h
index 98513eba..61f08af 100644
--- a/chrome/browser/ui/chrome_pages.h
+++ b/chrome/browser/ui/chrome_pages.h
@@ -34,6 +34,24 @@
   HELP_SOURCE_WEBUI,
 };
 
+// Sources of feedback requests.
+//
+// WARNING: The below enum MUST never be renamed, modified or reordered, as
+// they're written to logs. You can only insert a new element immediately
+// before the last.
+enum FeedbackSource {
+  kFeedbackSourceArcApp = 0,
+  kFeedbackSourceAsh,
+  kFeedbackSourceBrowserCommand,
+  kFeedbackSourceMdSettingsAboutPage,
+  kFeedbackSourceOldSettingsAboutPage,
+  kFeedbackSourceProfileErrorDialog,
+  kFeedbackSourceSadTabPage,
+  kFeedbackSourceSupervisedUserInterstitial,
+
+  // Must be last.
+  kFeedbackSourceCount,
+};
 
 void ShowBookmarkManager(Browser* browser);
 void ShowBookmarkManagerForNode(Browser* browser, int64_t node_id);
@@ -46,6 +64,7 @@
 // ShowFeedbackPage() uses |browser| to determine the URL of the current tab.
 // |browser| should be NULL if there are no currently open browser windows.
 void ShowFeedbackPage(Browser* browser,
+                      FeedbackSource source,
                       const std::string& description_template,
                       const std::string& category_tag);
 
diff --git a/chrome/browser/ui/profile_error_dialog.cc b/chrome/browser/ui/profile_error_dialog.cc
index a705b6f9..be87874 100644
--- a/chrome/browser/ui/profile_error_dialog.cc
+++ b/chrome/browser/ui/profile_error_dialog.cc
@@ -48,7 +48,8 @@
         l10n_util::GetStringUTF8(IDS_PROFILE_ERROR_FEEDBACK_DESCRIPTION);
     feedback_description += "\n" + diagnostics;
 
-    chrome::ShowFeedbackPage(nullptr, feedback_description,
+    chrome::ShowFeedbackPage(nullptr, chrome::kFeedbackSourceProfileErrorDialog,
+                             feedback_description,
                              kProfileErrorFeedbackCategory);
   }
 #endif
diff --git a/chrome/browser/ui/sad_tab.cc b/chrome/browser/ui/sad_tab.cc
index 5c88b0cc..c9b3cc42 100644
--- a/chrome/browser/ui/sad_tab.cc
+++ b/chrome/browser/ui/sad_tab.cc
@@ -53,7 +53,7 @@
   }
 }
 
-const char kCategoryTagCrash[] = "Crash";
+constexpr char kCategoryTagCrash[] = "Crash";
 
 bool ShouldShowFeedbackButton() {
 #if defined(GOOGLE_CHROME_BUILD)
@@ -158,9 +158,10 @@
     case Action::BUTTON:
       RecordEvent(show_feedback_button_, SadTabEvent::BUTTON_CLICKED);
       if (show_feedback_button_) {
-        chrome::ShowFeedbackPage(
-            chrome::FindBrowserWithWebContents(web_contents_),
-            l10n_util::GetStringUTF8(kind_ == chrome::SAD_TAB_KIND_CRASHED
+        ShowFeedbackPage(
+            FindBrowserWithWebContents(web_contents_),
+            kFeedbackSourceSadTabPage,
+            l10n_util::GetStringUTF8(kind_ == SAD_TAB_KIND_CRASHED
                                          ? IDS_CRASHED_TAB_FEEDBACK_MESSAGE
                                          : IDS_KILLED_TAB_FEEDBACK_MESSAGE),
             std::string(kCategoryTagCrash));
diff --git a/chrome/browser/ui/views/srt_prompt_dialog.cc b/chrome/browser/ui/views/srt_prompt_dialog.cc
index 21ccc1d..24d885b 100644
--- a/chrome/browser/ui/views/srt_prompt_dialog.cc
+++ b/chrome/browser/ui/views/srt_prompt_dialog.cc
@@ -4,33 +4,18 @@
 
 #include "chrome/browser/ui/views/srt_prompt_dialog.h"
 
-#include <vector>
-
-#include "base/memory/ptr_util.h"
 #include "base/strings/string16.h"
-#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/safe_browsing/srt_prompt_controller.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h"
 #include "components/constrained_window/constrained_window_views.h"
-#include "components/web_modal/web_contents_modal_dialog_host.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/events/event.h"
-#include "ui/gfx/animation/slide_animation.h"
-#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/native_widget_types.h"
-#include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/text_constants.h"
-#include "ui/native_theme/native_theme.h"
-#include "ui/views/border.h"
 #include "ui/views/controls/label.h"
-#include "ui/views/controls/scroll_view.h"
-#include "ui/views/controls/separator.h"
 #include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/fill_layout.h"
-#include "ui/views/layout/grid_layout.h"
 #include "ui/views/layout/layout_constants.h"
 #include "ui/views/widget/widget.h"
 
@@ -45,198 +30,30 @@
 }  // namespace chrome
 
 namespace {
-
-using LabelInfo = safe_browsing::SRTPromptController::LabelInfo;
-
 constexpr int kDialogWidth = 448;
-constexpr int kDetailsSectionMaxHeight = 150;
-constexpr int kBulletColumnWidth = 10;
-// Constants used for the layout of the label views.
-constexpr int kMainColumSetId = 0;
-constexpr int kBulletColumnSetId = 1;
-
-// Returns a view containing |item| with insets defined by |top|, |left|,
-// |bottom|, and |right|.
-views::View* CreateViewWithInsets(views::View* item,
-                                  int top,
-                                  int left,
-                                  int bottom,
-                                  int right) {
-  views::View* view = new views::View();
-  view->SetLayoutManager(new views::FillLayout());
-  view->SetBorder(views::CreateEmptyBorder(top, left, bottom, right));
-  view->AddChildView(item);
-  return view;
-}
-
-// Helper function used by |CreateLabelView()| below that adds |labels| to
-// |label_view|.
-void AddLabelsToLabelView(views::View* label_view,
-                          views::GridLayout* layout,
-                          const std::vector<LabelInfo>& labels) {
-  static constexpr base::char16 kBulletPoint[] = {0x2022, 0};
-
-  bool first_label = true;
-  bool last_label_was_bullet = false;
-  for (const LabelInfo& label_info : labels) {
-    const bool is_bullet = label_info.type == LabelInfo::BULLET_ITEM;
-    views::Label* label = new views::Label(label_info.text);
-    label->SetMultiLine(true);
-    label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-
-    // Do not add a padding row if
-    // - this is the first label being added, or
-    // - a bullet item is being added and the last label was also a bullet item.
-    bool skip_padding = first_label || (is_bullet && last_label_was_bullet);
-    if (!skip_padding)
-      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
-
-    layout->StartRow(0, is_bullet ? kBulletColumnSetId : kMainColumSetId);
-    if (is_bullet) {
-      views::Label* bullet = new views::Label(base::string16(kBulletPoint));
-      layout->AddView(bullet);
-    }
-    layout->AddView(label);
-
-    last_label_was_bullet = is_bullet;
-    first_label = false;
-  }
-}
-
-// Creates a view that displays two types of labels: multiline labels
-// representing whole paragraphs or indented labels that are start with a bullet
-// point.
-//
-// A vertical padding of size |top_vertical_space| is added before the labels.
-views::View* CreateLabelView(int top_vertical_space,
-                             const std::vector<LabelInfo>& labels) {
-  views::View* label_view = new views::View();
-  views::GridLayout* layout = new views::GridLayout(label_view);
-  layout->SetInsets(0, views::kButtonHEdgeMarginNew, 0,
-                    views::kButtonHEdgeMarginNew);
-
-  label_view->SetLayoutManager(layout);
-
-  views::ColumnSet* main_column_set = layout->AddColumnSet(kMainColumSetId);
-  main_column_set->AddColumn(views::GridLayout::FILL,
-                             views::GridLayout::LEADING,
-                             /*resize_percent=*/1, views::GridLayout::USE_PREF,
-                             /*fixed_width=*/0,
-                             /*min_width=*/0);
-
-  views::ColumnSet* bullet_column_set_ =
-      layout->AddColumnSet(kBulletColumnSetId);
-  bullet_column_set_->AddPaddingColumn(
-      /*resize_percent=*/0, views::kUnrelatedControlLargeHorizontalSpacing);
-  bullet_column_set_->AddColumn(
-      views::GridLayout::FILL, views::GridLayout::LEADING,
-      /*resize_percent=*/0, views::GridLayout::USE_PREF,
-      /*fixed_width=*/0,
-      /*min_width=*/0);
-  bullet_column_set_->AddPaddingColumn(/*resize_percent=*/0,
-                                       kBulletColumnWidth);
-  bullet_column_set_->AddColumn(
-      views::GridLayout::FILL, views::GridLayout::LEADING,
-      /*resize_percent=*/1, views::GridLayout::USE_PREF,
-      /*fixed_width=*/0,
-      /*min_width=*/0);
-
-  if (top_vertical_space > 0)
-    layout->AddPaddingRow(/*vertical_resize=*/0, top_vertical_space);
-
-  AddLabelsToLabelView(label_view, layout, labels);
-
-  layout->AddPaddingRow(/*vertical_resize=*/0,
-                        views::kUnrelatedControlLargeHorizontalSpacing);
-  return label_view;
-}
-
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
-// SRTPromptDialog::ExpandableMessageView
-//
-// A view, whose visibilty can be toggled, and will be used for the details
-// section the main dialog.
-class SRTPromptDialog::ExpandableMessageView : public views::View {
- public:
-  explicit ExpandableMessageView(const std::vector<LabelInfo>& labels);
-  ~ExpandableMessageView() override;
-
-  void AnimateToState(double state);
-
-  // views::View overrides.
-  gfx::Size GetPreferredSize() const override;
-  int GetHeightForWidth(int width) const override;
-
- private:
-  // A number between 0 and 1 that determines how much of the view's preferred
-  // height should be visible.
-  double animation_state_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExpandableMessageView);
-};
-
-SRTPromptDialog::ExpandableMessageView::ExpandableMessageView(
-    const std::vector<LabelInfo>& labels)
-    : animation_state_(0.0) {
-  // Add the main message view inside a scroll view.
-  views::View* label_view =
-      CreateLabelView(views::kUnrelatedControlLargeHorizontalSpacing, labels);
-  views::ScrollView* scroll_view = new views::ScrollView();
-  scroll_view->ClipHeightTo(kDetailsSectionMaxHeight, kDetailsSectionMaxHeight);
-  scroll_view->SetContents(label_view);
-  scroll_view->SetSize(gfx::Size(kDialogWidth, kDetailsSectionMaxHeight));
-  AddChildView(scroll_view);
-}
-
-SRTPromptDialog::ExpandableMessageView::~ExpandableMessageView() {}
-
-void SRTPromptDialog::ExpandableMessageView::AnimateToState(double state) {
-  DCHECK_LE(0.0, state);
-  DCHECK_GE(1.0, state);
-
-  animation_state_ = state;
-  PreferredSizeChanged();
-}
-
-gfx::Size SRTPromptDialog::ExpandableMessageView::GetPreferredSize() const {
-  return gfx::Size(kDialogWidth, kDetailsSectionMaxHeight * animation_state_);
-}
-
-int SRTPromptDialog::ExpandableMessageView::GetHeightForWidth(int width) const {
-  return GetPreferredSize().height();
-}
-
-////////////////////////////////////////////////////////////////////////////////
 // SRTPromptDialog
 
 SRTPromptDialog::SRTPromptDialog(safe_browsing::SRTPromptController* controller)
     : browser_(nullptr),
       controller_(controller),
-      slide_animation_(base::MakeUnique<gfx::SlideAnimation>(this)),
-      details_view_(new ExpandableMessageView(controller_->GetDetailsText())),
-      details_button_(
-          new views::LabelButton(this, controller_->GetShowDetailsLabel())) {
+      advanced_button_(
+          new views::LabelButton(this, controller_->GetAdvancedButtonLabel())) {
   DCHECK(controller_);
 
   SetLayoutManager(new views::BoxLayout(
       /*orientation=*/views::BoxLayout::kVertical,
-      /*inside_border_horizontal_spacing=*/0,
+      /*inside_border_horizontal_spacing=*/views::kButtonHEdgeMarginNew,
       /*inside_border_vertical_spacing=*/views::kPanelVertMargin,
       /*between_child_spacing=*/0));
+  views::Label* label = new views::Label(controller_->GetMainText());
+  label->SetMultiLine(true);
+  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  AddChildView(label);
 
-  AddChildView(CreateLabelView(0, controller_->GetMainText()));
-  AddChildView(new views::Separator());
-
-  AddChildView(details_view_);
-
-  details_button_->SetEnabledTextColors(GetDetailsButtonColor());
-  UpdateDetailsButton();
-  AddChildView(CreateViewWithInsets(
-      details_button_, views::kPanelVertMargin, views::kButtonHEdgeMarginNew,
-      views::kPanelVertMargin, views::kButtonHEdgeMarginNew));
-  AddChildView(new views::Separator());
+  advanced_button_->SetStyle(views::Button::STYLE_BUTTON);
 }
 
 SRTPromptDialog::~SRTPromptDialog() {
@@ -282,9 +99,13 @@
   DCHECK(button == ui::DIALOG_BUTTON_OK || button == ui::DIALOG_BUTTON_CANCEL);
   DCHECK(controller_);
 
-  if (button == ui::DIALOG_BUTTON_OK)
-    return controller_->GetAcceptButtonLabel();
-  return DialogDelegate::GetDialogButtonLabel(button);
+  return button == ui::DIALOG_BUTTON_OK
+             ? controller_->GetAcceptButtonLabel()
+             : DialogDelegate::GetDialogButtonLabel(button);
+}
+
+views::View* SRTPromptDialog::CreateExtraView() {
+  return advanced_button_;
 }
 
 bool SRTPromptDialog::Accept() {
@@ -303,6 +124,14 @@
   return true;
 }
 
+bool SRTPromptDialog::Close() {
+  if (controller_) {
+    controller_->Close();
+    controller_ = nullptr;
+  }
+  return true;
+}
+
 // View overrides.
 
 gfx::Size SRTPromptDialog::GetPreferredSize() const {
@@ -313,42 +142,14 @@
 
 void SRTPromptDialog::ButtonPressed(views::Button* sender,
                                     const ui::Event& event) {
-  DCHECK_EQ(sender, details_button_);
+  DCHECK_EQ(sender, advanced_button_);
   DCHECK(browser_);
 
-  if (slide_animation_->IsShowing())
-    slide_animation_->Hide();
-  else
-    slide_animation_->Show();
-}
-
-void SRTPromptDialog::AnimationProgressed(const gfx::Animation* animation) {
-  DCHECK_EQ(slide_animation_.get(), animation);
-
-  details_view_->AnimateToState(animation->GetCurrentValue());
-  ChromeWebModalDialogManagerDelegate* manager = browser_;
-  constrained_window::UpdateWidgetModalDialogPosition(
-      GetWidget(), manager->GetWebContentsModalDialogHost());
-}
-
-void SRTPromptDialog::AnimationEnded(const gfx::Animation* animation) {
-  DCHECK_EQ(slide_animation_.get(), animation);
-  UpdateDetailsButton();
-}
-
-SkColor SRTPromptDialog::GetDetailsButtonColor() {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_LinkEnabled);
-}
-
-void SRTPromptDialog::UpdateDetailsButton() {
-  DCHECK(controller_);
-  details_button_->SetText(slide_animation_->IsShowing()
-                               ? controller_->GetHideDetailsLabel()
-                               : controller_->GetShowDetailsLabel());
-  details_button_->SetImage(
-      views::Button::STATE_NORMAL,
-      slide_animation_->IsShowing()
-          ? gfx::CreateVectorIcon(kCaretUpIcon, GetDetailsButtonColor())
-          : gfx::CreateVectorIcon(kCaretDownIcon, GetDetailsButtonColor()));
+  // TODO(alito): Navigate to the webui version of the Chrome Cleaner UI when
+  // that is implemented.
+  if (controller_) {
+    controller_->AdvancedButtonClicked();
+    controller_ = nullptr;
+  }
+  GetWidget()->Close();
 }
diff --git a/chrome/browser/ui/views/srt_prompt_dialog.h b/chrome/browser/ui/views/srt_prompt_dialog.h
index 59e1110e..c2d7a76e 100644
--- a/chrome/browser/ui/views/srt_prompt_dialog.h
+++ b/chrome/browser/ui/views/srt_prompt_dialog.h
@@ -8,31 +8,18 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "ui/gfx/animation/animation_delegate.h"
-#include "ui/gfx/image/image_skia.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/window/dialog_delegate.h"
 
 class Browser;
 
-namespace gfx {
-class SlideAnimation;
-}
-
 namespace safe_browsing {
 class SRTPromptController;
 }
 
-// A modal dialog asking the user if they want to run the Chrome Cleanup
-// tool. The dialog will have the following sections:
-//
-// 1. Main section with general information about unwanted software that has
-// been found on the user's system.
-// 2. Expandable details section with more details about unwanted software that
-// will be removed and Chrome settings that will be reset.
-// 3. Checkbox asking for permissions to upload logs (not yet implemented).
+// A modal dialog asking the user if they want to remove harmful software from
+// their computers by running the Chrome Cleanup tool.
 //
 // The strings and icons used in the dialog are provided by a
 // |SRTPromptController| object, which will also receive information about how
@@ -41,8 +28,7 @@
 // interaction with the dialog. See the |SRTPromptController| class's
 // description for more details.
 class SRTPromptDialog : public views::DialogDelegateView,
-                        public views::ButtonListener,
-                        public gfx::AnimationDelegate {
+                        public views::ButtonListener {
  public:
   // The |controller| object manages its own lifetime and is not owned by
   // |SRTPromptDialog|. See the description of the |SRTPromptController| class
@@ -61,8 +47,10 @@
 
   // views::DialogDelegate overrides.
   base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
+  views::View* CreateExtraView() override;
   bool Accept() override;
   bool Cancel() override;
+  bool Close() override;
 
   // views::View overrides.
   gfx::Size GetPreferredSize() const override;
@@ -70,24 +58,13 @@
   // views::ButtonListener overrides.
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
-  // gfx::AnimationDelegate overrides.
-  void AnimationProgressed(const gfx::Animation* animation) override;
-  void AnimationEnded(const gfx::Animation* animation) override;
-
  private:
-  class ExpandableMessageView;
-
-  SkColor GetDetailsButtonColor();
-  void UpdateDetailsButton();
-
   Browser* browser_;
   // The pointer will be set to nullptr once the controller has been notified of
   // user interaction since the controller can delete itself after that point.
   safe_browsing::SRTPromptController* controller_;
 
-  std::unique_ptr<gfx::SlideAnimation> slide_animation_;
-  ExpandableMessageView* details_view_;
-  views::LabelButton* details_button_;
+  views::LabelButton* advanced_button_;
 
   DISALLOW_COPY_AND_ASSIGN(SRTPromptDialog);
 };
diff --git a/chrome/browser/ui/webui/help/help_handler.cc b/chrome/browser/ui/webui/help/help_handler.cc
index 3cb5e47..aeacd09 100644
--- a/chrome/browser/ui/webui/help/help_handler.cc
+++ b/chrome/browser/ui/webui/help/help_handler.cc
@@ -538,7 +538,8 @@
   DCHECK(args->empty());
   Browser* browser = chrome::FindBrowserWithWebContents(
       web_ui()->GetWebContents());
-  chrome::OpenFeedbackDialog(browser);
+  chrome::OpenFeedbackDialog(browser,
+                             chrome::kFeedbackSourceOldSettingsAboutPage);
 }
 
 void HelpHandler::OpenHelpPage(const base::ListValue* args) {
diff --git a/chrome/browser/ui/webui/settings/about_handler.cc b/chrome/browser/ui/webui/settings/about_handler.cc
index a4ff8df..e91c922 100644
--- a/chrome/browser/ui/webui/settings/about_handler.cc
+++ b/chrome/browser/ui/webui/settings/about_handler.cc
@@ -440,7 +440,8 @@
   DCHECK(args->empty());
   Browser* browser =
       chrome::FindBrowserWithWebContents(web_ui()->GetWebContents());
-  chrome::OpenFeedbackDialog(browser);
+  chrome::OpenFeedbackDialog(browser,
+                             chrome::kFeedbackSourceMdSettingsAboutPage);
 }
 
 void AboutHandler::HandleOpenHelpPage(const base::ListValue* args) {
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 82d9367..09c7441 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -68,6 +68,7 @@
     "chrome_content_client.cc",
     "chrome_content_client.h",
     "chrome_content_client_constants.cc",
+    "chrome_isolated_world_ids.h",
     "chrome_result_codes.h",
     "chrome_utility_messages.h",
     "common_message_generator.cc",
@@ -118,6 +119,7 @@
     "pause_tabs_field_trial.h",
     "pref_names_util.cc",
     "pref_names_util.h",
+    "prerender_messages.h",
     "prerender_types.h",
     "profiling.cc",
     "profiling.h",
@@ -245,6 +247,7 @@
     sources += [
       "cast_messages.cc",
       "cast_messages.h",
+      "extensions/api/automation_api_constants.h",
       "extensions/api/commands/commands_handler.cc",
       "extensions/api/commands/commands_handler.h",
       "extensions/api/extension_action/action_info.cc",
@@ -435,6 +438,7 @@
       "importer/profile_import_process_param_traits.cc",
       "importer/profile_import_process_param_traits.h",
       "importer/profile_import_process_param_traits_macros.h",
+      "importer/pstore_declarations.h",
       "importer/safari_importer_utils.h",
       "importer/safari_importer_utils.mm",
     ]
diff --git a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
index 69dde01..f39c1ee 100644
--- a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
+++ b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
@@ -81,6 +81,21 @@
     };
     chrome.networkingPrivate.onPortalDetectionCompleted.addListener(
         self.onPortalDetectionCompleted);
+  },
+  verifyTetherNetwork: function(
+      properties, expectedGuid, expectedName, expectedBatteryPercentage,
+      expectedCarrier, expectedSignalStrength) {
+    //assertEq(NetworkType.Tether, properties.Type);
+    assertEq(expectedGuid, properties.GUID);
+    assertEq(expectedName,
+             properties.Name.hasOwnProperty('Active') ? properties.Name.Active
+                                                      : properties.Name);
+    assertEq(expectedBatteryPercentage, properties.Tether.BatteryPercentage);
+    assertEq(expectedCarrier, properties.Tether.Carrier);
+    // TODO(khorimoto): Add the expected value as a parameter once it can be set
+    // via the Tether component.
+    assertFalse(properties.Tether.HasConnectedToHost);
+    assertEq(expectedSignalStrength, properties.Tether.SignalStrength);
   }
 };
 
@@ -923,6 +938,41 @@
       }, result);
     }));
   },
+  function getTetherNetworks() {
+    chrome.networkingPrivate.getNetworks(
+        {networkType: 'Tether'},
+        callbackPass(function(tetherNetworks) {
+          assertEq(2, tetherNetworks.length);
+          privateHelpers.verifyTetherNetwork(tetherNetworks[0], 'tetherGuid1',
+              'tetherName1', 50, 'tetherCarrier1', 75);
+          privateHelpers.verifyTetherNetwork(tetherNetworks[1], 'tetherGuid2',
+              'tetherName2', 75, 'tetherCarrier2', 100);
+        }));
+  },
+  function getTetherNetworkProperties() {
+    chrome.networkingPrivate.getProperties(
+        'tetherGuid1',
+        callbackPass(function(tetherNetwork) {
+          privateHelpers.verifyTetherNetwork(tetherNetwork, 'tetherGuid1',
+              'tetherName1', 50, 'tetherCarrier1', 75);
+        }));
+  },
+  function getTetherNetworkManagedProperties() {
+    chrome.networkingPrivate.getManagedProperties(
+        'tetherGuid1',
+        callbackPass(function(tetherNetwork) {
+          privateHelpers.verifyTetherNetwork(tetherNetwork, 'tetherGuid1',
+              'tetherName1', 50, 'tetherCarrier1', 75);
+        }));
+  },
+  function getTetherNetworkState() {
+    chrome.networkingPrivate.getState(
+        'tetherGuid1',
+        callbackPass(function(tetherNetwork) {
+          privateHelpers.verifyTetherNetwork(tetherNetwork, 'tetherGuid1',
+              'tetherName1', 50, 'tetherCarrier1', 75);
+        }));
+  },
 ];
 
 chrome.test.getConfig(function(config) {
diff --git a/chrome/test/data/payments/payment_request_id.js b/chrome/test/data/payments/payment_request_id.js
new file mode 100644
index 0000000..abfda37a
--- /dev/null
+++ b/chrome/test/data/payments/payment_request_id.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Launches the PaymentRequest UI including a details.id and prints the
+ * resulting requestId.
+ */
+function buy() {  // eslint-disable-line no-unused-vars
+  try {
+    new PaymentRequest(
+        [{supportedMethods: ['visa']}],
+        {id: 'my_payment_id',
+         total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}})
+        .show()
+        .then(function(resp) {
+          resp.complete('success')
+              .then(function() {
+                print(resp.requestId);
+              })
+              .catch(function(error) {
+                print(error.message);
+              });
+        })
+        .catch(function(error) {
+          print(error.message);
+        });
+  } catch (error) {
+    print(error.message);
+  }
+}
diff --git a/chrome/test/data/payments/payment_request_id_test.html b/chrome/test/data/payments/payment_request_id_test.html
new file mode 100644
index 0000000..922c9d0
--- /dev/null
+++ b/chrome/test/data/payments/payment_request_id_test.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<head>
+<title>Payment Request Id Test</title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+<link rel="stylesheet" type="text/css" href="style.css">
+</head>
+<body>
+<button onclick="buy()" id="buy">Payment Request Id Test</button>
+<pre id="result"></pre>
+<script src="util.js"></script>
+<script src="payment_request_id.js"></script>
+</body>
+</html>
diff --git a/chromecast/media/cdm/cast_cdm.cc b/chromecast/media/cdm/cast_cdm.cc
index 1f49273..59cb523 100644
--- a/chromecast/media/cdm/cast_cdm.cc
+++ b/chromecast/media/cdm/cast_cdm.cc
@@ -132,6 +132,11 @@
     player_tracker_impl_->NotifyNewKey();
 }
 
+void CastCdm::OnSessionExpirationUpdate(const std::string& session_id,
+                                        base::Time new_expiry_time) {
+  session_expiration_update_cb_.Run(session_id, new_expiry_time);
+}
+
 void CastCdm::KeyIdAndKeyPairsToInfo(const ::media::KeyIdAndKeyPairs& keys,
                                      ::media::CdmKeysInfo* keys_info) {
   DCHECK(keys_info);
diff --git a/chromecast/media/cdm/cast_cdm.h b/chromecast/media/cdm/cast_cdm.h
index 6ceefd81..004cd63 100644
--- a/chromecast/media/cdm/cast_cdm.h
+++ b/chromecast/media/cdm/cast_cdm.h
@@ -16,6 +16,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/sequenced_task_runner_helpers.h"
 #include "base/threading/thread_checker.h"
+#include "base/time/time.h"
 #include "chromecast/media/base/media_resource_tracker.h"
 #include "chromecast/media/cdm/cast_cdm_context.h"
 #include "chromecast/public/media/cast_key_status.h"
@@ -82,6 +83,8 @@
   void OnSessionKeysChange(const std::string& session_id,
                            bool newly_usable_keys,
                            ::media::CdmKeysInfo keys_info);
+  void OnSessionExpirationUpdate(const std::string& session_id,
+                                 base::Time new_expiry_time);
 
   void KeyIdAndKeyPairsToInfo(const ::media::KeyIdAndKeyPairs& keys,
                               ::media::CdmKeysInfo* key_info);
diff --git a/chromeos/network/network_configuration_handler_unittest.cc b/chromeos/network/network_configuration_handler_unittest.cc
index bfe3471..d07215f 100644
--- a/chromeos/network/network_configuration_handler_unittest.cc
+++ b/chromeos/network/network_configuration_handler_unittest.cc
@@ -366,6 +366,9 @@
       NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
 
   std::string kTetherGuid = "TetherGuid";
+  // TODO(khorimoto): Pass a has_connected_to_host parameter to this function
+  // and verify that it is present in the JSON below. Currently, it is hard-
+  // coded to false.
   network_state_handler_->AddTetherNetworkState(
       kTetherGuid, "TetherNetworkName", "TetherNetworkCarrier",
       100 /* battery_percentage */, 100 /* signal_strength */);
@@ -380,6 +383,7 @@
       "\"State\": \"\",\n   "
       "\"Tether.BatteryPercentage\": 100,\n   "
       "\"Tether.Carrier\": \"TetherNetworkCarrier\",\n   "
+      "\"Tether.HasConnectedToHost\": false,\n   "
       "\"Tether.SignalStrength\": 100,\n   "
       "\"Type\": \"wifi-tether\"\n"
       "}\n";
diff --git a/chromeos/network/network_state.cc b/chromeos/network/network_state.cc
index 838faa0..5577518 100644
--- a/chromeos/network/network_state.cc
+++ b/chromeos/network/network_state.cc
@@ -267,6 +267,8 @@
     dictionary->SetIntegerWithoutPathExpansion(kTetherBatteryPercentage,
                                                battery_percentage());
     dictionary->SetStringWithoutPathExpansion(kTetherCarrier, carrier());
+    dictionary->SetBooleanWithoutPathExpansion(kTetherHasConnectedToHost,
+                                               tether_has_connected_to_host());
     dictionary->SetIntegerWithoutPathExpansion(kTetherSignalStrength,
                                                signal_strength());
   }
diff --git a/chromeos/network/network_state.h b/chromeos/network/network_state.h
index 995207d3..d43c562 100644
--- a/chromeos/network/network_state.h
+++ b/chromeos/network/network_state.h
@@ -114,6 +114,12 @@
   }
   const std::string& carrier() const { return carrier_; }
   void set_carrier(const std::string& carrier) { carrier_ = carrier; }
+  bool tether_has_connected_to_host() const {
+    return tether_has_connected_to_host_;
+  }
+  void set_tether_has_connected_to_host(bool tether_has_connected_to_host) {
+    tether_has_connected_to_host_ = tether_has_connected_to_host;
+  }
   const std::string& tether_guid() const { return tether_guid_; }
   void set_tether_guid(const std::string& guid) { tether_guid_ = guid; }
 
@@ -229,6 +235,12 @@
   // Tether properties.
   std::string carrier_;
   int battery_percentage_;
+  // Whether the current device has already connected to the tether host device
+  // providing the hotspot corresponding to this NetworkState.
+  // Note: this means that the current device has already connected to the
+  // tether host, but it does not necessarily mean that the current device has
+  // connected to the Tether network corresponding to this NetworkState.
+  bool tether_has_connected_to_host_;
 
   // TODO(pneubeck): Remove this once (Managed)NetworkConfigurationHandler
   // provides proxy configuration. crbug.com/241775
diff --git a/chromeos/network/network_state_handler.cc b/chromeos/network/network_state_handler.cc
index 6aeacbe..09c70fc 100644
--- a/chromeos/network/network_state_handler.cc
+++ b/chromeos/network/network_state_handler.cc
@@ -476,8 +476,11 @@
     bool configured_only) const {
   ManagedState* managed =
       GetModifiableManagedState(&network_list_, service_path);
-  if (!managed)
-    return nullptr;
+  if (!managed) {
+    managed = GetModifiableManagedState(&tether_network_list_, service_path);
+    if (!managed)
+      return nullptr;
+  }
   const NetworkState* network = managed->AsNetworkState();
   DCHECK(network);
   if (!network->update_received() ||
@@ -528,6 +531,9 @@
   tether_network_state->set_connectable(true);
   tether_network_state->set_carrier(carrier);
   tether_network_state->set_battery_percentage(battery_percentage);
+  // TODO(khorimoto): Add this field as a parameter to this function and set it
+  // accordingly from the Tether component.
+  tether_network_state->set_tether_has_connected_to_host(false);
   tether_network_state->set_signal_strength(signal_strength);
 
   tether_network_list_.push_back(std::move(tether_network_state));
diff --git a/chromeos/network/network_state_unittest.cc b/chromeos/network/network_state_unittest.cc
index e151c96..5cd7489 100644
--- a/chromeos/network/network_state_unittest.cc
+++ b/chromeos/network/network_state_unittest.cc
@@ -262,6 +262,7 @@
   network_state_.set_type(kTypeTether);
   network_state_.set_carrier("Project Fi");
   network_state_.set_battery_percentage(85);
+  network_state_.set_tether_has_connected_to_host(true);
   network_state_.set_signal_strength(75);
 
   base::DictionaryValue dictionary;
@@ -277,6 +278,11 @@
       kTetherBatteryPercentage, &battery_percentage));
   EXPECT_EQ(85, battery_percentage);
 
+  bool tether_has_connected_to_host;
+  EXPECT_TRUE(dictionary.GetBooleanWithoutPathExpansion(
+      kTetherHasConnectedToHost, &tether_has_connected_to_host));
+  EXPECT_TRUE(tether_has_connected_to_host);
+
   std::string carrier;
   EXPECT_TRUE(
       dictionary.GetStringWithoutPathExpansion(kTetherCarrier, &carrier));
diff --git a/chromeos/network/onc/onc_signature.cc b/chromeos/network/onc/onc_signature.cc
index 78abfb2..58afa58 100644
--- a/chromeos/network/onc/onc_signature.cc
+++ b/chromeos/network/onc/onc_signature.cc
@@ -167,9 +167,12 @@
     {::onc::ethernet::kEAP, &kEAPSignature},
     {NULL}};
 
-const OncFieldSignature tether_fields[] = {
+const OncFieldSignature tether_fields[] = {{NULL}};
+
+const OncFieldSignature tether_with_state_fields[] = {
     {::onc::tether::kBatteryPercentage, &kIntegerSignature},
     {::onc::tether::kCarrier, &kStringSignature},
+    {::onc::tether::kHasConnectedToHost, &kBoolSignature},
     {::onc::tether::kSignalStrength, &kIntegerSignature},
     {NULL}};
 
@@ -336,7 +339,7 @@
     {::onc::network_config::kRestrictedConnectivity, &kBoolSignature},
     {::onc::network_config::kSavedIPConfig, &kSavedIPConfigSignature},
     {::onc::network_config::kSource, &kStringSignature},
-    {::onc::network_config::kTether, &kTetherSignature},
+    {::onc::network_config::kTether, &kTetherWithStateSignature},
     {::onc::network_config::kWiFi, &kWiFiWithStateSignature},
     {::onc::network_config::kWimax, &kWiMAXWithStateSignature},
     {NULL}};
@@ -401,8 +404,6 @@
                                          vpn_fields, NULL};
 const OncValueSignature kEthernetSignature = {base::Value::Type::DICTIONARY,
                                               ethernet_fields, NULL};
-const OncValueSignature kTetherSignature = {base::Value::Type::DICTIONARY,
-                                            tether_fields, NULL};
 const OncValueSignature kIPConfigSignature = {base::Value::Type::DICTIONARY,
                                               ipconfig_fields, NULL};
 const OncValueSignature kSavedIPConfigSignature = {
@@ -439,6 +440,11 @@
 const OncValueSignature kWiFiWithStateSignature = {
     base::Value::Type::DICTIONARY, wifi_with_state_fields, NULL,
     &kWiFiSignature};
+const OncValueSignature kTetherSignature = {base::Value::Type::DICTIONARY,
+                                            tether_fields, NULL};
+const OncValueSignature kTetherWithStateSignature = {
+    base::Value::Type::DICTIONARY, tether_with_state_fields, NULL,
+    &kTetherSignature};
 const OncValueSignature kWiMAXWithStateSignature = {
     base::Value::Type::DICTIONARY, wimax_with_state_fields, NULL,
     &kWiMAXSignature};
diff --git a/chromeos/network/onc/onc_signature.h b/chromeos/network/onc/onc_signature.h
index ebc8778..9b7a732 100644
--- a/chromeos/network/onc/onc_signature.h
+++ b/chromeos/network/onc/onc_signature.h
@@ -47,6 +47,7 @@
 CHROMEOS_EXPORT extern const OncValueSignature kVPNSignature;
 CHROMEOS_EXPORT extern const OncValueSignature kEthernetSignature;
 CHROMEOS_EXPORT extern const OncValueSignature kTetherSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kTetherWithStateSignature;
 CHROMEOS_EXPORT extern const OncValueSignature kIPConfigSignature;
 CHROMEOS_EXPORT extern const OncValueSignature kSavedIPConfigSignature;
 CHROMEOS_EXPORT extern const OncValueSignature kStaticIPConfigSignature;
diff --git a/chromeos/network/onc/onc_translation_tables.cc b/chromeos/network/onc/onc_translation_tables.cc
index 51fa269c..217a4de 100644
--- a/chromeos/network/onc/onc_translation_tables.cc
+++ b/chromeos/network/onc/onc_translation_tables.cc
@@ -114,6 +114,7 @@
 const FieldTranslationEntry tether_fields[] = {
     {::onc::tether::kBatteryPercentage, kTetherBatteryPercentage},
     {::onc::tether::kCarrier, kTetherCarrier},
+    {::onc::tether::kHasConnectedToHost, kTetherHasConnectedToHost},
     {::onc::tether::kSignalStrength, kTetherSignalStrength},
     {NULL}};
 
@@ -248,6 +249,7 @@
     {&kVerifyX509Signature, verify_x509_fields},
     {&kVPNSignature, vpn_fields},
     {&kTetherSignature, tether_fields},
+    {&kTetherWithStateSignature, tether_fields},
     {&kWiFiSignature, wifi_fields},
     {&kWiFiWithStateSignature, wifi_fields},
     {&kWiMAXSignature, wimax_fields},
diff --git a/chromeos/network/onc/onc_validator.cc b/chromeos/network/onc/onc_validator.cc
index 5842134e..cd83e3f4 100644
--- a/chromeos/network/onc/onc_validator.cc
+++ b/chromeos/network/onc/onc_validator.cc
@@ -129,7 +129,7 @@
       valid = ValidateEAP(repaired.get());
     } else if (&signature == &kCertificateSignature) {
       valid = ValidateCertificate(repaired.get());
-    } else if (&signature == &kTetherSignature) {
+    } else if (&signature == &kTetherWithStateSignature) {
       valid = ValidateTether(repaired.get());
     }
   }
@@ -1008,19 +1008,19 @@
 bool Validator::ValidateTether(base::DictionaryValue* result) {
   using namespace ::onc::tether;
 
-  int batteryPercentage;
+  int battery_percentage;
   if (!result->GetIntegerWithoutPathExpansion(kBatteryPercentage,
-                                              &batteryPercentage) ||
-      batteryPercentage < 0 || batteryPercentage > 100) {
+                                              &battery_percentage) ||
+      battery_percentage < 0 || battery_percentage > 100) {
     // Battery percentage must be present and within [0, 100].
     error_or_warning_found_ = true;
     return false;
   }
 
-  int signalStrength;
+  int signal_strength;
   if (!result->GetIntegerWithoutPathExpansion(kSignalStrength,
-                                              &signalStrength) ||
-      signalStrength < 0 || signalStrength > 100) {
+                                              &signal_strength) ||
+      signal_strength < 0 || signal_strength > 100) {
     // Signal strength must be present and within [0, 100].
     error_or_warning_found_ = true;
     return false;
@@ -1034,8 +1034,12 @@
     return false;
   }
 
-  // No required fields.
-  return true;
+  bool all_required_exist = RequireField(*result, kHasConnectedToHost);
+  if (!all_required_exist) {
+    error_or_warning_found_ = true;
+  }
+
+  return !error_on_missing_field_ || all_required_exist;
 }
 
 std::string Validator::MessageHeader() {
diff --git a/chromeos/network/onc/onc_validator_unittest.cc b/chromeos/network/onc/onc_validator_unittest.cc
index 4102a0c..917dffa 100644
--- a/chromeos/network/onc/onc_validator_unittest.cc
+++ b/chromeos/network/onc/onc_validator_unittest.cc
@@ -564,6 +564,10 @@
                                  &kNetworkWithStateSignature,
                                  true),
                        ExpectBothNotValid("", "")),
+        std::make_pair(OncParams("tether-missing-has-connected-to-host",
+                                 &kNetworkWithStateSignature,
+                                 true),
+                       ExpectBothNotValid("", "")),
         std::make_pair(OncParams("tether-missing-signal-strength",
                                  &kNetworkWithStateSignature,
                                  true),
diff --git a/chromeos/network/tether_constants.cc b/chromeos/network/tether_constants.cc
index 72fd96b..1a80364e7 100644
--- a/chromeos/network/tether_constants.cc
+++ b/chromeos/network/tether_constants.cc
@@ -9,6 +9,7 @@
 const char kTypeTether[] = "wifi-tether";
 const char kTetherBatteryPercentage[] = "Tether.BatteryPercentage";
 const char kTetherCarrier[] = "Tether.Carrier";
+const char kTetherHasConnectedToHost[] = "Tether.HasConnectedToHost";
 const char kTetherSignalStrength[] = "Tether.SignalStrength";
 const char kTetherDevicePath[] = "tether-device-path";
 const char kTetherDeviceName[] = "tether-name";
diff --git a/chromeos/network/tether_constants.h b/chromeos/network/tether_constants.h
index 4d73d1d..fb2fa60 100644
--- a/chromeos/network/tether_constants.h
+++ b/chromeos/network/tether_constants.h
@@ -20,6 +20,7 @@
 // Properties associated with tether networks.
 CHROMEOS_EXPORT extern const char kTetherBatteryPercentage[];
 CHROMEOS_EXPORT extern const char kTetherCarrier[];
+CHROMEOS_EXPORT extern const char kTetherHasConnectedToHost[];
 CHROMEOS_EXPORT extern const char kTetherSignalStrength[];
 
 // The device path used for the tether DeviceState.
diff --git a/chromeos/test/data/network/invalid_settings_with_repairs.json b/chromeos/test/data/network/invalid_settings_with_repairs.json
index c6e0e5c0..f7d9b6c 100644
--- a/chromeos/test/data/network/invalid_settings_with_repairs.json
+++ b/chromeos/test/data/network/invalid_settings_with_repairs.json
@@ -383,6 +383,7 @@
       "Type": "Tether",
       "Tether": {
         "Carrier": "Project Fi",
+        "HasConnectedToHost": true,
         "SignalStrength": 75
       }
     },
@@ -393,6 +394,7 @@
       "Tether": {
         "BatteryPercentage": -1,
         "Carrier": "Project Fi",
+        "HasConnectedToHost": true,
         "SignalStrength": 75
       }
     },
@@ -403,6 +405,7 @@
       "Tether": {
         "BatteryPercentage": 101,
         "Carrier": "Project Fi",
+        "HasConnectedToHost": true,
         "SignalStrength": 75
       }
     },
@@ -412,16 +415,28 @@
       "Type": "Tether",
       "Tether": {
         "BatteryPercentage": 85,
+        "HasConnectedToHost": true,
         "SignalStrength": 75
       }
     },
+    "tether-missing-has-connected-to-host": {
+      "GUID": "guid",
+      "Name": "name",
+      "Type": "Tether",
+      "Tether": {
+        "BatteryPercentage": 85,
+        "Carrier": "Project Fi",
+        "SignalStrength": 101
+      }
+    },
     "tether-missing-signal-strength": {
       "GUID": "guid",
       "Name": "name",
       "Type": "Tether",
       "Tether": {
         "BatteryPercentage": 85,
-        "Carrier": "Project Fi"
+        "Carrier": "Project Fi",
+        "HasConnectedToHost": true,
       }
     },
     "tether-negative-signal-strength": {
@@ -431,6 +446,7 @@
       "Tether": {
         "BatteryPercentage": 85,
         "Carrier": "Project Fi",
+        "HasConnectedToHost": true,
         "SignalStrength": -1
       }
     },
@@ -441,6 +457,7 @@
       "Tether": {
         "BatteryPercentage": 85,
         "Carrier": "Project Fi",
+        "HasConnectedToHost": true,
         "SignalStrength": 101
       }
     },
diff --git a/chromeos/test/data/network/shill_tether.json b/chromeos/test/data/network/shill_tether.json
index 2f7e18e..f22e879 100644
--- a/chromeos/test/data/network/shill_tether.json
+++ b/chromeos/test/data/network/shill_tether.json
@@ -3,5 +3,6 @@
   "Name": "Tether Network",
   "Tether.BatteryPercentage": 85,
   "Tether.Carrier": "Project Fi",
+  "Tether.HasConnectedToHost": true,
   "Tether.SignalStrength": 75
 }
diff --git a/chromeos/test/data/network/tether.onc b/chromeos/test/data/network/tether.onc
index 02970b1..3d63154 100644
--- a/chromeos/test/data/network/tether.onc
+++ b/chromeos/test/data/network/tether.onc
@@ -4,6 +4,7 @@
   "Tether": {
     "BatteryPercentage": 85,
     "Carrier": "Project Fi",
+    "HasConnectedToHost": true,
     "SignalStrength": 75
   }
 }
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 6921626..62b31f2 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -284,6 +284,9 @@
   registry->RegisterBooleanPref(prefs::kAutofillWalletImportEnabled, true);
   registry->RegisterBooleanPref(
       prefs::kAutofillWalletImportStorageCheckboxState, true);
+  registry->RegisterIntegerPref(
+      prefs::kAutofillAcceptSaveCreditCardPromptState,
+      prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE);
 }
 
 void AutofillManager::SetExternalDelegate(AutofillExternalDelegate* delegate) {
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index 7744239..5e04434 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -80,6 +80,23 @@
   NUM_FIELD_TYPE_GROUPS_FOR_METRICS
 };
 
+std::string PreviousSaveCreditCardPromptUserDecisionToString(
+    int previous_save_credit_card_prompt_user_decision) {
+  DCHECK_LT(previous_save_credit_card_prompt_user_decision,
+            prefs::NUM_PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISIONS);
+  std::string previous_response;
+  if (previous_save_credit_card_prompt_user_decision ==
+      prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED)
+    previous_response = ".PreviouslyAccepted";
+  else if (previous_save_credit_card_prompt_user_decision ==
+           prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED)
+    previous_response = ".PreviouslyDenied";
+  else
+    DCHECK_EQ(previous_save_credit_card_prompt_user_decision,
+              prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE);
+  return previous_response;
+}
+
 }  // namespace
 
 // First, translates |field_type| to the corresponding logical |group| from
@@ -298,16 +315,18 @@
 }
 
 // static
-void AutofillMetrics::LogCreditCardInfoBarMetric(InfoBarMetric metric,
-                                                 bool is_uploading) {
+void AutofillMetrics::LogCreditCardInfoBarMetric(
+    InfoBarMetric metric,
+    bool is_uploading,
+    int previous_save_credit_card_prompt_user_decision) {
   DCHECK_LT(metric, NUM_INFO_BAR_METRICS);
-  if (is_uploading) {
-    UMA_HISTOGRAM_ENUMERATION("Autofill.CreditCardInfoBar.Server", metric,
-                              NUM_INFO_BAR_METRICS);
-  } else {
-    UMA_HISTOGRAM_ENUMERATION("Autofill.CreditCardInfoBar.Local", metric,
-                              NUM_INFO_BAR_METRICS);
-  }
+
+  std::string destination = is_uploading ? ".Server" : ".Local";
+  LogUMAHistogramEnumeration(
+      "Autofill.CreditCardInfoBar" + destination +
+          PreviousSaveCreditCardPromptUserDecisionToString(
+              previous_save_credit_card_prompt_user_decision),
+      metric, NUM_INFO_BAR_METRICS);
 }
 
 // static
@@ -318,15 +337,19 @@
 }
 
 // static
-void AutofillMetrics::LogSaveCardPromptMetric(SaveCardPromptMetric metric,
-                                              bool is_uploading,
-                                              bool is_reshow) {
+void AutofillMetrics::LogSaveCardPromptMetric(
+    SaveCardPromptMetric metric,
+    bool is_uploading,
+    bool is_reshow,
+    int previous_save_credit_card_prompt_user_decision) {
   DCHECK_LT(metric, NUM_SAVE_CARD_PROMPT_METRICS);
   std::string destination = is_uploading ? ".Upload" : ".Local";
   std::string show = is_reshow ? ".Reshows" : ".FirstShow";
   LogUMAHistogramEnumeration(
-      "Autofill.SaveCreditCardPrompt" + destination + show, metric,
-      NUM_SAVE_CARD_PROMPT_METRICS);
+      "Autofill.SaveCreditCardPrompt" + destination + show +
+          PreviousSaveCreditCardPromptUserDecisionToString(
+              previous_save_credit_card_prompt_user_decision),
+      metric, NUM_SAVE_CARD_PROMPT_METRICS);
 }
 
 // static
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 58ab8d2..ac50c90 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -16,6 +16,7 @@
 #include "components/autofill/core/browser/autofill_profile.h"
 #include "components/autofill/core/browser/credit_card.h"
 #include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
 #include "components/autofill/core/common/form_field_data.h"
 
 namespace ukm {
@@ -645,12 +646,16 @@
   };
 
   static void LogCardUploadDecisionMetric(CardUploadDecisionMetric metric);
-  static void LogCreditCardInfoBarMetric(InfoBarMetric metric,
-                                         bool is_uploading);
+  static void LogCreditCardInfoBarMetric(
+      InfoBarMetric metric,
+      bool is_uploading,
+      int previous_save_credit_card_prompt_user_decision);
   static void LogCreditCardFillingInfoBarMetric(InfoBarMetric metric);
-  static void LogSaveCardPromptMetric(SaveCardPromptMetric metric,
-                                      bool is_uploading,
-                                      bool is_reshow);
+  static void LogSaveCardPromptMetric(
+      SaveCardPromptMetric metric,
+      bool is_uploading,
+      bool is_reshow,
+      int previous_save_credit_card_prompt_user_decision);
   static void LogScanCreditCardPromptMetric(ScanCreditCardPromptMetric metric);
 
   // Should be called when credit card scan is finished. |duration| should be
diff --git a/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.cc b/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.cc
index 28f7aea..85060ad 100644
--- a/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.cc
+++ b/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.cc
@@ -11,9 +11,11 @@
 #include "components/autofill/core/browser/credit_card.h"
 #include "components/autofill/core/browser/legal_message_line.h"
 #include "components/autofill/core/common/autofill_constants.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
 #include "components/grit/components_scaled_resources.h"
 #include "components/infobars/core/infobar.h"
 #include "components/infobars/core/infobar_manager.h"
+#include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/window_open_disposition.h"
@@ -25,10 +27,12 @@
     bool upload,
     const CreditCard& card,
     std::unique_ptr<base::DictionaryValue> legal_message,
-    const base::Closure& save_card_callback)
+    const base::Closure& save_card_callback,
+    PrefService* pref_service)
     : ConfirmInfoBarDelegate(),
       upload_(upload),
       save_card_callback_(save_card_callback),
+      pref_service_(pref_service),
       had_user_interaction_(false),
 #if defined(OS_IOS)
       // TODO(jdonnelly): Use credit card issuer images on iOS.
@@ -42,8 +46,10 @@
   if (legal_message)
     LegalMessageLine::Parse(*legal_message, &legal_messages_);
 
-  AutofillMetrics::LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_SHOWN,
-                                              upload_);
+  AutofillMetrics::LogCreditCardInfoBarMetric(
+      AutofillMetrics::INFOBAR_SHOWN, upload_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
 }
 
 AutofillSaveCardInfoBarDelegateMobile::
@@ -120,7 +126,15 @@
     AutofillMetrics::InfoBarMetric user_action) {
   DCHECK(!had_user_interaction_);
 
-  AutofillMetrics::LogCreditCardInfoBarMetric(user_action, upload_);
+  AutofillMetrics::LogCreditCardInfoBarMetric(
+      user_action, upload_,
+      pref_service_->GetInteger(
+          prefs::kAutofillAcceptSaveCreditCardPromptState));
+  pref_service_->SetInteger(
+      prefs::kAutofillAcceptSaveCreditCardPromptState,
+      user_action == AutofillMetrics::INFOBAR_ACCEPTED
+          ? prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED
+          : prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED);
   had_user_interaction_ = true;
 }
 
diff --git a/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.h b/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.h
index d507696..d1a0d229 100644
--- a/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.h
+++ b/components/autofill/core/browser/autofill_save_card_infobar_delegate_mobile.h
@@ -14,6 +14,8 @@
 #include "components/autofill/core/browser/legal_message_line.h"
 #include "components/infobars/core/confirm_infobar_delegate.h"
 
+class PrefService;
+
 namespace base {
 class DictionaryValue;
 }
@@ -30,7 +32,8 @@
       bool upload,
       const CreditCard& card,
       std::unique_ptr<base::DictionaryValue> legal_message,
-      const base::Closure& save_card_callback);
+      const base::Closure& save_card_callback,
+      PrefService* pref_service);
 
   ~AutofillSaveCardInfoBarDelegateMobile() override;
 
@@ -64,6 +67,9 @@
   // The callback to save credit card if the user accepts the infobar.
   base::Closure save_card_callback_;
 
+  // Weak reference to read & write |kAutofillAcceptSaveCreditCardPromptState|,
+  PrefService* pref_service_;
+
   // Did the user ever explicitly accept or dismiss this infobar?
   bool had_user_interaction_;
 
diff --git a/components/autofill/core/common/autofill_pref_names.cc b/components/autofill/core/common/autofill_pref_names.cc
index 1dc5b59..4f3b11dc 100644
--- a/components/autofill/core/common/autofill_pref_names.cc
+++ b/components/autofill/core/common/autofill_pref_names.cc
@@ -26,10 +26,17 @@
 // was run. This routine will be run once per version.
 const char kAutofillLastVersionDeduped[] = "autofill.last_version_deduped";
 
-// Boolean that allows the "Don't ask again for this card" checkbox to be
-// sticky.
+// Boolean that is set to the last choice user made when prompted for saving an
+// unmasked server card locally.
 const char kAutofillWalletImportStorageCheckboxState[] =
     "autofill.wallet_import_storage_checkbox_state";
 
+// Integer that is set to the last choice user made when prompted for saving a
+// credit card. The prompt is for user's consent in saving the card in the
+// server for signed in users and saving the card locally for non signed-in
+// users.
+const char kAutofillAcceptSaveCreditCardPromptState[] =
+    "autofill.accept_save_credit_card_prompt_state";
+
 }  // namespace prefs
 }  // namespace autofill
diff --git a/components/autofill/core/common/autofill_pref_names.h b/components/autofill/core/common/autofill_pref_names.h
index cab272d..e18a77a 100644
--- a/components/autofill/core/common/autofill_pref_names.h
+++ b/components/autofill/core/common/autofill_pref_names.h
@@ -11,6 +11,7 @@
 // Alphabetical list of preference names specific to the Autofill
 // component. Keep alphabetized, and document each in the .cc file.
 
+extern const char kAutofillAcceptSaveCreditCardPromptState[];
 extern const char kAutofillCreditCardSigninPromoImpressionCount[];
 extern const char kAutofillEnabled[];
 extern const char kAutofillProfileUseDatesFixed[];
@@ -18,6 +19,15 @@
 extern const char kAutofillWalletImportEnabled[];
 extern const char kAutofillWalletImportStorageCheckboxState[];
 
+// Possible values for previous user decision when we displayed a save credit
+// card prompt.
+enum PreviousSaveCreditCardPromptUserDecision {
+  PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_NONE,
+  PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED,
+  PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED,
+  NUM_PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISIONS
+};
+
 }  // namespace prefs
 }  // namespace autofill
 
diff --git a/components/chrome_cleaner/public/interfaces/BUILD.gn b/components/chrome_cleaner/public/interfaces/BUILD.gn
index 039f122..16089838 100644
--- a/components/chrome_cleaner/public/interfaces/BUILD.gn
+++ b/components/chrome_cleaner/public/interfaces/BUILD.gn
@@ -8,4 +8,7 @@
   sources = [
     "chrome_prompt.mojom",
   ]
+  deps = [
+    "//mojo/common:common_custom_types",
+  ]
 }
diff --git a/components/chrome_cleaner/public/interfaces/chrome_prompt.mojom b/components/chrome_cleaner/public/interfaces/chrome_prompt.mojom
index 7458a8e..b10a3a1 100644
--- a/components/chrome_cleaner/public/interfaces/chrome_prompt.mojom
+++ b/components/chrome_cleaner/public/interfaces/chrome_prompt.mojom
@@ -4,6 +4,8 @@
 
 module chrome_cleaner.mojom;
 
+import "mojo/common/file_path.mojom";
+
 // The behaviours that have been observed for a given UwS.
 struct ObservedBehaviours {
   bool ad_injector;
@@ -27,7 +29,7 @@
 
   // List of fully-qualified paths of the files that will be deleted by the
   // Chrome Cleanup Tool for this unwanted software.
-  array<string> files_to_delete;
+  array<mojo.common.mojom.FilePath> files_to_delete;
 };
 
 // Indicates if elevation will be required for cleanup.
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn
index b51708c0c..6af3c08 100644
--- a/components/cronet/android/BUILD.gn
+++ b/components/cronet/android/BUILD.gn
@@ -587,7 +587,6 @@
     "//base:base_java",
     "//base:base_java_test_support",
     "//net/android:net_java_test_support",
-    "//third_party/netty-tcnative:netty-tcnative_java",
     "//third_party/netty4:netty_all_java",
   ]
 
@@ -1024,7 +1023,6 @@
     "$root_out_dir/lib.java/components/cronet/android/cronet_test_apk_java.jar",
     "$root_out_dir/lib.java/net/android/net_java.jar",
     "$root_out_dir/lib.java/net/android/net_java_test_support.jar",
-    "$root_out_dir/lib.java/third_party/netty-tcnative/netty-tcnative_java.jar",
     "$root_out_dir/lib.java/url/url_java.jar",
     NETTY4_JAR_FILE,
   ]
@@ -1053,7 +1051,6 @@
     "//base:base_java_test_support",
     "//net/android:net_java",
     "//net/android:net_java_test_support",
-    "//third_party/netty-tcnative:netty-tcnative_java",
     "//third_party/netty4:netty_all_java",
     "//url:url_java",
   ]
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java
index b9811038..ae4e008 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/BidirectionalStreamTest.java
@@ -46,7 +46,7 @@
 
         mTestFramework = startCronetTestFrameworkWithUrlAndCronetEngineBuilder(null, builder);
         assertTrue(Http2TestServer.startHttp2TestServer(
-                getContext(), QuicTestServer.getServerCert(), QuicTestServer.getServerCertKey()));
+                getContext(), SERVER_CERT_PEM, SERVER_KEY_PKCS8_PEM));
     }
 
     @Override
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java
index b23e4da5..7a06ae7 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java
@@ -20,7 +20,7 @@
         // Load library first to create MockCertVerifier.
         System.loadLibrary("cronet_tests");
         assertTrue(Http2TestServer.startHttp2TestServer(
-                getContext(), QuicTestServer.getServerCert(), QuicTestServer.getServerCertKey()));
+                getContext(), SERVER_CERT_PEM, SERVER_KEY_PKCS8_PEM));
     }
 
     @Override
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestBase.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestBase.java
index bf932bd..4c34173 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestBase.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestBase.java
@@ -24,6 +24,16 @@
  * Base test class for all CronetTest based tests.
  */
 public class CronetTestBase extends AndroidTestCase {
+    /**
+     * Name of the file that contains the test server certificate in PEM format.
+     */
+    static final String SERVER_CERT_PEM = "quic_test.example.com.crt";
+
+    /**
+     * Name of the file that contains the test server private key in PKCS8 PEM format.
+     */
+    static final String SERVER_KEY_PKCS8_PEM = "quic_test.example.com.key.pkcs8.pem";
+
     private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "cronet_test";
     private static final String LOOPBACK_ADDRESS = "127.0.0.1";
 
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/ExperimentalOptionsTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/ExperimentalOptionsTest.java
index 505ee82c..93d96744 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/ExperimentalOptionsTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/ExperimentalOptionsTest.java
@@ -32,7 +32,7 @@
         CronetTestUtil.setMockCertVerifierForTesting(
                 mBuilder, QuicTestServer.createMockCertVerifier());
         assertTrue(Http2TestServer.startHttp2TestServer(
-                getContext(), QuicTestServer.getServerCert(), QuicTestServer.getServerCertKey()));
+                getContext(), SERVER_CERT_PEM, SERVER_KEY_PKCS8_PEM));
     }
 
     @Override
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java
index d130350..05958b6 100644
--- a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java
+++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java
@@ -18,6 +18,16 @@
 class ChromiumNativeTestSupport extends ChromiumPlatformOnlyTestSupport {
     private static final String TAG = ChromiumNativeTestSupport.class.getSimpleName();
 
+    /**
+     * Name of the file that contains the test server certificate in PEM format.
+     */
+    private static final String SERVER_CERT_PEM = "quic_test.example.com.crt";
+
+    /**
+     * Name of the file that contains the test server private key in PKCS8 PEM format.
+     */
+    private static final String SERVER_KEY_PKCS8_PEM = "quic_test.example.com.key.pkcs8.pem";
+
     @Override
     public TestServer createTestServer(Context context, Protocol protocol) {
         switch (protocol) {
@@ -87,9 +97,8 @@
         @Override
         public boolean start() {
             try {
-                return org.chromium.net.Http2TestServer.startHttp2TestServer(mContext,
-                        org.chromium.net.QuicTestServer.getServerCert(),
-                        org.chromium.net.QuicTestServer.getServerCertKey());
+                return org.chromium.net.Http2TestServer.startHttp2TestServer(
+                        mContext, SERVER_CERT_PEM, SERVER_KEY_PKCS8_PEM);
             } catch (Exception e) {
                 Log.e(TAG, "Exception during Http2TestServer start", e);
                 return false;
diff --git a/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.cc b/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.cc
index 9d8a362..2bd7ad5 100644
--- a/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.cc
+++ b/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.cc
@@ -6,9 +6,11 @@
 
 #include <vector>
 
+#include "base/android/callback_android.h"
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
 #include "base/feature_list.h"
 #include "components/feature_engagement_tracker/internal/feature_list.h"
 #include "components/feature_engagement_tracker/public/feature_engagement_tracker.h"
@@ -121,11 +123,22 @@
   feature_engagement_tracker_impl_->Dismissed();
 }
 
+bool FeatureEngagementTrackerImplAndroid::IsInitialized(
+    JNIEnv* env,
+    const base::android::JavaRef<jobject>& jobj) {
+  return feature_engagement_tracker_impl_->IsInitialized();
+}
+
 void FeatureEngagementTrackerImplAndroid::AddOnInitializedCallback(
     JNIEnv* env,
     const base::android::JavaRef<jobject>& jobj,
     const base::android::JavaParamRef<jobject>& j_callback_obj) {
-  // TODO(nyquist): Implement support for the wrapped base::Callback.
+  // Disambiguate RunCallbackAndroid to get the reference to the bool version.
+  void (*runBoolCallback)(const base::android::JavaRef<jobject>&, bool) =
+      &base::android::RunCallbackAndroid;
+  feature_engagement_tracker_impl_->AddOnInitializedCallback(
+      base::Bind(runBoolCallback,
+                 base::android::ScopedJavaGlobalRef<jobject>(j_callback_obj)));
 }
 
 }  // namespace feature_engagement_tracker
diff --git a/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.h b/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.h
index 1234ae8..79f3c41 100644
--- a/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.h
+++ b/components/feature_engagement_tracker/internal/android/feature_engagement_tracker_impl_android.h
@@ -54,6 +54,8 @@
       const base::android::JavaParamRef<jstring>& jfeature);
   virtual void Dismissed(JNIEnv* env,
                          const base::android::JavaRef<jobject>& jobj);
+  virtual bool IsInitialized(JNIEnv* env,
+                             const base::android::JavaRef<jobject>& jobj);
   virtual void AddOnInitializedCallback(
       JNIEnv* env,
       const base::android::JavaRef<jobject>& jobj,
diff --git a/components/feature_engagement_tracker/internal/android/java/src/org/chromium/components/feature_engagement_tracker/internal/FeatureEngagementTrackerImpl.java b/components/feature_engagement_tracker/internal/android/java/src/org/chromium/components/feature_engagement_tracker/internal/FeatureEngagementTrackerImpl.java
index 734b5ba2..8626790 100644
--- a/components/feature_engagement_tracker/internal/android/java/src/org/chromium/components/feature_engagement_tracker/internal/FeatureEngagementTrackerImpl.java
+++ b/components/feature_engagement_tracker/internal/android/java/src/org/chromium/components/feature_engagement_tracker/internal/FeatureEngagementTrackerImpl.java
@@ -48,6 +48,12 @@
     }
 
     @Override
+    public boolean isInitialized() {
+        assert mNativePtr != 0;
+        return nativeIsInitialized(mNativePtr);
+    }
+
+    @Override
     public void addOnInitializedCallback(Callback<Boolean> callback) {
         assert mNativePtr != 0;
         nativeAddOnInitializedCallback(mNativePtr, callback);
@@ -69,6 +75,7 @@
     private native boolean nativeShouldTriggerHelpUI(
             long nativeFeatureEngagementTrackerImplAndroid, String feature);
     private native void nativeDismissed(long nativeFeatureEngagementTrackerImplAndroid);
+    private native boolean nativeIsInitialized(long nativeFeatureEngagementTrackerImplAndroid);
     private native void nativeAddOnInitializedCallback(
             long nativeFeatureEngagementTrackerImplAndroid, Callback<Boolean> callback);
 }
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
index 4f1d3bf..ecda0ed 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "components/feature_engagement_tracker/internal/editable_configuration.h"
 #include "components/feature_engagement_tracker/internal/feature_list.h"
 #include "components/feature_engagement_tracker/internal/in_memory_store.h"
@@ -73,6 +74,7 @@
     std::unique_ptr<ConditionValidator> condition_validator,
     std::unique_ptr<StorageValidator> storage_validator)
     : condition_validator_(std::move(condition_validator)),
+      initialization_finished_(false),
       weak_ptr_factory_(this) {
   model_ = base::MakeUnique<ModelImpl>(
       std::move(store), std::move(configuration), std::move(storage_validator));
@@ -101,13 +103,31 @@
   model_->SetIsCurrentlyShowing(false);
 }
 
-void FeatureEngagementTrackerImpl::AddOnInitializedCallback(
-    OnInitializedCallback callback) {
-  // TODO(nyquist): Add support for this.
+bool FeatureEngagementTrackerImpl::IsInitialized() {
+  return model_->IsReady();
 }
 
-void FeatureEngagementTrackerImpl::OnModelInitializationFinished(bool result) {
-  // TODO(nyquist): Ensure that all OnInitializedCallbacks are invoked.
+void FeatureEngagementTrackerImpl::AddOnInitializedCallback(
+    OnInitializedCallback callback) {
+  if (initialization_finished_) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(callback, model_->IsReady()));
+    return;
+  }
+
+  on_initialized_callbacks_.push_back(callback);
+}
+
+void FeatureEngagementTrackerImpl::OnModelInitializationFinished(bool success) {
+  DCHECK_EQ(success, model_->IsReady());
+  initialization_finished_ = true;
+
+  for (auto& callback : on_initialized_callbacks_) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(callback, success));
+  }
+
+  on_initialized_callbacks_.clear();
 }
 
 }  // namespace feature_engagement_tracker
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h
index 630b396..9ce2dc9 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h
@@ -36,11 +36,12 @@
   void NotifyEvent(const std::string& event) override;
   bool ShouldTriggerHelpUI(const base::Feature& feature) override;
   void Dismissed() override;
+  bool IsInitialized() override;
   void AddOnInitializedCallback(OnInitializedCallback callback) override;
 
  private:
   // Invoked by the Model when it has been initialized.
-  void OnModelInitializationFinished(bool result);
+  void OnModelInitializationFinished(bool success);
 
   // The current model.
   std::unique_ptr<Model> model_;
@@ -49,6 +50,13 @@
   // help UI.
   std::unique_ptr<ConditionValidator> condition_validator_;
 
+  // Whether the initialization of the underlying model has finished.
+  bool initialization_finished_;
+
+  // The list of callbacks to invoke when initialization has finished. This
+  // is cleared after the initialization has happened.
+  std::vector<OnInitializedCallback> on_initialized_callbacks_;
+
   base::WeakPtrFactory<FeatureEngagementTrackerImpl> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(FeatureEngagementTrackerImpl);
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc
index 37245da..05eb055 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc
@@ -37,16 +37,55 @@
   configuration->SetConfiguration(&feature, config);
 }
 
+// An OnInitializedCallback that stores whether it has been invoked and what
+// the result was.
+class StoringInitializedCallback {
+ public:
+  StoringInitializedCallback() : invoked_(false), success_(false) {}
+
+  void OnInitialized(bool success) {
+    DCHECK(!invoked_);
+    invoked_ = true;
+    success_ = success;
+  }
+
+  bool invoked() { return invoked_; }
+
+  bool success() { return success_; }
+
+ private:
+  bool invoked_;
+  bool success_;
+
+  DISALLOW_COPY_AND_ASSIGN(StoringInitializedCallback);
+};
+
+// An InMemoryStore that is able to fake successful and unsuccessful
+// loading of state.
+class TestInMemoryStore : public InMemoryStore {
+ public:
+  explicit TestInMemoryStore(bool load_should_succeed)
+      : InMemoryStore(), load_should_succeed_(load_should_succeed) {}
+
+  void Load(const OnLoadedCallback& callback) override {
+    HandleLoadResult(callback, load_should_succeed_);
+  }
+
+ private:
+  // Denotes whether the call to Load(...) should succeed or not. This impacts
+  // both the ready-state and the result for the OnLoadedCallback.
+  bool load_should_succeed_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestInMemoryStore);
+};
+
 class FeatureEngagementTrackerImplTest : public ::testing::Test {
  public:
-  FeatureEngagementTrackerImplTest() {
-    std::unique_ptr<Store> store = base::MakeUnique<InMemoryStore>();
+  FeatureEngagementTrackerImplTest() = default;
+
+  void SetUp() override {
     std::unique_ptr<EditableConfiguration> configuration =
         base::MakeUnique<EditableConfiguration>();
-    std::unique_ptr<ConditionValidator> condition_validator =
-        base::MakeUnique<OnceConditionValidator>();
-    std::unique_ptr<StorageValidator> storage_validator =
-        base::MakeUnique<NeverStorageValidator>();
 
     RegisterFeatureConfig(configuration.get(), kTestFeatureFoo, true);
     RegisterFeatureConfig(configuration.get(), kTestFeatureBar, true);
@@ -56,21 +95,177 @@
         {kTestFeatureFoo, kTestFeatureBar, kTestFeatureQux}, {});
 
     tracker_.reset(new FeatureEngagementTrackerImpl(
-        std::move(store), std::move(configuration),
-        std::move(condition_validator), std::move(storage_validator)));
-    // Ensure all initialization is finished.
-    base::RunLoop().RunUntilIdle();
+        CreateStore(), std::move(configuration),
+        base::MakeUnique<OnceConditionValidator>(),
+        base::MakeUnique<NeverStorageValidator>()));
   }
 
  protected:
+  virtual std::unique_ptr<Store> CreateStore() {
+    // Returns a Store that will successfully initialize.
+    return base::MakeUnique<TestInMemoryStore>(true);
+  }
+
   base::MessageLoop message_loop_;
   std::unique_ptr<FeatureEngagementTrackerImpl> tracker_;
   base::test::ScopedFeatureList scoped_feature_list_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FeatureEngagementTrackerImplTest);
+};
+
+// A top-level test class where the store fails to initialize.
+class FailingInitFeatureEngagementTrackerImplTest
+    : public FeatureEngagementTrackerImplTest {
+ public:
+  FailingInitFeatureEngagementTrackerImplTest() = default;
+
+ protected:
+  std::unique_ptr<Store> CreateStore() override {
+    // Returns a Store that will fail to initialize.
+    return base::MakeUnique<TestInMemoryStore>(false);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FailingInitFeatureEngagementTrackerImplTest);
 };
 
 }  // namespace
 
+TEST_F(FeatureEngagementTrackerImplTest, TestInitialization) {
+  EXPECT_FALSE(tracker_->IsInitialized());
+
+  StoringInitializedCallback callback;
+  tracker_->AddOnInitializedCallback(base::Bind(
+      &StoringInitializedCallback::OnInitialized, base::Unretained(&callback)));
+  EXPECT_FALSE(callback.invoked());
+
+  // Ensure all initialization is finished.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(tracker_->IsInitialized());
+  EXPECT_TRUE(callback.invoked());
+  EXPECT_TRUE(callback.success());
+}
+
+TEST_F(FeatureEngagementTrackerImplTest, TestInitializationMultipleCallbacks) {
+  EXPECT_FALSE(tracker_->IsInitialized());
+
+  StoringInitializedCallback callback1;
+  StoringInitializedCallback callback2;
+
+  tracker_->AddOnInitializedCallback(
+      base::Bind(&StoringInitializedCallback::OnInitialized,
+                 base::Unretained(&callback1)));
+  tracker_->AddOnInitializedCallback(
+      base::Bind(&StoringInitializedCallback::OnInitialized,
+                 base::Unretained(&callback2)));
+  EXPECT_FALSE(callback1.invoked());
+  EXPECT_FALSE(callback2.invoked());
+
+  // Ensure all initialization is finished.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(tracker_->IsInitialized());
+  EXPECT_TRUE(callback1.invoked());
+  EXPECT_TRUE(callback2.invoked());
+  EXPECT_TRUE(callback1.success());
+  EXPECT_TRUE(callback2.success());
+}
+
+TEST_F(FeatureEngagementTrackerImplTest, TestAddingCallbackAfterInitFinished) {
+  EXPECT_FALSE(tracker_->IsInitialized());
+
+  // Ensure all initialization is finished.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(tracker_->IsInitialized());
+
+  StoringInitializedCallback callback;
+  tracker_->AddOnInitializedCallback(base::Bind(
+      &StoringInitializedCallback::OnInitialized, base::Unretained(&callback)));
+  EXPECT_FALSE(callback.invoked());
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(callback.invoked());
+}
+
+TEST_F(FeatureEngagementTrackerImplTest,
+       TestAddingCallbackBeforeAndAfterInitFinished) {
+  EXPECT_FALSE(tracker_->IsInitialized());
+
+  // Ensure all initialization is finished.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(tracker_->IsInitialized());
+
+  StoringInitializedCallback callback_before;
+  tracker_->AddOnInitializedCallback(
+      base::Bind(&StoringInitializedCallback::OnInitialized,
+                 base::Unretained(&callback_before)));
+  EXPECT_FALSE(callback_before.invoked());
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(callback_before.invoked());
+
+  StoringInitializedCallback callback_after;
+  tracker_->AddOnInitializedCallback(
+      base::Bind(&StoringInitializedCallback::OnInitialized,
+                 base::Unretained(&callback_after)));
+  EXPECT_FALSE(callback_after.invoked());
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(callback_after.invoked());
+}
+
+TEST_F(FailingInitFeatureEngagementTrackerImplTest, TestFailingInitialization) {
+  EXPECT_FALSE(tracker_->IsInitialized());
+
+  StoringInitializedCallback callback;
+  tracker_->AddOnInitializedCallback(base::Bind(
+      &StoringInitializedCallback::OnInitialized, base::Unretained(&callback)));
+  EXPECT_FALSE(callback.invoked());
+
+  // Ensure all initialization is finished.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(tracker_->IsInitialized());
+  EXPECT_TRUE(callback.invoked());
+  EXPECT_FALSE(callback.success());
+}
+
+TEST_F(FailingInitFeatureEngagementTrackerImplTest,
+       TestFailingInitializationMultipleCallbacks) {
+  EXPECT_FALSE(tracker_->IsInitialized());
+
+  StoringInitializedCallback callback1;
+  StoringInitializedCallback callback2;
+  tracker_->AddOnInitializedCallback(
+      base::Bind(&StoringInitializedCallback::OnInitialized,
+                 base::Unretained(&callback1)));
+  tracker_->AddOnInitializedCallback(
+      base::Bind(&StoringInitializedCallback::OnInitialized,
+                 base::Unretained(&callback2)));
+  EXPECT_FALSE(callback1.invoked());
+  EXPECT_FALSE(callback2.invoked());
+
+  // Ensure all initialization is finished.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(tracker_->IsInitialized());
+  EXPECT_TRUE(callback1.invoked());
+  EXPECT_TRUE(callback2.invoked());
+  EXPECT_FALSE(callback1.success());
+  EXPECT_FALSE(callback2.success());
+}
+
 TEST_F(FeatureEngagementTrackerImplTest, TestTriggering) {
+  // Ensure all initialization is finished.
+  base::RunLoop().RunUntilIdle();
+
   // The first time a feature triggers it should be shown.
   EXPECT_TRUE(tracker_->ShouldTriggerHelpUI(kTestFeatureFoo));
   EXPECT_FALSE(tracker_->ShouldTriggerHelpUI(kTestFeatureFoo));
diff --git a/components/feature_engagement_tracker/internal/model.h b/components/feature_engagement_tracker/internal/model.h
index 116ff42..f74a910 100644
--- a/components/feature_engagement_tracker/internal/model.h
+++ b/components/feature_engagement_tracker/internal/model.h
@@ -22,9 +22,9 @@
 // A Model provides all necessary runtime state.
 class Model {
  public:
-  // Callback for when model initialization has finished. The bool argument
-  // denotes whether the model was successfully initialized.
-  using OnModelInitializationFinished = base::Callback<void(bool)>;
+  // Callback for when model initialization has finished. The |success|
+  // argument denotes whether the model was successfully initialized.
+  using OnModelInitializationFinished = base::Callback<void(bool success)>;
 
   virtual ~Model() = default;
 
@@ -32,7 +32,7 @@
   // required operations have been finished, a callback is posted.
   virtual void Initialize(const OnModelInitializationFinished& callback) = 0;
 
-  // Returns whether the model is ready, i.e. whether it has been fully
+  // Returns whether the model is ready, i.e. whether it has been successfully
   // initialized.
   virtual bool IsReady() const = 0;
 
diff --git a/components/feature_engagement_tracker/public/android/java/src/org/chromium/components/feature_engagement_tracker/FeatureEngagementTracker.java b/components/feature_engagement_tracker/public/android/java/src/org/chromium/components/feature_engagement_tracker/FeatureEngagementTracker.java
index 36c8714..633993f 100644
--- a/components/feature_engagement_tracker/public/android/java/src/org/chromium/components/feature_engagement_tracker/FeatureEngagementTracker.java
+++ b/components/feature_engagement_tracker/public/android/java/src/org/chromium/components/feature_engagement_tracker/FeatureEngagementTracker.java
@@ -36,10 +36,22 @@
     void dismissed();
 
     /**
-     * For features that trigger on startup, they register a callback to ensure that they are told
-     * when the tracker has been initialized. The callback will be invoked when the tracker has
-     * been initialized. The boolean parameter indicated whether the initialization was a success
-     * and that the tracker is ready to receive calls.
+     * Returns whether the tracker has been successfully initialized. During startup, this will be
+     * false until the internal model has been loaded at which point it is set to true if the
+     * initialization was successful. The state will never change from initialized to uninitialized.
+     * Callers can invoke AddOnInitializedCallback(...) to be notified when the result of the
+     * initialization is ready.
+     */
+    boolean isInitialized();
+
+    /**
+     * For features that trigger on startup, they can register a callback to ensure that they are
+     * informed when the tracker has finished the initialization. If the tracker has already been
+     * initialized, the callback will still be invoked with the result. The callback is guaranteed
+     * to be invoked exactly one time.
+     *
+     * The |result| parameter indicates whether the initialization was a success and the tracker is
+     * ready to receive calls.
      */
     void addOnInitializedCallback(Callback<Boolean> callback);
 }
diff --git a/components/feature_engagement_tracker/public/feature_engagement_tracker.h b/components/feature_engagement_tracker/public/feature_engagement_tracker.h
index b151dda4..d969a81 100644
--- a/components/feature_engagement_tracker/public/feature_engagement_tracker.h
+++ b/components/feature_engagement_tracker/public/feature_engagement_tracker.h
@@ -37,8 +37,8 @@
 #endif  // defined(OS_ANDROID)
 
   // Invoked when the tracker has been initialized. The |success| parameter
-  // indicates that the initialization was a success and it it ready to receive
-  // calls.
+  // indicates that the initialization was a success and the tracker is ready to
+  // receive calls.
   using OnInitializedCallback = base::Callback<void(bool success)>;
 
   // The |storage_dir| is the path to where all local storage will be.
@@ -61,8 +61,19 @@
   // Must be called after display of feature enlightenment finishes.
   virtual void Dismissed() = 0;
 
-  // For features that trigger on startup, they register a callback to ensure
-  // that they are told when the tracker has been initialized.
+  // Returns whether the tracker has been successfully initialized. During
+  // startup, this will be false until the internal model has been loaded at
+  // which point it is set to true if the initialization was successful. The
+  // state will never change from initialized to uninitialized.
+  // Callers can invoke AddOnInitializedCallback(...) to be notified when the
+  // result of the initialization is ready.
+  virtual bool IsInitialized() = 0;
+
+  // For features that trigger on startup, they can register a callback to
+  // ensure that they are informed when the tracker has finished the
+  // initialization. If the tracker has already been initialized, the callback
+  // will still be invoked with the result. The callback is guaranteed to be
+  // invoked exactly one time.
   virtual void AddOnInitializedCallback(OnInitializedCallback callback) = 0;
 
  protected:
diff --git a/components/onc/docs/onc_spec.md b/components/onc/docs/onc_spec.md
index c42e357c..de07cb0 100644
--- a/components/onc/docs/onc_spec.md
+++ b/components/onc/docs/onc_spec.md
@@ -1456,6 +1456,12 @@
     * The name of the cellular carrier when the hotspot is provided by a
       cellular connection.
 
+* **HasConnectedToHost**
+    * (read-only) - **boolean**
+    * If *true*, the current device has already connected to a Tether network
+      created by the same external device which is providing this Tether
+      network.
+
 * **SignalStrength**
     * (optional, read-only) - **integer**
     * The current signal strength for the hotspot's connection in the range
diff --git a/components/onc/onc_constants.cc b/components/onc/onc_constants.cc
index d3e68e58..83f6b056 100644
--- a/components/onc/onc_constants.cc
+++ b/components/onc/onc_constants.cc
@@ -197,6 +197,7 @@
 namespace tether {
 const char kBatteryPercentage[] = "BatteryPercentage";
 const char kCarrier[] = "Carrier";
+const char kHasConnectedToHost[] = "HasConnectedToHost";
 const char kSignalStrength[] = "SignalStrength";
 }  // namespace tether
 
diff --git a/components/onc/onc_constants.h b/components/onc/onc_constants.h
index ff6301c..93c92ae 100644
--- a/components/onc/onc_constants.h
+++ b/components/onc/onc_constants.h
@@ -222,6 +222,7 @@
 namespace tether {
 ONC_EXPORT extern const char kBatteryPercentage[];
 ONC_EXPORT extern const char kCarrier[];
+ONC_EXPORT extern const char kHasConnectedToHost[];
 ONC_EXPORT extern const char kSignalStrength[];
 }  // namespace tether
 
diff --git a/components/payments/mojom/payment_request.mojom b/components/payments/mojom/payment_request.mojom
index 99dfa91ba..6192f84 100644
--- a/components/payments/mojom/payment_request.mojom
+++ b/components/payments/mojom/payment_request.mojom
@@ -193,6 +193,11 @@
   array<PaymentShippingOption> shipping_options;
   array<PaymentDetailsModifier> modifiers;
   string error = "";
+  // Identifier identifying the payment request, to be exposed
+  // to payment apps. It is optional since this structure is used
+  // by PaymentDetailsUpdate (next to PaymentDetailsInit) but
+  // PaymentDetailsUpdate has no id.
+  string? id;
 };
 
 enum PaymentShippingType {
diff --git a/components/printing/renderer/print_web_view_helper.cc b/components/printing/renderer/print_web_view_helper.cc
index 9e0c9d3..8d8cdfa 100644
--- a/components/printing/renderer/print_web_view_helper.cc
+++ b/components/printing/renderer/print_web_view_helper.cc
@@ -914,6 +914,7 @@
       is_loading_(false),
       is_scripted_preview_delayed_(false),
       ipc_nesting_level_(0),
+      render_frame_gone_(false),
       weak_ptr_factory_(this) {
   if (!delegate_->IsPrintPreviewEnabled())
     DisablePreview();
@@ -995,8 +996,6 @@
   // choose to ignore message or safely crash process.
   ++ipc_nesting_level_;
 
-  auto self = weak_ptr_factory_.GetWeakPtr();
-
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(PrintWebViewHelper, message)
 #if BUILDFLAG(ENABLE_BASIC_PRINTING)
@@ -1015,13 +1014,17 @@
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
 
-  // Check if |this| is still valid. e.g. when OnPrintPages() returns.
-  if (self)
-    --ipc_nesting_level_;
+  --ipc_nesting_level_;
+  if (ipc_nesting_level_ == 0 && render_frame_gone_)
+    base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
   return handled;
 }
 
 void PrintWebViewHelper::OnDestruct() {
+  if (ipc_nesting_level_ > 0) {
+    render_frame_gone_ = true;
+    return;
+  }
   delete this;
 }
 
@@ -1212,7 +1215,7 @@
   prep_frame_view_->CopySelectionIfNeeded(
       render_frame()->GetWebkitPreferences(),
       base::Bind(&PrintWebViewHelper::OnFramePreparedForPreviewDocument,
-                 base::Unretained(this)));
+                 weak_ptr_factory_.GetWeakPtr()));
 }
 
 void PrintWebViewHelper::OnFramePreparedForPreviewDocument() {
@@ -1839,7 +1842,7 @@
   prep_frame_view_->CopySelectionIfNeeded(
       render_frame()->GetWebkitPreferences(),
       base::Bind(&PrintWebViewHelper::OnFramePreparedForPrintPages,
-                 base::Unretained(this)));
+                 weak_ptr_factory_.GetWeakPtr()));
   return true;
 }
 #endif  // BUILDFLAG(ENABLE_BASIC_PRINTING)
@@ -1983,7 +1986,7 @@
         // DidStopLoading() is called. Defer showing the preview until then.
         on_stop_loading_closure_ =
             base::Bind(&PrintWebViewHelper::ShowScriptedPrintPreview,
-                       base::Unretained(this));
+                       weak_ptr_factory_.GetWeakPtr());
       } else {
         base::ThreadTaskRunnerHandle::Get()->PostTask(
             FROM_HERE, base::Bind(&PrintWebViewHelper::ShowScriptedPrintPreview,
@@ -2006,7 +2009,7 @@
       if (is_loading_ && GetPlugin(print_preview_context_.source_frame())) {
         on_stop_loading_closure_ =
             base::Bind(&PrintWebViewHelper::RequestPrintPreview,
-                       base::Unretained(this), type);
+                       weak_ptr_factory_.GetWeakPtr(), type);
         return;
       }
 
@@ -2022,7 +2025,7 @@
       if (is_loading_ && GetPlugin(print_preview_context_.source_frame())) {
         on_stop_loading_closure_ =
             base::Bind(&PrintWebViewHelper::RequestPrintPreview,
-                       base::Unretained(this), type);
+                       weak_ptr_factory_.GetWeakPtr(), type);
         return;
       }
 
@@ -2091,10 +2094,10 @@
     : total_page_count_(0),
       current_page_index_(0),
       generate_draft_pages_(true),
+      is_modifiable_(true),
       print_ready_metafile_page_count_(0),
       error_(PREVIEW_ERROR_NONE),
-      state_(UNINITIALIZED) {
-}
+      state_(UNINITIALIZED) {}
 
 PrintWebViewHelper::PrintPreviewContext::~PrintPreviewContext() {
 }
@@ -2106,6 +2109,7 @@
   state_ = INITIALIZED;
   source_frame_.Reset(web_frame);
   source_node_.Reset();
+  CalculateIsModifiable();
 }
 
 void PrintWebViewHelper::PrintPreviewContext::InitWithNode(
@@ -2116,6 +2120,7 @@
   state_ = INITIALIZED;
   source_frame_.Reset(web_node.GetDocument().GetFrame());
   source_node_ = web_node;
+  CalculateIsModifiable();
 }
 
 void PrintWebViewHelper::PrintPreviewContext::OnPrintPreview() {
@@ -2241,9 +2246,9 @@
   return state_ == RENDERING || state_ == DONE;
 }
 
-bool PrintWebViewHelper::PrintPreviewContext::IsModifiable() {
-  // The only kind of node we can print right now is a PDF node.
-  return !PrintingNodeOrPdfFrame(source_frame(), source_node_);
+bool PrintWebViewHelper::PrintPreviewContext::IsModifiable() const {
+  DCHECK(state_ != UNINITIALIZED);
+  return is_modifiable_;
 }
 
 bool PrintWebViewHelper::PrintPreviewContext::HasSelection() {
@@ -2320,6 +2325,11 @@
   error_ = PREVIEW_ERROR_NONE;
 }
 
+void PrintWebViewHelper::PrintPreviewContext::CalculateIsModifiable() {
+  // The only kind of node we can print right now is a PDF node.
+  is_modifiable_ = !PrintingNodeOrPdfFrame(source_frame(), source_node_);
+}
+
 void PrintWebViewHelper::SetPrintPagesParams(
     const PrintMsg_PrintPages_Params& settings) {
   print_pages_params_ = base::MakeUnique<PrintMsg_PrintPages_Params>(settings);
diff --git a/components/printing/renderer/print_web_view_helper.h b/components/printing/renderer/print_web_view_helper.h
index 652fbf03..15d2c28 100644
--- a/components/printing/renderer/print_web_view_helper.h
+++ b/components/printing/renderer/print_web_view_helper.h
@@ -449,7 +449,7 @@
     // Helper functions
     int GetNextPageNumber();
     bool IsRendering() const;
-    bool IsModifiable();
+    bool IsModifiable() const;
     bool HasSelection();
     bool IsLastPageOfPrintReadyMetafile() const;
     bool IsFinalPageRendered() const;
@@ -487,6 +487,8 @@
     // Reset some of the internal rendering context.
     void ClearContext();
 
+    void CalculateIsModifiable();
+
     // Specifies what to render for print preview.
     FrameReference source_frame_;
     blink::WebNode source_node_;
@@ -506,6 +508,9 @@
     // True, when draft pages needs to be generated.
     bool generate_draft_pages_;
 
+    // True, if the document source is modifiable. e.g. HTML and not PDF.
+    bool is_modifiable_;
+
     // Specifies the total number of pages in the print ready metafile.
     int print_ready_metafile_page_count_;
 
@@ -515,6 +520,8 @@
     enum PrintPreviewErrorBuckets error_;
 
     State state_;
+
+    DISALLOW_COPY_AND_ASSIGN(PrintPreviewContext);
   };
 
   class ScriptingThrottler {
@@ -541,6 +548,7 @@
   bool is_loading_;
   bool is_scripted_preview_delayed_;
   int ipc_nesting_level_;
+  bool render_frame_gone_;
 
   // Used to fix a race condition where the source is a PDF and print preview
   // hangs because RequestPrintPreview is called before DidStopLoading() is
diff --git a/components/proximity_auth/fake_remote_device_life_cycle.cc b/components/proximity_auth/fake_remote_device_life_cycle.cc
index e8d70396..c5bf7111 100644
--- a/components/proximity_auth/fake_remote_device_life_cycle.cc
+++ b/components/proximity_auth/fake_remote_device_life_cycle.cc
@@ -22,6 +22,10 @@
   return remote_device_;
 }
 
+cryptauth::Connection* FakeRemoteDeviceLifeCycle::GetConnection() const {
+  return connection_;
+}
+
 RemoteDeviceLifeCycle::State FakeRemoteDeviceLifeCycle::GetState() const {
   return state_;
 }
diff --git a/components/proximity_auth/fake_remote_device_life_cycle.h b/components/proximity_auth/fake_remote_device_life_cycle.h
index 548099b1..4307b3c 100644
--- a/components/proximity_auth/fake_remote_device_life_cycle.h
+++ b/components/proximity_auth/fake_remote_device_life_cycle.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "base/observer_list.h"
+#include "components/cryptauth/fake_connection.h"
 #include "components/cryptauth/remote_device.h"
 #include "components/proximity_auth/remote_device_life_cycle.h"
 
@@ -20,6 +21,7 @@
   // RemoteDeviceLifeCycle:
   void Start() override;
   cryptauth::RemoteDevice GetRemoteDevice() const override;
+  cryptauth::Connection* GetConnection() const override;
   State GetState() const override;
   Messenger* GetMessenger() override;
   void AddObserver(Observer* observer) override;
@@ -30,6 +32,10 @@
 
   void set_messenger(Messenger* messenger) { messenger_ = messenger; }
 
+  void set_connection(cryptauth::Connection* connection) {
+    connection_ = connection;
+  }
+
   bool started() { return started_; }
 
   base::ObserverList<Observer>& observers() { return observers_; }
@@ -43,6 +49,8 @@
 
   State state_;
 
+  cryptauth::Connection* connection_;
+
   Messenger* messenger_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeRemoteDeviceLifeCycle);
diff --git a/components/proximity_auth/messenger.h b/components/proximity_auth/messenger.h
index d88f7d88..7267982 100644
--- a/components/proximity_auth/messenger.h
+++ b/components/proximity_auth/messenger.h
@@ -42,6 +42,11 @@
   // Returns the SecureContext instance used by the messenger. Ownership of the
   // SecureContext is not passed.
   virtual cryptauth::SecureContext* GetSecureContext() const = 0;
+
+  // Returns the underlying raw connection. Note that you should use
+  // |GetSecureContext()| instead if you want to send and receive messages
+  // securely.
+  virtual cryptauth::Connection* GetConnection() const = 0;
 };
 
 }  // namespace proximity_auth
diff --git a/components/proximity_auth/messenger_impl.cc b/components/proximity_auth/messenger_impl.cc
index 1ffa646bd..da2c602 100644
--- a/components/proximity_auth/messenger_impl.cc
+++ b/components/proximity_auth/messenger_impl.cc
@@ -154,6 +154,10 @@
   return secure_context_.get();
 }
 
+cryptauth::Connection* MessengerImpl::GetConnection() const {
+  return connection_.get();
+}
+
 MessengerImpl::PendingMessage::PendingMessage() {}
 
 MessengerImpl::PendingMessage::PendingMessage(
diff --git a/components/proximity_auth/messenger_impl.h b/components/proximity_auth/messenger_impl.h
index 47310a0..e7c7fd5d 100644
--- a/components/proximity_auth/messenger_impl.h
+++ b/components/proximity_auth/messenger_impl.h
@@ -44,6 +44,7 @@
   void RequestDecryption(const std::string& challenge) override;
   void RequestUnlock() override;
   cryptauth::SecureContext* GetSecureContext() const override;
+  cryptauth::Connection* GetConnection() const override;
 
   // Exposed for testing.
   cryptauth::Connection* connection() { return connection_.get(); }
diff --git a/components/proximity_auth/proximity_monitor.h b/components/proximity_auth/proximity_monitor.h
index b2978bf..b441cce 100644
--- a/components/proximity_auth/proximity_monitor.h
+++ b/components/proximity_auth/proximity_monitor.h
@@ -13,8 +13,6 @@
 // sufficiently close to the local device to permit unlocking.
 class ProximityMonitor {
  public:
-  enum class Strategy { NONE, CHECK_RSSI, CHECK_TRANSMIT_POWER };
-
   virtual ~ProximityMonitor() {}
 
   // Activates the proximity monitor. No-op if the proximity monitor is already
@@ -25,18 +23,10 @@
   // already inactive.
   virtual void Stop() = 0;
 
-  // Returns the strategy used to determine whether the remote device is in
-  // proximity.
-  virtual Strategy GetStrategy() const = 0;
-
   // Returns |true| iff the remote device is close enough to the local device,
   // given the user's current settings.
   virtual bool IsUnlockAllowed() const = 0;
 
-  // Returns |true| iff the remote device is close enough to the local device,
-  // according to its RSSI measurement.
-  virtual bool IsInRssiRange() const = 0;
-
   // Records the current proximity measurements to UMA. This should be called
   // when the user successfully authenticates using proximity auth.
   virtual void RecordProximityMetricsOnAuthSuccess() = 0;
diff --git a/components/proximity_auth/proximity_monitor_impl.cc b/components/proximity_auth/proximity_monitor_impl.cc
index 02422ca..22bedf4 100644
--- a/components/proximity_auth/proximity_monitor_impl.cc
+++ b/components/proximity_auth/proximity_monitor_impl.cc
@@ -27,16 +27,15 @@
 
 // The RSSI threshold below which we consider the remote device to not be in
 // proximity.
-const int kRssiThreshold = -5;
+const int kRssiThreshold = -45;
 
 // The weight of the most recent RSSI sample.
 const double kRssiSampleWeight = 0.3;
 
 ProximityMonitorImpl::ProximityMonitorImpl(
-    const cryptauth::RemoteDevice& remote_device,
+    cryptauth::Connection* connection,
     std::unique_ptr<base::TickClock> clock)
-    : remote_device_(remote_device),
-      strategy_(Strategy::NONE),
+    : connection_(connection),
       remote_device_is_in_proximity_(false),
       is_active_(false),
       clock_(std::move(clock)),
@@ -50,11 +49,6 @@
     PA_LOG(ERROR) << "[Proximity] Proximity monitoring unavailable: "
                   << "Bluetooth is unsupported on this platform.";
   }
-
-  // TODO(isherman): Test prefs to set the strategy. Need to read from "Local
-  // State" prefs on the sign-in screen, and per-user prefs on the lock screen.
-  // TODO(isherman): Unlike in the JS app, destroy and recreate the proximity
-  // monitor when the connection state changes.
 }
 
 ProximityMonitorImpl::~ProximityMonitorImpl() {
@@ -71,17 +65,8 @@
   UpdatePollingState();
 }
 
-ProximityMonitor::Strategy ProximityMonitorImpl::GetStrategy() const {
-  return strategy_;
-}
-
 bool ProximityMonitorImpl::IsUnlockAllowed() const {
-  return strategy_ == Strategy::NONE || remote_device_is_in_proximity_;
-}
-
-bool ProximityMonitorImpl::IsInRssiRange() const {
-  return (strategy_ != Strategy::NONE && rssi_rolling_average_ &&
-          *rssi_rolling_average_ > kRssiThreshold);
+  return remote_device_is_in_proximity_;
 }
 
 void ProximityMonitorImpl::RecordProximityMetricsOnAuthSuccess() {
@@ -89,26 +74,12 @@
                                     ? *rssi_rolling_average_
                                     : metrics::kUnknownProximityValue;
 
-  int last_transmit_power_delta =
-      last_transmit_power_reading_
-          ? (last_transmit_power_reading_->transmit_power -
-             last_transmit_power_reading_->max_transmit_power)
-          : metrics::kUnknownProximityValue;
-
-  // If no zero RSSI value has been read, then record an overflow.
-  base::TimeDelta time_since_last_zero_rssi;
-  if (last_zero_rssi_timestamp_)
-    time_since_last_zero_rssi = clock_->NowTicks() - *last_zero_rssi_timestamp_;
-  else
-    time_since_last_zero_rssi = base::TimeDelta::FromDays(100);
-
   std::string remote_device_model = metrics::kUnknownDeviceModel;
-  if (remote_device_.name != remote_device_.bluetooth_address)
-    remote_device_model = remote_device_.name;
+  cryptauth::RemoteDevice remote_device = connection_->remote_device();
+  if (!remote_device.name.empty())
+    remote_device_model = remote_device.name;
 
   metrics::RecordAuthProximityRollingRssi(round(rssi_rolling_average));
-  metrics::RecordAuthProximityTransmitPowerDelta(last_transmit_power_delta);
-  metrics::RecordAuthProximityTimeSinceLastZeroRssi(time_since_last_zero_rssi);
   metrics::RecordAuthProximityRemoteDeviceModelHash(remote_device_model);
 }
 
@@ -120,24 +91,6 @@
   observers_.RemoveObserver(observer);
 }
 
-void ProximityMonitorImpl::SetStrategy(Strategy strategy) {
-  if (strategy_ == strategy)
-    return;
-  strategy_ = strategy;
-  CheckForProximityStateChange();
-  UpdatePollingState();
-}
-
-ProximityMonitorImpl::TransmitPowerReading::TransmitPowerReading(
-    int transmit_power,
-    int max_transmit_power)
-    : transmit_power(transmit_power), max_transmit_power(max_transmit_power) {
-}
-
-bool ProximityMonitorImpl::TransmitPowerReading::IsInProximity() const {
-  return transmit_power < max_transmit_power;
-}
-
 void ProximityMonitorImpl::OnAdapterInitialized(
     scoped_refptr<device::BluetoothAdapter> adapter) {
   bluetooth_adapter_ = adapter;
@@ -170,25 +123,23 @@
 }
 
 bool ProximityMonitorImpl::ShouldPoll() const {
-  // Note: We poll even if the strategy is NONE so we can record measurements.
   return is_active_ && bluetooth_adapter_;
 }
 
 void ProximityMonitorImpl::Poll() {
   DCHECK(ShouldPoll());
 
-  BluetoothDevice* device =
-      bluetooth_adapter_->GetDevice(remote_device_.bluetooth_address);
+  std::string address = connection_->GetDeviceAddress();
+  BluetoothDevice* device = bluetooth_adapter_->GetDevice(address);
 
   if (!device) {
-    PA_LOG(ERROR) << "Unknown Bluetooth device with address "
-                  << remote_device_.bluetooth_address;
+    PA_LOG(ERROR) << "Unknown Bluetooth device with address " << address;
     ClearProximityState();
     return;
   }
   if (!device->IsConnected()) {
-    PA_LOG(ERROR) << "Bluetooth device with address "
-                  << remote_device_.bluetooth_address << " is not connected.";
+    PA_LOG(ERROR) << "Bluetooth device with address " << address
+                  << " is not connected.";
     ClearProximityState();
     return;
   }
@@ -204,17 +155,12 @@
     return;
   }
 
-  if (connection_info.rssi != BluetoothDevice::kUnknownPower &&
-      connection_info.transmit_power != BluetoothDevice::kUnknownPower &&
-      connection_info.max_transmit_power != BluetoothDevice::kUnknownPower) {
+  if (connection_info.rssi != BluetoothDevice::kUnknownPower) {
     AddSample(connection_info);
   } else {
     PA_LOG(WARNING) << "[Proximity] Unkown values received from API: "
-                    << connection_info.rssi << " "
-                    << connection_info.transmit_power << " "
-                    << connection_info.max_transmit_power;
+                    << connection_info.rssi;
     rssi_rolling_average_.reset();
-    last_transmit_power_reading_.reset();
     CheckForProximityStateChange();
   }
 }
@@ -227,8 +173,6 @@
 
   remote_device_is_in_proximity_ = false;
   rssi_rolling_average_.reset();
-  last_transmit_power_reading_.reset();
-  last_zero_rssi_timestamp_.reset();
 }
 
 void ProximityMonitorImpl::AddSample(
@@ -240,33 +184,16 @@
     *rssi_rolling_average_ =
         weight * connection_info.rssi + (1 - weight) * (*rssi_rolling_average_);
   }
-  last_transmit_power_reading_.reset(new TransmitPowerReading(
-      connection_info.transmit_power, connection_info.max_transmit_power));
-
-  // It's rare but possible for the RSSI to be positive briefly.
-  if (connection_info.rssi >= 0)
-    last_zero_rssi_timestamp_.reset(new base::TimeTicks(clock_->NowTicks()));
 
   CheckForProximityStateChange();
 }
 
 void ProximityMonitorImpl::CheckForProximityStateChange() {
-  if (strategy_ == Strategy::NONE)
-    return;
+  bool is_now_in_proximity =
+      rssi_rolling_average_ && *rssi_rolling_average_ > kRssiThreshold;
 
-  bool is_now_in_proximity = false;
-  switch (strategy_) {
-    case Strategy::NONE:
-      return;
-
-    case Strategy::CHECK_RSSI:
-      is_now_in_proximity = IsInRssiRange();
-      break;
-
-    case Strategy::CHECK_TRANSMIT_POWER:
-      is_now_in_proximity = (last_transmit_power_reading_ &&
-                             last_transmit_power_reading_->IsInProximity());
-      break;
+  if (rssi_rolling_average_) {
+    LOG(WARNING) << "RSSI: " << *rssi_rolling_average_;
   }
 
   if (remote_device_is_in_proximity_ != is_now_in_proximity) {
diff --git a/components/proximity_auth/proximity_monitor_impl.h b/components/proximity_auth/proximity_monitor_impl.h
index 11cffd3..1aebd2e 100644
--- a/components/proximity_auth/proximity_monitor_impl.h
+++ b/components/proximity_auth/proximity_monitor_impl.h
@@ -11,13 +11,13 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "components/cryptauth/connection.h"
 #include "components/cryptauth/remote_device.h"
 #include "components/proximity_auth/proximity_monitor.h"
 #include "device/bluetooth/bluetooth_device.h"
 
 namespace base {
 class TickClock;
-class TimeTicks;
 }
 
 namespace device {
@@ -31,41 +31,20 @@
 // The concrete implemenation of the proximity monitor interface.
 class ProximityMonitorImpl : public ProximityMonitor {
  public:
-  // The |observer| is not owned, and must outlive |this| instance.
-  ProximityMonitorImpl(const cryptauth::RemoteDevice& remote_device,
+  // The |connection| is not owned, and must outlive |this| instance.
+  ProximityMonitorImpl(cryptauth::Connection* connection,
                        std::unique_ptr<base::TickClock> clock);
   ~ProximityMonitorImpl() override;
 
   // ProximityMonitor:
   void Start() override;
   void Stop() override;
-  Strategy GetStrategy() const override;
   bool IsUnlockAllowed() const override;
-  bool IsInRssiRange() const override;
   void RecordProximityMetricsOnAuthSuccess() override;
   void AddObserver(ProximityMonitorObserver* observer) override;
   void RemoveObserver(ProximityMonitorObserver* observer) override;
 
- protected:
-  // Sets the proximity detection strategy. Exposed for testing.
-  // TODO(isherman): Stop exposing this for testing once prefs are properly
-  // hooked up.
-  virtual void SetStrategy(Strategy strategy);
-
  private:
-  struct TransmitPowerReading {
-    TransmitPowerReading(int transmit_power, int max_transmit_power);
-
-    // Returns true if |this| transmit power reading indicates proximity.
-    bool IsInProximity() const;
-
-    // The current transmit power.
-    int transmit_power;
-
-    // The maximum possible transmit power.
-    int max_transmit_power;
-  };
-
   // Callback for asynchronous initialization of the Bluetooth adpater.
   void OnAdapterInitialized(scoped_refptr<device::BluetoothAdapter> adapter);
 
@@ -95,7 +74,7 @@
   void ClearProximityState();
 
   // Updates the proximity state with a new |connection_info| sample of the
-  // current RSSI and Tx power, and the device's maximum Tx power.
+  // current RSSI.
   void AddSample(
       const device::BluetoothDevice::ConnectionInfo& connection_info);
 
@@ -103,8 +82,9 @@
   // samples. Notifies |observers_| on a change.
   void CheckForProximityStateChange();
 
-  // The remote device being monitored.
-  const cryptauth::RemoteDevice remote_device_;
+  // The current connection being monitored. Not owned and must outlive this
+  // instance.
+  cryptauth::Connection* connection_;
 
   // The observers attached to the ProximityMonitor.
   base::ObserverList<ProximityMonitorObserver> observers_;
@@ -112,9 +92,6 @@
   // The Bluetooth adapter that will be polled for connection info.
   scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
 
-  // The strategy used to determine whether the remote device is in proximity.
-  Strategy strategy_;
-
   // Whether the remote device is currently in close proximity to the local
   // device.
   bool remote_device_is_in_proximity_;
@@ -129,18 +106,6 @@
   // measurement.
   std::unique_ptr<double> rssi_rolling_average_;
 
-  // The last TX power reading. Null if the monitor is inactive, has not
-  // recently observed a TX power reading, or the most recent connection info
-  // included an invalid measurement.
-  std::unique_ptr<TransmitPowerReading> last_transmit_power_reading_;
-
-  // The timestamp of the last zero RSSI reading. An RSSI value of 0 is special
-  // because both devices adjust their transmit powers such that the RSSI is in
-  // this golden range, if possible. Null if the monitor is inactive, has not
-  // recently observed an RSSI reading, or the most recent connection info
-  // included an invalid measurement.
-  std::unique_ptr<base::TimeTicks> last_zero_rssi_timestamp_;
-
   // Used to access non-decreasing time measurements.
   std::unique_ptr<base::TickClock> clock_;
 
diff --git a/components/proximity_auth/proximity_monitor_impl_unittest.cc b/components/proximity_auth/proximity_monitor_impl_unittest.cc
index af5f8eb..9130dfd 100644
--- a/components/proximity_auth/proximity_monitor_impl_unittest.cc
+++ b/components/proximity_auth/proximity_monitor_impl_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "components/cryptauth/fake_connection.h"
 #include "components/cryptauth/remote_device.h"
 #include "components/proximity_auth/logging/logging.h"
 #include "components/proximity_auth/proximity_monitor_observer.h"
@@ -33,24 +34,11 @@
 namespace {
 
 const char kRemoteDeviceUserId[] = "example@gmail.com";
-const char kBluetoothAddress[] = "AA:BB:CC:DD:EE:FF";
 const char kRemoteDevicePublicKey[] = "Remote Public Key";
 const char kRemoteDeviceName[] = "LGE Nexus 5";
+const char kBluetoothAddress[] = "AA:BB:CC:DD:EE:FF";
 const char kPersistentSymmetricKey[] = "PSK";
-
-class TestProximityMonitorImpl : public ProximityMonitorImpl {
- public:
-  explicit TestProximityMonitorImpl(
-      const cryptauth::RemoteDevice& remote_device,
-      std::unique_ptr<base::TickClock> clock)
-      : ProximityMonitorImpl(remote_device, std::move(clock)) {}
-  ~TestProximityMonitorImpl() override {}
-
-  using ProximityMonitorImpl::SetStrategy;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TestProximityMonitorImpl);
-};
+const int kRssiThreshold = -45;
 
 class MockProximityMonitorObserver : public ProximityMonitorObserver {
  public:
@@ -83,17 +71,17 @@
         remote_bluetooth_device_(&*bluetooth_adapter_,
                                  0,
                                  kRemoteDeviceName,
-                                 kBluetoothAddress,
+                                 "",
                                  false /* paired */,
                                  true /* connected */),
-        monitor_(
-            cryptauth::RemoteDevice(kRemoteDeviceUserId,
-                                    kRemoteDeviceName,
-                                    kRemoteDevicePublicKey,
-                                    kBluetoothAddress,
-                                    kPersistentSymmetricKey,
-                                    std::string()),
-            base::WrapUnique(clock_)),
+        remote_device_(kRemoteDeviceUserId,
+                       kRemoteDeviceName,
+                       kRemoteDevicePublicKey,
+                       kBluetoothAddress,
+                       kPersistentSymmetricKey,
+                       std::string()),
+        connection_(remote_device_),
+        monitor_(&connection_, base::WrapUnique(clock_)),
         task_runner_(new base::TestSimpleTaskRunner()),
         thread_task_runner_handle_(task_runner_) {
     ON_CALL(*bluetooth_adapter_, GetDevice(kBluetoothAddress))
@@ -102,6 +90,7 @@
         .WillByDefault(SaveArg<0>(&connection_info_callback_));
     monitor_.AddObserver(&observer_);
   }
+
   ~ProximityAuthProximityMonitorImplTest() override {}
 
   void RunPendingTasks() { task_runner_->RunPendingTasks(); }
@@ -126,9 +115,11 @@
   // Mocks used for verifying interactions with the Bluetooth subsystem.
   scoped_refptr<device::MockBluetoothAdapter> bluetooth_adapter_;
   NiceMock<device::MockBluetoothDevice> remote_bluetooth_device_;
+  cryptauth::RemoteDevice remote_device_;
+  cryptauth::FakeConnection connection_;
 
   // The proximity monitor under test.
-  TestProximityMonitorImpl monitor_;
+  ProximityMonitorImpl monitor_;
 
  private:
   scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
@@ -137,304 +128,124 @@
   ScopedDisableLoggingForTesting disable_logging_;
 };
 
-TEST_F(ProximityAuthProximityMonitorImplTest, GetStrategy) {
-  EXPECT_EQ(ProximityMonitor::Strategy::NONE, monitor_.GetStrategy());
-
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  EXPECT_EQ(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER,
-            monitor_.GetStrategy());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_StrategyIsNone) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::NONE);
-
-  EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_NeverStarted) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-
+TEST_F(ProximityAuthProximityMonitorImplTest, IsUnlockAllowed_NeverStarted) {
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_Started_NoConnectionInfoReceivedYet) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
+       IsUnlockAllowed_Started_NoConnectionInfoReceivedYet) {
+  monitor_.Start();
+  EXPECT_FALSE(monitor_.IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthProximityMonitorImplTest, IsUnlockAllowed_RssiInRange) {
+  monitor_.Start();
+  ProvideConnectionInfo({0, 4, 4});
+  EXPECT_TRUE(monitor_.IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthProximityMonitorImplTest, IsUnlockAllowed_UnknownRssi) {
   monitor_.Start();
 
+  ProvideConnectionInfo({0, 0, 4});
+  ProvideConnectionInfo({BluetoothDevice::kUnknownPower, 0, 4});
+
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_InformsObserverOfChanges) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-
+       IsUnlockAllowed_InformsObserverOfChanges) {
   // Initially, the device is not in proximity.
   monitor_.Start();
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
 
-  // Simulate a reading indicating proximity.
+  // Simulate receiving an RSSI reading in proximity.
   EXPECT_CALL(observer_, OnProximityStateChanged()).Times(1);
-  ProvideConnectionInfo({0, 0, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2, 4, 4});
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
 
   // Simulate a reading indicating non-proximity.
   EXPECT_CALL(observer_, OnProximityStateChanged()).Times(1);
-  ProvideConnectionInfo({0, 4, 4});
+  ProvideConnectionInfo({2 * kRssiThreshold, 4, 4});
+  ProvideConnectionInfo({2 * kRssiThreshold, 4, 4});
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
 }
 
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckRssi_RssiIndicatesProximity_TxPowerDoesNot) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
+TEST_F(ProximityAuthProximityMonitorImplTest, IsUnlockAllowed_StartThenStop) {
   monitor_.Start();
 
-  ProvideConnectionInfo({0, 4, 4});
-
+  ProvideConnectionInfo({0, 0, 4});
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
-}
 
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckRssi_TxPowerIndicatesProximity_RssiDoesNot) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-  monitor_.Start();
-
-  ProvideConnectionInfo({-10, 0, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckRssi_NeitherRssiNorTxPowerIndicatesProximity) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-  monitor_.Start();
-
-  ProvideConnectionInfo({-10, 4, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckRssi_BothRssiAndTxPowerIndicateProximity) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-
-  EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckRssi_UnknownRssi) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-  ProvideConnectionInfo({BluetoothDevice::kUnknownPower, 0, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckRssi_UnknownTxPower) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-  ProvideConnectionInfo({0, BluetoothDevice::kUnknownPower, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckRssi_UnknownMaxTxPower) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-  ProvideConnectionInfo({0, 0, BluetoothDevice::kUnknownPower});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckTxPower_RssiIndicatesProximity_TxPowerDoesNot) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 4, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckTxPower_TxPowerIndicatesProximity_RssiDoesNot) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  monitor_.Start();
-
-  ProvideConnectionInfo({-10, 0, 4});
-
-  EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckTxPower_NeitherRssiNorTxPowerIndicatesProximity) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  monitor_.Start();
-
-  ProvideConnectionInfo({-10, 4, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckTxPower_BothRssiAndTxPowerIndicateProximity) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-
-  EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckTxPower_UnknownRssi) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-  ProvideConnectionInfo({BluetoothDevice::kUnknownPower, 0, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckTxPower_UnknownTxPower) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-  ProvideConnectionInfo({0, BluetoothDevice::kUnknownPower, 4});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_CheckTxPower_UnknownMaxTxPower) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER);
-  monitor_.Start();
-
-  ProvideConnectionInfo({0, 0, 4});
-  ProvideConnectionInfo({0, 0, BluetoothDevice::kUnknownPower});
-
-  EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
-}
-
-TEST_F(ProximityAuthProximityMonitorImplTest, ProximityState_StartThenStop) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-
-  monitor_.Start();
-  ProvideConnectionInfo({0, 0, 4});
   monitor_.Stop();
-
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_StartThenStopThenStartAgain) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-
+       IsUnlockAllowed_StartThenStopThenStartAgain) {
   monitor_.Start();
-  ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2, 4, 4});
+  EXPECT_TRUE(monitor_.IsUnlockAllowed());
   monitor_.Stop();
 
   // Restarting the monitor should immediately reset the proximity state, rather
   // than building on the previous rolling average.
   monitor_.Start();
-  ProvideConnectionInfo({0, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold - 1, 4, 4});
 
-  EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
+  EXPECT_FALSE(monitor_.IsUnlockAllowed());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_RemoteDeviceRemainsInProximity) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-
+       IsUnlockAllowed_RemoteDeviceRemainsInProximity) {
   monitor_.Start();
-  ProvideConnectionInfo({0, 4, 4});
-  ProvideConnectionInfo({-1, 4, 4});
-  ProvideConnectionInfo({0, 4, 4});
-  ProvideConnectionInfo({-2, 4, 4});
-  ProvideConnectionInfo({-1, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2 + 1, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2 - 1, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2 + 2, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold / 2 - 3, 4, 4});
 
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
 
   // Brief drops in RSSI should be handled by weighted averaging.
-  ProvideConnectionInfo({-10, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold - 5, 4, 4});
 
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_RemoteDeviceLeavesProximity) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
+       IsUnlockAllowed_RemoteDeviceLeavesProximity) {
   monitor_.Start();
 
   // Start with a device in proximity.
   ProvideConnectionInfo({0, 4, 4});
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
 
   // Simulate readings for the remote device leaving proximity.
   ProvideConnectionInfo({-1, 4, 4});
   ProvideConnectionInfo({-4, 4, 4});
   ProvideConnectionInfo({0, 4, 4});
   ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-8, 4, 4});
   ProvideConnectionInfo({-15, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
-  ProvideConnectionInfo({-10, 4, 4});
+  ProvideConnectionInfo({-20, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold - 10, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold - 20, 4, 4});
+  ProvideConnectionInfo({kRssiThreshold - 20, 4, 4});
 
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_RemoteDeviceEntersProximity) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
+       IsUnlockAllowed_RemoteDeviceEntersProximity) {
   monitor_.Start();
 
-  // Start with a device in proximity.
-  ProvideConnectionInfo({-20, 4, 4});
+  // Start with a device out of proximity.
+  ProvideConnectionInfo({2 * kRssiThreshold, 4, 4});
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 
   // Simulate readings for the remote device entering proximity.
   ProvideConnectionInfo({-15, 4, 4});
@@ -448,18 +259,15 @@
   ProvideConnectionInfo({0, 4, 4});
 
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_DeviceNotKnownToAdapter) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
+       IsUnlockAllowed_DeviceNotKnownToAdapter) {
   monitor_.Start();
 
   // Start with the device known to the adapter and in proximity.
   ProvideConnectionInfo({0, 4, 4});
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
 
   // Simulate it being forgotten.
   ON_CALL(*bluetooth_adapter_, GetDevice(kBluetoothAddress))
@@ -468,18 +276,15 @@
   RunPendingTasks();
 
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_DeviceNotConnected) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
+       IsUnlockAllowed_DeviceNotConnected) {
   monitor_.Start();
 
   // Start with the device connected and in proximity.
   ProvideConnectionInfo({0, 4, 4});
   EXPECT_TRUE(monitor_.IsUnlockAllowed());
-  EXPECT_TRUE(monitor_.IsInRssiRange());
 
   // Simulate it disconnecting.
   ON_CALL(remote_bluetooth_device_, IsConnected()).WillByDefault(Return(false));
@@ -487,19 +292,14 @@
   RunPendingTasks();
 
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
-       ProximityState_ConnectionInfoReceivedAfterStopping) {
-  monitor_.SetStrategy(ProximityMonitor::Strategy::CHECK_RSSI);
-
+       IsUnlockAllowed_ConnectionInfoReceivedAfterStopping) {
   monitor_.Start();
   monitor_.Stop();
   ProvideConnectionInfo({0, 4, 4});
-
   EXPECT_FALSE(monitor_.IsUnlockAllowed());
-  EXPECT_FALSE(monitor_.IsInRssiRange());
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
@@ -516,10 +316,6 @@
   histogram_tester.ExpectUniqueSample("EasyUnlock.AuthProximity.RollingRssi",
                                       -6, 1);
   histogram_tester.ExpectUniqueSample(
-      "EasyUnlock.AuthProximity.TransmitPowerDelta", -1, 1);
-  histogram_tester.ExpectUniqueSample(
-      "EasyUnlock.AuthProximity.TimeSinceLastZeroRssi", 304, 1);
-  histogram_tester.ExpectUniqueSample(
       "EasyUnlock.AuthProximity.RemoteDeviceModelHash",
       1881443083 /* hash of "LGE Nexus 5" */, 1);
 }
@@ -533,20 +329,18 @@
   monitor_.RecordProximityMetricsOnAuthSuccess();
   histogram_tester.ExpectUniqueSample("EasyUnlock.AuthProximity.RollingRssi",
                                       -100, 1);
-  histogram_tester.ExpectUniqueSample(
-      "EasyUnlock.AuthProximity.TransmitPowerDelta", 50, 1);
 }
 
 TEST_F(ProximityAuthProximityMonitorImplTest,
        RecordProximityMetricsOnAuthSuccess_UnknownValues) {
-  // Note: A device without a recorded name will have its Bluetooth address as
-  // its name.
+  // Note: A device without a recorded name will have "Unknown" as its name.
   cryptauth::RemoteDevice unnamed_remote_device(
-      kRemoteDeviceUserId, kBluetoothAddress, kRemoteDevicePublicKey,
+      kRemoteDeviceUserId, "" /* name */, kRemoteDevicePublicKey,
       kBluetoothAddress, kPersistentSymmetricKey, std::string());
+  cryptauth::FakeConnection connection(unnamed_remote_device);
 
   std::unique_ptr<base::TickClock> clock(new base::SimpleTestTickClock());
-  ProximityMonitorImpl monitor(unnamed_remote_device, std::move(clock));
+  ProximityMonitorImpl monitor(&connection, std::move(clock));
   monitor.AddObserver(&observer_);
   monitor.Start();
   ProvideConnectionInfo({127, 127, 127});
@@ -556,11 +350,6 @@
   histogram_tester.ExpectUniqueSample("EasyUnlock.AuthProximity.RollingRssi",
                                       127, 1);
   histogram_tester.ExpectUniqueSample(
-      "EasyUnlock.AuthProximity.TransmitPowerDelta", 127, 1);
-  histogram_tester.ExpectUniqueSample(
-      "EasyUnlock.AuthProximity.TimeSinceLastZeroRssi",
-      base::TimeDelta::FromSeconds(10).InMilliseconds(), 1);
-  histogram_tester.ExpectUniqueSample(
       "EasyUnlock.AuthProximity.RemoteDeviceModelHash",
       -1808066424 /* hash of "Unknown" */, 1);
 }
diff --git a/components/proximity_auth/remote_device_life_cycle.h b/components/proximity_auth/remote_device_life_cycle.h
index ed7f4c6..2990e95b8 100644
--- a/components/proximity_auth/remote_device_life_cycle.h
+++ b/components/proximity_auth/remote_device_life_cycle.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_PROXIMITY_AUTH_REMOTE_DEVICE_LIFE_CYCLE_H
 
 #include "base/macros.h"
+#include "components/cryptauth/connection.h"
 #include "components/cryptauth/remote_device.h"
 
 namespace proximity_auth {
@@ -57,6 +58,9 @@
   // Returns the RemoteDevice instance that this life cycle manages.
   virtual cryptauth::RemoteDevice GetRemoteDevice() const = 0;
 
+  // Returns the current Connection, or null if the device is not yet connected.
+  virtual cryptauth::Connection* GetConnection() const = 0;
+
   // Returns the current state of in the life cycle.
   virtual State GetState() const = 0;
 
diff --git a/components/proximity_auth/remote_device_life_cycle_impl.cc b/components/proximity_auth/remote_device_life_cycle_impl.cc
index 0661088b..54e4b71f 100644
--- a/components/proximity_auth/remote_device_life_cycle_impl.cc
+++ b/components/proximity_auth/remote_device_life_cycle_impl.cc
@@ -62,6 +62,14 @@
   return remote_device_;
 }
 
+cryptauth::Connection* RemoteDeviceLifeCycleImpl::GetConnection() const {
+  if (connection_)
+    return connection_.get();
+  if (messenger_)
+    return messenger_->GetConnection();
+  return nullptr;
+}
+
 RemoteDeviceLifeCycle::State RemoteDeviceLifeCycleImpl::GetState() const {
   return state_;
 }
diff --git a/components/proximity_auth/remote_device_life_cycle_impl.h b/components/proximity_auth/remote_device_life_cycle_impl.h
index 2bc82767..4df1973 100644
--- a/components/proximity_auth/remote_device_life_cycle_impl.h
+++ b/components/proximity_auth/remote_device_life_cycle_impl.h
@@ -41,6 +41,7 @@
   // RemoteDeviceLifeCycle:
   void Start() override;
   cryptauth::RemoteDevice GetRemoteDevice() const override;
+  cryptauth::Connection* GetConnection() const override;
   RemoteDeviceLifeCycle::State GetState() const override;
   Messenger* GetMessenger() override;
   void AddObserver(Observer* observer) override;
diff --git a/components/proximity_auth/unlock_manager_impl.cc b/components/proximity_auth/unlock_manager_impl.cc
index 19babab..2d8b30b 100644
--- a/components/proximity_auth/unlock_manager_impl.cc
+++ b/components/proximity_auth/unlock_manager_impl.cc
@@ -141,7 +141,6 @@
 
   life_cycle_ = life_cycle;
   if (life_cycle_) {
-    proximity_monitor_ = CreateProximityMonitor(life_cycle->GetRemoteDevice());
     SetWakingUpState(true);
   } else {
     proximity_monitor_.reset();
@@ -156,8 +155,12 @@
                << static_cast<int>(state);
 
   remote_screenlock_state_.reset();
-  if (state == RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED)
+  if (state == RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED) {
+    DCHECK(life_cycle_->GetConnection());
+    DCHECK(GetMessenger());
+    proximity_monitor_ = CreateProximityMonitor(life_cycle_->GetConnection());
     GetMessenger()->AddObserver(this);
+  }
 
   if (state == RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED)
     SetWakingUpState(false);
@@ -320,9 +323,9 @@
 }
 
 std::unique_ptr<ProximityMonitor> UnlockManagerImpl::CreateProximityMonitor(
-    const cryptauth::RemoteDevice& remote_device) {
+    cryptauth::Connection* connection) {
   return base::MakeUnique<ProximityMonitorImpl>(
-      remote_device, base::WrapUnique(new base::DefaultTickClock()));
+      connection, base::WrapUnique(new base::DefaultTickClock()));
 }
 
 void UnlockManagerImpl::SendSignInChallenge() {
@@ -356,7 +359,10 @@
       RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED)
     return ScreenlockState::PHONE_NOT_AUTHENTICATED;
 
-  if (is_waking_up_)
+  if (is_waking_up_ ||
+      life_cycle_->GetState() == RemoteDeviceLifeCycle::State::AUTHENTICATING ||
+      life_cycle_->GetState() ==
+          RemoteDeviceLifeCycle::State::FINDING_CONNECTION)
     return ScreenlockState::BLUETOOTH_CONNECTING;
 
   if (!bluetooth_adapter_ || !bluetooth_adapter_->IsPowered())
@@ -370,8 +376,7 @@
   // If the RSSI is too low, then the remote device is nowhere near the local
   // device. This message should take priority over messages about screen lock
   // states.
-  if (!proximity_monitor_->IsUnlockAllowed() &&
-      !proximity_monitor_->IsInRssiRange())
+  if (!proximity_monitor_->IsUnlockAllowed())
     return ScreenlockState::RSSI_TOO_LOW;
 
   if (remote_screenlock_state_) {
@@ -380,11 +385,6 @@
         return ScreenlockState::PHONE_NOT_LOCKABLE;
 
       case RemoteScreenlockState::LOCKED:
-        if (proximity_monitor_->GetStrategy() ==
-                ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER &&
-            !proximity_monitor_->IsUnlockAllowed()) {
-          return ScreenlockState::PHONE_LOCKED_AND_TX_POWER_TOO_HIGH;
-        }
         return ScreenlockState::PHONE_LOCKED;
 
       case RemoteScreenlockState::UNKNOWN:
@@ -396,18 +396,6 @@
     }
   }
 
-  if (!proximity_monitor_->IsUnlockAllowed()) {
-    ProximityMonitor::Strategy strategy = proximity_monitor_->GetStrategy();
-    if (strategy != ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER) {
-      // CHECK_RSSI should have been handled above, and no other states should
-      // prevent unlocking.
-      PA_LOG(ERROR) << "[Unlock] Invalid ProximityMonitor strategy: "
-                    << static_cast<int>(strategy);
-      return ScreenlockState::NO_PHONE;
-    }
-    return ScreenlockState::TX_POWER_TOO_HIGH;
-  }
-
   return ScreenlockState::NO_PHONE;
 }
 
diff --git a/components/proximity_auth/unlock_manager_impl.h b/components/proximity_auth/unlock_manager_impl.h
index 3e1c03c..104f01b 100644
--- a/components/proximity_auth/unlock_manager_impl.h
+++ b/components/proximity_auth/unlock_manager_impl.h
@@ -52,10 +52,10 @@
       ScreenlockBridge::LockHandler::AuthType auth_type) override;
 
  protected:
-  // Creates a ProximityMonitor instance for the given |remote_device|.
+  // Creates a ProximityMonitor instance for the given |connection|.
   // Exposed for testing.
   virtual std::unique_ptr<ProximityMonitor> CreateProximityMonitor(
-      const cryptauth::RemoteDevice& remote_device);
+      cryptauth::Connection* connection);
 
  private:
   // The possible lock screen states for the remote device.
diff --git a/components/proximity_auth/unlock_manager_impl_unittest.cc b/components/proximity_auth/unlock_manager_impl_unittest.cc
index 6dec288b..004ae9d 100644
--- a/components/proximity_auth/unlock_manager_impl_unittest.cc
+++ b/components/proximity_auth/unlock_manager_impl_unittest.cc
@@ -13,9 +13,11 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "components/cryptauth/cryptauth_test_util.h"
+#include "components/cryptauth/fake_connection.h"
 #include "components/cryptauth/fake_secure_context.h"
 #include "components/cryptauth/secure_context.h"
 #include "components/proximity_auth/fake_lock_handler.h"
+#include "components/proximity_auth/fake_remote_device_life_cycle.h"
 #include "components/proximity_auth/logging/logging.h"
 #include "components/proximity_auth/messenger.h"
 #include "components/proximity_auth/mock_proximity_auth_client.h"
@@ -66,6 +68,7 @@
   MOCK_CONST_METHOD0(GetRemoteDevice, cryptauth::RemoteDevice());
   MOCK_CONST_METHOD0(GetState, State());
   MOCK_METHOD0(GetMessenger, Messenger*());
+  MOCK_CONST_METHOD0(GetConnection, cryptauth::Connection*());
   MOCK_METHOD1(AddObserver, void(Observer*));
   MOCK_METHOD1(RemoveObserver, void(Observer*));
 };
@@ -82,6 +85,7 @@
   MOCK_METHOD1(RequestDecryption, void(const std::string& challenge));
   MOCK_METHOD0(RequestUnlock, void());
   MOCK_CONST_METHOD0(GetSecureContext, cryptauth::SecureContext*());
+  MOCK_CONST_METHOD0(GetConnection, cryptauth::Connection*());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockMessenger);
@@ -89,24 +93,25 @@
 
 class MockProximityMonitor : public ProximityMonitor {
  public:
-  MockProximityMonitor() {
-    ON_CALL(*this, GetStrategy())
-        .WillByDefault(Return(ProximityMonitor::Strategy::NONE));
+  MockProximityMonitor() : started_(false), stopped_(false) {
     ON_CALL(*this, IsUnlockAllowed()).WillByDefault(Return(true));
-    ON_CALL(*this, IsInRssiRange()).WillByDefault(Return(false));
   }
   ~MockProximityMonitor() override {}
 
-  MOCK_METHOD0(Start, void());
-  MOCK_METHOD0(Stop, void());
-  MOCK_CONST_METHOD0(GetStrategy, Strategy());
+  void Start() override { started_ = true; }
+  void Stop() override { stopped_ = true; }
   MOCK_CONST_METHOD0(IsUnlockAllowed, bool());
-  MOCK_CONST_METHOD0(IsInRssiRange, bool());
   MOCK_METHOD0(RecordProximityMetricsOnAuthSuccess, void());
   MOCK_METHOD1(AddObserver, void(ProximityMonitorObserver*));
   MOCK_METHOD1(RemoveObserver, void(ProximityMonitorObserver*));
 
+  bool started() { return started_; }
+  bool stopped() { return stopped_; }
+
  private:
+  bool started_;
+  bool stopped_;
+
   DISALLOW_COPY_AND_ASSIGN(MockProximityMonitor);
 };
 
@@ -132,8 +137,9 @@
 
  private:
   std::unique_ptr<ProximityMonitor> CreateProximityMonitor(
-      const cryptauth::RemoteDevice& remote_device) override {
-    EXPECT_EQ(cryptauth::kTestRemoteDevicePublicKey, remote_device.public_key);
+      cryptauth::Connection* connection) override {
+    EXPECT_EQ(cryptauth::kTestRemoteDevicePublicKey,
+              connection->remote_device().public_key);
     std::unique_ptr<MockProximityMonitor> proximity_monitor(
         new NiceMock<MockProximityMonitor>());
     proximity_monitor_ = proximity_monitor.get();
@@ -162,17 +168,18 @@
  public:
   ProximityAuthUnlockManagerImplTest()
       : remote_device_(cryptauth::CreateClassicRemoteDeviceForTest()),
+        life_cycle_(remote_device_),
+        connection_(remote_device_),
         bluetooth_adapter_(CreateAndRegisterMockBluetoothAdapter()),
         task_runner_(new base::TestSimpleTaskRunner()),
         thread_task_runner_handle_(task_runner_) {
     ON_CALL(*bluetooth_adapter_, IsPowered()).WillByDefault(Return(true));
-    ON_CALL(life_cycle_, GetMessenger()).WillByDefault(Return(&messenger_));
-    ON_CALL(life_cycle_, GetRemoteDevice())
-        .WillByDefault(Return(remote_device_));
     ON_CALL(messenger_, SupportsSignIn()).WillByDefault(Return(true));
     ON_CALL(messenger_, GetSecureContext())
         .WillByDefault(Return(&secure_context_));
 
+    life_cycle_.set_connection(&connection_);
+    life_cycle_.set_messenger(&messenger_);
     ScreenlockBridge::Get()->SetLockHandler(&lock_handler_);
 
 #if defined(OS_CHROMEOS)
@@ -207,15 +214,10 @@
   }
 
   void SimulateUserPresentState() {
-    ON_CALL(life_cycle_, GetState())
-        .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
     unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
-
-    ON_CALL(life_cycle_, GetState())
-        .WillByDefault(
-            Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
+    life_cycle_.ChangeState(
+        RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
     unlock_manager_->OnLifeCycleStateChanged();
-
     unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
   }
 
@@ -227,12 +229,13 @@
 
  protected:
   cryptauth::RemoteDevice remote_device_;
+  FakeRemoteDeviceLifeCycle life_cycle_;
+  cryptauth::FakeConnection connection_;
 
   // Mock used for verifying interactions with the Bluetooth subsystem.
   scoped_refptr<device::MockBluetoothAdapter> bluetooth_adapter_;
 
   NiceMock<MockProximityAuthClient> proximity_auth_client_;
-  NiceMock<MockRemoteDeviceLifeCycle> life_cycle_;
   NiceMock<MockMessenger> messenger_;
   std::unique_ptr<TestUnlockManager> unlock_manager_;
   cryptauth::FakeSecureContext secure_context_;
@@ -253,10 +256,10 @@
        IsUnlockAllowed_SessionLock_AllGood) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
   unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
 
   EXPECT_TRUE(unlock_manager_->IsUnlockAllowed());
@@ -264,14 +267,10 @@
 
 TEST_F(ProximityAuthUnlockManagerImplTest, IsUnlockAllowed_SignIn_AllGood) {
   CreateUnlockManager(ProximityAuthSystem::SIGN_IN);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
   unlock_manager_->OnLifeCycleStateChanged();
 
   ON_CALL(messenger_, SupportsSignIn()).WillByDefault(Return(true));
@@ -283,14 +282,10 @@
 TEST_F(ProximityAuthUnlockManagerImplTest,
        IsUnlockAllowed_SignIn_MessengerDoesNotSupportSignIn) {
   CreateUnlockManager(ProximityAuthSystem::SIGN_IN);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
   unlock_manager_->OnLifeCycleStateChanged();
 
   ON_CALL(messenger_, SupportsSignIn()).WillByDefault(Return(false));
@@ -300,30 +295,16 @@
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
-       IsUnlockAllowed_SignIn_MessengerIsNull) {
-  CreateUnlockManager(ProximityAuthSystem::SIGN_IN);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
-  ON_CALL(life_cycle_, GetMessenger()).WillByDefault(Return(nullptr));
-  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
-  unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
-
-  EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
-}
-
-TEST_F(ProximityAuthUnlockManagerImplTest,
        IsUnlockAllowed_DisallowedByProximityMonitor) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
-  unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
 
   ON_CALL(*proximity_monitor(), IsUnlockAllowed()).WillByDefault(Return(false));
+  unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
   EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
 }
 
@@ -331,9 +312,9 @@
        IsUnlockAllowed_SecureChannelNotEstablished) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::AUTHENTICATING));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::AUTHENTICATING);
+  unlock_manager_->OnLifeCycleStateChanged();
   unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
 
   EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
@@ -353,10 +334,10 @@
        IsUnlockAllowed_RemoteScreenlockStateLocked) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
   unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenLocked);
 
   EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
@@ -366,10 +347,10 @@
        IsUnlockAllowed_RemoteScreenlockStateUnknown) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
   unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenlockStateUnknown);
 
   EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
@@ -379,10 +360,10 @@
        IsUnlockAllowed_RemoteScreenlockStateDisabled) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
   unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenlockDisabled);
 
   EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
@@ -392,10 +373,10 @@
        IsUnlockAllowed_RemoteScreenlockStateNotYetReceived) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
 
   EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
 }
@@ -419,29 +400,14 @@
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
-       SetRemoteDeviceLifeCycle_NullThenExistingRemoteDeviceLifeCycle) {
-  CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-  SimulateUserPresentState();
-
-  EXPECT_CALL(proximity_auth_client_,
-              UpdateScreenlockState(ScreenlockState::INACTIVE));
-  unlock_manager_->SetRemoteDeviceLifeCycle(nullptr);
-
-  EXPECT_CALL(proximity_auth_client_,
-              UpdateScreenlockState(ScreenlockState::AUTHENTICATED));
-  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
-}
-
-TEST_F(ProximityAuthUnlockManagerImplTest,
        SetRemoteDeviceLifeCycle_AuthenticationFailed) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
 
   unlock_manager_->SetRemoteDeviceLifeCycle(nullptr);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED));
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED);
+
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::PHONE_NOT_AUTHENTICATED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
@@ -453,8 +419,8 @@
 
   unlock_manager_->SetRemoteDeviceLifeCycle(nullptr);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::FINDING_CONNECTION));
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
+
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
@@ -473,27 +439,23 @@
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::FINDING_CONNECTION));
-
-  EXPECT_CALL(*proximity_monitor(), Stop()).Times(AtLeast(1));
   unlock_manager_->OnLifeCycleStateChanged();
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
+  unlock_manager_->OnLifeCycleStateChanged();
+  EXPECT_TRUE(proximity_monitor()->stopped());
 }
 
 TEST_F(
     ProximityAuthUnlockManagerImplTest,
     SetRemoteDeviceLifeCycle_ConnectedRemoteDeviceLifeCycle_StartsProximityMonitor) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
-  EXPECT_CALL(*proximity_monitor(), Start()).Times(AtLeast(1));
   unlock_manager_->OnLifeCycleStateChanged();
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
+  EXPECT_TRUE(proximity_monitor()->started());
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
@@ -508,7 +470,7 @@
        OnLifeCycleStateChanged_StartsProximityMonitor) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
-  EXPECT_CALL(*proximity_monitor(), Start()).Times(AtLeast(1));
+  EXPECT_TRUE(proximity_monitor()->started());
   unlock_manager_->OnLifeCycleStateChanged();
 }
 
@@ -517,12 +479,10 @@
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED));
-
-  EXPECT_CALL(*proximity_monitor(), Stop()).Times(AtLeast(1));
   unlock_manager_->OnLifeCycleStateChanged();
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED);
+  unlock_manager_->OnLifeCycleStateChanged();
+  EXPECT_TRUE(proximity_monitor()->stopped());
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
@@ -530,11 +490,9 @@
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
-
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::INACTIVE));
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::STOPPED);
   unlock_manager_->OnLifeCycleStateChanged();
 }
 
@@ -543,44 +501,31 @@
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED));
-
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::PHONE_NOT_AUTHENTICATED));
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED);
   unlock_manager_->OnLifeCycleStateChanged();
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
        OnLifeCycleStateChanged_FindingConnection_UpdatesScreenlockState) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::FINDING_CONNECTION));
-
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
   unlock_manager_->OnLifeCycleStateChanged();
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
        OnLifeCycleStateChanged_Authenticating_UpdatesScreenlockState) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::AUTHENTICATING));
-
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::AUTHENTICATING);
   unlock_manager_->OnLifeCycleStateChanged();
 }
 
@@ -588,17 +533,12 @@
     ProximityAuthUnlockManagerImplTest,
     OnLifeCycleStateChanged_SecureChannelEstablished_UpdatesScreenlockState) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
-
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
   unlock_manager_->OnLifeCycleStateChanged();
 }
 
@@ -606,10 +546,8 @@
        OnDisconnected_UnregistersAsObserver) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED));
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED);
+  unlock_manager_->OnLifeCycleStateChanged();
 
   EXPECT_CALL(messenger_, RemoveObserver(unlock_manager_.get()))
       .Times(testing::AtLeast(1));
@@ -622,27 +560,23 @@
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
 
-  EXPECT_CALL(*proximity_monitor(), Stop());
   unlock_manager_.get()->OnScreenDidUnlock(
       ScreenlockBridge::LockHandler::LOCK_SCREEN);
+  EXPECT_TRUE(proximity_monitor()->stopped());
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
        OnScreenDidLock_StartsProximityMonitor) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::STOPPED));
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(
-          Return(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED));
-  unlock_manager_->OnLifeCycleStateChanged();
-
-  EXPECT_CALL(*proximity_monitor(), Start());
   unlock_manager_.get()->OnScreenDidLock(
       ScreenlockBridge::LockHandler::LOCK_SCREEN);
+
+  life_cycle_.ChangeState(
+      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
+  unlock_manager_->OnLifeCycleStateChanged();
+  EXPECT_TRUE(proximity_monitor()->started());
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest, OnScreenDidLock_SetsWakingUpState) {
@@ -652,14 +586,11 @@
   unlock_manager_.get()->OnScreenDidUnlock(
       ScreenlockBridge::LockHandler::LOCK_SCREEN);
 
-  ON_CALL(life_cycle_, GetState())
-      .WillByDefault(Return(RemoteDeviceLifeCycle::State::FINDING_CONNECTION));
-  unlock_manager_->OnLifeCycleStateChanged();
-
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
-  unlock_manager_.get()->OnScreenDidLock(
-      ScreenlockBridge::LockHandler::LOCK_SCREEN);
+
+  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
+  unlock_manager_->OnLifeCycleStateChanged();
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
diff --git a/components/ui_devtools/string_util.h b/components/ui_devtools/string_util.h
index 4efb8353..d08b8b6f 100644
--- a/components/ui_devtools/string_util.h
+++ b/components/ui_devtools/string_util.h
@@ -45,6 +45,11 @@
   static String fromDouble(double number) {
     return base::DoubleToString(number);
   }
+  static double toDouble(const char* s, size_t len, bool* ok) {
+    double v = 0.0;
+    *ok = base::StringToDouble(std::string(s, len), &v);
+    return *ok ? v : 0.0;
+  }
   static void builderAppend(StringBuilder& builder, const String& s) {
     builder.append(s);
   }
diff --git a/content/browser/android/content_view_core_impl.cc b/content/browser/android/content_view_core_impl.cc
index f79636db..d4bc3f8 100644
--- a/content/browser/android/content_view_core_impl.cc
+++ b/content/browser/android/content_view_core_impl.cc
@@ -398,7 +398,7 @@
   return rwhva->GetCachedBackgroundColor();
 }
 
-// All positions and sizes are in CSS pixels.
+// All positions and sizes (except |top_shown_pix|) are in CSS pixels.
 // Note that viewport_width/height is a best effort based.
 // ContentViewCore has the actual information about the physical viewport size.
 void ContentViewCoreImpl::UpdateFrameInfo(
@@ -407,16 +407,16 @@
     const gfx::Vector2dF& page_scale_factor_limits,
     const gfx::SizeF& content_size,
     const gfx::SizeF& viewport_size,
-    const float top_controls_height,
-    const float top_controls_shown_ratio,
+    const float content_offset,
+    const float top_shown_pix,
+    bool top_changed,
     bool is_mobile_optimized_hint) {
   JNIEnv* env = AttachCurrentThread();
   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
   if (obj.is_null() || !GetWindowAndroid())
     return;
 
-  GetViewAndroid()->set_content_offset(
-      gfx::Vector2dF(0.0f, top_controls_height * top_controls_shown_ratio));
+  GetViewAndroid()->set_content_offset(gfx::Vector2dF(0.0f, content_offset));
 
   page_scale_ = page_scale_factor;
 
@@ -424,7 +424,7 @@
       env, obj, scroll_offset.x(), scroll_offset.y(), page_scale_factor,
       page_scale_factor_limits.x(), page_scale_factor_limits.y(),
       content_size.width(), content_size.height(), viewport_size.width(),
-      viewport_size.height(), top_controls_height, top_controls_shown_ratio,
+      viewport_size.height(), top_shown_pix, top_changed,
       is_mobile_optimized_hint);
 }
 
diff --git a/content/browser/android/content_view_core_impl.h b/content/browser/android/content_view_core_impl.h
index f4a2fef..edefe59 100644
--- a/content/browser/android/content_view_core_impl.h
+++ b/content/browser/android/content_view_core_impl.h
@@ -252,14 +252,16 @@
   // Hides a visible popup menu.
   void HideSelectPopupMenu();
 
-  // All sizes and offsets are in CSS pixels as cached by the renderer.
+  // All sizes and offsets are in CSS pixels (except |top_show_pix|)
+  // as cached by the renderer.
   void UpdateFrameInfo(const gfx::Vector2dF& scroll_offset,
                        float page_scale_factor,
                        const gfx::Vector2dF& page_scale_factor_limits,
                        const gfx::SizeF& content_size,
                        const gfx::SizeF& viewport_size,
-                       const float top_controls_height,
-                       const float top_controls_shown_ratio,
+                       const float content_offset,
+                       const float top_shown_pix,
+                       bool top_changed,
                        bool is_mobile_optimized_hint);
 
   bool HasFocus();
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index f86207f9..111d755 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -959,6 +959,25 @@
   EXPECT_EQ(0u, notifications_.size());
 }
 
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteCrash) {
+  set_agent_host_can_close();
+  host_resolver()->AddRule("*", "127.0.0.1");
+  content::SetupCrossSiteRedirector(embedded_test_server());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  GURL test_url1 =
+      embedded_test_server()->GetURL("A.com", "/devtools/navigation.html");
+  NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1, 1);
+  Attach();
+  CrashTab(shell()->web_contents());
+
+  GURL test_url2 =
+      embedded_test_server()->GetURL("B.com", "/devtools/navigation.html");
+  NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2, 1);
+
+  // Should not crash at this point.
+}
+
 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, ReconnectPreservesState) {
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
diff --git a/content/browser/devtools/protocol_string.h b/content/browser/devtools/protocol_string.h
index 90dbd4ab..38dd03e4 100644
--- a/content/browser/devtools/protocol_string.h
+++ b/content/browser/devtools/protocol_string.h
@@ -52,6 +52,11 @@
       s = "0" + s;
     return s;
   }
+  static double toDouble(const char* s, size_t len, bool* ok) {
+    double v = 0.0;
+    *ok = base::StringToDouble(std::string(s, len), &v);
+    return *ok ? v : 0.0;
+  }
   static size_t find(const String& s, const char* needle) {
     return s.find(needle);
   }
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 204903c3..1a6be75 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -762,7 +762,7 @@
   if (session())
     protocol::TargetHandler::FromSession(session())->UpdateFrames();
 
-  if (IsBrowserSideNavigationEnabled())
+  if (IsBrowserSideNavigationEnabled() && !current_frame_crashed_)
     return;
 
   DCHECK(!pending_ || pending_->host() != old_host);
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 9f6cba6..03367bc 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -1429,8 +1429,9 @@
       gfx::Vector2dF(frame_metadata.min_page_scale_factor,
                      frame_metadata.max_page_scale_factor),
       frame_metadata.root_layer_size, frame_metadata.scrollable_viewport_size,
-      frame_metadata.top_controls_height,
-      frame_metadata.top_controls_shown_ratio, is_mobile_optimized);
+      frame_metadata.top_controls_height *
+          frame_metadata.top_controls_shown_ratio,
+      top_shown_pix, top_changed, is_mobile_optimized);
 }
 
 void RenderWidgetHostViewAndroid::ShowInternal() {
diff --git a/content/browser/resources/gpu/info_view.js b/content/browser/resources/gpu/info_view.js
index e54cc17..d124ac1 100644
--- a/content/browser/resources/gpu/info_view.js
+++ b/content/browser/resources/gpu/info_view.js
@@ -38,10 +38,6 @@
       if (browserBridge.clientInfo) {
         var clientInfo = browserBridge.clientInfo;
 
-        var commandLineParts = clientInfo.command_line.split(' ');
-        commandLineParts.shift(); // Pop off the exe path
-        var commandLineString = commandLineParts.join(' ')
-
         this.setTable_('client-info', [
           {
             description: 'Data exported',
@@ -72,8 +68,8 @@
             value: clientInfo.graphics_backend
           },
           {
-            description: 'Command Line Args',
-            value: commandLineString
+            description: 'Command Line',
+            value: clientInfo.command_line
           }]);
       } else {
         this.setText_('client-info', '... loading...');
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc
index 0bd18de1..2eaab7a4 100644
--- a/content/browser/tracing/tracing_controller_impl.cc
+++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -837,9 +837,23 @@
   metadata_dict->SetString("user-agent", GetContentClient()->GetUserAgent());
 
   // OS
+#if defined(OS_CHROMEOS)
+  metadata_dict->SetString("os-name", "CrOS");
+  int32_t major_version;
+  int32_t minor_version;
+  int32_t bugfix_version;
+  // OperatingSystemVersion only has a POSIX implementation which returns the
+  // wrong versions for CrOS.
+  base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version,
+                                               &bugfix_version);
+  metadata_dict->SetString(
+      "os-version", base::StringPrintf("%d.%d.%d", major_version, minor_version,
+                                       bugfix_version));
+#else
   metadata_dict->SetString("os-name", base::SysInfo::OperatingSystemName());
   metadata_dict->SetString("os-version",
                            base::SysInfo::OperatingSystemVersion());
+#endif
   metadata_dict->SetString("os-arch",
                            base::SysInfo::OperatingSystemArchitecture());
 
diff --git a/content/browser/webui/url_data_manager_backend.cc b/content/browser/webui/url_data_manager_backend.cc
index 247fc00..3a7431f 100644
--- a/content/browser/webui/url_data_manager_backend.cc
+++ b/content/browser/webui/url_data_manager_backend.cc
@@ -145,7 +145,6 @@
   void Kill() override;
   int ReadRawData(net::IOBuffer* buf, int buf_size) override;
   bool GetMimeType(std::string* mime_type) const override;
-  int GetResponseCode() const override;
   void GetResponseInfo(net::HttpResponseInfo* info) override;
   std::unique_ptr<net::SourceStream> SetUpSourceStream() override;
 
@@ -358,10 +357,6 @@
   return !mime_type_.empty();
 }
 
-int URLRequestChromeJob::GetResponseCode() const {
-  return net::HTTP_OK;
-}
-
 void URLRequestChromeJob::GetResponseInfo(net::HttpResponseInfo* info) {
   DCHECK(!info->headers.get());
   // Set the headers so that requests serviced by ChromeURLDataManager return a
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index d7b13a1..1eada46 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -426,7 +426,6 @@
     "javatests/src/org/chromium/content/browser/JavaBridgeReturnValuesTest.java",
     "javatests/src/org/chromium/content/browser/JavaBridgeTestBase.java",
     "javatests/src/org/chromium/content/browser/JavaBridgeTestCommon.java",
-    "javatests/src/org/chromium/content/browser/LocationProviderTest.java",
     "javatests/src/org/chromium/content/browser/MediaResourceGetterTest.java",
     "javatests/src/org/chromium/content/browser/MediaSessionTest.java",
     "javatests/src/org/chromium/content/browser/NavigationTest.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
index 3de174c..6c35e91 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
@@ -1576,9 +1576,8 @@
     @CalledByNative
     private void updateFrameInfo(float scrollOffsetX, float scrollOffsetY, float pageScaleFactor,
             float minPageScaleFactor, float maxPageScaleFactor, float contentWidth,
-            float contentHeight, float viewportWidth, float viewportHeight,
-            float browserControlsHeightDp, float browserControlsShownRatio,
-            boolean isMobileOptimizedHint) {
+            float contentHeight, float viewportWidth, float viewportHeight, float topBarShownPix,
+            boolean topBarChanged, boolean isMobileOptimizedHint) {
         TraceEvent.begin("ContentViewCore:updateFrameInfo");
         mIsMobileOptimizedHint = isMobileOptimizedHint;
         // Adjust contentWidth/Height to be always at least as big as
@@ -1588,8 +1587,6 @@
                 mViewportWidthPix / (deviceScale * pageScaleFactor));
         contentHeight = Math.max(contentHeight,
                 mViewportHeightPix / (deviceScale * pageScaleFactor));
-        final float topBarShownPix =
-                browserControlsHeightDp * deviceScale * browserControlsShownRatio;
 
         final boolean contentSizeChanged =
                 contentWidth != mRenderCoordinates.getContentWidthCss()
@@ -1603,8 +1600,6 @@
                 pageScaleChanged
                 || scrollOffsetX != mRenderCoordinates.getScrollX()
                 || scrollOffsetY != mRenderCoordinates.getScrollY();
-        final boolean topBarChanged = Float.compare(topBarShownPix,
-                mRenderCoordinates.getContentOffsetYPix()) != 0;
 
         final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged;
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java
index 2067654..df43628 100644
--- a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java
@@ -455,6 +455,8 @@
         mActionMenuDescriptor = createActionMenuDescriptor();
         mActionMenuDescriptor.apply(menu);
 
+        if (isInsertion() || isSelectionPassword()) return;
+
         initializeTextProcessingMenu(menu);
     }
 
@@ -857,15 +859,7 @@
                 mSelectionRect.set(left, top, right, bottom);
                 mHasSelection = true;
                 mUnselectAllOnDismiss = true;
-                // When this event comes as the result of SelectAll, SelectionClient should not
-                // change the selection range (http://crbug.com/714106). We assume that two or
-                // more selected words means SelectAll.
-                // TODO(amaralp): Find a better way to know that SELECTION_HANDLES_SHOWN was
-                // caused by SelectAll.
-                boolean oneWordSelected =
-                        !getSelectedText().isEmpty() && !getSelectedText().matches(".*\\s+.*");
-                if (!oneWordSelected || mSelectionClient == null
-                        || !mSelectionClient.sendsSelectionPopupUpdates()) {
+                if (mSelectionClient == null || !mSelectionClient.sendsSelectionPopupUpdates()) {
                     showActionModeOrClearOnFailure();
                 } else {
                     // Rely on |mSelectionClient| sending a classification request and the request
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/LocationProviderTest.java b/content/public/android/javatests/src/org/chromium/content/browser/LocationProviderTest.java
deleted file mode 100644
index c74b604..0000000
--- a/content/public/android/javatests/src/org/chromium/content/browser/LocationProviderTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser;
-
-import android.app.Activity;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.UiThreadTestRule;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.annotations.SuppressFBWarnings;
-import org.chromium.base.test.util.Feature;
-import org.chromium.content.browser.test.ContentJUnit4ClassRunner;
-import org.chromium.device.geolocation.LocationProviderAdapter;
-
-/**
- * Test suite for LocationProvider.
- */
-@RunWith(ContentJUnit4ClassRunner.class)
-public class LocationProviderTest {
-    private Activity mActivity;
-    private LocationProviderAdapter mLocationProvider;
-
-    @Rule
-    public UiThreadTestRule mRule = new UiThreadTestRule();
-
-    @Before
-    @SuppressFBWarnings("URF_UNREAD_FIELD")
-    public void setUp() {
-        mActivity = new Activity();
-        mLocationProvider = LocationProviderAdapter.create(
-                InstrumentationRegistry.getInstrumentation().getTargetContext());
-    }
-
-    /**
-     * Verify a normal start/stop call pair without any activity pauses.
-     */
-    @Test
-    @SmallTest
-    @UiThreadTest
-    @Feature({"Location"})
-    public void testStartStop() throws Exception {
-        mLocationProvider.start(false);
-        Assert.assertTrue("Should be running", mLocationProvider.isRunning());
-        mLocationProvider.stop();
-        Assert.assertFalse("Should have stopped", mLocationProvider.isRunning());
-    }
-
-    /**
-     * Verify a start/upgrade/stop call sequence without any activity pauses.
-     */
-    @Test
-    @SmallTest
-    @UiThreadTest
-    @Feature({"Location"})
-    public void testStartUpgradeStop() throws Exception {
-        mLocationProvider.start(false);
-        Assert.assertTrue("Should be running", mLocationProvider.isRunning());
-        mLocationProvider.start(true);
-        Assert.assertTrue("Should be running", mLocationProvider.isRunning());
-        mLocationProvider.stop();
-        Assert.assertFalse("Should have stopped", mLocationProvider.isRunning());
-    }
-}
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/OWNERS b/content/public/android/javatests/src/org/chromium/content/browser/OWNERS
index 05c5396..60ede9cb 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/OWNERS
+++ b/content/public/android/javatests/src/org/chromium/content/browser/OWNERS
@@ -1,9 +1,6 @@
 # Device Motion / Orientation API related
 per-file DeviceSensorsTest.java=timvolodine@chromium.org
 
-# Geolocation API related
-per-file LocationProviderTest.java=timvolodine@chromium.org
-
 # Screen Orientation API related
 per-file ScreenOrientation*.java=mlamouri@chromium.org
 
diff --git a/content/public/test/test_download_request_handler.cc b/content/public/test/test_download_request_handler.cc
index d7d05a8..9e854264 100644
--- a/content/public/test/test_download_request_handler.cc
+++ b/content/public/test/test_download_request_handler.cc
@@ -109,7 +109,6 @@
   void GetResponseInfo(net::HttpResponseInfo* response_info) override;
   int64_t GetTotalReceivedBytes() const override;
   bool GetMimeType(std::string* mime_type) const override;
-  int GetResponseCode() const override;
   int ReadRawData(net::IOBuffer* buf, int buf_size) override;
 
  private:
@@ -275,11 +274,6 @@
   return !parameters_->content_type.empty();
 }
 
-int TestDownloadRequestHandler::PartialResponseJob::GetResponseCode() const {
-  return response_info_.headers.get() ? response_info_.headers->response_code()
-                                      : 0;
-}
-
 int TestDownloadRequestHandler::PartialResponseJob::ReadRawData(
     net::IOBuffer* buf,
     int buf_size) {
diff --git a/content/renderer/media_recorder/video_track_recorder.cc b/content/renderer/media_recorder/video_track_recorder.cc
index 49def8d1..5108aa8 100644
--- a/content/renderer/media_recorder/video_track_recorder.cc
+++ b/content/renderer/media_recorder/video_track_recorder.cc
@@ -109,11 +109,6 @@
   return;
 #endif
 
-#if defined(OS_ANDROID)
-  // See https://crbug.com/653864.
-  return;
-#endif
-
   content::RenderThreadImpl* const render_thread_impl =
       content::RenderThreadImpl::current();
   if (!render_thread_impl) {
@@ -132,12 +127,17 @@
       gpu_factories->GetVideoEncodeAcceleratorSupportedProfiles();
   for (const auto& supported_profile : vea_supported_profiles) {
     for (auto& codec_id_and_profile : kPreferredCodecIdAndVEAProfiles) {
-      if (supported_profile.profile >= codec_id_and_profile.min_profile &&
-          supported_profile.profile <= codec_id_and_profile.max_profile) {
-        DVLOG(2) << "Accelerated codec found: "
-                 << media::GetProfileName(supported_profile.profile);
-        codec_id_to_profile_.insert(std::make_pair(
-            codec_id_and_profile.codec_id, supported_profile.profile));
+      const media::VideoCodecProfile codec = supported_profile.profile;
+#if defined(OS_ANDROID)
+      // TODO(mcasas): enable other codecs, https://crbug.com/653864.
+      if (codec < media::VP8PROFILE_MIN || codec > media::VP8PROFILE_MAX)
+        continue;
+#endif
+      if (codec >= codec_id_and_profile.min_profile &&
+          codec <= codec_id_and_profile.max_profile) {
+        DVLOG(2) << "Accelerated codec found: " << media::GetProfileName(codec);
+        codec_id_to_profile_.insert(
+            std::make_pair(codec_id_and_profile.codec_id, codec));
       }
     }
   }
diff --git a/content/renderer/media_recorder/video_track_recorder.h b/content/renderer/media_recorder/video_track_recorder.h
index 53a871f..e53cef4 100644
--- a/content/renderer/media_recorder/video_track_recorder.h
+++ b/content/renderer/media_recorder/video_track_recorder.h
@@ -32,8 +32,13 @@
 }  // namespace media
 
 namespace video_track_recorder {
+#if defined(OS_ANDROID)
+const int kVEAEncoderMinResolutionWidth = 176;
+const int kVEAEncoderMinResolutionHeight = 144;
+#else
 const int kVEAEncoderMinResolutionWidth = 640;
 const int kVEAEncoderMinResolutionHeight = 480;
+#endif
 }  // namespace video_track_recorder
 
 namespace content {
diff --git a/content/renderer/mus/renderer_window_tree_client.cc b/content/renderer/mus/renderer_window_tree_client.cc
index 5d5915c..328673da 100644
--- a/content/renderer/mus/renderer_window_tree_client.cc
+++ b/content/renderer/mus/renderer_window_tree_client.cc
@@ -242,9 +242,8 @@
 
 void RendererWindowTreeClient::OnWindowFocused(ui::Id focused_window_id) {}
 
-void RendererWindowTreeClient::OnWindowPredefinedCursorChanged(
-    ui::Id window_id,
-    ui::mojom::CursorType cursor) {}
+void RendererWindowTreeClient::OnWindowCursorChanged(ui::Id window_id,
+                                                     ui::CursorData cursor) {}
 
 void RendererWindowTreeClient::OnWindowSurfaceChanged(
     ui::Id window_id,
diff --git a/content/renderer/mus/renderer_window_tree_client.h b/content/renderer/mus/renderer_window_tree_client.h
index 7ac9daf5..cefe8797 100644
--- a/content/renderer/mus/renderer_window_tree_client.h
+++ b/content/renderer/mus/renderer_window_tree_client.h
@@ -134,8 +134,7 @@
                               uint32_t window_id,
                               int64_t display_id) override;
   void OnWindowFocused(ui::Id focused_window_id) override;
-  void OnWindowPredefinedCursorChanged(ui::Id window_id,
-                                       ui::mojom::CursorType cursor) override;
+  void OnWindowCursorChanged(ui::Id window_id, ui::CursorData cursor) override;
   void OnWindowSurfaceChanged(ui::Id window_id,
                               const cc::SurfaceInfo& surface_info) override;
   void OnDragDropStart(
diff --git a/content/renderer/pepper/pepper_webplugin_impl.cc b/content/renderer/pepper/pepper_webplugin_impl.cc
index 4b62d314..8abe3757 100644
--- a/content/renderer/pepper/pepper_webplugin_impl.cc
+++ b/content/renderer/pepper/pepper_webplugin_impl.cc
@@ -183,7 +183,9 @@
 }
 
 void PepperWebPluginImpl::Paint(WebCanvas* canvas, const WebRect& rect) {
-  if (!instance_->FlashIsFullscreenOrPending())
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (instance_ && !instance_->FlashIsFullscreenOrPending())
     instance_->Paint(canvas, plugin_rect_, rect);
 }
 
@@ -200,7 +202,10 @@
 
 void PepperWebPluginImpl::UpdateFocus(bool focused,
                                       blink::WebFocusType focus_type) {
-  instance_->SetWebKitFocus(focused);
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (instance_)
+    instance_->SetWebKitFocus(focused);
 }
 
 void PepperWebPluginImpl::UpdateVisibility(bool visible) {}
@@ -208,7 +213,9 @@
 blink::WebInputEventResult PepperWebPluginImpl::HandleInputEvent(
     const blink::WebInputEvent& event,
     blink::WebCursorInfo& cursor_info) {
-  if (instance_->FlashIsFullscreenOrPending())
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_ || instance_->FlashIsFullscreenOrPending())
     return blink::WebInputEventResult::kNotHandled;
   return instance_->HandleInputEvent(event, &cursor_info)
              ? blink::WebInputEventResult::kHandledApplication
@@ -217,11 +224,19 @@
 
 void PepperWebPluginImpl::DidReceiveResponse(
     const blink::WebURLResponse& response) {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return;
   DCHECK(!instance_->document_loader());
   instance_->HandleDocumentLoad(response);
 }
 
 void PepperWebPluginImpl::DidReceiveData(const char* data, int data_length) {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return;
   blink::WebAssociatedURLLoaderClient* document_loader =
       instance_->document_loader();
   if (document_loader)
@@ -229,6 +244,10 @@
 }
 
 void PepperWebPluginImpl::DidFinishLoading() {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return;
   blink::WebAssociatedURLLoaderClient* document_loader =
       instance_->document_loader();
   if (document_loader)
@@ -236,6 +255,10 @@
 }
 
 void PepperWebPluginImpl::DidFailLoading(const blink::WebURLError& error) {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return;
   blink::WebAssociatedURLLoaderClient* document_loader =
       instance_->document_loader();
   if (document_loader)
@@ -247,62 +270,113 @@
 }
 
 WebString PepperWebPluginImpl::SelectionAsText() const {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return WebString();
   return WebString::FromUTF16(instance_->GetSelectedText(false));
 }
 
 WebString PepperWebPluginImpl::SelectionAsMarkup() const {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return WebString();
   return WebString::FromUTF16(instance_->GetSelectedText(true));
 }
 
 WebURL PepperWebPluginImpl::LinkAtPosition(const WebPoint& position) const {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return GURL();
   return GURL(instance_->GetLinkAtPosition(position));
 }
 
 bool PepperWebPluginImpl::StartFind(const blink::WebString& search_text,
                                     bool case_sensitive,
                                     int identifier) {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return false;
   return instance_->StartFind(search_text.Utf8(), case_sensitive, identifier);
 }
 
 void PepperWebPluginImpl::SelectFindResult(bool forward, int identifier) {
-  instance_->SelectFindResult(forward, identifier);
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (instance_)
+    instance_->SelectFindResult(forward, identifier);
 }
 
 void PepperWebPluginImpl::StopFind() {
-  instance_->StopFind();
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (instance_)
+    instance_->StopFind();
 }
 
 bool PepperWebPluginImpl::SupportsPaginatedPrint() {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return false;
   return instance_->SupportsPrintInterface();
 }
 
 bool PepperWebPluginImpl::IsPrintScalingDisabled() {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return false;
   return instance_->IsPrintScalingDisabled();
 }
 
 int PepperWebPluginImpl::PrintBegin(const WebPrintParams& print_params) {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return 0;
   return instance_->PrintBegin(print_params);
 }
 
 void PepperWebPluginImpl::PrintPage(int page_number, blink::WebCanvas* canvas) {
-  instance_->PrintPage(page_number, canvas);
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (instance_)
+    instance_->PrintPage(page_number, canvas);
 }
 
 void PepperWebPluginImpl::PrintEnd() {
-  instance_->PrintEnd();
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (instance_)
+    instance_->PrintEnd();
 }
 
 bool PepperWebPluginImpl::GetPrintPresetOptionsFromDocument(
     blink::WebPrintPresetOptions* preset_options) {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return false;
   return instance_->GetPrintPresetOptionsFromDocument(preset_options);
 }
 
 bool PepperWebPluginImpl::CanRotateView() {
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (!instance_)
+    return false;
   return instance_->CanRotateView();
 }
 
 void PepperWebPluginImpl::RotateView(RotationType type) {
-  instance_->RotateView(type);
+  // Re-entrancy may cause JS to try to execute script on the plugin before it
+  // is fully initialized. See: crbug.com/715747.
+  if (instance_)
+    instance_->RotateView(type);
 }
 
 bool PepperWebPluginImpl::IsPlaceholder() {
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 8cf1916d..4313af0 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -696,6 +696,8 @@
         ['linux'], bug=483282)
     self.Fail('conformance2/textures/image_bitmap_from_image/' +
               'tex-3d-r16f-red-float.html', ['linux'], bug=679695)
+    self.Fail('conformance2/textures/canvas_sub_rectangle/' +
+              'tex-2d-rgb16f-rgb-float.html', ['linux'], bug=715696)
 
     # Linux Multi-vendor failures.
     self.Skip('deqp/data/gles3/shaders/qualification_order.html',
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
index 489b476..d9345de 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
+++ b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
@@ -1,3 +1,3 @@
 # AUTOGENERATED FILE - DO NOT EDIT
 # SEE roll_webgl_conformance.py
-Current webgl revision 40d75ac0e565191d93ff3c4e8fd19f8b2df7f617
+Current webgl revision f7157c2751220a8f0def9f3f7f6ff37392ac83f0
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 3b481aa6..1df91d7 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -7,6 +7,7 @@
 import("//testing/test.gni")
 
 if (is_android) {
+  import("//build/config/android/config.gni")
   import("//build/config/android/rules.gni")  # For generate_jni().
 }
 
@@ -303,14 +304,20 @@
     java_files = [
       "gamepad/android/junit/src/org/chromium/device/gamepad/GamepadMappingsTest.java",
       "generic_sensor/android/junit/src/org/chromium/device/sensors/PlatformSensorAndProviderTest.java",
+      "geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java",
       "nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java",
     ]
     deps = [
+      "$google_play_services_package:google_play_services_base_java",
+      "$google_play_services_package:google_play_services_basement_java",
+      "$google_play_services_package:google_play_services_location_java",
       "//base:base_java",
       "//base:base_java_test_support",
       "//device/gamepad:java",
       "//device/generic_sensor:java",
       "//device/generic_sensor/public/interfaces:interfaces_java",
+      "//device/geolocation:geolocation_java",
+      "//device/geolocation:geolocation_java_test_support",
       "//device/nfc:mojo_bindings_java",
       "//device/nfc/android:java",
       "//mojo/public/java:bindings_java",
diff --git a/device/geolocation/BUILD.gn b/device/geolocation/BUILD.gn
index 19ba0bf1..51b1284 100644
--- a/device/geolocation/BUILD.gn
+++ b/device/geolocation/BUILD.gn
@@ -5,6 +5,7 @@
 import("//build/config/features.gni")
 
 if (is_android) {
+  import("//build/config/android/config.gni")
   import("//build/config/android/rules.gni")  # For generate_jni().
 }
 
@@ -127,11 +128,15 @@
       "android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java",
       "android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java",
       "android/java/src/org/chromium/device/geolocation/LocationProviderFactory.java",
+      "android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java",
     ]
 
     deps = [
       ":geolocation",
       ":geolocation_jni_headers",
+      "$google_play_services_package:google_play_services_base_java",
+      "$google_play_services_package:google_play_services_basement_java",
+      "$google_play_services_package:google_play_services_location_java",
       "//base:base_java",
     ]
   }
diff --git a/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java
index bd2ffb9..c735159 100644
--- a/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java
+++ b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java
@@ -5,7 +5,9 @@
 package org.chromium.device.geolocation;
 
 import android.content.Context;
+import android.location.Location;
 
+import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
@@ -24,6 +26,8 @@
 @MainDex
 @VisibleForTesting
 public class LocationProviderAdapter {
+    private static final String TAG = "cr_LocationProvider";
+
     // Delegate handling the real work in the main thread.
     private LocationProviderFactory.LocationProvider mImpl;
 
@@ -75,14 +79,15 @@
         return mImpl.isRunning();
     }
 
-    public static void newLocationAvailable(double latitude, double longitude, double timestamp,
-            boolean hasAltitude, double altitude, boolean hasAccuracy, double accuracy,
-            boolean hasHeading, double heading, boolean hasSpeed, double speed) {
-        nativeNewLocationAvailable(latitude, longitude, timestamp, hasAltitude, altitude,
-                hasAccuracy, accuracy, hasHeading, heading, hasSpeed, speed);
+    public static void onNewLocationAvailable(Location location) {
+        nativeNewLocationAvailable(location.getLatitude(), location.getLongitude(),
+                location.getTime() / 1000.0, location.hasAltitude(), location.getAltitude(),
+                location.hasAccuracy(), location.getAccuracy(), location.hasBearing(),
+                location.getBearing(), location.hasSpeed(), location.getSpeed());
     }
 
     public static void newErrorAvailable(String message) {
+        Log.e(TAG, "newErrorAvailable %s", message);
         nativeNewErrorAvailable(message);
     }
 
diff --git a/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java
index 261ce9d..68c4b46 100644
--- a/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java
+++ b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java
@@ -13,6 +13,7 @@
 
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
 
 import java.util.List;
 
@@ -37,17 +38,20 @@
 
     @Override
     public void start(boolean enableHighAccuracy) {
+        ThreadUtils.assertOnUiThread();
         unregisterFromLocationUpdates();
         registerForLocationUpdates(enableHighAccuracy);
     }
 
     @Override
     public void stop() {
+        ThreadUtils.assertOnUiThread();
         unregisterFromLocationUpdates();
     }
 
     @Override
     public boolean isRunning() {
+        ThreadUtils.assertOnUiThread();
         return mIsRunning;
     }
 
@@ -57,7 +61,7 @@
         // possible that we receive callbacks after unregistering. At this point, the
         // native object will no longer exist.
         if (mIsRunning) {
-            updateNewLocation(location);
+            LocationProviderAdapter.onNewLocationAvailable(location);
         }
     }
 
@@ -70,7 +74,12 @@
     @Override
     public void onProviderDisabled(String provider) {}
 
-    private void ensureLocationManagerCreated() {
+    @VisibleForTesting
+    public void setLocationManagerForTesting(LocationManager manager) {
+        mLocationManager = manager;
+    }
+
+    private void createLocationManagerIfNeeded() {
         if (mLocationManager != null) return;
         mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
         if (mLocationManager == null) {
@@ -82,7 +91,7 @@
      * Registers this object with the location service.
      */
     private void registerForLocationUpdates(boolean enableHighAccuracy) {
-        ensureLocationManagerCreated();
+        createLocationManagerIfNeeded();
         if (usePassiveOneShotLocation()) return;
 
         assert !mIsRunning;
@@ -103,8 +112,8 @@
             unregisterFromLocationUpdates();
             // Propagate an error to JavaScript, this can happen in case of WebView
             // when the embedding app does not have sufficient permissions.
-            LocationProviderAdapter.newErrorAvailable("application does not have sufficient "
-                    + "geolocation permissions.");
+            LocationProviderAdapter.newErrorAvailable(
+                    "application does not have sufficient geolocation permissions.");
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Caught IllegalArgumentException registering for location updates.");
             unregisterFromLocationUpdates();
@@ -116,33 +125,26 @@
      * Unregisters this object from the location service.
      */
     private void unregisterFromLocationUpdates() {
-        if (mIsRunning) {
-            mIsRunning = false;
-            mLocationManager.removeUpdates(this);
-        }
-    }
-
-    private void updateNewLocation(Location location) {
-        LocationProviderAdapter.newLocationAvailable(location.getLatitude(),
-                location.getLongitude(), location.getTime() / 1000.0, location.hasAltitude(),
-                location.getAltitude(), location.hasAccuracy(), location.getAccuracy(),
-                location.hasBearing(), location.getBearing(), location.hasSpeed(),
-                location.getSpeed());
+        if (!mIsRunning) return;
+        mIsRunning = false;
+        mLocationManager.removeUpdates(this);
     }
 
     private boolean usePassiveOneShotLocation() {
-        if (!isOnlyPassiveLocationProviderEnabled()) return false;
+        if (!isOnlyPassiveLocationProviderEnabled()) {
+            return false;
+        }
 
         // Do not request a location update if the only available location provider is
         // the passive one. Make use of the last known location and call
-        // onLocationChanged directly.
+        // onNewLocationAvailable directly.
         final Location location =
                 mLocationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
         if (location != null) {
             ThreadUtils.runOnUiThread(new Runnable() {
                 @Override
                 public void run() {
-                    updateNewLocation(location);
+                    LocationProviderAdapter.onNewLocationAvailable(location);
                 }
             });
         }
@@ -154,7 +156,7 @@
      * in the system.
      */
     private boolean isOnlyPassiveLocationProviderEnabled() {
-        List<String> providers = mLocationManager.getProviders(true);
+        final List<String> providers = mLocationManager.getProviders(true);
         return providers != null && providers.size() == 1
                 && providers.get(0).equals(LocationManager.PASSIVE_PROVIDER);
     }
diff --git a/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderFactory.java b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderFactory.java
index 3ffc36a..33809e9 100644
--- a/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderFactory.java
+++ b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderFactory.java
@@ -9,19 +9,20 @@
 import org.chromium.base.VisibleForTesting;
 
 /**
- * Factory to create a LocationProvider to allow us to inject
- * a mock for tests.
+ * Factory to create a LocationProvider to allow us to inject a mock for tests.
  */
 public class LocationProviderFactory {
     private static LocationProviderFactory.LocationProvider sProviderImpl;
+    private static boolean sUseGmsCoreLocationProvider;
 
     /**
      * LocationProviderFactory.create() returns an instance of this interface.
      */
     public interface LocationProvider {
         /**
-         * Start listening for location updates.
-         * @param enableHighAccuracy Whether or not to enable high accuracy location providers.
+         * Start listening for location updates. Calling several times before stop() is interpreted
+         * as restart.
+         * @param enableHighAccuracy Whether or not to enable high accuracy location.
          */
         public void start(boolean enableHighAccuracy);
 
@@ -40,12 +41,20 @@
 
     @VisibleForTesting
     public static void setLocationProviderImpl(LocationProviderFactory.LocationProvider provider) {
-        assert sProviderImpl == null;
         sProviderImpl = provider;
     }
 
+    public static void useGmsCoreLocationProvider() {
+        sUseGmsCoreLocationProvider = true;
+    }
+
     public static LocationProvider create(Context context) {
-        if (sProviderImpl == null) {
+        if (sProviderImpl != null) return sProviderImpl;
+
+        if (sUseGmsCoreLocationProvider
+                && LocationProviderGmsCore.isGooglePlayServicesAvailable(context)) {
+            sProviderImpl = new LocationProviderGmsCore(context);
+        } else {
             sProviderImpl = new LocationProviderAndroid(context);
         }
         return sProviderImpl;
diff --git a/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java
new file mode 100644
index 0000000..aed6822
--- /dev/null
+++ b/device/geolocation/android/java/src/org/chromium/device/geolocation/LocationProviderGmsCore.java
@@ -0,0 +1,136 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.device.geolocation;
+
+import android.content.Context;
+import android.location.Location;
+import android.os.Bundle;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
+import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
+import com.google.android.gms.location.FusedLocationProviderApi;
+import com.google.android.gms.location.LocationListener;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationServices;
+
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+
+/**
+ * This is a LocationProvider using Google Play Services.
+ *
+ * https://developers.google.com/android/reference/com/google/android/gms/location/package-summary
+ */
+public class LocationProviderGmsCore implements ConnectionCallbacks, OnConnectionFailedListener,
+                                                LocationListener,
+                                                LocationProviderFactory.LocationProvider {
+    private static final String TAG = "cr_LocationProvider";
+
+    // Values for the LocationRequest's setInterval for normal and high accuracy, respectively.
+    private static final long UPDATE_INTERVAL_MS = 1000;
+    private static final long UPDATE_INTERVAL_FAST_MS = 500;
+
+    private final GoogleApiClient mGoogleApiClient;
+    private FusedLocationProviderApi mLocationProviderApi = LocationServices.FusedLocationApi;
+
+    private boolean mEnablehighAccuracy;
+    private LocationRequest mLocationRequest;
+
+    public static boolean isGooglePlayServicesAvailable(Context context) {
+        return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
+                == ConnectionResult.SUCCESS;
+    }
+
+    LocationProviderGmsCore(Context context) {
+        Log.i(TAG, "Google Play Services");
+        mGoogleApiClient = new GoogleApiClient.Builder(context)
+                                   .addApi(LocationServices.API)
+                                   .addConnectionCallbacks(this)
+                                   .addOnConnectionFailedListener(this)
+                                   .build();
+        assert mGoogleApiClient != null;
+    }
+
+    LocationProviderGmsCore(GoogleApiClient client, FusedLocationProviderApi locationApi) {
+        mGoogleApiClient = client;
+        mLocationProviderApi = locationApi;
+    }
+
+    // ConnectionCallbacks implementation
+    @Override
+    public void onConnected(Bundle connectionHint) {
+        ThreadUtils.assertOnUiThread();
+
+        mLocationRequest = LocationRequest.create();
+        if (mEnablehighAccuracy) {
+            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
+                    .setInterval(UPDATE_INTERVAL_FAST_MS);
+        } else {
+            mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
+                    .setInterval(UPDATE_INTERVAL_MS);
+        }
+
+        final Location location = mLocationProviderApi.getLastLocation(mGoogleApiClient);
+        if (location != null) {
+            LocationProviderAdapter.onNewLocationAvailable(location);
+        }
+
+        try {
+            // Request updates on UI Thread replicating LocationProviderAndroid's behaviour.
+            mLocationProviderApi.requestLocationUpdates(
+                    mGoogleApiClient, mLocationRequest, this, ThreadUtils.getUiThreadLooper());
+        } catch (IllegalStateException e) {
+            // Happens "If this method is executed in a thread that has not called Looper.prepare()"
+            Log.e(TAG, "Unexpected exception " + e);
+            assert false;
+        }
+    }
+
+    @Override
+    public void onConnectionSuspended(int cause) {}
+
+    // OnConnectionFailedListener implementation
+    @Override
+    public void onConnectionFailed(ConnectionResult result) {
+        LocationProviderAdapter.newErrorAvailable(
+                "Failed to connect to Google Play Services: " + result.toString());
+    }
+
+    // LocationProviderFactory.LocationProvider implementation
+    @Override
+    public void start(boolean enableHighAccuracy) {
+        ThreadUtils.assertOnUiThread();
+        if (mGoogleApiClient.isConnected()) mGoogleApiClient.disconnect();
+
+        mEnablehighAccuracy = enableHighAccuracy;
+        mGoogleApiClient.connect(); // Should return via onConnected().
+    }
+
+    @Override
+    public void stop() {
+        ThreadUtils.assertOnUiThread();
+        if (!mGoogleApiClient.isConnected()) return;
+
+        mLocationProviderApi.removeLocationUpdates(mGoogleApiClient, this);
+
+        mGoogleApiClient.disconnect();
+    }
+
+    @Override
+    public boolean isRunning() {
+        assert ThreadUtils.runningOnUiThread();
+        if (mGoogleApiClient == null) return false;
+        return mGoogleApiClient.isConnecting() || mGoogleApiClient.isConnected();
+    }
+
+    // LocationListener implementation
+    @Override
+    public void onLocationChanged(Location location) {
+        LocationProviderAdapter.onNewLocationAvailable(location);
+    }
+}
diff --git a/device/geolocation/android/javatests/src/org/chromium/device/geolocation/MockLocationProvider.java b/device/geolocation/android/javatests/src/org/chromium/device/geolocation/MockLocationProvider.java
index ae99afb..2ed205da5 100644
--- a/device/geolocation/android/javatests/src/org/chromium/device/geolocation/MockLocationProvider.java
+++ b/device/geolocation/android/javatests/src/org/chromium/device/geolocation/MockLocationProvider.java
@@ -4,6 +4,7 @@
 
 package org.chromium.device.geolocation;
 
+import android.location.Location;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -77,7 +78,9 @@
     }
 
     private void newLocation() {
-        LocationProviderAdapter.newLocationAvailable(
-                0, 0, System.currentTimeMillis() / 1000.0, false, 0, true, 0.5, false, 0, false, 0);
+        Location location = new Location("MockLocationProvider");
+        location.setTime(System.currentTimeMillis());
+        location.setAccuracy(0.5f);
+        LocationProviderAdapter.onNewLocationAvailable(location);
     }
 };
diff --git a/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java b/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java
new file mode 100644
index 0000000..5b09459
--- /dev/null
+++ b/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java
@@ -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.
+
+package org.chromium.device.geolocation;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+
+import android.content.Context;
+import android.location.LocationManager;
+
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.location.FusedLocationProviderApi;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.ParameterizedRobolectricTestRunner;
+import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.internal.Shadow;
+import org.robolectric.shadows.ShadowLocationManager;
+import org.robolectric.shadows.ShadowLog; // remove me ?
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.Feature;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Test suite for Java Geolocation.
+ */
+@RunWith(ParameterizedRobolectricTestRunner.class)
+@Config(sdk = 21, manifest = Config.NONE)
+public class LocationProviderTest {
+    public static enum LocationProviderType { MOCK, ANDROID, GMS_CORE }
+
+    @Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {{LocationProviderType.MOCK},
+                {LocationProviderType.ANDROID}, {LocationProviderType.GMS_CORE}});
+    }
+
+    @Mock
+    private Context mContext;
+
+    // Member variables for LocationProviderType.GMS_CORE case.
+    @Mock
+    private GoogleApiClient mGoogleApiClient;
+    private boolean mGoogleApiClientIsConnected;
+
+    // Member variables for LocationProviderType.ANDROID case.
+    private LocationManager mLocationManager;
+    private ShadowLocationManager mShadowLocationManager;
+
+    private LocationProviderAdapter mLocationProviderAdapter;
+
+    private final LocationProviderType mApi;
+
+    public LocationProviderTest(LocationProviderType api) {
+        mApi = api;
+    }
+
+    @Before
+    public void setUp() {
+        ShadowLog.stream = System.out;
+        MockitoAnnotations.initMocks(this);
+
+        mContext = Mockito.mock(Context.class);
+    }
+
+    /**
+     * Verify a normal start/stop call pair with the given LocationProvider.
+     */
+    @Test
+    @Feature({"Location"})
+    public void testStartStop() {
+        assertTrue("Should be on UI thread", ThreadUtils.runningOnUiThread());
+
+        setLocationProvider();
+
+        createLocationProviderAdapter();
+        startLocationProviderAdapter(false);
+        stopLocationProviderAdapter();
+    }
+
+    /**
+     * Verify a start/upgrade/stop call sequencewith the given LocationProvider.
+     */
+    @Test
+    @Feature({"Location"})
+    public void testStartUpgradeStop() {
+        assertTrue("Should be on UI thread", ThreadUtils.runningOnUiThread());
+
+        setLocationProvider();
+
+        createLocationProviderAdapter();
+        startLocationProviderAdapter(false);
+        startLocationProviderAdapter(true);
+        stopLocationProviderAdapter();
+    }
+
+    private void createLocationProviderAdapter() {
+        mLocationProviderAdapter = LocationProviderAdapter.create(mContext);
+        assertNotNull("LocationProvider", mLocationProviderAdapter);
+    }
+
+    private void setLocationProvider() {
+        if (mApi == LocationProviderType.MOCK) {
+            setLocationProviderMock();
+        } else if (mApi == LocationProviderType.ANDROID) {
+            setLocationProviderAndroid();
+        } else if (mApi == LocationProviderType.GMS_CORE) {
+            setLocationProviderGmsCore();
+        } else {
+            assert false;
+        }
+    }
+
+    private void setLocationProviderMock() {
+        LocationProviderFactory.setLocationProviderImpl(new MockLocationProvider());
+    }
+
+    private void setLocationProviderAndroid() {
+        LocationProviderAndroid locationProviderAndroid = new LocationProviderAndroid(mContext);
+
+        // Robolectric has a ShadowLocationManager class that mocks the behaviour of the real
+        // class very closely. Use it here.
+        mLocationManager = Shadow.newInstanceOf(LocationManager.class);
+        mShadowLocationManager = Shadows.shadowOf(mLocationManager);
+        locationProviderAndroid.setLocationManagerForTesting(mLocationManager);
+        LocationProviderFactory.setLocationProviderImpl(locationProviderAndroid);
+    }
+
+    private void setLocationProviderGmsCore() {
+        // Robolectric has a ShadowGoogleApiClientBuilder class that mocks the behaviour of the real
+        // class very closely, but it's not available in our build
+        mGoogleApiClient = Mockito.mock(GoogleApiClient.class);
+        mGoogleApiClientIsConnected = false;
+        doAnswer(new Answer<Boolean>() {
+            public Boolean answer(InvocationOnMock invocation) {
+                return mGoogleApiClientIsConnected;
+            }
+        })
+                .when(mGoogleApiClient)
+                .isConnected();
+
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                mGoogleApiClientIsConnected = true;
+                return null;
+            }
+        })
+                .when(mGoogleApiClient)
+                .connect();
+
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                mGoogleApiClientIsConnected = false;
+                return null;
+            }
+        })
+                .when(mGoogleApiClient)
+                .disconnect();
+
+        FusedLocationProviderApi fusedLocationProviderApi =
+                Mockito.mock(FusedLocationProviderApi.class);
+
+        LocationProviderGmsCore locationProviderGmsCore =
+                new LocationProviderGmsCore(mGoogleApiClient, fusedLocationProviderApi);
+
+        LocationProviderFactory.setLocationProviderImpl(locationProviderGmsCore);
+    }
+
+    private void startLocationProviderAdapter(boolean highResolution) {
+        mLocationProviderAdapter.start(highResolution);
+        assertTrue("Should be running", mLocationProviderAdapter.isRunning());
+    }
+
+    private void stopLocationProviderAdapter() {
+        mLocationProviderAdapter.stop();
+        assertFalse("Should have stopped", mLocationProviderAdapter.isRunning());
+    }
+}
diff --git a/device/geolocation/wifi_data_provider_win.cc b/device/geolocation/wifi_data_provider_win.cc
index 8b296ae..908d06c 100644
--- a/device/geolocation/wifi_data_provider_win.cc
+++ b/device/geolocation/wifi_data_provider_win.cc
@@ -2,25 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Windows Vista uses the Native Wifi (WLAN) API for accessing WiFi cards. See
-// http://msdn.microsoft.com/en-us/library/ms705945(VS.85).aspx. Windows XP
-// Service Pack 3 (and Windows XP Service Pack 2, if upgraded with a hot fix)
-// also support a limited version of the WLAN API. See
-// http://msdn.microsoft.com/en-us/library/bb204766.aspx. The WLAN API uses
-// wlanapi.h, which is not part of the SDK used by Gears, so is replicated
-// locally using data from the MSDN.
-//
-// Windows XP from Service Pack 2 onwards supports the Wireless Zero
-// Configuration (WZC) programming interface. See
-// http://msdn.microsoft.com/en-us/library/ms706587(VS.85).aspx.
-//
-// The MSDN recommends that one use the WLAN API where available, and WZC
-// otherwise.
-//
-// However, it seems that WZC fails for some wireless cards. Also, WLAN seems
-// not to work on XP SP3. So we use WLAN on Vista, and use NDIS directly
-// otherwise.
-
 #include "device/geolocation/wifi_data_provider_win.h"
 
 #include <windows.h>
@@ -31,29 +12,20 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/timer/elapsed_timer.h"
 #include "base/win/windows_version.h"
 #include "device/geolocation/wifi_data_provider_common.h"
 #include "device/geolocation/wifi_data_provider_common_win.h"
 #include "device/geolocation/wifi_data_provider_manager.h"
 
-// Taken from ndis.h for WinCE.
-#define NDIS_STATUS_INVALID_LENGTH ((NDIS_STATUS)0xC0010014L)
-#define NDIS_STATUS_BUFFER_TOO_SHORT ((NDIS_STATUS)0xC0010016L)
-
 namespace device {
+
 namespace {
-// The limits on the size of the buffer used for the OID query.
-const int kInitialBufferSize = 2 << 12;  // Good for about 50 APs.
-const int kMaximumBufferSize = 2 << 20;  // 2MB
 
-// Length for generic string buffers passed to Windows APIs.
-const int kStringLength = 512;
-
-// The time periods, in milliseconds, between successive polls of the wifi data.
-const int kDefaultPollingInterval = 10000;                 // 10s
-const int kNoChangePollingInterval = 120000;               // 2 mins
-const int kTwoNoChangePollingInterval = 600000;            // 10 mins
-const int kNoWifiPollingIntervalMilliseconds = 20 * 1000;  // 20s
+static const int kDefaultPollingIntervalMs = 10 * 1000;           // 10s
+static const int kNoChangePollingIntervalMs = 2 * 60 * 1000;      // 2 mins
+static const int kTwoNoChangePollingIntervalMs = 10 * 60 * 1000;  // 10 mins
+static const int kNoWifiPollingIntervalMs = 20 * 1000;            // 20s
 
 // WlanOpenHandle
 typedef DWORD(WINAPI* WlanOpenHandleFunction)(DWORD dwClientVersion,
@@ -84,29 +56,47 @@
 typedef DWORD(WINAPI* WlanCloseHandleFunction)(HANDLE hClientHandle,
                                                PVOID pReserved);
 
-// Local classes and functions
+// Extracts data for an access point and converts to AccessPointData.
+AccessPointData GetNetworkData(const WLAN_BSS_ENTRY& bss_entry) {
+  AccessPointData access_point_data;
+  // Currently we get only MAC address, signal strength and SSID.
+  access_point_data.mac_address = MacAddressAsString16(bss_entry.dot11Bssid);
+  access_point_data.radio_signal_strength = bss_entry.lRssi;
+  // bss_entry.dot11Ssid.ucSSID is not null-terminated.
+  base::UTF8ToUTF16(reinterpret_cast<const char*>(bss_entry.dot11Ssid.ucSSID),
+                    static_cast<ULONG>(bss_entry.dot11Ssid.uSSIDLength),
+                    &access_point_data.ssid);
+
+  // TODO(steveblock): Is it possible to get the following?
+  // access_point_data.signal_to_noise
+  // access_point_data.age
+  // access_point_data.channel
+  return access_point_data;
+}
+
+// This class encapsulates loading and interacting with wlan_api.dll, which can
+// not be loaded statically because it's not available in Server 2008 R2, where
+// it must be installed explicitly by the user if and when they wants to use the
+// Wireless interface.
+// https://www.bonusbits.com/wiki/KB:Wlanapi.dll_missing_on_Windows_Server_2008_R2
 class WindowsWlanApi : public WifiDataProviderCommon::WlanApiInterface {
  public:
-  // Factory function. Will return NULL if this API is unavailable.
   static std::unique_ptr<WindowsWlanApi> Create();
 
   // Takes ownership of the library handle.
   explicit WindowsWlanApi(HINSTANCE library);
   ~WindowsWlanApi() override;
 
-  // WlanApiInterface
+  // WlanApiInterface implementation
   bool GetAccessPointData(WifiData::AccessPointDataSet* data) override;
 
  private:
-  // Loads the required functions from the DLL.
-  void GetWLANFunctions(HINSTANCE wlan_library);
-  int GetInterfaceDataWLAN(HANDLE wlan_handle,
-                           const GUID& interface_id,
-                           WifiData::AccessPointDataSet* data);
-
   // Logs number of detected wlan interfaces.
   static void LogWlanInterfaceCount(int count);
 
+  bool GetInterfaceDataWLAN(HANDLE wlan_handle,
+                            const GUID& interface_id,
+                            WifiData::AccessPointDataSet* data);
   // Handle to the wlanapi.dll library.
   HINSTANCE library_;
 
@@ -118,130 +108,56 @@
   WlanCloseHandleFunction WlanCloseHandle_function_;
 };
 
-class WindowsNdisApi : public WifiDataProviderCommon::WlanApiInterface {
- public:
-  static std::unique_ptr<WindowsNdisApi> Create();
-
-  // Swaps in content of the vector passed
-  explicit WindowsNdisApi(std::vector<base::string16>* interface_service_names);
-  ~WindowsNdisApi() override;
-
-  // WlanApiInterface
-  bool GetAccessPointData(WifiData::AccessPointDataSet* data) override;
-
- private:
-  static bool GetInterfacesNDIS(
-      std::vector<base::string16>* interface_service_names_out);
-  bool GetInterfaceDataNDIS(HANDLE adapter_handle,
-                            WifiData::AccessPointDataSet* data);
-  // NDIS variables.
-  std::vector<base::string16> interface_service_names_;
-
-  // Remembers scan result buffer size across calls.
-  int oid_buffer_size_;
-};
-
-// Extracts data for an access point and converts to Gears format.
-bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
-                    AccessPointData* access_point_data);
-bool UndefineDosDevice(const base::string16& device_name);
-bool DefineDosDeviceIfNotExists(const base::string16& device_name);
-HANDLE GetFileHandle(const base::string16& device_name);
-// Makes the OID query and returns a Windows API error code.
-int PerformQuery(HANDLE adapter_handle,
-                 BYTE* buffer,
-                 DWORD buffer_size,
-                 DWORD* bytes_out);
-bool ResizeBuffer(int requested_size,
-                  std::unique_ptr<BYTE, base::FreeDeleter>* buffer);
-// Gets the system directory and appends a trailing slash if not already
-// present.
-bool GetSystemDirectory(base::string16* path);
-}  // anonymous namespace
-
-WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() {
-  return new WifiDataProviderWin();
+// static
+std::unique_ptr<WindowsWlanApi> WindowsWlanApi::Create() {
+  // Use an absolute path to load the DLL to avoid DLL preloading attacks.
+  static const wchar_t* const kDLL = L"%WINDIR%\\system32\\wlanapi.dll";
+  wchar_t path[MAX_PATH] = {0};
+  ExpandEnvironmentStrings(kDLL, path, arraysize(path));
+  HINSTANCE library = LoadLibraryEx(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+  if (!library)
+    return nullptr;
+  return base::MakeUnique<WindowsWlanApi>(library);
 }
 
-WifiDataProviderWin::WifiDataProviderWin() {}
-
-WifiDataProviderWin::~WifiDataProviderWin() {}
-
-std::unique_ptr<WifiDataProviderCommon::WlanApiInterface>
-WifiDataProviderWin::CreateWlanApi() {
-  // Use the WLAN interface if we're on Vista and if it's available. Otherwise,
-  // use NDIS.
-  std::unique_ptr<WlanApiInterface> api = WindowsWlanApi::Create();
-  if (api)
-    return api;
-  return WindowsNdisApi::Create();
-}
-
-std::unique_ptr<WifiPollingPolicy> WifiDataProviderWin::CreatePollingPolicy() {
-  return base::MakeUnique<GenericWifiPollingPolicy<
-      kDefaultPollingInterval, kNoChangePollingInterval,
-      kTwoNoChangePollingInterval, kNoWifiPollingIntervalMilliseconds>>();
-}
-
-// Local classes and functions
-namespace {
-
-// WindowsWlanApi
 WindowsWlanApi::WindowsWlanApi(HINSTANCE library) : library_(library) {
-  GetWLANFunctions(library_);
+  DCHECK(library_);
+  // Extract all methods from |library_|.
+  WlanOpenHandle_function_ = reinterpret_cast<WlanOpenHandleFunction>(
+      GetProcAddress(library_, "WlanOpenHandle"));
+  WlanEnumInterfaces_function_ = reinterpret_cast<WlanEnumInterfacesFunction>(
+      GetProcAddress(library_, "WlanEnumInterfaces"));
+  WlanGetNetworkBssList_function_ =
+      reinterpret_cast<WlanGetNetworkBssListFunction>(
+          GetProcAddress(library_, "WlanGetNetworkBssList"));
+  WlanFreeMemory_function_ = reinterpret_cast<WlanFreeMemoryFunction>(
+      GetProcAddress(library_, "WlanFreeMemory"));
+  WlanCloseHandle_function_ = reinterpret_cast<WlanCloseHandleFunction>(
+      GetProcAddress(library_, "WlanCloseHandle"));
+
+  DCHECK(WlanOpenHandle_function_ && WlanEnumInterfaces_function_ &&
+         WlanGetNetworkBssList_function_ && WlanFreeMemory_function_ &&
+         WlanCloseHandle_function_);
 }
 
 WindowsWlanApi::~WindowsWlanApi() {
   FreeLibrary(library_);
 }
 
-std::unique_ptr<WindowsWlanApi> WindowsWlanApi::Create() {
-  if (base::win::GetVersion() < base::win::VERSION_VISTA)
-    return nullptr;
-  // We use an absolute path to load the DLL to avoid DLL preloading attacks.
-  base::string16 system_directory;
-  if (!GetSystemDirectory(&system_directory))
-    return nullptr;
-  DCHECK(!system_directory.empty());
-  base::string16 dll_path = system_directory + L"wlanapi.dll";
-  HINSTANCE library =
-      LoadLibraryEx(dll_path.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
-  if (!library)
-    return nullptr;
-  return base::MakeUnique<WindowsWlanApi>(library);
-}
-
-void WindowsWlanApi::GetWLANFunctions(HINSTANCE wlan_library) {
-  DCHECK(wlan_library);
-  WlanOpenHandle_function_ = reinterpret_cast<WlanOpenHandleFunction>(
-      GetProcAddress(wlan_library, "WlanOpenHandle"));
-  WlanEnumInterfaces_function_ = reinterpret_cast<WlanEnumInterfacesFunction>(
-      GetProcAddress(wlan_library, "WlanEnumInterfaces"));
-  WlanGetNetworkBssList_function_ =
-      reinterpret_cast<WlanGetNetworkBssListFunction>(
-          GetProcAddress(wlan_library, "WlanGetNetworkBssList"));
-  WlanFreeMemory_function_ = reinterpret_cast<WlanFreeMemoryFunction>(
-      GetProcAddress(wlan_library, "WlanFreeMemory"));
-  WlanCloseHandle_function_ = reinterpret_cast<WlanCloseHandleFunction>(
-      GetProcAddress(wlan_library, "WlanCloseHandle"));
-  DCHECK(WlanOpenHandle_function_ && WlanEnumInterfaces_function_ &&
-         WlanGetNetworkBssList_function_ && WlanFreeMemory_function_ &&
-         WlanCloseHandle_function_);
-}
-
+// static
 void WindowsWlanApi::LogWlanInterfaceCount(int count) {
-  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.Wifi.InterfaceCount", count, 1, 5, 6);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.Wifi.InterfaceCount", count, 1 /* min */,
+                              5 /* max */, 6 /* bucket_count */);
 }
 
 bool WindowsWlanApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
   DCHECK(data);
 
-  // Get the handle to the WLAN API.
   DWORD negotiated_version;
-  HANDLE wlan_handle = NULL;
-  // We could be executing on either Windows XP or Windows Vista, so use the
-  // lower version of the client WLAN API. It seems that the negotiated version
-  // is the Vista version irrespective of what we pass!
+  HANDLE wlan_handle = nullptr;
+  // Highest WLAN API version supported by the client; pass the lowest. It seems
+  // that the negotiated version is the Vista version (the highest) irrespective
+  // of what we pass!
   static const int kXpWlanClientVersion = 1;
   if ((*WlanOpenHandle_function_)(kXpWlanClientVersion, NULL,
                                   &negotiated_version,
@@ -251,8 +167,8 @@
   }
   DCHECK(wlan_handle);
 
-  // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
-  WLAN_INTERFACE_INFO_LIST* interface_list = NULL;
+  // Get the list of interfaces. WlanEnumInterfaces allocates |interface_list|.
+  WLAN_INTERFACE_INFO_LIST* interface_list = nullptr;
   if ((*WlanEnumInterfaces_function_)(wlan_handle, NULL, &interface_list) !=
       ERROR_SUCCESS) {
     LogWlanInterfaceCount(0);
@@ -263,324 +179,78 @@
   LogWlanInterfaceCount(interface_list->dwNumberOfItems);
 
   // Go through the list of interfaces and get the data for each.
-  for (int i = 0; i < static_cast<int>(interface_list->dwNumberOfItems); ++i) {
+  for (size_t i = 0; i < interface_list->dwNumberOfItems; ++i) {
+    const WLAN_INTERFACE_INFO interface_info = interface_list->InterfaceInfo[i];
+
     // Skip any interface that is midway through association; the
     // WlanGetNetworkBssList function call is known to hang indefinitely
-    // when it's in this state. http://crbug.com/39300
-    if (interface_list->InterfaceInfo[i].isState ==
-        wlan_interface_state_associating) {
-      LOG(WARNING) << "Skipping wifi scan on adapter " << i << " ("
-                   << interface_list->InterfaceInfo[i].strInterfaceDescription
-                   << ") in 'associating' state. Repeated occurrences "
-                      "indicates a non-responding adapter.";
+    // when it's in this state. https://crbug.com/39300
+    if (interface_info.isState == wlan_interface_state_associating) {
+      DLOG(WARNING) << "Skipping wifi scan on adapter " << i << " ("
+                    << interface_info.strInterfaceDescription
+                    << ") in 'associating' state. Repeated occurrences "
+                       "indicates a non-responding adapter.";
       continue;
     }
-    GetInterfaceDataWLAN(wlan_handle,
-                         interface_list->InterfaceInfo[i].InterfaceGuid, data);
+    GetInterfaceDataWLAN(wlan_handle, interface_info.InterfaceGuid, data);
   }
 
-  // Free interface_list.
   (*WlanFreeMemory_function_)(interface_list);
 
-  // Close the handle.
-  if ((*WlanCloseHandle_function_)(wlan_handle, NULL) != ERROR_SUCCESS) {
-    return false;
-  }
-
-  return true;
+  return (*WlanCloseHandle_function_)(wlan_handle, NULL) == ERROR_SUCCESS;
 }
 
-// Appends the data for a single interface to the data vector. Returns the
-// number of access points found, or -1 on error.
-int WindowsWlanApi::GetInterfaceDataWLAN(const HANDLE wlan_handle,
-                                         const GUID& interface_id,
-                                         WifiData::AccessPointDataSet* data) {
-  DCHECK(data);
-
-  const base::TimeTicks start_time = base::TimeTicks::Now();
-
-  // WlanGetNetworkBssList allocates bss_list.
-  WLAN_BSS_LIST* bss_list = NULL;
+// Appends the data for a single interface to |data|. Returns false for error.
+bool WindowsWlanApi::GetInterfaceDataWLAN(const HANDLE wlan_handle,
+                                          const GUID& interface_id,
+                                          WifiData::AccessPointDataSet* data) {
+  base::ElapsedTimer wlan_get_network_list_timer;
+  // WlanGetNetworkBssList allocates |bss_list|.
+  WLAN_BSS_LIST* bss_list = nullptr;
   if ((*WlanGetNetworkBssList_function_)(wlan_handle, &interface_id,
                                          NULL,  // Use all SSIDs.
                                          dot11_BSS_type_any,
                                          false,  // bSecurityEnabled - unused
                                          NULL,   // reserved
                                          &bss_list) != ERROR_SUCCESS) {
-    return -1;
+    return false;
   }
-  // According to http://www.attnetclient.com/kb/questions.php?questionid=75
-  // WlanGetNetworkBssList can sometimes return success, but leave the bss
-  // list as NULL.
+  // WlanGetNetworkBssList() can return success without filling |bss_list|.
   if (!bss_list)
-    return -1;
+    return false;
 
-  const base::TimeDelta duration = base::TimeTicks::Now() - start_time;
-
-  UMA_HISTOGRAM_CUSTOM_TIMES("Net.Wifi.ScanLatency", duration,
+  UMA_HISTOGRAM_CUSTOM_TIMES("Net.Wifi.ScanLatency",
+                             wlan_get_network_list_timer.Elapsed(),
                              base::TimeDelta::FromMilliseconds(1),
                              base::TimeDelta::FromMinutes(1), 100);
 
-  int found = 0;
-  for (int i = 0; i < static_cast<int>(bss_list->dwNumberOfItems); ++i) {
-    AccessPointData access_point_data;
-    if (GetNetworkData(bss_list->wlanBssEntries[i], &access_point_data)) {
-      ++found;
-      data->insert(access_point_data);
-    }
-  }
+  for (size_t i = 0; i < bss_list->dwNumberOfItems; ++i)
+    data->insert(GetNetworkData(bss_list->wlanBssEntries[i]));
 
   (*WlanFreeMemory_function_)(bss_list);
 
-  return found;
-}
-
-// WindowsNdisApi
-WindowsNdisApi::WindowsNdisApi(
-    std::vector<base::string16>* interface_service_names)
-    : oid_buffer_size_(kInitialBufferSize) {
-  DCHECK(!interface_service_names->empty());
-  interface_service_names_.swap(*interface_service_names);
-}
-
-WindowsNdisApi::~WindowsNdisApi() {}
-
-std::unique_ptr<WindowsNdisApi> WindowsNdisApi::Create() {
-  std::vector<base::string16> interface_service_names;
-  if (GetInterfacesNDIS(&interface_service_names))
-    return base::MakeUnique<WindowsNdisApi>(&interface_service_names);
-  return nullptr;
-}
-
-bool WindowsNdisApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
-  DCHECK(data);
-  int interfaces_failed = 0;
-  int interfaces_succeeded = 0;
-
-  for (int i = 0; i < static_cast<int>(interface_service_names_.size()); ++i) {
-    // First, check that we have a DOS device for this adapter.
-    if (!DefineDosDeviceIfNotExists(interface_service_names_[i]))
-      continue;
-
-    // Get the handle to the device. This will fail if the named device is not
-    // valid.
-    HANDLE adapter_handle = GetFileHandle(interface_service_names_[i]);
-    if (adapter_handle == INVALID_HANDLE_VALUE)
-      continue;
-
-    // Get the data.
-    if (GetInterfaceDataNDIS(adapter_handle, data))
-      ++interfaces_succeeded;
-    else
-      ++interfaces_failed;
-
-    // Clean up.
-    CloseHandle(adapter_handle);
-    UndefineDosDevice(interface_service_names_[i]);
-  }
-
-  // Return true if at least one interface succeeded, or at the very least none
-  // failed.
-  return interfaces_succeeded > 0 || interfaces_failed == 0;
-}
-
-bool WindowsNdisApi::GetInterfacesNDIS(
-    std::vector<base::string16>* interface_service_names_out) {
-  HKEY network_cards_key = NULL;
-  if (RegOpenKeyEx(
-          HKEY_LOCAL_MACHINE,
-          L"Software\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards", 0,
-          KEY_READ, &network_cards_key) != ERROR_SUCCESS) {
-    return false;
-  }
-  DCHECK(network_cards_key);
-
-  for (int i = 0;; ++i) {
-    TCHAR name[kStringLength];
-    DWORD name_size = kStringLength;
-    FILETIME time;
-    if (RegEnumKeyEx(network_cards_key, i, name, &name_size, NULL, NULL, NULL,
-                     &time) != ERROR_SUCCESS) {
-      break;
-    }
-    HKEY hardware_key = NULL;
-    if (RegOpenKeyEx(network_cards_key, name, 0, KEY_READ, &hardware_key) !=
-        ERROR_SUCCESS) {
-      break;
-    }
-    DCHECK(hardware_key);
-
-    TCHAR service_name[kStringLength];
-    DWORD service_name_size = kStringLength;
-    DWORD type = 0;
-    if (RegQueryValueEx(hardware_key, L"ServiceName", NULL, &type,
-                        reinterpret_cast<LPBYTE>(service_name),
-                        &service_name_size) == ERROR_SUCCESS) {
-      interface_service_names_out->push_back(service_name);
-    }
-    RegCloseKey(hardware_key);
-  }
-
-  RegCloseKey(network_cards_key);
   return true;
 }
 
-bool WindowsNdisApi::GetInterfaceDataNDIS(HANDLE adapter_handle,
-                                          WifiData::AccessPointDataSet* data) {
-  DCHECK(data);
+}  // anonymous namespace
 
-  std::unique_ptr<BYTE, base::FreeDeleter> buffer(
-      static_cast<BYTE*>(malloc(oid_buffer_size_)));
-  if (!buffer)
-    return false;
-
-  DWORD bytes_out;
-  int result;
-
-  while (true) {
-    bytes_out = 0;
-    result = PerformQuery(adapter_handle, buffer.get(), oid_buffer_size_,
-                          &bytes_out);
-    if (result == ERROR_GEN_FAILURE ||  // Returned by some Intel cards.
-        result == ERROR_INSUFFICIENT_BUFFER || result == ERROR_MORE_DATA ||
-        result == NDIS_STATUS_INVALID_LENGTH ||
-        result == NDIS_STATUS_BUFFER_TOO_SHORT) {
-      // The buffer we supplied is too small, so increase it. bytes_out should
-      // provide the required buffer size, but this is not always the case.
-      if (bytes_out > static_cast<DWORD>(oid_buffer_size_))
-        oid_buffer_size_ = bytes_out;
-      else
-        oid_buffer_size_ *= 2;
-
-      if (!ResizeBuffer(oid_buffer_size_, &buffer)) {
-        oid_buffer_size_ = kInitialBufferSize;  // Reset for next time.
-        return false;
-      }
-    } else {
-      // The buffer is not too small.
-      break;
-    }
-  }
-  DCHECK(buffer.get());
-
-  if (result == ERROR_SUCCESS) {
-    NDIS_802_11_BSSID_LIST* bssid_list =
-        reinterpret_cast<NDIS_802_11_BSSID_LIST*>(buffer.get());
-    GetDataFromBssIdList(*bssid_list, oid_buffer_size_, data);
-  }
-
-  return true;
+WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() {
+  return new WifiDataProviderWin();
 }
 
-bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
-                    AccessPointData* access_point_data) {
-  // Currently we get only MAC address, signal strength and SSID.
-  DCHECK(access_point_data);
-  access_point_data->mac_address = MacAddressAsString16(bss_entry.dot11Bssid);
-  access_point_data->radio_signal_strength = bss_entry.lRssi;
-  // bss_entry.dot11Ssid.ucSSID is not null-terminated.
-  base::UTF8ToUTF16(reinterpret_cast<const char*>(bss_entry.dot11Ssid.ucSSID),
-                    static_cast<ULONG>(bss_entry.dot11Ssid.uSSIDLength),
-                    &access_point_data->ssid);
-  // TODO(steveblock): Is it possible to get the following?
-  // access_point_data->signal_to_noise
-  // access_point_data->age
-  // access_point_data->channel
-  return true;
+WifiDataProviderWin::WifiDataProviderWin() = default;
+
+WifiDataProviderWin::~WifiDataProviderWin() = default;
+
+std::unique_ptr<WifiDataProviderCommon::WlanApiInterface>
+WifiDataProviderWin::CreateWlanApi() {
+  return WindowsWlanApi::Create();
 }
 
-bool UndefineDosDevice(const base::string16& device_name) {
-  // We remove only the mapping we use, that is \Device\<device_name>.
-  base::string16 target_path = L"\\Device\\" + device_name;
-  return DefineDosDevice(DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION |
-                             DDD_EXACT_MATCH_ON_REMOVE,
-                         device_name.c_str(), target_path.c_str()) == TRUE;
+std::unique_ptr<WifiPollingPolicy> WifiDataProviderWin::CreatePollingPolicy() {
+  return base::MakeUnique<GenericWifiPollingPolicy<
+      kDefaultPollingIntervalMs, kNoChangePollingIntervalMs,
+      kTwoNoChangePollingIntervalMs, kNoWifiPollingIntervalMs>>();
 }
 
-bool DefineDosDeviceIfNotExists(const base::string16& device_name) {
-  // We create a DOS device name for the device at \Device\<device_name>.
-  base::string16 target_path = L"\\Device\\" + device_name;
-
-  TCHAR target[kStringLength];
-  if (QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
-      target_path.compare(target) == 0) {
-    // Device already exists.
-    return true;
-  }
-
-  if (GetLastError() != ERROR_FILE_NOT_FOUND)
-    return false;
-
-  if (!DefineDosDevice(DDD_RAW_TARGET_PATH, device_name.c_str(),
-                       target_path.c_str())) {
-    return false;
-  }
-
-  // Check that the device is really there.
-  return QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
-         target_path.compare(target) == 0;
-}
-
-HANDLE GetFileHandle(const base::string16& device_name) {
-  // We access a device with DOS path \Device\<device_name> at
-  // \\.\<device_name>.
-  base::string16 formatted_device_name = L"\\\\.\\" + device_name;
-
-  return CreateFile(formatted_device_name.c_str(), GENERIC_READ,
-                    FILE_SHARE_READ | FILE_SHARE_WRITE,  // share mode
-                    0,                                   // security attributes
-                    OPEN_EXISTING,
-                    0,  // flags and attributes
-                    INVALID_HANDLE_VALUE);
-}
-
-int PerformQuery(HANDLE adapter_handle,
-                 BYTE* buffer,
-                 DWORD buffer_size,
-                 DWORD* bytes_out) {
-  DWORD oid = OID_802_11_BSSID_LIST;
-  if (!DeviceIoControl(adapter_handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid,
-                       sizeof(oid), buffer, buffer_size, bytes_out, NULL)) {
-    return GetLastError();
-  }
-  return ERROR_SUCCESS;
-}
-
-bool ResizeBuffer(int requested_size,
-                  std::unique_ptr<BYTE, base::FreeDeleter>* buffer) {
-  DCHECK_GT(requested_size, 0);
-  DCHECK(buffer);
-  if (requested_size > kMaximumBufferSize) {
-    buffer->reset();
-    return false;
-  }
-
-  buffer->reset(
-      reinterpret_cast<BYTE*>(realloc(buffer->release(), requested_size)));
-  return buffer != NULL;
-}
-
-bool GetSystemDirectory(base::string16* path) {
-  DCHECK(path);
-  // Return value includes terminating NULL.
-  int buffer_size = ::GetSystemDirectory(NULL, 0);
-  if (buffer_size == 0)
-    return false;
-  std::unique_ptr<base::char16[]> buffer(new base::char16[buffer_size]);
-
-  // Return value excludes terminating NULL.
-  int characters_written = ::GetSystemDirectory(buffer.get(), buffer_size);
-  if (characters_written == 0)
-    return false;
-  DCHECK_EQ(buffer_size - 1, characters_written);
-
-  path->assign(buffer.get(), characters_written);
-
-  if (*path->rbegin() != L'\\')
-    path->append(L"\\");
-  DCHECK_EQ(L'\\', *path->rbegin());
-  return true;
-}
-}  // namespace
-
 }  // namespace device
diff --git a/extensions/browser/api/system_cpu/cpu_info_provider.cc b/extensions/browser/api/system_cpu/cpu_info_provider.cc
index e926aaa..aca7c2a5 100644
--- a/extensions/browser/api/system_cpu/cpu_info_provider.cc
+++ b/extensions/browser/api/system_cpu/cpu_info_provider.cc
@@ -6,6 +6,10 @@
 
 #include "base/sys_info.h"
 
+#if defined(OS_CHROMEOS)
+#include "chromeos/system/cpu_temperature_reader.h"
+#endif  // defined(OS_CHROMEOS)
+
 namespace extensions {
 
 using api::system_cpu::CpuInfo;
@@ -39,6 +43,19 @@
   // Initialize the ProcessorInfos, or return an empty array if that fails.
   if (!QueryCpuTimePerProcessor(&info_.processors))
     info_.processors.clear();
+
+#if defined(OS_CHROMEOS)
+  using CPUTemperatureInfo =
+      chromeos::system::CPUTemperatureReader::CPUTemperatureInfo;
+  std::vector<CPUTemperatureInfo> cpu_temp_info =
+      chromeos::system::CPUTemperatureReader().GetCPUTemperatures();
+  info_.temperatures.clear();
+  info_.temperatures.reserve(cpu_temp_info.size());
+  for (const CPUTemperatureInfo& info : cpu_temp_info) {
+    info_.temperatures.push_back(info.temp_celsius);
+  }
+#endif  // defined(OS_CHROMEOS)
+
   return true;
 }
 
diff --git a/extensions/browser/api/system_cpu/system_cpu_apitest.cc b/extensions/browser/api/system_cpu/system_cpu_apitest.cc
index eabacff..5685db5 100644
--- a/extensions/browser/api/system_cpu/system_cpu_apitest.cc
+++ b/extensions/browser/api/system_cpu/system_cpu_apitest.cc
@@ -28,6 +28,10 @@
     info_.processors[0].usage.user = 2;
     info_.processors[0].usage.idle = 3;
     info_.processors[0].usage.total = 6;
+
+    // The fractional part of these values should be exactly represented as
+    // floating points to avoid rounding errors.
+    info_.temperatures = {30.125, 40.0625};
     return true;
   }
 
diff --git a/extensions/common/api/networking_private.idl b/extensions/common/api/networking_private.idl
index 4e3be9b..b0b0126 100644
--- a/extensions/common/api/networking_private.idl
+++ b/extensions/common/api/networking_private.idl
@@ -583,15 +583,10 @@
   dictionary TetherProperties {
     long? BatteryPercentage;
     DOMString? Carrier;
+    boolean HasConnectedToHost;
     long? SignalStrength;
   };
 
-  dictionary ManagedTetherProperties {
-    ManagedLong? BatteryPercentage;
-    ManagedDOMString? Carrier;
-    ManagedLong? SignalStrength;
-  };
-
   dictionary VPNProperties {
     boolean? AutoConnect;
     DOMString? Host;
@@ -732,7 +727,7 @@
     ManagedIPConfigProperties? StaticIPConfig;
     IPConfigProperties? SavedIPConfig;
     DOMString? Source;
-    ManagedTetherProperties? Tether;
+    TetherProperties? Tether;
     NetworkType Type;
     ManagedVPNProperties? VPN;
     ManagedWiFiProperties? WiFi;
diff --git a/extensions/common/api/system_cpu.idl b/extensions/common/api/system_cpu.idl
index bb3ac85..e962261 100644
--- a/extensions/common/api/system_cpu.idl
+++ b/extensions/common/api/system_cpu.idl
@@ -44,6 +44,12 @@
 
     // Information about each logical processor.
     ProcessorInfo[] processors;
+
+    // List of CPU temperature readings from each thermal zone of the CPU.
+    // Temperatures are in degrees Celsius.
+    //
+    // <b>Currently supported on Chrome OS only.</b>
+    double[] temperatures;
   };
 
   callback CpuInfoCallback = void (CpuInfo info);
diff --git a/extensions/common/extension.h b/extensions/common/extension.h
index b87cc5f2dd..704b7dd 100644
--- a/extensions/common/extension.h
+++ b/extensions/common/extension.h
@@ -324,14 +324,19 @@
     return (creation_flags_ & WAS_INSTALLED_BY_OEM) != 0;
   }
 
-  // Type-related queries.
+  // Type-related queries. These are all mutually exclusive.
+  //
+  // The differences between the types of Extension are documented here:
+  // https://chromium.googlesource.com/chromium/src/+/HEAD/extensions/docs/extension_and_app_types.md
+  bool is_platform_app() const;         // aka "V2 app", "V2 packaged app"
+  bool is_hosted_app() const;           // Hosted app (or bookmark app)
+  bool is_legacy_packaged_app() const;  // aka "V1 packaged app"
+  bool is_extension() const;            // Regular browser extension, not an app
+  bool is_shared_module() const;        // Shared module
+  bool is_theme() const;                // Theme
+
+  // True if this is a platform app, hosted app, or legacy packaged app.
   bool is_app() const;
-  bool is_platform_app() const;
-  bool is_hosted_app() const;
-  bool is_legacy_packaged_app() const;
-  bool is_extension() const;
-  bool is_shared_module() const;
-  bool is_theme() const;
 
   void AddWebExtentPattern(const URLPattern& pattern);
   const URLPatternSet& web_extent() const { return extent_; }
diff --git a/extensions/docs/extension_and_app_types.md b/extensions/docs/extension_and_app_types.md
index b409a4b..d9ce660 100644
--- a/extensions/docs/extension_and_app_types.md
+++ b/extensions/docs/extension_and_app_types.md
@@ -14,6 +14,25 @@
 without any UI. They may interact with the browser or tab contents, and can
 request more extensive permissions than apps.
 
+A browser extension can be identified by a `manifest.json` file without any key
+named `app`, `export`, or `theme`.
+
+## Themes
+
+A theme is a special kind of extension that changes the way the browser looks.
+Themes are packaged like regular extensions, but they don't contain JavaScript
+or HTML code.
+
+A theme can be identified by the presence of a `theme` key in `manifest.json`.
+
+## Shared Modules
+
+Shared modules are permissionless collections of resources that can be shared
+between other extensions and apps.
+
+A shared module can be identified by the presence of an `export` key in
+`manifest.json`.
+
 ## Apps
 
 ### Platform app
@@ -29,6 +48,10 @@
 
 Platform apps are deprecated on non-Chrome OS platforms.
 
+A platform app can be identified by the presence of an `app.background` key
+in the manifest, which provides the script that runs when the app is
+launched.
+
 ### Packaged app (legacy)
 
 [Legacy (v1) packaged apps](https://developer.chrome.com/extensions/apps)
@@ -36,40 +59,53 @@
 around a website -- with the power of extension APIs. With the launch of
 platform apps and the app-specific APIs, legacy packaged apps are deprecated.
 
+A packaged app can be identified by the presence of an
+`app.launch.local_url` key in `manifest.json`, which identifies the resource
+in the .crx that's loaded when the app is launched.
+
 ### Hosted app
 
 A [hosted app](https://developer.chrome.com/webstore/hosted_apps) is mostly
 metadata: a web URL to launch, a list of associated URLs, and a list of HTML5
 permissions. Chrome ask for these permissions during the app's installation,
 allowing the associated URL to bypass the normal Chrome permission prompts for
-HTML5 features.
+HTML5 features. Other than metadata in the manifest and an icon, none of a
+hosted app's resources come from the extension system.
+
+A hosted app can be identified by the presence of an `app.launch.web_url` key in
+`manifest.json`, which provides http/https URL that is loaded when the app is
+launched.
 
 ### Bookmark app
 
 A bookmark app is a simplified hosted app that Chrome creates on demand. When
-the user taps "Add to desktop" (or "Add to shelf" on Chrome OS) in the Chrome
-menu, Chrome creates a barebones app whose manifest specifies the current tab's
-URL. A shortcut to this URL appears in chrome://apps using the site's favicon.
+the user taps "More Tools > Add to desktop..." (or "Add to shelf" on Chrome OS)
+in the Chrome menu, Chrome creates a barebones app whose manifest specifies the
+current tab's URL. A shortcut to this URL appears in chrome://apps using the
+site's favicon.
 
 Chrome then creates a desktop shortcut that will open a browser window with
 flags that specify the app and profile. Activating the icon launches the
 "bookmarked" URL in a tab or a window.
 
-## Manifest
+A bookmark app's `manifest.json` identifies it as a hosted app. However, in the
+C++ code, the `Extension` object will return true from its `from_bookmark()`
+method.
 
-A particular manifest key in an extension's `manifest.json` file determines what
-kind of extension it is:
+## Ambiguity surrounding the term "Extension"
 
-* Platform app: `app.background.page` and/or `app.background.scripts`
-* Legacy packaged app: `app.launch.local_path`
-* Hosted app: `app.launch.web_url`
-* Browser extension: none of the above
+In the C++ code, all of the above flavors of extensions and apps are implemented
+in terms of the `Extension` class type, and the `//extensions` module. This can
+cause confusion, since it means that an app *is-an* `Extension`, although
+`Extension::is_extension()` is false.
 
-## Notes
+In code comments, "extension" may be used to refer to non-app extensions, also
+known as *browser extensions*.
 
-`Extension` is the class type for all extensions and apps, so technically
-speaking, an app *is-an* `Extension`. The word "extension" usually refers only
-to non-app extensions, a.k.a. *browser extensions*.
+The three categories of apps are significantly different in terms of
+implementation, capabilities, process model, and restrictions. It is usually
+necessary to consider them as three separate cases, rather than lumping them
+together.
 
 ## See also
 
diff --git a/extensions/test/data/system/cpu/test_cpu_api.js b/extensions/test/data/system/cpu/test_cpu_api.js
index 746b46e9..ff7f978c 100644
--- a/extensions/test/data/system/cpu/test_cpu_api.js
+++ b/extensions/test/data/system/cpu/test_cpu_api.js
@@ -22,6 +22,7 @@
         chrome.test.assertEq("unknown", result.modelName);
         chrome.test.assertEq(["mmx", "avx"], result.features);
         chrome.test.assertEq(expectedProcessors, result.processors);
+        chrome.test.assertEq([30.125, 40.0625], result.temperatures);
       }));
     }
   }
diff --git a/gpu/ipc/service/pass_through_image_transport_surface.cc b/gpu/ipc/service/pass_through_image_transport_surface.cc
index a35c9ee..a8e7cbc 100644
--- a/gpu/ipc/service/pass_through_image_transport_surface.cc
+++ b/gpu/ipc/service/pass_through_image_transport_surface.cc
@@ -170,7 +170,7 @@
     g_num_swaps_in_current_swap_generation_++;
 
     bool should_override_vsync =
-        (g_num_swaps_in_current_swap_generation_ > 1) &&
+        (g_num_swaps_in_current_swap_generation_ > 1) ||
         (g_current_swap_generation_ - g_last_multi_window_swap_generation_ <
          kMultiWindowSwapEnableVSyncDelay);
     gl::GLContext::GetCurrent()->ForceSwapIntervalZero(should_override_vsync);
diff --git a/infra/config/cq.cfg b/infra/config/cq.cfg
index b9cb514..903732636 100644
--- a/infra/config/cq.cfg
+++ b/infra/config/cq.cfg
@@ -3,7 +3,6 @@
 
 version: 1
 cq_name: "chromium"
-in_production: false
 cq_status_url: "https://chromium-cq-status.appspot.com"
 git_repo_url: "https://chromium.googlesource.com/chromium/src"
 commit_burst_delay: 60
diff --git a/ios/chrome/browser/ui/autofill/autofill_client_ios.mm b/ios/chrome/browser/ui/autofill/autofill_client_ios.mm
index 377e7e74f..763bc49 100644
--- a/ios/chrome/browser/ui/autofill/autofill_client_ios.mm
+++ b/ios/chrome/browser/ui/autofill/autofill_client_ios.mm
@@ -118,7 +118,7 @@
   infobar_manager_->AddInfoBar(CreateSaveCardInfoBarMobile(
       base::MakeUnique<AutofillSaveCardInfoBarDelegateMobile>(
           false, card, std::unique_ptr<base::DictionaryValue>(nullptr),
-          callback)));
+          callback, GetPrefs())));
 }
 
 void AutofillClientIOS::ConfirmSaveCreditCardToCloud(
@@ -128,7 +128,7 @@
     const base::Closure& callback) {
   infobar_manager_->AddInfoBar(CreateSaveCardInfoBarMobile(
       base::MakeUnique<AutofillSaveCardInfoBarDelegateMobile>(
-          true, card, std::move(legal_message), callback)));
+          true, card, std::move(legal_message), callback, GetPrefs())));
 }
 
 void AutofillClientIOS::ConfirmCreditCardFillAssist(
diff --git a/mash/simple_wm/move_event_handler.cc b/mash/simple_wm/move_event_handler.cc
index 99582ed..4e1d58aa 100644
--- a/mash/simple_wm/move_event_handler.cc
+++ b/mash/simple_wm/move_event_handler.cc
@@ -9,32 +9,33 @@
 #include "ui/aura/mus/window_port_mus.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_delegate.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/base/hit_test.h"
 #include "ui/events/event.h"
 
 namespace simple_wm {
 namespace {
 
-ui::mojom::CursorType CursorForWindowComponent(int window_component) {
+ui::CursorType CursorForWindowComponent(int window_component) {
   switch (window_component) {
     case HTBOTTOM:
-      return ui::mojom::CursorType::kSouthResize;
+      return ui::CursorType::kSouthResize;
     case HTBOTTOMLEFT:
-      return ui::mojom::CursorType::kSouthWestResize;
+      return ui::CursorType::kSouthWestResize;
     case HTBOTTOMRIGHT:
-      return ui::mojom::CursorType::kSouthEastResize;
+      return ui::CursorType::kSouthEastResize;
     case HTLEFT:
-      return ui::mojom::CursorType::kWestResize;
+      return ui::CursorType::kWestResize;
     case HTRIGHT:
-      return ui::mojom::CursorType::kEastResize;
+      return ui::CursorType::kEastResize;
     case HTTOP:
-      return ui::mojom::CursorType::kNorthResize;
+      return ui::CursorType::kNorthResize;
     case HTTOPLEFT:
-      return ui::mojom::CursorType::kNorthWestResize;
+      return ui::CursorType::kNorthWestResize;
     case HTTOPRIGHT:
-      return ui::mojom::CursorType::kNorthEastResize;
+      return ui::CursorType::kNorthEastResize;
     default:
-      return ui::mojom::CursorType::kNull;
+      return ui::CursorType::kNull;
   }
 }
 
@@ -78,8 +79,8 @@
       move_loop_ = MoveLoop::Create(window_, ht_location, *pointer_event.get());
   } else if (pointer_event->type() == ui::ET_POINTER_MOVED) {
     const int ht_location = GetNonClientComponentForEvent(pointer_event.get());
-    aura::WindowPortMus::Get(window_)->SetPredefinedCursor(
-        CursorForWindowComponent(ht_location));
+    aura::WindowPortMus::Get(window_)->SetCursor(
+        ui::CursorData(CursorForWindowComponent(ht_location)));
   }
   if (had_move_loop || move_loop_)
     event->SetHandled();
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 97695bee..19b1879 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -331,7 +331,7 @@
 
 bool WebMediaPlayerImpl::SupportsOverlayFullscreenVideo() {
 #if defined(OS_ANDROID)
-  return true;
+  return !using_media_player_renderer_;
 #else
   return false;
 #endif
diff --git a/media/filters/decoder_stream.cc b/media/filters/decoder_stream.cc
index e5d592ef..53cf47c 100644
--- a/media/filters/decoder_stream.cc
+++ b/media/filters/decoder_stream.cc
@@ -289,6 +289,8 @@
     decrypting_demuxer_stream_ = std::move(decrypting_demuxer_stream);
     stream_ = decrypting_demuxer_stream_.get();
   }
+  if (decoder_change_observer_cb_)
+    decoder_change_observer_cb_.Run(decoder_.get());
 
   // TODO(tguilbert): crbug.com/603713 support config changes on decoder reinit.
   if (received_config_change_during_reinit_) {
diff --git a/media/filters/decoder_stream.h b/media/filters/decoder_stream.h
index 28a11eb..97ab970 100644
--- a/media/filters/decoder_stream.h
+++ b/media/filters/decoder_stream.h
@@ -106,12 +106,19 @@
   // Allows callers to register for notification of config changes; this is
   // called immediately after receiving the 'kConfigChanged' status from the
   // DemuxerStream, before any action is taken to handle the config change.
-  typedef base::Closure ConfigChangeObserverCB;
+  using ConfigChangeObserverCB = base::Closure;
   void set_config_change_observer(
-      const ConfigChangeObserverCB& config_change_observer) {
+      ConfigChangeObserverCB config_change_observer) {
     config_change_observer_cb_ = config_change_observer;
   }
 
+  // Allows tests to keep track the currently selected decoder.
+  using DecoderChangeObserverCB = base::RepeatingCallback<void(Decoder*)>;
+  void set_decoder_change_observer_for_testing(
+      DecoderChangeObserverCB decoder_change_observer_cb) {
+    decoder_change_observer_cb_ = std::move(decoder_change_observer_cb);
+  }
+
   int get_pending_buffers_size_for_testing() const {
     return pending_buffers_.size();
   }
@@ -207,6 +214,7 @@
   std::unique_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream_;
 
   ConfigChangeObserverCB config_change_observer_cb_;
+  DecoderChangeObserverCB decoder_change_observer_cb_;
 
   // An end-of-stream buffer has been sent for decoding, no more buffers should
   // be sent for decoding until it completes.
diff --git a/media/filters/fake_video_decoder.cc b/media/filters/fake_video_decoder.cc
index 013ceca..06c4e7f 100644
--- a/media/filters/fake_video_decoder.cc
+++ b/media/filters/fake_video_decoder.cc
@@ -10,10 +10,12 @@
 
 namespace media {
 
-FakeVideoDecoder::FakeVideoDecoder(int decoding_delay,
+FakeVideoDecoder::FakeVideoDecoder(const std::string& decoder_name,
+                                   int decoding_delay,
                                    int max_parallel_decoding_requests,
                                    const BytesDecodedCB& bytes_decoded_cb)
-    : decoding_delay_(decoding_delay),
+    : decoder_name_(decoder_name),
+      decoding_delay_(decoding_delay),
       max_parallel_decoding_requests_(max_parallel_decoding_requests),
       bytes_decoded_cb_(bytes_decoded_cb),
       state_(STATE_UNINITIALIZED),
@@ -47,7 +49,7 @@
 }
 
 std::string FakeVideoDecoder::GetDisplayName() const {
-  return "FakeVideoDecoder";
+  return decoder_name_;
 }
 
 void FakeVideoDecoder::Initialize(const VideoDecoderConfig& config,
diff --git a/media/filters/fake_video_decoder.h b/media/filters/fake_video_decoder.h
index 3ac932f..4c5568f 100644
--- a/media/filters/fake_video_decoder.h
+++ b/media/filters/fake_video_decoder.h
@@ -34,7 +34,8 @@
   // Constructs an object with a decoding delay of |decoding_delay| frames.
   // |bytes_decoded_cb| is called after each decode. The sum of the byte
   // count over all calls will be equal to total_bytes_decoded().
-  FakeVideoDecoder(int decoding_delay,
+  FakeVideoDecoder(const std::string& decoder_name,
+                   int decoding_delay,
                    int max_parallel_decoding_requests,
                    const BytesDecodedCB& bytes_decoded_cb);
   ~FakeVideoDecoder() override;
@@ -98,6 +99,7 @@
 
   base::ThreadChecker thread_checker_;
 
+  const std::string decoder_name_;
   const size_t decoding_delay_;
   const int max_parallel_decoding_requests_;
   BytesDecodedCB bytes_decoded_cb_;
diff --git a/media/filters/fake_video_decoder_unittest.cc b/media/filters/fake_video_decoder_unittest.cc
index 2cb7f16..7ebcf5e 100644
--- a/media/filters/fake_video_decoder_unittest.cc
+++ b/media/filters/fake_video_decoder_unittest.cc
@@ -32,6 +32,7 @@
  public:
   FakeVideoDecoderTest()
       : decoder_(new FakeVideoDecoder(
+            "FakeVideoDecoder",
             GetParam().decoding_delay,
             GetParam().max_decode_requests,
             base::Bind(&FakeVideoDecoderTest::OnBytesDecoded,
@@ -276,9 +277,10 @@
 }
 
 TEST_P(FakeVideoDecoderTest, Read_ZeroDelay) {
-  decoder_.reset(new FakeVideoDecoder(
-      0, 1, base::Bind(&FakeVideoDecoderTest::OnBytesDecoded,
-                       base::Unretained(this))));
+  decoder_.reset(
+      new FakeVideoDecoder("FakeVideoDecoder", 0, 1,
+                           base::Bind(&FakeVideoDecoderTest::OnBytesDecoded,
+                                      base::Unretained(this))));
   Initialize();
 
   while (num_input_buffers_ < kTotalBuffers) {
diff --git a/media/filters/video_frame_stream_unittest.cc b/media/filters/video_frame_stream_unittest.cc
index 0f6752d..88b5af8 100644
--- a/media/filters/video_frame_stream_unittest.cc
+++ b/media/filters/video_frame_stream_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
 #include "media/base/fake_demuxer_stream.h"
 #include "media/base/gmock_callback_support.h"
 #include "media/base/mock_filters.h"
@@ -28,11 +29,15 @@
 using ::testing::SaveArg;
 using ::testing::StrictMock;
 
-static const int kNumConfigs = 4;
-static const int kNumBuffersInOneConfig = 5;
-
 namespace media {
 
+const int kNumConfigs = 4;
+const int kNumBuffersInOneConfig = 5;
+
+static std::string GetDecoderName(int i) {
+  return std::string("VideoDecoder") + base::IntToString(i);
+}
+
 struct VideoFrameStreamTestParams {
   VideoFrameStreamTestParams(bool is_encrypted,
                              bool has_decryptor,
@@ -70,30 +75,32 @@
     BytesDecodedCB bytes_decoded_cb = base::Bind(
         &VideoFrameStreamTest::OnBytesDecoded, base::Unretained(this));
 
-    decoder1_ = new FakeVideoDecoder(decoding_delay, parallel_decoding,
-                                     bytes_decoded_cb);
-    decoder2_ = new FakeVideoDecoder(decoding_delay, parallel_decoding,
-                                     bytes_decoded_cb);
-    decoder3_ = new FakeVideoDecoder(decoding_delay, parallel_decoding,
-                                     bytes_decoded_cb);
-
+    // Provide 3 decoders to test fallback cases.
     // TODO(xhwang): We should test the case where only certain decoder
     // supports encrypted streams. Currently this is hard to test because we use
     // parameterized tests which need to pass in all combinations.
-    if (GetParam().is_encrypted && !GetParam().has_decryptor) {
-      decoder1_->EnableEncryptedConfigSupport();
-      decoder2_->EnableEncryptedConfigSupport();
-      decoder3_->EnableEncryptedConfigSupport();
-    }
-
     ScopedVector<VideoDecoder> decoders;
-    decoders.push_back(decoder1_);
-    decoders.push_back(decoder2_);
-    decoders.push_back(decoder3_);
+    for (int i = 0; i < 3; ++i) {
+      FakeVideoDecoder* decoder =
+          new FakeVideoDecoder(GetDecoderName(i), decoding_delay,
+                               parallel_decoding, bytes_decoded_cb);
+
+      if (GetParam().is_encrypted && !GetParam().has_decryptor)
+        decoder->EnableEncryptedConfigSupport();
+
+      decoders.push_back(decoder);
+
+      // Keep a copy of the raw pointers so we can change the behavior of each
+      // decoder.
+      decoders_.push_back(decoder);
+    }
 
     video_frame_stream_.reset(new VideoFrameStream(
         message_loop_.task_runner(), std::move(decoders), &media_log_));
 
+    video_frame_stream_->set_decoder_change_observer_for_testing(base::Bind(
+        &VideoFrameStreamTest::OnDecoderChanged, base::Unretained(this)));
+
     if (GetParam().is_encrypted && GetParam().has_decryptor) {
       decryptor_.reset(new NiceMock<MockDecryptor>());
 
@@ -118,9 +125,7 @@
     EXPECT_EQ(num_decoded_bytes_unreported_, 0);
 
     is_initialized_ = false;
-    decoder1_ = NULL;
-    decoder2_ = NULL;
-    decoder3_ = NULL;
+    decoders_.clear();
     video_frame_stream_.reset();
     base::RunLoop().RunUntilIdle();
 
@@ -140,6 +145,11 @@
     num_decoded_bytes_unreported_ += count;
   }
 
+  void SimulateDecoderInitFailure(const std::vector<int>& decoder_indices) {
+    for (const auto& i : decoder_indices)
+      decoders_[i]->SimulateFailureToInit();
+  }
+
   void OnInitialized(bool success) {
     DCHECK(!pending_read_);
     DCHECK(!pending_reset_);
@@ -147,14 +157,11 @@
     pending_initialize_ = false;
 
     is_initialized_ = success;
-    if (!success) {
-      decoder1_ = NULL;
-      decoder2_ = NULL;
-      decoder3_ = NULL;
-    }
+    if (!success)
+      decoders_.clear();
   }
 
-  void InitializeVideoFrameStream() {
+  void Initialize() {
     pending_initialize_ = true;
     video_frame_stream_->Initialize(
         demuxer_stream_.get(), base::Bind(&VideoFrameStreamTest::OnInitialized,
@@ -166,6 +173,18 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void OnDecoderChanged(VideoDecoder* decoder) {
+    if (!decoder) {
+      decoder_ = nullptr;
+      return;
+    }
+
+    std::string name = decoder->GetDisplayName();
+    ASSERT_TRUE(GetDecoderName(0) == name || GetDecoderName(1) == name ||
+                GetDecoderName(2) == name);
+    decoder_ = static_cast<FakeVideoDecoder*>(decoder);
+  }
+
   // Fake Decrypt() function used by DecryptingDemuxerStream. It does nothing
   // but removes the DecryptConfig to make the buffer unencrypted.
   void Decrypt(Decryptor::StreamType stream_type,
@@ -240,17 +259,12 @@
     DEMUXER_READ_NORMAL,
     DEMUXER_READ_CONFIG_CHANGE,
     DECRYPTOR_NO_KEY,
-    DECODER_INIT,
     DECODER_REINIT,
     DECODER_DECODE,
     DECODER_RESET
   };
 
   void EnterPendingState(PendingState state) {
-    EnterPendingState(state, decoder1_);
-  }
-
-  void EnterPendingState(PendingState state, FakeVideoDecoder* decoder) {
     DCHECK_NE(state, NOT_PENDING);
     switch (state) {
       case DEMUXER_READ_NORMAL:
@@ -271,23 +285,18 @@
         ReadOneFrame();
         break;
 
-      case DECODER_INIT:
-        decoder->HoldNextInit();
-        InitializeVideoFrameStream();
-        break;
-
       case DECODER_REINIT:
-        decoder->HoldNextInit();
+        decoder_->HoldNextInit();
         ReadUntilPending();
         break;
 
       case DECODER_DECODE:
-        decoder->HoldDecode();
+        decoder_->HoldDecode();
         ReadUntilPending();
         break;
 
       case DECODER_RESET:
-        decoder->HoldNextReset();
+        decoder_->HoldNextReset();
         pending_reset_ = true;
         video_frame_stream_->Reset(base::Bind(&VideoFrameStreamTest::OnReset,
                                               base::Unretained(this)));
@@ -301,10 +310,6 @@
   }
 
   void SatisfyPendingCallback(PendingState state) {
-    SatisfyPendingCallback(state, decoder1_);
-  }
-
-  void SatisfyPendingCallback(PendingState state, FakeVideoDecoder* decoder) {
     DCHECK_NE(state, NOT_PENDING);
     switch (state) {
       case DEMUXER_READ_NORMAL:
@@ -318,20 +323,16 @@
         NOTREACHED();
         break;
 
-      case DECODER_INIT:
-        decoder->SatisfyInit();
-        break;
-
       case DECODER_REINIT:
-        decoder->SatisfyInit();
+        decoder_->SatisfyInit();
         break;
 
       case DECODER_DECODE:
-        decoder->SatisfyDecode();
+        decoder_->SatisfyDecode();
         break;
 
       case DECODER_RESET:
-        decoder->SatisfyReset();
+        decoder_->SatisfyReset();
         break;
 
       case NOT_PENDING:
@@ -342,11 +343,6 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  void Initialize() {
-    EnterPendingState(DECODER_INIT);
-    SatisfyPendingCallback(DECODER_INIT);
-  }
-
   void Read() {
     EnterPendingState(DECODER_DECODE);
     SatisfyPendingCallback(DECODER_DECODE);
@@ -357,9 +353,9 @@
     SatisfyPendingCallback(DECODER_RESET);
   }
 
-  void ReadUntilDecoderReinitialized(FakeVideoDecoder* decoder) {
-    EnterPendingState(DECODER_REINIT, decoder);
-    SatisfyPendingCallback(DECODER_REINIT, decoder);
+  void ReadUntilDecoderReinitialized() {
+    EnterPendingState(DECODER_REINIT);
+    SatisfyPendingCallback(DECODER_REINIT);
   }
 
   base::MessageLoop message_loop_;
@@ -373,11 +369,13 @@
   // e.g. RegisterNewKeyCB().
   std::unique_ptr<NiceMock<MockDecryptor>> decryptor_;
 
+  // Raw pointers to the list of decoders to be select from by DecoderSelector.
   // Three decoders are needed to test that decoder fallback can occur more than
   // once on a config change. They are owned by |video_frame_stream_|.
-  FakeVideoDecoder* decoder1_;
-  FakeVideoDecoder* decoder2_;
-  FakeVideoDecoder* decoder3_;
+  std::vector<FakeVideoDecoder*> decoders_;
+
+  // The current decoder used by |video_frame_stream_|.
+  FakeVideoDecoder* decoder_;
 
   bool is_initialized_;
   int num_decoded_frames_;
@@ -425,16 +423,13 @@
 }
 
 TEST_P(VideoFrameStreamTest, AllDecoderInitializationFails) {
-  decoder1_->SimulateFailureToInit();
-  decoder2_->SimulateFailureToInit();
-  decoder3_->SimulateFailureToInit();
+  SimulateDecoderInitFailure({0, 1, 2});
   Initialize();
   EXPECT_FALSE(is_initialized_);
 }
 
 TEST_P(VideoFrameStreamTest, PartialDecoderInitializationFails) {
-  decoder1_->SimulateFailureToInit();
-  decoder2_->SimulateFailureToInit();
+  SimulateDecoderInitFailure({0, 1});
   Initialize();
   EXPECT_TRUE(is_initialized_);
 }
@@ -493,7 +488,7 @@
 
   Initialize();
   demuxer_stream_->HoldNextRead();
-  decoder1_->HoldDecode();
+  decoder_->HoldDecode();
   ReadOneFrame();
   EXPECT_TRUE(pending_read_);
 
@@ -508,7 +503,7 @@
 
     // Always keep one decode request pending.
     if (demuxed_buffers > 1) {
-      decoder1_->SatisfySingleDecode();
+      decoder_->SatisfySingleDecode();
       base::RunLoop().RunUntilIdle();
     }
   }
@@ -518,12 +513,12 @@
 
   // Unblocking one decode request should unblock read even when demuxer is
   // still blocked.
-  decoder1_->SatisfySingleDecode();
+  decoder_->SatisfySingleDecode();
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(pending_read_);
 
   // Stream should still be blocked on the demuxer after unblocking the decoder.
-  decoder1_->SatisfyDecode();
+  decoder_->SatisfyDecode();
   ReadUntilPending();
   EXPECT_TRUE(pending_read_);
 
@@ -544,7 +539,7 @@
     return;
 
   Initialize();
-  decoder1_->HoldDecode();
+  decoder_->HoldDecode();
 
   // Read all of the frames up to end of stream. Since parallel decoding is
   // enabled, the end of stream buffer will be sent to the decoder immediately,
@@ -553,7 +548,7 @@
     for (int frame = 0; frame < kNumBuffersInOneConfig; frame++) {
       ReadOneFrame();
       while (pending_read_) {
-        decoder1_->SatisfySingleDecode();
+        decoder_->SatisfySingleDecode();
         base::RunLoop().RunUntilIdle();
       }
     }
@@ -564,7 +559,7 @@
   ASSERT_TRUE(pending_read_);
 
   // Satisfy decoding of the end of stream buffer. The read should complete.
-  decoder1_->SatisfySingleDecode();
+  decoder_->SatisfySingleDecode();
   base::RunLoop().RunUntilIdle();
   ASSERT_FALSE(pending_read_);
   EXPECT_EQ(last_read_status_, VideoFrameStream::OK);
@@ -665,7 +660,8 @@
 }
 
 TEST_P(VideoFrameStreamTest, Destroy_DuringInitialization) {
-  EnterPendingState(DECODER_INIT);
+  decoders_[0]->HoldNextInit();
+  Initialize();
 }
 
 TEST_P(VideoFrameStreamTest, Destroy_AfterInitialization) {
@@ -748,17 +744,20 @@
   Reset();
 }
 
+// The following tests cover the fallback logic.
+
 TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnInitialDecodeError) {
   Initialize();
-  decoder1_->SimulateError();
+  decoder_->SimulateError();
   ReadOneFrame();
 
-  // |video_frame_stream_| should have fallen back to |decoder2_|.
+  // |video_frame_stream_| should have fallen back to a new decoder.
+  ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
   ASSERT_FALSE(pending_read_);
   ASSERT_EQ(VideoFrameStream::OK, last_read_status_);
 
-  // Can't check |decoder1_| right now, it might have been destroyed already.
-  ASSERT_GT(decoder2_->total_bytes_decoded(), 0);
+  // Check that we fallbacked to Decoder2.
+  ASSERT_GT(decoder_->total_bytes_decoded(), 0);
 
   // Verify no frame was dropped.
   ReadAllFrames();
@@ -771,14 +770,14 @@
     return;
 
   Initialize();
-  decoder1_->HoldDecode();
+  decoder_->HoldDecode();
   ReadOneFrame();
 
   // One buffer should have already pulled from the demuxer stream. Set the next
   // one to be an EOS.
   demuxer_stream_->SeekToEndOfStream();
 
-  decoder1_->SatisfySingleDecode();
+  decoder_->SatisfySingleDecode();
   base::RunLoop().RunUntilIdle();
 
   // |video_frame_stream_| should not have emited a frame.
@@ -787,15 +786,17 @@
   // Pending buffers should contain a regular buffer and an EOS buffer.
   EXPECT_EQ(video_frame_stream_->get_pending_buffers_size_for_testing(), 2);
 
-  decoder1_->SimulateError();
+  decoder_->SimulateError();
   base::RunLoop().RunUntilIdle();
 
-  //  A frame should have been emited
+  ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
+
+  // A frame should have been emitted.
   EXPECT_FALSE(pending_read_);
   EXPECT_EQ(last_read_status_, VideoFrameStream::OK);
   EXPECT_FALSE(
       frame_read_->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM));
-  EXPECT_GT(decoder2_->total_bytes_decoded(), 0);
+  EXPECT_GT(decoder_->total_bytes_decoded(), 0);
 
   ReadOneFrame();
 
@@ -811,7 +812,7 @@
     return;
 
   Initialize();
-  decoder1_->HoldDecode();
+  decoder_->HoldDecode();
 
   // Queue one read, defer the second.
   frame_read_ = nullptr;
@@ -822,43 +823,48 @@
 
   // Force an error to occur on the first decode, but ensure it isn't propagated
   // until after the next read has been started.
-  decoder1_->SimulateError();
-  decoder2_->HoldDecode();
+  decoder_->SimulateError();
+  decoders_[1]->HoldDecode();
 
   // Complete the fallback to the second decoder with the read still pending.
   base::RunLoop().RunUntilIdle();
 
-  // Can't check |decoder1_| right now, it might have been destroyed already.
-  // Verify that there was nothing decoded until we kicked the decoder.
-  EXPECT_EQ(decoder2_->total_bytes_decoded(), 0);
-  decoder2_->SatisfyDecode();
-  const int first_decoded_bytes = decoder2_->total_bytes_decoded();
+  ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
+
+  // Can't check the original decoder right now, it might have been destroyed
+  // already. Verify that there was nothing decoded until we kicked the decoder.
+  EXPECT_EQ(decoder_->total_bytes_decoded(), 0);
+  decoder_->SatisfyDecode();
+  const int first_decoded_bytes = decoder_->total_bytes_decoded();
   ASSERT_GT(first_decoded_bytes, 0);
 
   // Satisfy the previously pending read and ensure it is decoded.
   demuxer_stream_->SatisfyRead();
   base::RunLoop().RunUntilIdle();
-  ASSERT_GT(decoder2_->total_bytes_decoded(), first_decoded_bytes);
+  ASSERT_GT(decoder_->total_bytes_decoded(), first_decoded_bytes);
 }
 
 TEST_P(VideoFrameStreamTest,
        FallbackDecoder_SelectedOnInitialDecodeError_Twice) {
   Initialize();
-  decoder1_->SimulateError();
-  decoder2_->HoldNextInit();
+  decoder_->SimulateError();
+
+  decoders_[1]->HoldNextInit();
   ReadOneFrame();
 
-  decoder2_->SatisfyInit();
-  decoder2_->SimulateError();
+  decoders_[1]->SatisfyInit();
+  decoders_[1]->SimulateError();
   base::RunLoop().RunUntilIdle();
 
-  // |video_frame_stream_| should have fallen back to |decoder3_|.
+  ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName());
+
+  // |video_frame_stream_| should have fallen back to |decoders_[2]|.
   ASSERT_FALSE(pending_read_);
   ASSERT_EQ(VideoFrameStream::OK, last_read_status_);
 
-  // Can't check |decoder1_| or |decoder2_| right now, they might have been
+  // Can't check previously selected decoder(s) right now, they might have been
   // destroyed already.
-  ASSERT_GT(decoder3_->total_bytes_decoded(), 0);
+  ASSERT_GT(decoder_->total_bytes_decoded(), 0);
 
   // Verify no frame was dropped.
   ReadAllFrames();
@@ -891,20 +897,20 @@
   EnterPendingState(DEMUXER_READ_CONFIG_CHANGE);
   EXPECT_GT(video_frame_stream_->get_pending_buffers_size_for_testing(), 0);
 
-  decoder1_->HoldDecode();
+  decoder_->HoldDecode();
   SatisfyPendingCallback(DEMUXER_READ_CONFIG_CHANGE);
 
   // The flush request should have been sent and held.
   EXPECT_EQ(video_frame_stream_->get_pending_buffers_size_for_testing(), 0);
   EXPECT_TRUE(pending_read_);
 
-  // Triggering an error here will cause the frames in |decoder1_| to be lost.
-  // There are no pending buffers buffers to give to give to |decoder2_| due to
-  // crbug.com/603713.
-  decoder1_->SimulateError();
+  // Triggering an error here will cause the frames in selected decoder to be
+  // lost. There are no pending buffers to give to |decoders_[1]| due to
+  // http://crbug.com/603713
+  decoder_->SimulateError();
   base::RunLoop().RunUntilIdle();
 
-  // We want to make sure that |decoder2_| can decode the rest of the frames
+  // We want to make sure the fallback decoder can decode the rest of the frames
   // in the demuxer stream.
   ReadAllFrames(kNumBuffersInOneConfig * (kNumConfigs - 1));
 }
@@ -923,7 +929,7 @@
 
   // Block on demuxer read and decoder decode so we can step through.
   demuxer_stream_->HoldNextRead();
-  decoder1_->HoldDecode();
+  decoder_->HoldDecode();
   ReadOneFrame();
 
   int demuxer_reads_satisfied = 0;
@@ -936,7 +942,7 @@
     ++demuxer_reads_satisfied;
 
     // Decode one buffer.
-    decoder1_->SatisfySingleDecode();
+    decoder_->SatisfySingleDecode();
     base::RunLoop().RunUntilIdle();
     EXPECT_TRUE(pending_read_);
     EXPECT_EQ(demuxer_reads_satisfied,
@@ -948,18 +954,21 @@
   // Hold the init before triggering the error, to verify internal state.
   demuxer_stream_->SatisfyReadAndHoldNext();
   ++demuxer_reads_satisfied;
-  decoder2_->HoldNextInit();
-  decoder1_->SimulateError();
+
+  decoder_->SimulateError();
+  decoders_[1]->HoldNextInit();
   base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(pending_read_);
   EXPECT_EQ(demuxer_reads_satisfied,
             video_frame_stream_->get_pending_buffers_size_for_testing());
 
-  decoder2_->SatisfyInit();
-  decoder2_->HoldDecode();
+  decoders_[1]->SatisfyInit();
+  decoders_[1]->HoldDecode();
   base::RunLoop().RunUntilIdle();
 
+  ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
+
   // Make sure the pending buffers have been transfered to fallback buffers.
   // One call to Decode() during the initialization process, so we expect one
   // buffer to already have been consumed from the fallback buffers.
@@ -969,11 +978,11 @@
   EXPECT_EQ(demuxer_reads_satisfied,
             video_frame_stream_->get_pending_buffers_size_for_testing());
 
-  decoder2_->SatisfyDecode();
+  decoder_->SatisfyDecode();
   base::RunLoop().RunUntilIdle();
 
-  // Make sure all buffers consumed by |decoder2_| have come from the fallback.
-  // Pending buffers should not have been cleared yet.
+  // Make sure all buffers consumed by |decoders_[1]| have come from the
+  // fallback. Pending buffers should not have been cleared yet.
   EXPECT_EQ(0, video_frame_stream_->get_fallback_buffers_size_for_testing());
   EXPECT_EQ(demuxer_reads_satisfied,
             video_frame_stream_->get_pending_buffers_size_for_testing());
@@ -995,37 +1004,43 @@
 
 TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnDecodeThenInitErrors) {
   Initialize();
-  decoder1_->SimulateError();
-  decoder2_->SimulateFailureToInit();
+  decoder_->SimulateError();
+  SimulateDecoderInitFailure({1});
   ReadOneFrame();
 
-  // |video_frame_stream_| should have fallen back to |decoder3_|
+  ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName());
+
+  // |video_frame_stream_| should have fallen back to |decoders_[2]|
   ASSERT_FALSE(pending_read_);
   ASSERT_EQ(VideoFrameStream::OK, last_read_status_);
 
-  // Can't check |decoder1_| or |decoder2_| right now, they might have been
+  // Can't check previously selected decoder(s) right now, they might have been
   // destroyed already.
-  ASSERT_GT(decoder3_->total_bytes_decoded(), 0);
+  ASSERT_GT(decoder_->total_bytes_decoded(), 0);
 
   // Verify no frame was dropped.
   ReadAllFrames();
 }
 
 TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnInitThenDecodeErrors) {
-  decoder1_->SimulateFailureToInit();
-  decoder2_->HoldDecode();
+  SimulateDecoderInitFailure({0});
   Initialize();
+  ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
+
+  decoder_->HoldDecode();
   ReadOneFrame();
-  decoder2_->SimulateError();
+  decoder_->SimulateError();
   base::RunLoop().RunUntilIdle();
 
-  // |video_frame_stream_| should have fallen back to |decoder3_|
+  // |video_frame_stream_| should have fallen back to |decoders_[2]|
+  ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName());
+
   ASSERT_FALSE(pending_read_);
   ASSERT_EQ(VideoFrameStream::OK, last_read_status_);
 
-  // Can't check |decoder1_| or |decoder2_| right now, they might have been
+  // Can't check previously selected decoder(s) right now, they might have been
   // destroyed already.
-  ASSERT_GT(decoder3_->total_bytes_decoded(), 0);
+  ASSERT_GT(decoder_->total_bytes_decoded(), 0);
 
   // Verify no frame was dropped.
   ReadAllFrames();
@@ -1038,9 +1053,9 @@
 
   // Successfully received a frame.
   EXPECT_FALSE(pending_read_);
-  ASSERT_GT(decoder1_->total_bytes_decoded(), 0);
+  ASSERT_GT(decoder_->total_bytes_decoded(), 0);
 
-  decoder1_->SimulateError();
+  decoder_->SimulateError();
 
   // The error must surface from Read() as DECODE_ERROR.
   while (last_read_status_ == VideoFrameStream::OK) {
@@ -1049,26 +1064,26 @@
     EXPECT_FALSE(pending_read_);
   }
 
-  // Verify the error was surfaced, rather than falling back to |decoder2_|.
+  // Verify the error was surfaced, rather than falling back to other decoders.
+  ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
   EXPECT_FALSE(pending_read_);
-  ASSERT_EQ(decoder2_->total_bytes_decoded(), 0);
   ASSERT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_);
 }
 
 TEST_P(VideoFrameStreamTest, DecoderErrorWhenNotReading) {
   Initialize();
-  decoder1_->HoldDecode();
+  decoder_->HoldDecode();
   ReadOneFrame();
   EXPECT_TRUE(pending_read_);
 
   // Satisfy decode requests until we get the first frame out.
   while (pending_read_) {
-    decoder1_->SatisfySingleDecode();
+    decoder_->SatisfySingleDecode();
     base::RunLoop().RunUntilIdle();
   }
 
   // Trigger an error in the decoding.
-  decoder1_->SimulateError();
+  decoder_->SimulateError();
 
   // The error must surface from Read() as DECODE_ERROR.
   while (last_read_status_ == VideoFrameStream::OK) {
@@ -1081,29 +1096,31 @@
 
 TEST_P(VideoFrameStreamTest, FallbackDecoderSelectedOnFailureToReinitialize) {
   Initialize();
-  decoder1_->SimulateFailureToInit();
-  ReadUntilDecoderReinitialized(decoder1_);
+  decoder_->SimulateFailureToInit();
+  ReadUntilDecoderReinitialized();
+  ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
   ReadAllFrames();
-  ASSERT_GT(decoder2_->total_bytes_decoded(), 0);
+  ASSERT_GT(decoder_->total_bytes_decoded(), 0);
 }
 
 TEST_P(VideoFrameStreamTest,
        FallbackDecoderSelectedOnFailureToReinitialize_Twice) {
   Initialize();
-  decoder1_->SimulateFailureToInit();
-  ReadUntilDecoderReinitialized(decoder1_);
+  decoder_->SimulateFailureToInit();
+  ReadUntilDecoderReinitialized();
+  ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
   ReadOneFrame();
-  decoder2_->SimulateFailureToInit();
-  ReadUntilDecoderReinitialized(decoder2_);
+  decoder_->SimulateFailureToInit();
+  ReadUntilDecoderReinitialized();
+  ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName());
   ReadAllFrames();
 }
 
 TEST_P(VideoFrameStreamTest, DecodeErrorAfterFallbackDecoderSelectionFails) {
   Initialize();
-  decoder1_->SimulateFailureToInit();
-  decoder2_->SimulateFailureToInit();
-  decoder3_->SimulateFailureToInit();
-  ReadUntilDecoderReinitialized(decoder1_);
+  decoder_->SimulateFailureToInit();
+  SimulateDecoderInitFailure({1, 2});
+  ReadUntilDecoderReinitialized();
   // The error will surface from Read() as DECODE_ERROR.
   while (last_read_status_ == VideoFrameStream::OK) {
     ReadOneFrame();
@@ -1115,9 +1132,9 @@
 
 TEST_P(VideoFrameStreamTest, Destroy_DuringFallbackDecoderSelection) {
   Initialize();
-  decoder1_->SimulateFailureToInit();
+  decoder_->SimulateFailureToInit();
   EnterPendingState(DECODER_REINIT);
-  decoder2_->HoldNextInit();
+  decoders_[1]->HoldNextInit();
   SatisfyPendingCallback(DECODER_REINIT);
 }
 
diff --git a/mojo/public/js/associated_bindings.js b/mojo/public/js/associated_bindings.js
index 18ac452..869eedb 100644
--- a/mojo/public/js/associated_bindings.js
+++ b/mojo/public/js/associated_bindings.js
@@ -116,6 +116,13 @@
     this.reset();
   };
 
+  // Indicates whether an error has been encountered. If true, method calls
+  // on this interface will be dropped (and may already have been dropped).
+  AssociatedInterfacePtrController.prototype.getEncounteredError = function() {
+    return this.interfaceEndpointClient_ ?
+        this.interfaceEndpointClient_.getEncounteredError() : false;
+  };
+
   AssociatedInterfacePtrController.prototype.setConnectionErrorHandler =
       function(callback) {
     if (!this.isBound()) {
diff --git a/mojo/public/js/lib/interface_endpoint_client.js b/mojo/public/js/lib/interface_endpoint_client.js
index b74b6d2..3235434d 100644
--- a/mojo/public/js/lib/interface_endpoint_client.js
+++ b/mojo/public/js/lib/interface_endpoint_client.js
@@ -232,6 +232,10 @@
     this.controlMessageProxy_.requireVersion(version);
   };
 
+  InterfaceEndpointClient.prototype.getEncounteredError = function() {
+    return this.encounteredError_;
+  };
+
   var exports = {};
   exports.InterfaceEndpointClient = InterfaceEndpointClient;
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 4a6d7da..66ba36e9 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1424,8 +1424,6 @@
       "reporting/reporting_client.h",
       "reporting/reporting_context.cc",
       "reporting/reporting_context.h",
-      "reporting/reporting_delegate.cc",
-      "reporting/reporting_delegate.h",
       "reporting/reporting_delivery_agent.cc",
       "reporting/reporting_delivery_agent.h",
       "reporting/reporting_endpoint_manager.cc",
diff --git a/net/data/ssl/certificates/quic_test.example.com.key.pkcs8.pem b/net/data/ssl/certificates/quic_test.example.com.key.pkcs8.pem
new file mode 100644
index 0000000..e52b455f
--- /dev/null
+++ b/net/data/ssl/certificates/quic_test.example.com.key.pkcs8.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVyhl5jqmrRvBO
+t1hts6OaaBBSr/AAlK40vbRQH6MmpJ4ckDdbPejXO7yT+wD7x0lUm/HQmvJRhHtZ
+i71m866SWrljjGSn0J7jDFDSz5Od6UoRV5PB3q97WkQdCowiph3GremPFo1OkfHT
+8fOC/vZV3HLxEQd17LvpOjWHQ4Fe3ENKt3yhGtXSwUA5aX2JrWQbMTSo6p5eJvxx
+0sZr5cJzMD9ZpzWNqaXpPUNBvVTyKuEVDDUwa4vyd8pcB49Y9FR3Xq/OscErp7vA
+6X3vGtcD7o9nrcbmHannkT9B59aGIIxTs9h5CeJLFVrYkjtiT2jky9CkTrZ9Pl+w
+JOpiYc97AgMBAAECggEAEjCHylfhB7mZaJkg5PSLzu9btC1T9jiwObyKQubuMrN+
+4F9E1naGAQoMGWsZwlJKYvCBuoX0aAslk5DYJJJHIByn+VhQmBaf7iF2HvmaTa0z
+qBYTdENGGvCrKu1izu/jSKwzWwFINI8mTCoh1dtrihKflPMl91qVAlr5gvCzaSac
+ovZwe8cm51rN5XclJx+o6SONhPUdlUWJ5yEpA4mjV3eC6/IvPOoMmr8GRS9oBB6k
+m7yhcaGrSUClc+K3lpFF+3gXD57ABtT23usrNGnti6xnURDYDm+fUX2j8kZEREvj
+YGrCNGp+EecrTzhjgaFukysyRXRp7mLvGEqekodcgQKBgQDm1bmKZmE+0FZJIDXD
+4xMiipc+srd6KuaCYdJ5i0xt9xTC5KJA5eSmoQn0+hWa8065A91tHiR8u49Ql5xP
+6oQw8kA3dpZliZJSL/o7V+aXoMO9/R3H8tbiZn0y7cMYaYr7PGmXC/giSQ5Yi7qE
++yKjKgx35DGEiTajt4FWJ6/siQKBgQDtGKlPyT/k8XGz305qw2W7d8nFFsR4kSJl
+pbbWBwrfouiw0hOARCzevZDq6zSGx9QK8Sv5ACM06TrOxSVNsljKayFK0E+Hc5N/
+bNjCDxJd+4CgUNhM2Ta4wvZqW8FGQgphJVUnYu/YahjETWhMiAEOPcm/j7E9k/HQ
+BiZixroC4wKBgB3zBuKtC9rxfvB37GHg+V+W6a6p02JXZJbwCDXa2+y8jQYIUgDn
+kvYHmNofBGSZQtKAbN82dPd2Ak8rjI1V2Rbcp3ZKvZKo+cIOFYJTkkiEBEGHMLD7
+kePH9mCANrrZHr4gBXcih2wzXFgisO2GA+V1lC6N/dq7TsqJCY/bEFk5AoGBAMAI
+nnHCBd9P85EFiAUPGCHb5u+b/ivNGXgM3WbCs3ro/uDgde0IyvLpxSuQr62Owl7O
+cZgvFVTwprH8mbcxgZsJZCCtUgzafpfRuEqNXIoEf2zZrieoMxs4xc7lXEikirWe
+QDczeiHl5QNx0s1RxtEbGIHwR1Uhs9SSdprAbL6TAoGBAMT3QpWdPNlDoPbSkAC9
+hoUIaRZS8rN/8Ls1lwYf5RxaiTcEKuQ2IYphMRsSzqHbpDxLtWbsWJGd9VARbgc8
+W5DzCmditgUlt9FsawbxqDGO7bkqD4QEkZS67zehDzM2Ir8sy00QZxCi1dJXEBR2
+DUChLmnVRf1yCYz0xmGSRskj
+-----END PRIVATE KEY-----
diff --git a/net/reporting/reporting_context.cc b/net/reporting/reporting_context.cc
index 047a4a3..c6f01fd 100644
--- a/net/reporting/reporting_context.cc
+++ b/net/reporting/reporting_context.cc
@@ -15,7 +15,6 @@
 #include "base/time/time.h"
 #include "net/base/backoff_entry.h"
 #include "net/reporting/reporting_cache.h"
-#include "net/reporting/reporting_delegate.h"
 #include "net/reporting/reporting_delivery_agent.h"
 #include "net/reporting/reporting_endpoint_manager.h"
 #include "net/reporting/reporting_garbage_collector.h"
@@ -34,10 +33,8 @@
 class ReportingContextImpl : public ReportingContext {
  public:
   ReportingContextImpl(const ReportingPolicy& policy,
-                       std::unique_ptr<ReportingDelegate> delegate,
                        URLRequestContext* request_context)
       : ReportingContext(policy,
-                         std::move(delegate),
                          base::MakeUnique<base::DefaultClock>(),
                          base::MakeUnique<base::DefaultTickClock>(),
                          ReportingUploader::Create(request_context)) {}
@@ -48,29 +45,12 @@
 // static
 std::unique_ptr<ReportingContext> ReportingContext::Create(
     const ReportingPolicy& policy,
-    std::unique_ptr<ReportingDelegate> delegate,
     URLRequestContext* request_context) {
-  return base::MakeUnique<ReportingContextImpl>(policy, std::move(delegate),
-                                                request_context);
+  return base::MakeUnique<ReportingContextImpl>(policy, request_context);
 }
 
 ReportingContext::~ReportingContext() {}
 
-void ReportingContext::Initialize() {
-  DCHECK(!initialized_);
-
-  // This order isn't *critical*, but things will work better with it in this
-  // order: with the DeliveryAgent after the Persister, it can schedule delivery
-  // of persisted reports instead of waiting for a new one to be generated, and
-  // with the GarbageCollector in between, it won't bother scheduling delivery
-  // of reports that should be discarded instead.
-  persister_->Initialize();
-  garbage_collector_->Initialize();
-  delivery_agent_->Initialize();
-
-  initialized_ = true;
-}
-
 void ReportingContext::AddObserver(ReportingObserver* observer) {
   DCHECK(!observers_.HasObserver(observer));
   observers_.AddObserver(observer);
@@ -82,24 +62,18 @@
 }
 
 void ReportingContext::NotifyCacheUpdated() {
-  if (!initialized_)
-    return;
-
   for (auto& observer : observers_)
     observer.OnCacheUpdated();
 }
 
 ReportingContext::ReportingContext(const ReportingPolicy& policy,
-                                   std::unique_ptr<ReportingDelegate> delegate,
                                    std::unique_ptr<base::Clock> clock,
                                    std::unique_ptr<base::TickClock> tick_clock,
                                    std::unique_ptr<ReportingUploader> uploader)
     : policy_(policy),
-      delegate_(std::move(delegate)),
       clock_(std::move(clock)),
       tick_clock_(std::move(tick_clock)),
       uploader_(std::move(uploader)),
-      initialized_(false),
       cache_(base::MakeUnique<ReportingCache>(this)),
       endpoint_manager_(base::MakeUnique<ReportingEndpointManager>(this)),
       delivery_agent_(ReportingDeliveryAgent::Create(this)),
diff --git a/net/reporting/reporting_context.h b/net/reporting/reporting_context.h
index ca495a7..34e95d3 100644
--- a/net/reporting/reporting_context.h
+++ b/net/reporting/reporting_context.h
@@ -21,7 +21,6 @@
 namespace net {
 
 class ReportingCache;
-class ReportingDelegate;
 class ReportingDeliveryAgent;
 class ReportingEndpointManager;
 class ReportingGarbageCollector;
@@ -37,23 +36,11 @@
  public:
   static std::unique_ptr<ReportingContext> Create(
       const ReportingPolicy& policy,
-      std::unique_ptr<ReportingDelegate> delegate,
       URLRequestContext* request_context);
 
   ~ReportingContext();
 
-  // Initializes the ReportingContext. This may take a while (e.g. it may
-  // involve reloading state persisted to disk). Should be called only once.
-  //
-  // Components of the ReportingContext won't reference their dependencies (e.g.
-  // the Clock/TickClock or Timers inside the individual components) until
-  // during/after the call to Init.
-  void Initialize();
-
-  bool initialized() const { return initialized_; }
-
   const ReportingPolicy& policy() { return policy_; }
-  ReportingDelegate* delegate() { return delegate_.get(); }
 
   base::Clock* clock() { return clock_.get(); }
   base::TickClock* tick_clock() { return tick_clock_.get(); }
@@ -77,21 +64,18 @@
 
  protected:
   ReportingContext(const ReportingPolicy& policy,
-                   std::unique_ptr<ReportingDelegate> delegate,
                    std::unique_ptr<base::Clock> clock,
                    std::unique_ptr<base::TickClock> tick_clock,
                    std::unique_ptr<ReportingUploader> uploader);
 
  private:
   ReportingPolicy policy_;
-  std::unique_ptr<ReportingDelegate> delegate_;
 
   std::unique_ptr<base::Clock> clock_;
   std::unique_ptr<base::TickClock> tick_clock_;
   std::unique_ptr<ReportingUploader> uploader_;
 
   base::ObserverList<ReportingObserver, /* check_empty= */ true> observers_;
-  bool initialized_;
 
   std::unique_ptr<ReportingCache> cache_;
 
@@ -102,8 +86,7 @@
   // and |endpoint_manager_|.
   std::unique_ptr<ReportingDeliveryAgent> delivery_agent_;
 
-  // |persister_| must come after |delegate_|, |clock_|, |tick_clock_|, and
-  // |cache_|.
+  // |persister_| must come after |clock_|, |tick_clock_|, and |cache_|.
   std::unique_ptr<ReportingPersister> persister_;
 
   // |garbage_collector_| must come after |tick_clock_| and |cache_|.
diff --git a/net/reporting/reporting_delegate.cc b/net/reporting/reporting_delegate.cc
deleted file mode 100644
index cef5baf..0000000
--- a/net/reporting/reporting_delegate.cc
+++ /dev/null
@@ -1,13 +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 "net/reporting/reporting_delegate.h"
-
-namespace net {
-
-ReportingDelegate::~ReportingDelegate() {}
-
-ReportingDelegate::ReportingDelegate() {}
-
-}  // namespace net
diff --git a/net/reporting/reporting_delegate.h b/net/reporting/reporting_delegate.h
deleted file mode 100644
index 57d01ab..0000000
--- a/net/reporting/reporting_delegate.h
+++ /dev/null
@@ -1,45 +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 NET_REPORTING_REPORTING_DELEGATE_H_
-#define NET_REPORTING_REPORTING_DELEGATE_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "net/base/net_export.h"
-
-namespace base {
-class Value;
-}  // namespace base
-
-namespace net {
-
-// Delegate for things that the Reporting system can't do by itself, like
-// persisting data across embedder restarts.
-class NET_EXPORT ReportingDelegate {
- public:
-  virtual ~ReportingDelegate();
-
-  // Gets previously persisted data, if any is available. Returns a null pointer
-  // if no data is available. Can be called any number of times.
-  virtual std::unique_ptr<const base::Value> GetPersistedData() = 0;
-
-  // Sets data to be persisted across embedder restarts. Ideally, this data will
-  // be returned by any future calls to GetPersistedData() in this or future
-  // sessions (until newer data is persisted), but no guarantee is made, since
-  // the underlying persistence mechanism may or may not be reliable.
-  virtual void PersistData(
-      std::unique_ptr<const base::Value> persisted_data) = 0;
-
- protected:
-  ReportingDelegate();
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ReportingDelegate);
-};
-
-}  // namespace net
-
-#endif  // NET_REPORTING_REPORTING_DELEGATE_H_
diff --git a/net/reporting/reporting_delivery_agent.cc b/net/reporting/reporting_delivery_agent.cc
index e614aa1..209ea8f 100644
--- a/net/reporting/reporting_delivery_agent.cc
+++ b/net/reporting/reporting_delivery_agent.cc
@@ -62,11 +62,6 @@
 
   ~ReportingDeliveryAgentImpl() override { context_->RemoveObserver(this); }
 
-  void Initialize() override {
-    if (CacheHasReports())
-      StartTimer();
-  }
-
   void SetTimerForTesting(std::unique_ptr<base::Timer> timer) override {
     DCHECK(!timer_->IsRunning());
     timer_ = std::move(timer);
diff --git a/net/reporting/reporting_delivery_agent.h b/net/reporting/reporting_delivery_agent.h
index e4ed370..d071b7e 100644
--- a/net/reporting/reporting_delivery_agent.h
+++ b/net/reporting/reporting_delivery_agent.h
@@ -55,11 +55,6 @@
 
   virtual ~ReportingDeliveryAgent();
 
-  // Initializes the DeliveryAgent, which schedules delivery (after the Policy's
-  // delivery_interval) for any previously-persisted reports that can still be
-  // delivered.
-  virtual void Initialize() = 0;
-
   // Replaces the internal Timer used for scheduling report delivery attempts
   // with a caller-specified one so that unittests can provide a MockTimer.
   virtual void SetTimerForTesting(std::unique_ptr<base::Timer> timer) = 0;
diff --git a/net/reporting/reporting_garbage_collector.cc b/net/reporting/reporting_garbage_collector.cc
index 76dbf3a..b6486b4 100644
--- a/net/reporting/reporting_garbage_collector.cc
+++ b/net/reporting/reporting_garbage_collector.cc
@@ -24,28 +24,22 @@
                                       public ReportingObserver {
  public:
   ReportingGarbageCollectorImpl(ReportingContext* context)
-      : context_(context), timer_(base::MakeUnique<base::OneShotTimer>()) {}
+      : context_(context), timer_(base::MakeUnique<base::OneShotTimer>()) {
+    context_->AddObserver(this);
+  }
 
   // ReportingGarbageCollector implementation:
 
   ~ReportingGarbageCollectorImpl() override {
-    DCHECK(context_->initialized());
     context_->RemoveObserver(this);
   }
 
-  void Initialize() override {
-    context_->AddObserver(this);
-    CollectGarbage();
-  }
-
   void SetTimerForTesting(std::unique_ptr<base::Timer> timer) override {
-    DCHECK(!context_->initialized());
     timer_ = std::move(timer);
   }
 
   // ReportingObserver implementation:
   void OnCacheUpdated() override {
-    DCHECK(context_->initialized());
     if (!timer_->IsRunning())
       StartTimer();
   }
diff --git a/net/reporting/reporting_garbage_collector.h b/net/reporting/reporting_garbage_collector.h
index 17bd9ab..228f14e 100644
--- a/net/reporting/reporting_garbage_collector.h
+++ b/net/reporting/reporting_garbage_collector.h
@@ -28,10 +28,6 @@
 
   virtual ~ReportingGarbageCollector();
 
-  // Initializes the GarbageCollector, which performs an initial garbage
-  // collection pass over any data already in the Cache.
-  virtual void Initialize() = 0;
-
   // Replaces the internal Timer used for scheduling garbage collection passes
   // with a caller-specified one so that unittests can provide a MockTimer.
   virtual void SetTimerForTesting(std::unique_ptr<base::Timer> timer) = 0;
diff --git a/net/reporting/reporting_persister.cc b/net/reporting/reporting_persister.cc
index ec2a37b..4dcc032 100644
--- a/net/reporting/reporting_persister.cc
+++ b/net/reporting/reporting_persister.cc
@@ -16,7 +16,6 @@
 #include "net/reporting/reporting_cache.h"
 #include "net/reporting/reporting_client.h"
 #include "net/reporting/reporting_context.h"
-#include "net/reporting/reporting_delegate.h"
 #include "net/reporting/reporting_observer.h"
 #include "net/reporting/reporting_policy.h"
 #include "net/reporting/reporting_report.h"
@@ -61,49 +60,15 @@
   return true;
 }
 
-class ReportingPersisterImpl : public ReportingPersister,
-                               public ReportingObserver {
+class ReportingPersisterImpl : public ReportingPersister {
  public:
-  ReportingPersisterImpl(ReportingContext* context)
-      : context_(context), timer_(base::MakeUnique<base::OneShotTimer>()) {}
+  ReportingPersisterImpl(ReportingContext* context) : context_(context) {}
 
   // ReportingPersister implementation:
 
-  ~ReportingPersisterImpl() override {
-    DCHECK(context_->initialized());
-    context_->RemoveObserver(this);
-  }
-
-  void Initialize() override {
-    std::unique_ptr<const base::Value> persisted_data =
-        context_->delegate()->GetPersistedData();
-    if (persisted_data)
-      Deserialize(*persisted_data);
-    context_->AddObserver(this);
-  }
-
-  void SetTimerForTesting(std::unique_ptr<base::Timer> timer) override {
-    DCHECK(!context_->initialized());
-    timer_ = std::move(timer);
-  }
-
-  // ReportingObserver implementation:
-
-  void OnCacheUpdated() override {
-    DCHECK(context_->initialized());
-    if (!timer_->IsRunning())
-      StartTimer();
-  }
+  ~ReportingPersisterImpl() override {}
 
  private:
-  void StartTimer() {
-    timer_->Start(
-        FROM_HERE, context_->policy().persistence_interval,
-        base::Bind(&ReportingPersisterImpl::Persist, base::Unretained(this)));
-  }
-
-  void Persist() { delegate()->PersistData(Serialize()); }
-
   std::string SerializeTicks(base::TimeTicks time_ticks) {
     base::Time time = time_ticks - tick_clock()->NowTicks() + clock()->Now();
     return base::Int64ToString(time.ToInternalValue());
@@ -336,13 +301,11 @@
   }
 
   const ReportingPolicy& policy() { return context_->policy(); }
-  ReportingDelegate* delegate() { return context_->delegate(); }
   base::Clock* clock() { return context_->clock(); }
   base::TickClock* tick_clock() { return context_->tick_clock(); }
   ReportingCache* cache() { return context_->cache(); }
 
   ReportingContext* context_;
-  std::unique_ptr<base::Timer> timer_;
 };
 
 }  // namespace
diff --git a/net/reporting/reporting_persister.h b/net/reporting/reporting_persister.h
index 27463a5..c6b52f87 100644
--- a/net/reporting/reporting_persister.h
+++ b/net/reporting/reporting_persister.h
@@ -9,30 +9,18 @@
 
 #include "net/base/net_export.h"
 
-namespace base {
-class Timer;
-}  // namespace base
-
 namespace net {
 
 class ReportingContext;
 
-// Periodically persists the state of the Reporting system to (reasonably)
-// stable storage using the methods provided by the ReportingDelegate.
+// Will persist the state of the Reporting system to (reasonably) stable
+// storage using an as-yet-unwritten persistence mechanism within //net.
 class NET_EXPORT ReportingPersister {
  public:
   // Creates a ReportingPersister. |context| must outlive the persister.
   static std::unique_ptr<ReportingPersister> Create(ReportingContext* context);
 
   virtual ~ReportingPersister();
-
-  // Initializes the Persister, which deserializes any previously-persisted data
-  // that is available through the Context's Delegate.
-  virtual void Initialize() = 0;
-
-  // Replaces the internal Timer used for scheduling writes to stable storage
-  // with a caller-specified one so that unittests can provide a MockTimer.
-  virtual void SetTimerForTesting(std::unique_ptr<base::Timer> timer) = 0;
 };
 
 }  // namespace net
diff --git a/net/reporting/reporting_persister_unittest.cc b/net/reporting/reporting_persister_unittest.cc
index ed109ed..c0bda9f 100644
--- a/net/reporting/reporting_persister_unittest.cc
+++ b/net/reporting/reporting_persister_unittest.cc
@@ -30,7 +30,8 @@
   const std::string kType_ = "default";
 };
 
-TEST_F(ReportingPersisterTest, Test) {
+// Disabled because the Persister has no persistence layer to use yet.
+TEST_F(ReportingPersisterTest, DISABLED_Test) {
   ReportingPolicy policy;
   policy.persist_reports_across_restarts = true;
   policy.persist_clients_across_restarts = true;
@@ -49,8 +50,7 @@
                      kGroup_,
                      tick_clock()->NowTicks() + base::TimeDelta::FromDays(1));
 
-  EXPECT_TRUE(persistence_timer()->IsRunning());
-  persistence_timer()->Fire();
+  // TODO: Actually trigger persistence, once it's possible.
 
   SimulateRestart(/* delta= */ base::TimeDelta::FromHours(1),
                   /* delta_ticks= */ base::TimeDelta::FromHours(-3));
diff --git a/net/reporting/reporting_service.cc b/net/reporting/reporting_service.cc
index 6d2b215..4dcee9da 100644
--- a/net/reporting/reporting_service.cc
+++ b/net/reporting/reporting_service.cc
@@ -14,7 +14,6 @@
 #include "base/values.h"
 #include "net/reporting/reporting_cache.h"
 #include "net/reporting/reporting_context.h"
-#include "net/reporting/reporting_delegate.h"
 #include "net/reporting/reporting_header_parser.h"
 #include "url/gurl.h"
 
@@ -25,11 +24,7 @@
 class ReportingServiceImpl : public ReportingService {
  public:
   ReportingServiceImpl(std::unique_ptr<ReportingContext> context)
-      : context_(std::move(context)) {
-    // TODO(juliatuttle): This can be slow, so it might be better to expose it
-    // as a separate method and call it separately from constructing everything.
-    context_->Initialize();
-  }
+      : context_(std::move(context)) {}
 
   ~ReportingServiceImpl() override {}
 
@@ -37,14 +32,12 @@
                    const std::string& group,
                    const std::string& type,
                    std::unique_ptr<const base::Value> body) override {
-    DCHECK(context_->initialized());
     context_->cache()->AddReport(url, group, type, std::move(body),
                                  context_->tick_clock()->NowTicks(), 0);
   }
 
   void ProcessHeader(const GURL& url,
                      const std::string& header_value) override {
-    DCHECK(context_->initialized());
     ReportingHeaderParser::ParseHeader(context_.get(), url, header_value);
   }
 
@@ -61,10 +54,9 @@
 // static
 std::unique_ptr<ReportingService> ReportingService::Create(
     const ReportingPolicy& policy,
-    URLRequestContext* request_context,
-    std::unique_ptr<ReportingDelegate> delegate) {
+    URLRequestContext* request_context) {
   return base::MakeUnique<ReportingServiceImpl>(
-      ReportingContext::Create(policy, std::move(delegate), request_context));
+      ReportingContext::Create(policy, request_context));
 }
 
 // static
diff --git a/net/reporting/reporting_service.h b/net/reporting/reporting_service.h
index c305410..831e0711 100644
--- a/net/reporting/reporting_service.h
+++ b/net/reporting/reporting_service.h
@@ -21,7 +21,6 @@
 namespace net {
 
 class ReportingContext;
-class ReportingDelegate;
 struct ReportingPolicy;
 class URLRequestContext;
 
@@ -32,12 +31,10 @@
   virtual ~ReportingService();
 
   // Creates a ReportingService. |policy| will be copied. |request_context| must
-  // outlive the ReportingService. The ReportingService will take ownership of
-  // |delegate| and destroy it when the service is destroyed.
+  // outlive the ReportingService.
   static std::unique_ptr<ReportingService> Create(
       const ReportingPolicy& policy,
-      URLRequestContext* request_context,
-      std::unique_ptr<ReportingDelegate> delegate);
+      URLRequestContext* request_context);
 
   // Creates a ReportingService for testing purposes using an
   // already-constructed ReportingContext. The ReportingService will take
diff --git a/net/reporting/reporting_service_unittest.cc b/net/reporting/reporting_service_unittest.cc
index 20c8656..064700e 100644
--- a/net/reporting/reporting_service_unittest.cc
+++ b/net/reporting/reporting_service_unittest.cc
@@ -12,7 +12,6 @@
 #include "base/values.h"
 #include "net/reporting/reporting_cache.h"
 #include "net/reporting/reporting_context.h"
-#include "net/reporting/reporting_delegate.h"
 #include "net/reporting/reporting_policy.h"
 #include "net/reporting/reporting_report.h"
 #include "net/reporting/reporting_service.h"
diff --git a/net/reporting/reporting_test_util.cc b/net/reporting/reporting_test_util.cc
index 74d0cb4..2a7d1e0c 100644
--- a/net/reporting/reporting_test_util.cc
+++ b/net/reporting/reporting_test_util.cc
@@ -17,7 +17,6 @@
 #include "net/reporting/reporting_cache.h"
 #include "net/reporting/reporting_client.h"
 #include "net/reporting/reporting_context.h"
-#include "net/reporting/reporting_delegate.h"
 #include "net/reporting/reporting_delivery_agent.h"
 #include "net/reporting/reporting_garbage_collector.h"
 #include "net/reporting/reporting_persister.h"
@@ -91,20 +90,6 @@
   return nullptr;
 }
 
-TestReportingDelegate::TestReportingDelegate() {}
-TestReportingDelegate::~TestReportingDelegate() {}
-
-void TestReportingDelegate::PersistData(
-    std::unique_ptr<const base::Value> persisted_data) {
-  persisted_data_ = std::move(persisted_data);
-}
-
-std::unique_ptr<const base::Value> TestReportingDelegate::GetPersistedData() {
-  if (!persisted_data_)
-    return std::unique_ptr<const base::Value>();
-  return persisted_data_->CreateDeepCopy();
-}
-
 TestReportingUploader::PendingUpload::~PendingUpload() {}
 TestReportingUploader::PendingUpload::PendingUpload() {}
 
@@ -120,18 +105,14 @@
 
 TestReportingContext::TestReportingContext(const ReportingPolicy& policy)
     : ReportingContext(policy,
-                       base::MakeUnique<TestReportingDelegate>(),
                        base::MakeUnique<base::SimpleTestClock>(),
                        base::MakeUnique<base::SimpleTestTickClock>(),
                        base::MakeUnique<TestReportingUploader>()),
       delivery_timer_(new base::MockTimer(/* retain_user_task= */ false,
                                           /* is_repeating= */ false)),
-      persistence_timer_(new base::MockTimer(/* retain_user_task= */ false,
-                                             /* is_repeating= */ false)),
       garbage_collection_timer_(
           new base::MockTimer(/* retain_user_task= */ false,
                               /* is_repeating= */ false)) {
-  persister()->SetTimerForTesting(base::WrapUnique(persistence_timer_));
   garbage_collector()->SetTimerForTesting(
       base::WrapUnique(garbage_collection_timer_));
   delivery_agent()->SetTimerForTesting(base::WrapUnique(delivery_timer_));
@@ -139,7 +120,6 @@
 
 TestReportingContext::~TestReportingContext() {
   delivery_timer_ = nullptr;
-  persistence_timer_ = nullptr;
   garbage_collection_timer_ = nullptr;
 }
 
@@ -148,34 +128,27 @@
   ReportingPolicy policy;
   policy.endpoint_backoff_policy.jitter_factor = 0.0;
 
-  CreateAndInitializeContext(policy, std::unique_ptr<const base::Value>(),
-                             base::Time::Now(), base::TimeTicks::Now());
+  CreateContext(policy, base::Time::Now(), base::TimeTicks::Now());
 }
 
 ReportingTestBase::~ReportingTestBase() {}
 
 void ReportingTestBase::UsePolicy(const ReportingPolicy& new_policy) {
-  CreateAndInitializeContext(new_policy, delegate()->GetPersistedData(),
-                             clock()->Now(), tick_clock()->NowTicks());
+  CreateContext(new_policy, clock()->Now(), tick_clock()->NowTicks());
 }
 
 void ReportingTestBase::SimulateRestart(base::TimeDelta delta,
                                         base::TimeDelta delta_ticks) {
-  CreateAndInitializeContext(policy(), delegate()->GetPersistedData(),
-                             clock()->Now() + delta,
-                             tick_clock()->NowTicks() + delta_ticks);
+  CreateContext(policy(), clock()->Now() + delta,
+                tick_clock()->NowTicks() + delta_ticks);
 }
 
-void ReportingTestBase::CreateAndInitializeContext(
-    const ReportingPolicy& policy,
-    std::unique_ptr<const base::Value> persisted_data,
-    base::Time now,
-    base::TimeTicks now_ticks) {
+void ReportingTestBase::CreateContext(const ReportingPolicy& policy,
+                                      base::Time now,
+                                      base::TimeTicks now_ticks) {
   context_ = base::MakeUnique<TestReportingContext>(policy);
-  delegate()->PersistData(std::move(persisted_data));
   clock()->SetNow(now);
   tick_clock()->SetNowTicks(now_ticks);
-  context_->Initialize();
 }
 
 base::TimeTicks ReportingTestBase::yesterday() {
diff --git a/net/reporting/reporting_test_util.h b/net/reporting/reporting_test_util.h
index 2fa0d46c..48b63d9 100644
--- a/net/reporting/reporting_test_util.h
+++ b/net/reporting/reporting_test_util.h
@@ -11,7 +11,6 @@
 
 #include "base/macros.h"
 #include "net/reporting/reporting_context.h"
-#include "net/reporting/reporting_delegate.h"
 #include "net/reporting/reporting_uploader.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -40,24 +39,6 @@
                                          const url::Origin& origin,
                                          const GURL& endpoint);
 
-// A simple implementation of ReportingDelegate that only persists data in RAM.
-class TestReportingDelegate : public ReportingDelegate {
- public:
-  TestReportingDelegate();
-
-  ~TestReportingDelegate() override;
-
-  // ReportingDelegate implementation:
-  std::unique_ptr<const base::Value> GetPersistedData() override;
-
-  void PersistData(std::unique_ptr<const base::Value> persisted_data) override;
-
- private:
-  std::unique_ptr<const base::Value> persisted_data_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestReportingDelegate);
-};
-
 // A test implementation of ReportingUploader that holds uploads for tests to
 // examine and complete with a specified outcome.
 class TestReportingUploader : public ReportingUploader {
@@ -95,15 +76,12 @@
 };
 
 // A test implementation of ReportingContext that uses test versions of
-// ReportingDelegate, Clock, TickClock, and ReportingUploader.
+// Clock, TickClock, Timer, and ReportingUploader.
 class TestReportingContext : public ReportingContext {
  public:
   TestReportingContext(const ReportingPolicy& policy);
   ~TestReportingContext();
 
-  TestReportingDelegate* test_delegate() {
-    return reinterpret_cast<TestReportingDelegate*>(delegate());
-  }
   base::SimpleTestClock* test_clock() {
     return reinterpret_cast<base::SimpleTestClock*>(clock());
   }
@@ -111,7 +89,6 @@
     return reinterpret_cast<base::SimpleTestTickClock*>(tick_clock());
   }
   base::MockTimer* test_delivery_timer() { return delivery_timer_; }
-  base::MockTimer* test_persistence_timer() { return persistence_timer_; }
   base::MockTimer* test_garbage_collection_timer() {
     return garbage_collection_timer_;
   }
@@ -124,7 +101,6 @@
   // here to preserve type:
 
   base::MockTimer* delivery_timer_;
-  base::MockTimer* persistence_timer_;
   base::MockTimer* garbage_collection_timer_;
 
   DISALLOW_COPY_AND_ASSIGN(TestReportingContext);
@@ -139,8 +115,7 @@
 
   void UsePolicy(const ReportingPolicy& policy);
 
-  // Simulates an embedder restart, preserving the ReportingPolicy and any data
-  // persisted via the TestReportingDelegate, but nothing else.
+  // Simulates an embedder restart, preserving the ReportingPolicy.
   //
   // Advances the Clock by |delta|, and the TickClock by |delta_ticks|. Both can
   // be zero or negative.
@@ -150,15 +125,11 @@
 
   const ReportingPolicy& policy() { return context_->policy(); }
 
-  TestReportingDelegate* delegate() { return context_->test_delegate(); }
   base::SimpleTestClock* clock() { return context_->test_clock(); }
   base::SimpleTestTickClock* tick_clock() {
     return context_->test_tick_clock();
   }
   base::MockTimer* delivery_timer() { return context_->test_delivery_timer(); }
-  base::MockTimer* persistence_timer() {
-    return context_->test_persistence_timer();
-  }
   base::MockTimer* garbage_collection_timer() {
     return context_->test_garbage_collection_timer();
   }
@@ -187,11 +158,9 @@
   }
 
  private:
-  void CreateAndInitializeContext(
-      const ReportingPolicy& policy,
-      std::unique_ptr<const base::Value> persisted_data,
-      base::Time now,
-      base::TimeTicks now_ticks);
+  void CreateContext(const ReportingPolicy& policy,
+                     base::Time now,
+                     base::TimeTicks now_ticks);
 
   std::unique_ptr<TestReportingContext> context_;
 
diff --git a/net/url_request/url_request_test_job.cc b/net/url_request/url_request_test_job.cc
index ab359b4..a3512f9 100644
--- a/net/url_request/url_request_test_job.cc
+++ b/net/url_request/url_request_test_job.cc
@@ -296,12 +296,6 @@
   load_timing_info->request_start_time = request_start_time;
 }
 
-int URLRequestTestJob::GetResponseCode() const {
-  if (response_headers_.get())
-    return response_headers_->response_code();
-  return -1;
-}
-
 int64_t URLRequestTestJob::GetTotalReceivedBytes() const {
   return response_headers_length_ + offset_;
 }
diff --git a/net/url_request/url_request_test_job.h b/net/url_request/url_request_test_job.h
index 7082678..cd899bc 100644
--- a/net/url_request/url_request_test_job.h
+++ b/net/url_request/url_request_test_job.h
@@ -138,7 +138,6 @@
   bool GetMimeType(std::string* mime_type) const override;
   void GetResponseInfo(HttpResponseInfo* info) override;
   void GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const override;
-  int GetResponseCode() const override;
   int64_t GetTotalReceivedBytes() const override;
   bool IsRedirectResponse(GURL* location, int* http_status_code) override;
 
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index f196463b..b46d035 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -96,6 +96,8 @@
     "continue_window_linux.cc",
     "continue_window_mac.mm",
     "continue_window_win.cc",
+    "current_process_stats_agent.cc",
+    "current_process_stats_agent.h",
     "curtain_mode.h",
     "curtain_mode_linux.cc",
     "curtain_mode_mac.cc",
@@ -131,6 +133,8 @@
     "disconnect_window_win.cc",
     "dns_blackhole_checker.cc",
     "dns_blackhole_checker.h",
+    "forward_process_stats_agent.cc",
+    "forward_process_stats_agent.h",
     "gcd_rest_client.cc",
     "gcd_rest_client.h",
     "gcd_state_updater.cc",
@@ -223,6 +227,11 @@
     "policy_watcher.h",
     "posix/signal_handler.cc",
     "posix/signal_handler.h",
+    "process_stats_agent.h",
+    "process_stats_sender.cc",
+    "process_stats_sender.h",
+    "process_stats_util.cc",
+    "process_stats_util.h",
     "register_support_host_request.cc",
     "register_support_host_request.h",
     "remote_input_filter.cc",
@@ -460,6 +469,7 @@
     "pairing_registry_delegate_win_unittest.cc",
     "pin_hash_unittest.cc",
     "policy_watcher_unittest.cc",
+    "process_stats_sender_unittest.cc",
     "register_support_host_request_unittest.cc",
     "remote_input_filter_unittest.cc",
     "resizing_host_observer_unittest.cc",
diff --git a/remoting/host/current_process_stats_agent.cc b/remoting/host/current_process_stats_agent.cc
new file mode 100644
index 0000000..35ff5ac
--- /dev/null
+++ b/remoting/host/current_process_stats_agent.cc
@@ -0,0 +1,27 @@
+// 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 "remoting/host/current_process_stats_agent.h"
+
+#include "base/process/process_metrics.h"
+
+namespace remoting {
+
+CurrentProcessStatsAgent::CurrentProcessStatsAgent(
+    const std::string& process_name)
+    : process_name_(process_name),
+      metrics_(base::ProcessMetrics::CreateCurrentProcessMetrics()) {}
+
+CurrentProcessStatsAgent::~CurrentProcessStatsAgent() = default;
+
+protocol::ProcessResourceUsage CurrentProcessStatsAgent::GetResourceUsage() {
+  protocol::ProcessResourceUsage current;
+  current.set_process_name(process_name_);
+  current.set_processor_usage(metrics_->GetPlatformIndependentCPUUsage());
+  current.set_working_set_size(metrics_->GetWorkingSetSize());
+  current.set_pagefile_size(metrics_->GetPagefileUsage());
+  return current;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/current_process_stats_agent.h b/remoting/host/current_process_stats_agent.h
new file mode 100644
index 0000000..82e1163
--- /dev/null
+++ b/remoting/host/current_process_stats_agent.h
@@ -0,0 +1,36 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_CURRENT_PROCESS_STATS_AGENT_H_
+#define REMOTING_HOST_CURRENT_PROCESS_STATS_AGENT_H_
+
+#include <memory>
+#include <string>
+
+#include "remoting/host/process_stats_agent.h"
+#include "remoting/proto/process_stats.pb.h"
+
+namespace base {
+class ProcessMetrics;
+}  // namespace base
+
+namespace remoting {
+
+// A component to report statistic data of the current process.
+class CurrentProcessStatsAgent final : public ProcessStatsAgent {
+ public:
+  explicit CurrentProcessStatsAgent(const std::string& process_name);
+  ~CurrentProcessStatsAgent() override;
+
+  // ProcessStatsAgent implementation.
+  protocol::ProcessResourceUsage GetResourceUsage() override;
+
+ private:
+  const std::string process_name_;
+  const std::unique_ptr<base::ProcessMetrics> metrics_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_CURRENT_PROCESS_STATS_AGENT_H_
diff --git a/remoting/host/forward_process_stats_agent.cc b/remoting/host/forward_process_stats_agent.cc
new file mode 100644
index 0000000..c75bc517
--- /dev/null
+++ b/remoting/host/forward_process_stats_agent.cc
@@ -0,0 +1,25 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/forward_process_stats_agent.h"
+
+#include "base/logging.h"
+
+namespace remoting {
+
+ForwardProcessStatsAgent::ForwardProcessStatsAgent() = default;
+ForwardProcessStatsAgent::~ForwardProcessStatsAgent() = default;
+
+protocol::ProcessResourceUsage ForwardProcessStatsAgent::GetResourceUsage() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return usage_;
+}
+
+void ForwardProcessStatsAgent::OnProcessStats(
+    const protocol::ProcessResourceUsage& usage) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  usage_ = usage;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/forward_process_stats_agent.h b/remoting/host/forward_process_stats_agent.h
new file mode 100644
index 0000000..321f5ad
--- /dev/null
+++ b/remoting/host/forward_process_stats_agent.h
@@ -0,0 +1,36 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_FORWARD_PROCESS_STATS_AGENT_H_
+#define REMOTING_HOST_FORWARD_PROCESS_STATS_AGENT_H_
+
+#include "base/threading/thread_checker.h"
+#include "remoting/host/process_stats_agent.h"
+#include "remoting/proto/process_stats.pb.h"
+
+namespace remoting {
+
+// ProcessStatsAgent implementation that returns stats passed to
+// OnProcessStats(). Used to collect stats from processes other than the current
+// one. All public functions, not including constructor and destructor, of the
+// object of ForwardProcessStatsAgent are expected to be called in a same
+// thread.
+class ForwardProcessStatsAgent final : public ProcessStatsAgent {
+ public:
+  ForwardProcessStatsAgent();
+  ~ForwardProcessStatsAgent() override;
+
+  void OnProcessStats(const protocol::ProcessResourceUsage& usage);
+
+  // ProcessStatsAgent implementation.
+  protocol::ProcessResourceUsage GetResourceUsage() override;
+
+ private:
+  base::ThreadChecker thread_checker_;
+  protocol::ProcessResourceUsage usage_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_FORWARD_PROCESS_STATS_AGENT_H_
diff --git a/remoting/host/process_stats_agent.h b/remoting/host/process_stats_agent.h
new file mode 100644
index 0000000..dc242d3
--- /dev/null
+++ b/remoting/host/process_stats_agent.h
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_PROCESS_STATS_AGENT_H_
+#define REMOTING_HOST_PROCESS_STATS_AGENT_H_
+
+#include "remoting/proto/process_stats.pb.h"
+
+namespace remoting {
+
+// An interface to report the process statistic data.
+class ProcessStatsAgent {
+ public:
+  ProcessStatsAgent() = default;
+  virtual ~ProcessStatsAgent() = default;
+
+  // This function is expected to be executed in an IO-disallowed thread: if the
+  // implementation requires IO access or may cost a significant amount of time,
+  // using a background thread is preferred.
+  virtual protocol::ProcessResourceUsage GetResourceUsage() = 0;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_PROCESS_STATS_AGENT_H_
diff --git a/remoting/host/process_stats_sender.cc b/remoting/host/process_stats_sender.cc
new file mode 100644
index 0000000..a734877
--- /dev/null
+++ b/remoting/host/process_stats_sender.cc
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/process_stats_sender.h"
+
+#include <utility>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "remoting/host/process_stats_agent.h"
+#include "remoting/host/process_stats_util.h"
+
+namespace remoting {
+
+ProcessStatsSender::ProcessStatsSender(
+    protocol::ProcessStatsStub* host_stats_stub,
+    base::TimeDelta interval,
+    std::initializer_list<ProcessStatsAgent*> agents)
+    : host_stats_stub_(host_stats_stub),
+      agents_(agents),
+      thread_checker_() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(host_stats_stub_);
+  DCHECK(!interval.is_zero());
+  DCHECK(!agents_.empty());
+
+  timer_.Start(FROM_HERE, interval, this, &ProcessStatsSender::ReportUsage);
+}
+
+ProcessStatsSender::~ProcessStatsSender() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  timer_.Stop();
+}
+
+void ProcessStatsSender::ReportUsage() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  std::vector<protocol::ProcessResourceUsage> usages;
+  for (auto* const agent : agents_) {
+    DCHECK(agent);
+    protocol::ProcessResourceUsage usage = agent->GetResourceUsage();
+    if (!IsEmptyProcessResourceUsage(usage)) {
+      usages.push_back(std::move(usage));
+    }
+  }
+
+  host_stats_stub_->OnProcessStats(AggregateProcessResourceUsage(usages));
+}
+
+}  // namespace remoting
diff --git a/remoting/host/process_stats_sender.h b/remoting/host/process_stats_sender.h
new file mode 100644
index 0000000..c33af89
--- /dev/null
+++ b/remoting/host/process_stats_sender.h
@@ -0,0 +1,47 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_PROCESS_STATS_SENDER_H_
+#define REMOTING_HOST_PROCESS_STATS_SENDER_H_
+
+#include <initializer_list>
+#include <vector>
+
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "remoting/proto/process_stats.pb.h"
+#include "remoting/protocol/process_stats_stub.h"
+
+namespace remoting {
+
+class ProcessStatsAgent;
+
+// A component to report process statistic data regularly, it starts immediately
+// after construction, and stops after destruction.
+// All public functions, including constructor and destructor of the object of
+// ProcessStatsSender need to be executed in a same thread.
+class ProcessStatsSender final {
+ public:
+  // ProcessStatsSender reports statistic data to |host_stats_stub| once per
+  // |interval|.
+  // ProcessStatsSender does not take the ownership of both |host_stats_stub|
+  // and |agents|. They must outlive the ProcessStatsSender object.
+  ProcessStatsSender(protocol::ProcessStatsStub* host_stats_stub,
+                     base::TimeDelta interval,
+                     std::initializer_list<ProcessStatsAgent*> agents);
+
+  ~ProcessStatsSender();
+ private:
+  void ReportUsage();
+
+  protocol::ProcessStatsStub* const host_stats_stub_;
+  std::vector<ProcessStatsAgent*> agents_;
+  base::RepeatingTimer timer_;
+  const base::ThreadChecker thread_checker_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_PROCESS_STATS_SENDER_H_
diff --git a/remoting/host/process_stats_sender_unittest.cc b/remoting/host/process_stats_sender_unittest.cc
new file mode 100644
index 0000000..6d08818c
--- /dev/null
+++ b/remoting/host/process_stats_sender_unittest.cc
@@ -0,0 +1,180 @@
+// 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 "remoting/host/process_stats_sender.h"
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "remoting/host/process_stats_agent.h"
+#include "remoting/proto/process_stats.pb.h"
+#include "remoting/protocol/process_stats_stub.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+namespace {
+
+class FakeProcessStatsStub : public protocol::ProcessStatsStub {
+ public:
+  FakeProcessStatsStub() = default;
+  ~FakeProcessStatsStub() override = default;
+
+  void OnProcessStats(
+      const protocol::AggregatedProcessResourceUsage& usage) override {
+    received_.push_back(usage);
+    DCHECK_LE(received_.size(), expected_usage_count_);
+    DCHECK(!quit_closure_.is_null());
+    if (received_.size() == expected_usage_count_) {
+      quit_closure_.Run();
+    }
+  }
+
+  const std::vector<protocol::AggregatedProcessResourceUsage>& received()
+      const {
+    return received_;
+  }
+
+  void set_quit_closure(base::Closure quit_closure) {
+    quit_closure_ = quit_closure;
+  }
+
+  void set_expected_usage_count(size_t expected_usage_count) {
+    expected_usage_count_ = expected_usage_count;
+  }
+
+ private:
+  std::vector<protocol::AggregatedProcessResourceUsage> received_;
+  size_t expected_usage_count_ = 0;
+  base::Closure quit_closure_;
+};
+
+class FakeProcessStatsAgent : public ProcessStatsAgent {
+ public:
+  FakeProcessStatsAgent() = default;
+  ~FakeProcessStatsAgent() override = default;
+
+  protocol::ProcessResourceUsage GetResourceUsage() override {
+    protocol::ProcessResourceUsage usage;
+    usage.set_process_name("FakeProcessStatsAgent");
+    usage.set_processor_usage(index_);
+    usage.set_working_set_size(index_);
+    usage.set_pagefile_size(index_);
+    index_++;
+    return usage;
+  }
+
+  // Checks the expected usage based on index.
+  static void AssertExpected(
+      const protocol::AggregatedProcessResourceUsage& usage,
+      size_t index) {
+    ASSERT_EQ(usage.processor_usage(), index);
+    ASSERT_EQ(usage.working_set_size(), index);
+    ASSERT_EQ(usage.pagefile_size(), index);
+  }
+
+  static void AssertExpected(const protocol::ProcessResourceUsage& usage,
+                             size_t index) {
+    ASSERT_EQ(usage.processor_usage(), index);
+    ASSERT_EQ(usage.working_set_size(), index);
+    ASSERT_EQ(usage.pagefile_size(), index);
+  }
+
+  size_t issued_times() const { return index_; }
+
+ private:
+  size_t index_ = 0;
+};
+
+}  // namespace
+
+TEST(ProcessStatsSenderTest, ReportUsage) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+  FakeProcessStatsStub stub;
+  std::unique_ptr<ProcessStatsSender> stats;
+  FakeProcessStatsAgent agent;
+
+  stub.set_expected_usage_count(10);
+  stub.set_quit_closure(base::Bind(
+      [](std::unique_ptr<ProcessStatsSender>* stats,
+         const FakeProcessStatsStub& stub, const FakeProcessStatsAgent& agent,
+         base::RunLoop* run_loop) -> void {
+        ASSERT_EQ(stub.received().size(), agent.issued_times());
+        stats->reset();
+        run_loop->Quit();
+      },
+      base::Unretained(&stats), base::ConstRef(stub), base::ConstRef(agent),
+      base::Unretained(&run_loop)));
+  message_loop.task_runner()->PostTask(
+      FROM_HERE,
+      base::Bind(
+          [](std::unique_ptr<ProcessStatsSender>* stats,
+             FakeProcessStatsStub* stub, FakeProcessStatsAgent* agent) -> void {
+            stats->reset(new ProcessStatsSender(
+                stub, base::TimeDelta::FromMilliseconds(1), { agent }));
+          },
+          base::Unretained(&stats), base::Unretained(&stub),
+          base::Unretained(&agent)));
+  run_loop.Run();
+
+  ASSERT_EQ(stub.received().size(), 10U);
+  for (size_t i = 0; i < stub.received().size(); i++) {
+    FakeProcessStatsAgent::AssertExpected(stub.received()[i], i);
+  }
+}
+
+TEST(ProcessStatsSenderTest, MergeUsage) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+  FakeProcessStatsStub stub;
+  std::unique_ptr<ProcessStatsSender> stats;
+  // Owned by |stats|.
+  FakeProcessStatsAgent agent1;
+  FakeProcessStatsAgent agent2;
+
+  stub.set_expected_usage_count(10);
+  stub.set_quit_closure(base::Bind(
+      [](std::unique_ptr<ProcessStatsSender>* stats,
+         const FakeProcessStatsStub& stub, const FakeProcessStatsAgent& agent1,
+         const FakeProcessStatsAgent& agent2, base::RunLoop* run_loop) -> void {
+        ASSERT_EQ(stub.received().size(), agent1.issued_times());
+        ASSERT_EQ(stub.received().size(), agent2.issued_times());
+        stats->reset();
+        run_loop->Quit();
+      },
+      base::Unretained(&stats), base::ConstRef(stub), base::ConstRef(agent1),
+      base::ConstRef(agent2), base::Unretained(&run_loop)));
+  message_loop.task_runner()->PostTask(
+      FROM_HERE,
+      base::Bind(
+          [](std::unique_ptr<ProcessStatsSender>* stats,
+             FakeProcessStatsStub* stub, FakeProcessStatsAgent* agent1,
+             FakeProcessStatsAgent* agent2) -> void {
+            stats->reset(new ProcessStatsSender(
+                stub, base::TimeDelta::FromMilliseconds(1),
+                { agent1, agent2 } ));
+          },
+          base::Unretained(&stats), base::Unretained(&stub),
+          base::Unretained(&agent1), base::Unretained(&agent2)));
+  run_loop.Run();
+
+  ASSERT_EQ(stub.received().size(), 10U);
+  for (size_t i = 0; i < stub.received().size(); i++) {
+    FakeProcessStatsAgent::AssertExpected(stub.received()[i], i * 2);
+    for (int j = 0; j < stub.received()[i].usages_size(); j++) {
+      FakeProcessStatsAgent::AssertExpected(
+          stub.received()[i].usages().Get(j), i);
+    }
+  }
+}
+
+}  // namespace remoting
diff --git a/remoting/host/process_stats_util.cc b/remoting/host/process_stats_util.cc
new file mode 100644
index 0000000..b87402f
--- /dev/null
+++ b/remoting/host/process_stats_util.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/process_stats_util.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+
+namespace remoting {
+
+bool IsEmptyProcessResourceUsage(const protocol::ProcessResourceUsage& usage) {
+  return !usage.has_process_name() && !usage.has_processor_usage() &&
+         !usage.has_working_set_size() && !usage.has_pagefile_size();
+}
+
+protocol::AggregatedProcessResourceUsage AggregateProcessResourceUsage(
+    const std::vector<protocol::ProcessResourceUsage>& usages) {
+  if (usages.empty()) {
+    return protocol::AggregatedProcessResourceUsage();
+  }
+
+  if (usages.size() == 1) {
+    const protocol::ProcessResourceUsage& usage = usages[0];
+    protocol::AggregatedProcessResourceUsage aggregated;
+    aggregated.set_name(usage.process_name());
+    aggregated.set_processor_usage(usage.processor_usage());
+    aggregated.set_working_set_size(usage.working_set_size());
+    aggregated.set_pagefile_size(usage.pagefile_size());
+    return aggregated;
+  }
+
+  std::string name = "aggregate { ";
+  double processor_usage = 0;
+  uint64_t working_set_size = 0;
+  uint64_t pagefile_size = 0;
+  protocol::AggregatedProcessResourceUsage aggregated;
+  for (const auto& usage : usages) {
+    name.append(usage.process_name()).append(", ");
+    processor_usage += usage.processor_usage();
+    working_set_size += usage.working_set_size();
+    pagefile_size += usage.pagefile_size();
+    *aggregated.add_usages() = usage;
+  }
+  name += " }";
+  aggregated.set_name(std::move(name));
+  aggregated.set_processor_usage(processor_usage);
+  aggregated.set_working_set_size(working_set_size);
+  aggregated.set_pagefile_size(pagefile_size);
+
+  return aggregated;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/process_stats_util.h b/remoting/host/process_stats_util.h
new file mode 100644
index 0000000..6ad9739
--- /dev/null
+++ b/remoting/host/process_stats_util.h
@@ -0,0 +1,24 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_PROCESS_STATS_UTIL_H_
+#define REMOTING_HOST_PROCESS_STATS_UTIL_H_
+
+#include <vector>
+
+#include "remoting/proto/process_stats.pb.h"
+
+namespace remoting {
+
+// Whether the |usage| is empty, i.e. all fields hold initial values.
+bool IsEmptyProcessResourceUsage(const protocol::ProcessResourceUsage& usage);
+
+// Merges several ProcessResourceUsage instances into one
+// AggregatedProcessResourceUsage.
+protocol::AggregatedProcessResourceUsage AggregateProcessResourceUsage(
+    const std::vector<protocol::ProcessResourceUsage>& usages);
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_PROCESS_STATS_UTIL_H_
diff --git a/remoting/proto/BUILD.gn b/remoting/proto/BUILD.gn
index 853267e..64e0f23 100644
--- a/remoting/proto/BUILD.gn
+++ b/remoting/proto/BUILD.gn
@@ -18,6 +18,7 @@
     "event.proto",
     "internal.proto",
     "mux.proto",
+    "process_stats.proto",
     "video.proto",
     "video_stats.proto",
   ]
diff --git a/remoting/proto/process_stats.proto b/remoting/proto/process_stats.proto
new file mode 100644
index 0000000..77d9c8f
--- /dev/null
+++ b/remoting/proto/process_stats.proto
@@ -0,0 +1,47 @@
+// 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.
+
+// Protocol for process resource usage.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package remoting.protocol;
+
+// The resource usage of a single process.
+// Next Id: 5
+message ProcessResourceUsage {
+  // The name or friendly name of the process.
+  optional string process_name = 1;
+
+  // The processor usage. It should be a consistent value on all platforms in
+  // range of 0 to (100 * NumCPUCores).
+  optional double processor_usage = 2;
+
+  // Memory usage of working set.
+  optional uint64 working_set_size = 3;
+
+  // Memory usage of page file.
+  optional uint64 pagefile_size = 4;
+}
+
+// The resource usage of several processes.
+// Next Id: 6
+message AggregatedProcessResourceUsage {
+  // A friendly name of the processes current instance aggregated from.
+  optional string name = 1;
+
+  // The total processor usage.
+  optional double processor_usage = 2;
+
+  // The total memory usage of working set.
+  optional uint64 working_set_size = 3;
+
+  // The total memory usage of page file.
+  optional uint64 pagefile_size = 4;
+
+  // The process resource usage of each individual process.
+  repeated ProcessResourceUsage usages = 5;
+}
diff --git a/remoting/protocol/BUILD.gn b/remoting/protocol/BUILD.gn
index 92a4256..f9d4099 100644
--- a/remoting/protocol/BUILD.gn
+++ b/remoting/protocol/BUILD.gn
@@ -128,6 +128,7 @@
     "port_allocator_factory.h",
     "port_range.cc",
     "port_range.h",
+    "process_stats_stub.h",
     "pseudotcp_adapter.cc",
     "pseudotcp_adapter.h",
     "pseudotcp_channel_factory.cc",
diff --git a/remoting/protocol/process_stats_stub.h b/remoting/protocol/process_stats_stub.h
new file mode 100644
index 0000000..b59d7c6
--- /dev/null
+++ b/remoting/protocol/process_stats_stub.h
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_PROTOCOL_PROCESS_STATS_STUB_H_
+#define REMOTING_PROTOCOL_PROCESS_STATS_STUB_H_
+
+#include "remoting/proto/process_stats.pb.h"
+
+namespace remoting {
+namespace protocol {
+
+// An interface to receive process statistic data.
+class ProcessStatsStub {
+ public:
+  ProcessStatsStub() = default;
+  virtual ~ProcessStatsStub() = default;
+
+  virtual void OnProcessStats(
+      const protocol::AggregatedProcessResourceUsage& usage) = 0;
+};
+
+}  // namespace protocol
+}  // namespace remoting
+
+#endif  // REMOTING_PROTOCOL_PROCESS_STATS_STUB_H_
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
index fbacd2c..7fc239f 100644
--- a/remoting/protocol/webrtc_transport.cc
+++ b/remoting/protocol/webrtc_transport.cc
@@ -208,6 +208,9 @@
     // See crbug.com/660081.
     base::ThreadRestrictions::ScopedAllowIO allow_io;
     peer_connection_->Close();
+    peer_connection_ = nullptr;
+    peer_connection_factory_ = nullptr;
+    audio_module_ = nullptr;
   }
 
   WebrtcAudioModule* audio_module() {
diff --git a/services/ui/public/interfaces/cursor/cursor.mojom b/services/ui/public/interfaces/cursor/cursor.mojom
index 30becc8b..c228539a 100644
--- a/services/ui/public/interfaces/cursor/cursor.mojom
+++ b/services/ui/public/interfaces/cursor/cursor.mojom
@@ -5,7 +5,7 @@
 module ui.mojom;
 
 import "mojo/common/time.mojom";
-import "skia/public/interfaces/bitmap_array.mojom";
+import "skia/public/interfaces/bitmap.mojom";
 import "ui/gfx/geometry/mojo/geometry.mojom";
 
 // Standard Cursor numbers. These are the same as Chrome's ui::Cursor and
@@ -72,7 +72,7 @@
   gfx.mojom.Point hotspot_in_pixels;
 
   // The frames of the cursor.
-  skia.mojom.BitmapArray cursor_frames;
+  array<skia.mojom.Bitmap?> cursor_frames;
 
   // This is the image scale of this cursor.
   float scale_factor;
diff --git a/services/ui/public/interfaces/cursor/cursor_struct_traits_unittest.cc b/services/ui/public/interfaces/cursor/cursor_struct_traits_unittest.cc
index db2c6c4..011b412 100644
--- a/services/ui/public/interfaces/cursor/cursor_struct_traits_unittest.cc
+++ b/services/ui/public/interfaces/cursor/cursor_struct_traits_unittest.cc
@@ -99,4 +99,21 @@
   }
 }
 
+// Test that we deal with empty bitmaps. (When a cursor resource isn't loaded
+// in the renderer, the renderer will send a kCurstomCursor with an empty
+// bitmap.)
+TEST_F(CursorStructTraitsTest, TestEmptyCursor) {
+  const base::TimeDelta kFrameDelay = base::TimeDelta::FromMilliseconds(15);
+  const gfx::Point kHotspot = gfx::Point(5, 2);
+  const float kScale = 2.0f;
+
+  ui::CursorData input(kHotspot, {SkBitmap()}, kScale, kFrameDelay);
+
+  ui::CursorData output;
+  ASSERT_TRUE(EchoCursorData(input, &output));
+
+  ASSERT_EQ(1u, output.cursor_frames().size());
+  EXPECT_TRUE(output.cursor_frames().front().empty());
+}
+
 }  // namespace ui
diff --git a/services/ui/public/interfaces/window_manager.mojom b/services/ui/public/interfaces/window_manager.mojom
index 2d25904..a0b935cf 100644
--- a/services/ui/public/interfaces/window_manager.mojom
+++ b/services/ui/public/interfaces/window_manager.mojom
@@ -298,7 +298,7 @@
   WmSetFrameDecorationValues(FrameDecorationValues values);
 
   // Sets the cursor that the non-client areas of the window should use.
-  WmSetNonClientCursor(uint32 window_id, CursorType cursor_id);
+  WmSetNonClientCursor(uint32 window_id, CursorData cursor);
 
   // Response from WmCreateTopLevelWindow() informing the client of the id for
   // the new window.
diff --git a/services/ui/public/interfaces/window_tree.mojom b/services/ui/public/interfaces/window_tree.mojom
index b8c4fabc..47c812c 100644
--- a/services/ui/public/interfaces/window_tree.mojom
+++ b/services/ui/public/interfaces/window_tree.mojom
@@ -244,12 +244,10 @@
   // Marks the specified window as being able to receive focus.
   SetCanFocus(uint32 window_id, bool can_focus);
 
-  // Sets the cursor when the pointer is inside |window_id| to a system standard
-  // cursor provided by the window manager.
-  SetPredefinedCursor(uint32 change_id, uint32 window_id, CursorType cursor_id);
+  // Sets the cursor when the pointer is inside |window_id|.
+  SetCursor(uint32 change_id, uint32 window_id, CursorData cursor);
 
-  // TODO(erg): Additional cursor methods. Image based cursors, visibility,
-  // and cursor locking.
+  // TODO(erg): Additional cursor methods. Visibility, and cursor locking.
 
   // Set text input state for the given window.
   SetWindowTextInputState(uint32 window_id, mojo.TextInputState state);
@@ -467,7 +465,7 @@
   // initiate the change.
   OnWindowFocused(uint32 focused_window_id);
 
-  OnWindowPredefinedCursorChanged(uint32 window_id, CursorType cursor_id);
+  OnWindowCursorChanged(uint32 window_id, CursorData cursor);
 
   // Invoked when a client window submits a new surface ID. The surface ID and
   // associated information is propagated to the parent connection. The parent
diff --git a/services/ui/ws/cursor_unittest.cc b/services/ui/ws/cursor_unittest.cc
index ee0d8865..1b75b86 100644
--- a/services/ui/ws/cursor_unittest.cc
+++ b/services/ui/ws/cursor_unittest.cc
@@ -24,6 +24,7 @@
 #include "services/ui/ws/window_tree.h"
 #include "services/ui/ws/window_tree_binding.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/events/event.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -42,7 +43,9 @@
   TestWindowServerDelegate* window_server_delegate() {
     return ws_test_helper_.window_server_delegate();
   }
-  mojom::CursorType cursor() const { return ws_test_helper_.cursor(); }
+  ui::CursorType cursor_type() const {
+    return ws_test_helper_.cursor().cursor_type();
+  }
 
  protected:
   // testing::Test:
@@ -108,81 +111,86 @@
 
 TEST_F(CursorTest, ChangeByMouseMove) {
   ServerWindow* win = BuildServerWindow();
-  win->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  win->parent()->SetPredefinedCursor(mojom::CursorType::kCell);
-  EXPECT_EQ(mojom::CursorType::kIBeam, win->cursor());
-  win->SetNonClientCursor(mojom::CursorType::kEastResize);
-  EXPECT_EQ(mojom::CursorType::kEastResize, win->non_client_cursor());
+  win->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  win->parent()->SetCursor(ui::CursorData(ui::CursorType::kCell));
+  EXPECT_EQ(ui::CursorType::kIBeam, win->cursor().cursor_type());
+  win->SetNonClientCursor(ui::CursorData(ui::CursorType::kEastResize));
+  EXPECT_EQ(ui::CursorType::kEastResize,
+            win->non_client_cursor().cursor_type());
 
   // Non client area
   MoveCursorTo(gfx::Point(15, 15));
-  EXPECT_EQ(mojom::CursorType::kEastResize, cursor());
+  EXPECT_EQ(ui::CursorType::kEastResize, cursor_type());
 
   // Client area, which comes from win->parent().
   MoveCursorTo(gfx::Point(25, 25));
-  EXPECT_EQ(mojom::CursorType::kCell, cursor());
+  EXPECT_EQ(ui::CursorType::kCell, cursor_type());
 }
 
 TEST_F(CursorTest, ChangeByClientAreaChange) {
   ServerWindow* win = BuildServerWindow();
-  win->parent()->SetPredefinedCursor(mojom::CursorType::kCross);
-  win->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kIBeam, mojom::CursorType(win->cursor()));
-  win->SetNonClientCursor(mojom::CursorType::kEastResize);
-  EXPECT_EQ(mojom::CursorType::kEastResize, win->non_client_cursor());
+  win->parent()->SetCursor(ui::CursorData(ui::CursorType::kCross));
+  win->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kIBeam, win->cursor().cursor_type());
+  win->SetNonClientCursor(ui::CursorData(ui::CursorType::kEastResize));
+  EXPECT_EQ(ui::CursorType::kEastResize,
+            win->non_client_cursor().cursor_type());
 
   // Non client area before we move.
   MoveCursorTo(gfx::Point(15, 15));
-  EXPECT_EQ(mojom::CursorType::kEastResize, cursor());
+  EXPECT_EQ(ui::CursorType::kEastResize, cursor_type());
 
   // Changing the client area should cause a change. The cursor for the client
   // area comes from root ancestor, which is win->parent().
   win->SetClientArea(gfx::Insets(1, 1), std::vector<gfx::Rect>());
-  EXPECT_EQ(mojom::CursorType::kCross, cursor());
+  EXPECT_EQ(ui::CursorType::kCross, cursor_type());
 }
 
 TEST_F(CursorTest, NonClientCursorChange) {
   ServerWindow* win = BuildServerWindow();
-  win->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kIBeam, win->cursor());
-  win->SetNonClientCursor(mojom::CursorType::kEastResize);
-  EXPECT_EQ(mojom::CursorType::kEastResize, win->non_client_cursor());
+  win->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kIBeam, win->cursor().cursor_type());
+  win->SetNonClientCursor(ui::CursorData(ui::CursorType::kEastResize));
+  EXPECT_EQ(ui::CursorType::kEastResize,
+            win->non_client_cursor().cursor_type());
 
   MoveCursorTo(gfx::Point(15, 15));
-  EXPECT_EQ(mojom::CursorType::kEastResize, cursor());
+  EXPECT_EQ(ui::CursorType::kEastResize, cursor_type());
 
-  win->SetNonClientCursor(mojom::CursorType::kWestResize);
-  EXPECT_EQ(mojom::CursorType::kWestResize, cursor());
+  win->SetNonClientCursor(ui::CursorData(ui::CursorType::kWestResize));
+  EXPECT_EQ(ui::CursorType::kWestResize, cursor_type());
 }
 
 TEST_F(CursorTest, IgnoreClientCursorChangeInNonClientArea) {
   ServerWindow* win = BuildServerWindow();
-  win->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kIBeam, win->cursor());
-  win->SetNonClientCursor(mojom::CursorType::kEastResize);
-  EXPECT_EQ(mojom::CursorType::kEastResize, win->non_client_cursor());
+  win->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kIBeam, win->cursor().cursor_type());
+  win->SetNonClientCursor(ui::CursorData(ui::CursorType::kEastResize));
+  EXPECT_EQ(ui::CursorType::kEastResize,
+            win->non_client_cursor().cursor_type());
 
   MoveCursorTo(gfx::Point(15, 15));
-  EXPECT_EQ(mojom::CursorType::kEastResize, cursor());
+  EXPECT_EQ(ui::CursorType::kEastResize, cursor_type());
 
-  win->SetPredefinedCursor(mojom::CursorType::kHelp);
-  EXPECT_EQ(mojom::CursorType::kEastResize, cursor());
+  win->SetCursor(ui::CursorData(ui::CursorType::kHelp));
+  EXPECT_EQ(ui::CursorType::kEastResize, cursor_type());
 }
 
 TEST_F(CursorTest, NonClientToClientByBoundsChange) {
   ServerWindow* win = BuildServerWindow();
-  win->parent()->SetPredefinedCursor(mojom::CursorType::kCopy);
-  win->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kIBeam, win->cursor());
-  win->SetNonClientCursor(mojom::CursorType::kEastResize);
-  EXPECT_EQ(mojom::CursorType::kEastResize, win->non_client_cursor());
+  win->parent()->SetCursor(ui::CursorData(ui::CursorType::kCopy));
+  win->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kIBeam, win->cursor().cursor_type());
+  win->SetNonClientCursor(ui::CursorData(ui::CursorType::kEastResize));
+  EXPECT_EQ(ui::CursorType::kEastResize,
+            win->non_client_cursor().cursor_type());
 
   // Non client area before we move.
   MoveCursorTo(gfx::Point(15, 15));
-  EXPECT_EQ(mojom::CursorType::kEastResize, cursor());
+  EXPECT_EQ(ui::CursorType::kEastResize, cursor_type());
 
   win->SetBounds(gfx::Rect(0, 0, 30, 30));
-  EXPECT_EQ(mojom::CursorType::kCopy, cursor());
+  EXPECT_EQ(ui::CursorType::kCopy, cursor_type());
 }
 
 }  // namespace test
diff --git a/services/ui/ws/display.cc b/services/ui/ws/display.cc
index 98c6cfa..becd40ed 100644
--- a/services/ui/ws/display.cc
+++ b/services/ui/ws/display.cc
@@ -37,7 +37,8 @@
 namespace ws {
 
 Display::Display(WindowServer* window_server)
-    : window_server_(window_server), last_cursor_(mojom::CursorType::kNull) {
+    : window_server_(window_server),
+      last_cursor_(ui::CursorData(ui::CursorType::kNull)) {
   window_server_->window_manager_window_tree_factory_set()->AddObserver(this);
   window_server_->user_id_tracker()->AddObserver(this);
 }
@@ -197,10 +198,10 @@
   NOTREACHED();
 }
 
-void Display::UpdateNativeCursor(mojom::CursorType cursor_id) {
-  if (cursor_id != last_cursor_) {
-    platform_display_->SetCursorById(cursor_id);
-    last_cursor_ = cursor_id;
+void Display::UpdateNativeCursor(const ui::CursorData& cursor) {
+  if (!last_cursor_.IsSameAs(cursor)) {
+    platform_display_->SetCursor(cursor);
+    last_cursor_ = cursor;
   }
 }
 
diff --git a/services/ui/ws/display.h b/services/ui/ws/display.h
index b83ce21..f2d8c732 100644
--- a/services/ui/ws/display.h
+++ b/services/ui/ws/display.h
@@ -149,7 +149,7 @@
   // |display_root| being destroyed.
   void RemoveWindowManagerDisplayRoot(WindowManagerDisplayRoot* display_root);
 
-  void UpdateNativeCursor(mojom::CursorType cursor_id);
+  void UpdateNativeCursor(const ui::CursorData& cursor);
 
   // mojom::WindowTreeHost:
   void SetSize(const gfx::Size& size) override;
@@ -219,7 +219,7 @@
   display::Display display_;
 
   // The last cursor set. Used to track whether we need to change the cursor.
-  mojom::CursorType last_cursor_;
+  ui::CursorData last_cursor_;
 
   ServerWindowTracker activation_parents_;
 
diff --git a/services/ui/ws/drag_controller.cc b/services/ui/ws/drag_controller.cc
index d5bce60..cc3b999 100644
--- a/services/ui/ws/drag_controller.cc
+++ b/services/ui/ws/drag_controller.cc
@@ -13,6 +13,7 @@
 #include "services/ui/ws/drag_target_connection.h"
 #include "services/ui/ws/event_dispatcher.h"
 #include "services/ui/ws/server_window.h"
+#include "ui/base/cursor/cursor.h"
 
 namespace ui {
 namespace ws {
@@ -52,7 +53,7 @@
       cursor_updater_(cursor_updater),
       drag_operations_(drag_operations),
       drag_pointer_id_(drag_pointer),
-      current_cursor_(ui::mojom::CursorType::kNoDrop),
+      current_cursor_(ui::CursorType::kNoDrop),
       source_window_(source_window),
       source_connection_(source_connection),
       mime_data_(mime_data),
@@ -187,11 +188,12 @@
   }
 }
 
-ui::mojom::CursorType DragController::CursorForEffectBitmask(
+ui::CursorData DragController::CursorForEffectBitmask(
     DropEffectBitmask bitmask) {
   DropEffectBitmask combined = bitmask & drag_operations_;
-  return combined == ui::mojom::kDropEffectNone ? ui::mojom::CursorType::kNoDrop
-                                                : ui::mojom::CursorType::kCopy;
+  return combined == ui::mojom::kDropEffectNone
+             ? ui::CursorData(ui::CursorType::kNoDrop)
+             : ui::CursorData(ui::CursorType::kCopy);
 }
 
 void DragController::SetCurrentTargetWindow(ServerWindow* current_target) {
@@ -204,7 +206,7 @@
     current_cursor_ = CursorForEffectBitmask(state.bitmask);
   } else {
     // Can't drop in empty areas.
-    current_cursor_ = ui::mojom::CursorType::kNoDrop;
+    current_cursor_ = ui::CursorData(ui::CursorType::kNoDrop);
   }
 
   cursor_updater_->OnDragCursorUpdated();
diff --git a/services/ui/ws/drag_controller.h b/services/ui/ws/drag_controller.h
index de8ea9a..83e6abc 100644
--- a/services/ui/ws/drag_controller.h
+++ b/services/ui/ws/drag_controller.h
@@ -53,7 +53,7 @@
       DropEffectBitmask drag_operations);
   ~DragController() override;
 
-  ui::mojom::CursorType current_cursor() const { return current_cursor_; }
+  const ui::CursorData& current_cursor() const { return current_cursor_; }
 
   // Cancels the current drag, ie, due to the user pressing Escape.
   void Cancel();
@@ -89,9 +89,9 @@
   // bitmask of the current valid drag operations.
   void SetWindowDropOperations(ServerWindow* window, DropEffectBitmask bitmask);
 
-  // Returns the ui::mojom::Cursor for the window |bitmask|, adjusted for types
-  // that the drag source allows.
-  ui::mojom::CursorType CursorForEffectBitmask(DropEffectBitmask bitmask);
+  // Returns the cursor for the window |bitmask|, adjusted for types that the
+  // drag source allows.
+  ui::CursorData CursorForEffectBitmask(DropEffectBitmask bitmask);
 
   // Ensure that |window| has an entry in |window_state_| and that we're an
   // observer.
@@ -126,7 +126,7 @@
   const int32_t drag_pointer_id_;
 
   // The current mouse cursor during the drag.
-  ui::mojom::CursorType current_cursor_;
+  ui::CursorData current_cursor_;
 
   // Sending OnDragOver() to our |source_| destroys us; there is a period where
   // we have to continue to exist, but not process any more pointer events.
diff --git a/services/ui/ws/drag_controller_unittest.cc b/services/ui/ws/drag_controller_unittest.cc
index 6484f8c..6e10d0f 100644
--- a/services/ui/ws/drag_controller_unittest.cc
+++ b/services/ui/ws/drag_controller_unittest.cc
@@ -18,6 +18,7 @@
 #include "services/ui/ws/test_server_window_delegate.h"
 #include "services/ui/ws/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/events/base_event_utils.h"
 
 namespace ui {
@@ -162,7 +163,7 @@
     // It would be nice if we could just let the observer method fire, but it
     // fires during the constructor when we haven't assigned the unique_ptr
     // yet.
-    cursor_ = ui::mojom::CursorType(drag_operation_->current_cursor());
+    cursor_ = drag_operation_->current_cursor().cursor_type();
   }
 
   void DispatchDrag(DragTestWindow* window,
@@ -204,7 +205,7 @@
     return drag_completed_value_;
   }
 
-  ui::mojom::CursorType cursor() { return cursor_; }
+  ui::CursorType cursor_type() const { return cursor_; }
 
  private:
   // Overridden from testing::Test:
@@ -232,7 +233,7 @@
   // Overridden from DragCursorUpdater:
   void OnDragCursorUpdated() override {
     if (drag_operation_)
-      cursor_ = ui::mojom::CursorType(drag_operation_->current_cursor());
+      cursor_ = drag_operation_->current_cursor().cursor_type();
   }
 
   // Overridden from DragControllerSource:
@@ -260,7 +261,7 @@
 
   int window_id_ = 3;
 
-  ui::mojom::CursorType cursor_;
+  ui::CursorType cursor_;
 
   std::map<WindowId, ServerWindow*> server_window_by_id_;
   std::map<ServerWindow*, DragTargetConnection*> connection_by_window_;
@@ -282,14 +283,14 @@
   std::unique_ptr<DragTestWindow> window = BuildWindow();
   StartDragOperation(window.get(), ui::mojom::kDropEffectMove);
 
-  EXPECT_EQ(ui::mojom::CursorType::kNoDrop, cursor());
+  EXPECT_EQ(ui::CursorType::kNoDrop, cursor_type());
 
   DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1));
   EXPECT_EQ(QueuedType::ENTER, window->queue_response_type());
   window->Respond(true);
 
   // (Even though we're doing a move, the cursor name is COPY.)
-  EXPECT_EQ(ui::mojom::CursorType::kCopy, cursor());
+  EXPECT_EQ(ui::CursorType::kCopy, cursor_type());
 
   DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2));
   EXPECT_EQ(QueuedType::OVER, window->queue_response_type());
@@ -538,7 +539,7 @@
   std::unique_ptr<DragTestWindow> window1 = BuildWindow();
   std::unique_ptr<DragTestWindow> window2 = BuildWindow();
   StartDragOperation(window1.get(), ui::mojom::kDropEffectMove);
-  EXPECT_EQ(ui::mojom::CursorType::kNoDrop, cursor());
+  EXPECT_EQ(ui::CursorType::kNoDrop, cursor_type());
 
   // Send some events to |window|.
   DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON,
@@ -548,13 +549,13 @@
   DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON,
                gfx::Point(1, 1));
   window2->Respond(true);
-  EXPECT_EQ(ui::mojom::CursorType::kCopy, cursor());
+  EXPECT_EQ(ui::CursorType::kCopy, cursor_type());
 
   // Force the destruction of |window.window|.
   window2.reset();
 
   // The cursor no loner indicates that it can drop on |window2|.
-  EXPECT_EQ(ui::mojom::CursorType::kNoDrop, cursor());
+  EXPECT_EQ(ui::CursorType::kNoDrop, cursor_type());
 }
 
 TEST_F(DragControllerTest, SourceWindowClosedWhileDrag) {
@@ -640,20 +641,20 @@
   std::unique_ptr<DragTestWindow> window = BuildWindow();
   StartDragOperation(window.get(), ui::mojom::kDropEffectMove);
 
-  EXPECT_EQ(ui::mojom::CursorType::kNoDrop, cursor());
+  EXPECT_EQ(ui::CursorType::kNoDrop, cursor_type());
 
   DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1));
   EXPECT_EQ(QueuedType::ENTER, window->queue_response_type());
   window->Respond(true);
 
-  EXPECT_EQ(ui::mojom::CursorType::kCopy, cursor());
+  EXPECT_EQ(ui::CursorType::kCopy, cursor_type());
 
   DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2));
   EXPECT_EQ(QueuedType::OVER, window->queue_response_type());
 
   // At this point, we respond with no available drag actions at this pixel.
   window->Respond(false);
-  EXPECT_EQ(ui::mojom::CursorType::kNoDrop, cursor());
+  EXPECT_EQ(ui::CursorType::kNoDrop, cursor_type());
 }
 
 TEST_F(DragControllerTest, ResopnseFromOtherWindowDoesntChangeCursor) {
@@ -668,7 +669,7 @@
   DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON,
                gfx::Point(1, 1));
 
-  EXPECT_EQ(ui::mojom::CursorType::kNoDrop, cursor());
+  EXPECT_EQ(ui::CursorType::kNoDrop, cursor_type());
 
   // Now enter |window1|, and respond.
   DispatchDrag(window1.get(), false, ui::EF_LEFT_MOUSE_BUTTON,
@@ -676,13 +677,13 @@
   EXPECT_EQ(QueuedType::ENTER, window1->queue_response_type());
   window1->Respond(true);
 
-  EXPECT_EQ(ui::mojom::CursorType::kCopy, cursor());
+  EXPECT_EQ(ui::CursorType::kCopy, cursor_type());
 
   // Window 2 responding negatively to its queued messages shouldn't change the
   // cursor.
   window2->Respond(false);
 
-  EXPECT_EQ(ui::mojom::CursorType::kCopy, cursor());
+  EXPECT_EQ(ui::CursorType::kCopy, cursor_type());
 }
 
 }  // namespace ws
diff --git a/services/ui/ws/event_dispatcher.cc b/services/ui/ws/event_dispatcher.cc
index 9edd3bc9..83e8b37 100644
--- a/services/ui/ws/event_dispatcher.cc
+++ b/services/ui/ws/event_dispatcher.cc
@@ -16,6 +16,7 @@
 #include "services/ui/ws/server_window_delegate.h"
 #include "services/ui/ws/window_coordinate_conversions.h"
 #include "services/ui/ws/window_finder.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/events/event_utils.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_conversions.h"
@@ -86,18 +87,18 @@
   delegate_->OnMouseCursorLocationChanged(screen_location);
 }
 
-ui::mojom::CursorType EventDispatcher::GetCurrentMouseCursor() const {
+ui::CursorData EventDispatcher::GetCurrentMouseCursor() const {
   if (drag_controller_)
     return drag_controller_->current_cursor();
 
   if (!mouse_cursor_source_window_)
-    return ui::mojom::CursorType::kPointer;
+    return ui::CursorData(ui::CursorType::kPointer);
 
   if (mouse_cursor_in_non_client_area_)
     return mouse_cursor_source_window_->non_client_cursor();
 
   const ServerWindow* window = GetWindowForMouseCursor();
-  return window ? window->cursor() : ui::mojom::CursorType::kPointer;
+  return window ? window->cursor() : ui::CursorData(ui::CursorType::kPointer);
 }
 
 bool EventDispatcher::SetCaptureWindow(ServerWindow* window,
diff --git a/services/ui/ws/event_dispatcher.h b/services/ui/ws/event_dispatcher.h
index 5d9c786..8b084d7 100644
--- a/services/ui/ws/event_dispatcher.h
+++ b/services/ui/ws/event_dispatcher.h
@@ -66,7 +66,7 @@
 
   // Returns the cursor for the current target, or POINTER if the mouse is not
   // over a valid target.
-  ui::mojom::CursorType GetCurrentMouseCursor() const;
+  ui::CursorData GetCurrentMouseCursor() const;
 
   // |capture_window_| will receive all input. See window_tree.mojom for
   // details.
diff --git a/services/ui/ws/platform_display.h b/services/ui/ws/platform_display.h
index 7f82a61..bdfb5b4 100644
--- a/services/ui/ws/platform_display.h
+++ b/services/ui/ws/platform_display.h
@@ -46,7 +46,7 @@
 
   virtual void ReleaseCapture() = 0;
 
-  virtual void SetCursorById(mojom::CursorType cursor) = 0;
+  virtual void SetCursor(const ui::CursorData& cursor) = 0;
 
   virtual void UpdateTextInputState(const ui::TextInputState& state) = 0;
   virtual void SetImeVisibility(bool visible) = 0;
diff --git a/services/ui/ws/platform_display_default.cc b/services/ui/ws/platform_display_default.cc
index 93611c8..b879e335 100644
--- a/services/ui/ws/platform_display_default.cc
+++ b/services/ui/ws/platform_display_default.cc
@@ -26,6 +26,7 @@
 #elif defined(OS_ANDROID)
 #include "ui/platform_window/android/platform_window_android.h"
 #elif defined(USE_OZONE)
+#include "ui/ozone/public/cursor_factory_ozone.h"
 #include "ui/ozone/public/ozone_platform.h"
 #endif
 
@@ -100,22 +101,43 @@
   platform_window_->ReleaseCapture();
 }
 
-void PlatformDisplayDefault::SetCursorById(mojom::CursorType cursor_id) {
+void PlatformDisplayDefault::SetCursor(const ui::CursorData& cursor_data) {
   if (!image_cursors_)
     return;
 
-  // TODO(erg): This still isn't sufficient, and will only use native cursors
-  // that chrome would use, not custom image cursors. For that, we should
-  // delegate to the window manager to load images from resource packs.
-  //
-  // We probably also need to deal with different DPIs.
-  ui::CursorType type;
-  if (mojo::EnumTraits<ui::mojom::CursorType, ui::CursorType>::FromMojom(
-          cursor_id, &type)) {
-    ui::Cursor cursor(type);
-    image_cursors_->SetPlatformCursor(&cursor);
-    platform_window_->SetCursor(cursor.platform());
+  ui::Cursor native_cursor(cursor_data.cursor_type());
+
+#if defined(USE_OZONE)
+  if (cursor_data.cursor_type() != ui::CursorType::kCustom) {
+    image_cursors_->SetPlatformCursor(&native_cursor);
+  } else {
+    // In Ozone builds, we have an interface available which turns bitmap data
+    // into platform cursors.
+    ui::CursorFactoryOzone* cursor_factory =
+        delegate_->GetOzonePlatform()->GetCursorFactoryOzone();
+    native_cursor.SetPlatformCursor(cursor_factory->CreateAnimatedCursor(
+        cursor_data.cursor_frames(), cursor_data.hotspot_in_pixels(),
+        cursor_data.frame_delay().InMilliseconds(),
+        cursor_data.scale_factor()));
   }
+#else
+  // Outside of ozone builds, there isn't a single interface for creating
+  // PlatformCursors. The closest thing to one is in //content/ instead of
+  // //ui/ which means we can't use it from here. For now, just don't handle
+  // custom image cursors.
+  //
+  // TODO(erg): Once blink speaks directly to mus, make blink perform its own
+  // cursor management on its own mus windows so we can remove Webcursor from
+  // //content/ and do this in way that's safe cross-platform, instead of as an
+  // ozone-specific hack.
+  if (cursor_data.cursor_type() == ui::CursorType::kCustom) {
+    NOTIMPLEMENTED() << "No custom cursor support on non-ozone yet.";
+    native_cursor = ui::Cursor(ui::CursorType::kPointer);
+  }
+  image_cursors_->SetPlatformCursor(&native_cursor);
+#endif
+
+  platform_window_->SetCursor(native_cursor.platform());
 }
 
 void PlatformDisplayDefault::UpdateTextInputState(
diff --git a/services/ui/ws/platform_display_default.h b/services/ui/ws/platform_display_default.h
index 3c7d590..5ca1ba6 100644
--- a/services/ui/ws/platform_display_default.h
+++ b/services/ui/ws/platform_display_default.h
@@ -44,7 +44,7 @@
   void SetTitle(const base::string16& title) override;
   void SetCapture() override;
   void ReleaseCapture() override;
-  void SetCursorById(mojom::CursorType cursor) override;
+  void SetCursor(const ui::CursorData& cursor) override;
   void UpdateTextInputState(const ui::TextInputState& state) override;
   void SetImeVisibility(bool visible) override;
   void UpdateViewportMetrics(const display::ViewportMetrics& metrics) override;
diff --git a/services/ui/ws/server_window.cc b/services/ui/ws/server_window.cc
index 2f59aac1..c3760afd 100644
--- a/services/ui/ws/server_window.cc
+++ b/services/ui/ws/server_window.cc
@@ -14,6 +14,7 @@
 #include "services/ui/ws/server_window_compositor_frame_sink_manager.h"
 #include "services/ui/ws/server_window_delegate.h"
 #include "services/ui/ws/server_window_observer.h"
+#include "ui/base/cursor/cursor.h"
 
 namespace ui {
 namespace ws {
@@ -34,8 +35,8 @@
       visible_(false),
       // Default to kPointer as kNull doesn't change the cursor, it leaves
       // the last non-null cursor.
-      cursor_id_(mojom::CursorType::kPointer),
-      non_client_cursor_id_(mojom::CursorType::kPointer),
+      cursor_(ui::CursorType::kPointer),
+      non_client_cursor_(ui::CursorType::kPointer),
       opacity_(1),
       can_focus_(true),
       properties_(properties),
@@ -299,20 +300,20 @@
     observer.OnWindowOpacityChanged(this, old_opacity, opacity_);
 }
 
-void ServerWindow::SetPredefinedCursor(ui::mojom::CursorType value) {
-  if (value == cursor_id_)
+void ServerWindow::SetCursor(ui::CursorData value) {
+  if (cursor_.IsSameAs(value))
     return;
-  cursor_id_ = value;
+  cursor_ = std::move(value);
   for (auto& observer : observers_)
-    observer.OnWindowPredefinedCursorChanged(this, value);
+    observer.OnWindowCursorChanged(this, cursor_);
 }
 
-void ServerWindow::SetNonClientCursor(ui::mojom::CursorType value) {
-  if (value == non_client_cursor_id_)
+void ServerWindow::SetNonClientCursor(ui::CursorData value) {
+  if (non_client_cursor_.IsSameAs(value))
     return;
-  non_client_cursor_id_ = value;
+  non_client_cursor_ = std::move(value);
   for (auto& observer : observers_)
-    observer.OnWindowNonClientCursorChanged(this, value);
+    observer.OnWindowNonClientCursorChanged(this, non_client_cursor_);
 }
 
 void ServerWindow::SetTransform(const gfx::Transform& transform) {
diff --git a/services/ui/ws/server_window.h b/services/ui/ws/server_window.h
index 3a7fa5a..cc8fce16 100644
--- a/services/ui/ws/server_window.h
+++ b/services/ui/ws/server_window.h
@@ -105,10 +105,8 @@
   bool can_accept_drops() const { return accepts_drops_; }
   void SetCanAcceptDrops(bool accepts_drags);
 
-  ui::mojom::CursorType cursor() const { return cursor_id_; }
-  ui::mojom::CursorType non_client_cursor() const {
-    return non_client_cursor_id_;
-  }
+  const ui::CursorData& cursor() const { return cursor_; }
+  const ui::CursorData& non_client_cursor() const { return non_client_cursor_; }
 
   const ServerWindow* parent() const { return parent_; }
   ServerWindow* parent() { return parent_; }
@@ -151,8 +149,8 @@
   float opacity() const { return opacity_; }
   void SetOpacity(float value);
 
-  void SetPredefinedCursor(ui::mojom::CursorType cursor_id);
-  void SetNonClientCursor(ui::mojom::CursorType cursor_id);
+  void SetCursor(ui::CursorData cursor);
+  void SetNonClientCursor(ui::CursorData cursor);
 
   const gfx::Transform& transform() const { return transform_; }
   void SetTransform(const gfx::Transform& transform);
@@ -252,8 +250,8 @@
   std::vector<gfx::Rect> additional_client_areas_;
   std::unique_ptr<ServerWindowCompositorFrameSinkManager>
       compositor_frame_sink_manager_;
-  mojom::CursorType cursor_id_;
-  mojom::CursorType non_client_cursor_id_;
+  ui::CursorData cursor_;
+  ui::CursorData non_client_cursor_;
   float opacity_;
   bool can_focus_;
   mojom::EventTargetingPolicy event_targeting_policy_ =
diff --git a/services/ui/ws/server_window_observer.h b/services/ui/ws/server_window_observer.h
index 265c408..400c087 100644
--- a/services/ui/ws/server_window_observer.h
+++ b/services/ui/ws/server_window_observer.h
@@ -66,10 +66,11 @@
                                       float old_opacity,
                                       float new_opacity) {}
 
-  virtual void OnWindowPredefinedCursorChanged(ServerWindow* window,
-                                               mojom::CursorType cursor_id) {}
-  virtual void OnWindowNonClientCursorChanged(ServerWindow* window,
-                                              mojom::CursorType cursor_id) {}
+  virtual void OnWindowCursorChanged(ServerWindow* window,
+                                     const ui::CursorData& cursor_data) {}
+  virtual void OnWindowNonClientCursorChanged(
+      ServerWindow* window,
+      const ui::CursorData& cursor_data) {}
 
   virtual void OnWindowTextInputStateChanged(ServerWindow* window,
                                              const ui::TextInputState& state) {}
diff --git a/services/ui/ws/test_change_tracker.cc b/services/ui/ws/test_change_tracker.cc
index b27d565..104cc614 100644
--- a/services/ui/ws/test_change_tracker.cc
+++ b/services/ui/ws/test_change_tracker.cc
@@ -10,6 +10,7 @@
 #include "base/strings/stringprintf.h"
 #include "mojo/public/cpp/bindings/map.h"
 #include "services/ui/common/util.h"
+#include "ui/base/cursor/cursor.h"
 
 namespace ui {
 
@@ -131,9 +132,9 @@
                                 WindowIdToString(change.window_id).c_str());
 
     case CHANGE_TYPE_CURSOR_CHANGED:
-      return base::StringPrintf("CursorChanged id=%s cursor_id=%d",
+      return base::StringPrintf("CursorChanged id=%s cursor_type=%d",
                                 WindowIdToString(change.window_id).c_str(),
-                                change.cursor_id);
+                                static_cast<int>(change.cursor_type));
     case CHANGE_TYPE_ON_CHANGE_COMPLETED:
       return base::StringPrintf("ChangeCompleted id=%d sucess=%s",
                                 change.change_id,
@@ -230,7 +231,7 @@
       direction(mojom::OrderDirection::ABOVE),
       bool_value(false),
       float_value(0.f),
-      cursor_id(0),
+      cursor_type(ui::CursorType::kNull),
       change_id(0u) {}
 
 Change::Change(const Change& other) = default;
@@ -420,13 +421,12 @@
   AddChange(change);
 }
 
-void TestChangeTracker::OnWindowPredefinedCursorChanged(
-    Id window_id,
-    mojom::CursorType cursor_id) {
+void TestChangeTracker::OnWindowCursorChanged(Id window_id,
+                                              const ui::CursorData& cursor) {
   Change change;
   change.type = CHANGE_TYPE_CURSOR_CHANGED;
   change.window_id = window_id;
-  change.cursor_id = static_cast<int32_t>(cursor_id);
+  change.cursor_type = cursor.cursor_type();
   AddChange(change);
 }
 
diff --git a/services/ui/ws/test_change_tracker.h b/services/ui/ws/test_change_tracker.h
index 98f259f..c4162ad 100644
--- a/services/ui/ws/test_change_tracker.h
+++ b/services/ui/ws/test_change_tracker.h
@@ -89,7 +89,7 @@
   float float_value;
   std::string property_key;
   std::string property_value;
-  int32_t cursor_id;
+  ui::CursorType cursor_type;
   uint32_t change_id;
   cc::SurfaceId surface_id;
   gfx::Size frame_size;
@@ -179,8 +179,7 @@
       const std::string& name,
       const base::Optional<std::vector<uint8_t>>& data);
   void OnWindowFocused(Id window_id);
-  void OnWindowPredefinedCursorChanged(Id window_id,
-                                       mojom::CursorType cursor_id);
+  void OnWindowCursorChanged(Id window_id, const ui::CursorData& cursor);
   void OnChangeCompleted(uint32_t change_id, bool success);
   void OnTopLevelCreated(uint32_t change_id,
                          mojom::WindowDataPtr window_data,
diff --git a/services/ui/ws/test_utils.cc b/services/ui/ws/test_utils.cc
index fe0428c..e926d366 100644
--- a/services/ui/ws/test_utils.cc
+++ b/services/ui/ws/test_utils.cc
@@ -16,6 +16,7 @@
 #include "services/ui/ws/window_manager_access_policy.h"
 #include "services/ui/ws/window_manager_window_tree_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/gfx/geometry/dip_util.h"
 
 namespace ui {
@@ -28,7 +29,7 @@
 class TestPlatformDisplay : public PlatformDisplay {
  public:
   explicit TestPlatformDisplay(const display::ViewportMetrics& metrics,
-                               mojom::CursorType* cursor_storage)
+                               ui::CursorData* cursor_storage)
       : metrics_(metrics), cursor_storage_(cursor_storage) {}
   ~TestPlatformDisplay() override {}
 
@@ -40,7 +41,7 @@
   void SetTitle(const base::string16& title) override {}
   void SetCapture() override {}
   void ReleaseCapture() override {}
-  void SetCursorById(mojom::CursorType cursor) override {
+  void SetCursor(const ui::CursorData& cursor) override {
     *cursor_storage_ = cursor;
   }
   void UpdateTextInputState(const ui::TextInputState& state) override {}
@@ -56,7 +57,7 @@
 
  private:
   display::ViewportMetrics metrics_;
-  mojom::CursorType* cursor_storage_;
+  ui::CursorData* cursor_storage_;
 
   DISALLOW_COPY_AND_ASSIGN(TestPlatformDisplay);
 };
@@ -149,7 +150,7 @@
 // TestPlatformDisplayFactory  -------------------------------------------------
 
 TestPlatformDisplayFactory::TestPlatformDisplayFactory(
-    mojom::CursorType* cursor_storage)
+    ui::CursorData* cursor_storage)
     : cursor_storage_(cursor_storage) {}
 
 TestPlatformDisplayFactory::~TestPlatformDisplayFactory() {}
@@ -423,10 +424,9 @@
   tracker_.OnWindowFocused(focused_window_id);
 }
 
-void TestWindowTreeClient::OnWindowPredefinedCursorChanged(
-    uint32_t window_id,
-    mojom::CursorType cursor_id) {
-  tracker_.OnWindowPredefinedCursorChanged(window_id, cursor_id);
+void TestWindowTreeClient::OnWindowCursorChanged(uint32_t window_id,
+                                                 ui::CursorData cursor) {
+  tracker_.OnWindowCursorChanged(window_id, cursor);
 }
 
 void TestWindowTreeClient::OnWindowSurfaceChanged(
@@ -530,8 +530,7 @@
 // WindowServerTestHelper  ---------------------------------------------------
 
 WindowServerTestHelper::WindowServerTestHelper()
-    : cursor_id_(mojom::CursorType::kNull),
-      platform_display_factory_(&cursor_id_) {
+    : cursor_(ui::CursorType::kNull), platform_display_factory_(&cursor_) {
   // Some tests create their own message loop, for example to add a task runner.
   if (!base::MessageLoop::current())
     message_loop_ = base::MakeUnique<base::MessageLoop>();
diff --git a/services/ui/ws/test_utils.h b/services/ui/ws/test_utils.h
index f5e0bb08..5be2f294 100644
--- a/services/ui/ws/test_utils.h
+++ b/services/ui/ws/test_utils.h
@@ -151,7 +151,7 @@
 
   void OnEvent(ui::Event* event) { display_->OnEventFromSource(event); }
 
-  mojom::CursorType last_cursor() const { return display_->last_cursor_; }
+  const ui::CursorData& last_cursor() const { return display_->last_cursor_; }
 
  private:
   Display* display_;
@@ -300,7 +300,7 @@
 // Factory that dispenses TestPlatformDisplays.
 class TestPlatformDisplayFactory : public PlatformDisplayFactory {
  public:
-  explicit TestPlatformDisplayFactory(mojom::CursorType* cursor_storage);
+  explicit TestPlatformDisplayFactory(ui::CursorData* cursor_storage);
   ~TestPlatformDisplayFactory();
 
   // PlatformDisplayFactory:
@@ -309,7 +309,7 @@
       const display::ViewportMetrics& metrics) override;
 
  private:
-  mojom::CursorType* cursor_storage_;
+  ui::CursorData* cursor_storage_;
 
   DISALLOW_COPY_AND_ASSIGN(TestPlatformDisplayFactory);
 };
@@ -490,8 +490,8 @@
                               uint32_t window_id,
                               int64_t display_id) override;
   void OnWindowFocused(uint32_t focused_window_id) override;
-  void OnWindowPredefinedCursorChanged(uint32_t window_id,
-                                       mojom::CursorType cursor_id) override;
+  void OnWindowCursorChanged(uint32_t window_id,
+                             ui::CursorData cursor) override;
   void OnWindowSurfaceChanged(Id window_id,
                               const cc::SurfaceInfo& surface_info) override;
   void OnDragDropStart(
@@ -622,14 +622,14 @@
   ~WindowServerTestHelper();
 
   WindowServer* window_server() { return window_server_.get(); }
-  mojom::CursorType cursor() const { return cursor_id_; }
+  ui::CursorData cursor() const { return cursor_; }
 
   TestWindowServerDelegate* window_server_delegate() {
     return &window_server_delegate_;
   }
 
  private:
-  mojom::CursorType cursor_id_;
+  ui::CursorData cursor_;
   TestPlatformDisplayFactory platform_display_factory_;
   TestWindowServerDelegate window_server_delegate_;
   std::unique_ptr<WindowServer> window_server_;
@@ -665,7 +665,9 @@
   // Sets the task runner for |message_loop_|
   void SetTaskRunner(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
 
-  mojom::CursorType cursor() const { return ws_test_helper_.cursor(); }
+  ui::CursorType cursor_type() const {
+    return ws_test_helper_.cursor().cursor_type();
+  }
   Display* display() { return display_; }
   TestWindowTreeBinding* last_binding() {
     return ws_test_helper_.window_server_delegate()->last_binding();
diff --git a/services/ui/ws/window_manager_state.cc b/services/ui/ws/window_manager_state.cc
index 35ff9b97..295eb4d 100644
--- a/services/ui/ws/window_manager_state.cc
+++ b/services/ui/ws/window_manager_state.cc
@@ -588,10 +588,9 @@
 }
 
 void WindowManagerState::UpdateNativeCursorFromDispatcher() {
-  const ui::mojom::CursorType cursor_id =
-      event_dispatcher_.GetCurrentMouseCursor();
+  const ui::CursorData cursor = event_dispatcher_.GetCurrentMouseCursor();
   for (Display* display : display_manager()->displays())
-    display->UpdateNativeCursor(cursor_id);
+    display->UpdateNativeCursor(cursor);
 }
 
 void WindowManagerState::OnCaptureChanged(ServerWindow* new_capture,
diff --git a/services/ui/ws/window_manager_state_unittest.cc b/services/ui/ws/window_manager_state_unittest.cc
index ce0c8ce3c..2fee93d3 100644
--- a/services/ui/ws/window_manager_state_unittest.cc
+++ b/services/ui/ws/window_manager_state_unittest.cc
@@ -26,6 +26,7 @@
 #include "services/ui/ws/window_server.h"
 #include "services/ui/ws/window_tree.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/events/event.h"
 
 namespace ui {
@@ -649,8 +650,9 @@
   window_tree()->AddWindow(FirstRootId(window_tree()), child_window_id);
   child_window->SetVisible(true);
   child_window->SetBounds(gfx::Rect(0, 0, 20, 20));
-  child_window->parent()->SetPredefinedCursor(ui::mojom::CursorType::kCopy);
-  EXPECT_EQ(ui::mojom::CursorType::kCopy, display_test_api.last_cursor());
+  child_window->parent()->SetCursor(ui::CursorData(ui::CursorType::kCopy));
+  EXPECT_EQ(ui::CursorType::kCopy,
+            display_test_api.last_cursor().cursor_type());
   // Move the mouse outside the bounds of the child, so that the mouse is not
   // over any valid windows. Cursor should change to POINTER.
   ui::PointerEvent move(
@@ -660,7 +662,8 @@
   window_manager_state()->ProcessEvent(move, 0);
   // The event isn't over a valid target, which should trigger resetting the
   // cursor to POINTER.
-  EXPECT_EQ(ui::mojom::CursorType::kPointer, display_test_api.last_cursor());
+  EXPECT_EQ(ui::CursorType::kPointer,
+            display_test_api.last_cursor().cursor_type());
 }
 
 }  // namespace test
diff --git a/services/ui/ws/window_server.cc b/services/ui/ws/window_server.cc
index d012b886..f48149ee5 100644
--- a/services/ui/ws/window_server.cc
+++ b/services/ui/ws/window_server.cc
@@ -417,11 +417,10 @@
     pair.second->ProcessWindowDeleted(window, IsOperationSource(pair.first));
 }
 
-void WindowServer::ProcessWillChangeWindowPredefinedCursor(
-    ServerWindow* window,
-    mojom::CursorType cursor_id) {
+void WindowServer::ProcessWillChangeWindowCursor(ServerWindow* window,
+                                                 const ui::CursorData& cursor) {
   for (auto& pair : tree_map_) {
-    pair.second->ProcessCursorChanged(window, cursor_id,
+    pair.second->ProcessCursorChanged(window, cursor,
                                       IsOperationSource(pair.first));
   }
 }
@@ -750,19 +749,19 @@
         window);
 }
 
-void WindowServer::OnWindowPredefinedCursorChanged(
-    ServerWindow* window,
-    mojom::CursorType cursor_id) {
+void WindowServer::OnWindowCursorChanged(ServerWindow* window,
+                                         const ui::CursorData& cursor) {
   if (in_destructor_)
     return;
 
-  ProcessWillChangeWindowPredefinedCursor(window, cursor_id);
+  ProcessWillChangeWindowCursor(window, cursor);
 
   UpdateNativeCursorIfOver(window);
 }
 
-void WindowServer::OnWindowNonClientCursorChanged(ServerWindow* window,
-                                                  mojom::CursorType cursor_id) {
+void WindowServer::OnWindowNonClientCursorChanged(
+    ServerWindow* window,
+    const ui::CursorData& cursor) {
   if (in_destructor_)
     return;
 
diff --git a/services/ui/ws/window_server.h b/services/ui/ws/window_server.h
index d01c2e0..f79cf96 100644
--- a/services/ui/ws/window_server.h
+++ b/services/ui/ws/window_server.h
@@ -188,8 +188,8 @@
                             const ServerWindow* relative_window,
                             const mojom::OrderDirection direction);
   void ProcessWindowDeleted(ServerWindow* window);
-  void ProcessWillChangeWindowPredefinedCursor(ServerWindow* window,
-                                               mojom::CursorType cursor_id);
+  void ProcessWillChangeWindowCursor(ServerWindow* window,
+                                     const ui::CursorData& cursor);
 
   // Sends an |event| to all WindowTrees belonging to |user_id| that might be
   // observing events. Skips |ignore_tree| if it is non-null. |target_window| is
@@ -322,10 +322,10 @@
       ServerWindow* window,
       const std::string& name,
       const std::vector<uint8_t>* new_data) override;
-  void OnWindowPredefinedCursorChanged(ServerWindow* window,
-                                       mojom::CursorType cursor_id) override;
+  void OnWindowCursorChanged(ServerWindow* window,
+                             const ui::CursorData& cursor) override;
   void OnWindowNonClientCursorChanged(ServerWindow* window,
-                                      mojom::CursorType cursor_id) override;
+                                      const ui::CursorData& cursor) override;
   void OnWindowTextInputStateChanged(ServerWindow* window,
                                      const ui::TextInputState& state) override;
   void OnTransientWindowAdded(ServerWindow* window,
diff --git a/services/ui/ws/window_tree.cc b/services/ui/ws/window_tree.cc
index 76cca865..086fd81 100644
--- a/services/ui/ws/window_tree.cc
+++ b/services/ui/ws/window_tree.cc
@@ -30,6 +30,7 @@
 #include "services/ui/ws/window_manager_state.h"
 #include "services/ui/ws/window_server.h"
 #include "services/ui/ws/window_tree_binding.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/display/display.h"
 #include "ui/display/display_list.h"
 #include "ui/display/screen_base.h"
@@ -898,7 +899,7 @@
 }
 
 void WindowTree::ProcessCursorChanged(const ServerWindow* window,
-                                      mojom::CursorType cursor_id,
+                                      const ui::CursorData& cursor,
                                       bool originated_change) {
   if (originated_change)
     return;
@@ -906,7 +907,7 @@
   if (!IsWindowKnown(window, &client_window_id))
     return;
 
-  client()->OnWindowPredefinedCursorChanged(client_window_id.id, cursor_id);
+  client()->OnWindowCursorChanged(client_window_id.id, cursor);
 }
 
 void WindowTree::ProcessFocusChanged(const ServerWindow* old_focused_window,
@@ -1773,19 +1774,45 @@
     window->set_event_targeting_policy(policy);
 }
 
-void WindowTree::SetPredefinedCursor(uint32_t change_id,
-                                     Id transport_window_id,
-                                     ui::mojom::CursorType cursor_id) {
+void WindowTree::SetCursor(uint32_t change_id,
+                           Id transport_window_id,
+                           ui::CursorData cursor) {
   ServerWindow* window =
       GetWindowByClientId(ClientWindowId(transport_window_id));
+  if (!window) {
+    DVLOG(1) << "SetCursor failed (invalid id)";
+    client()->OnChangeCompleted(change_id, false);
+    return;
+  }
 
   // Only the owner of the window can change the bounds.
-  bool success = window && access_policy_->CanSetCursorProperties(window);
-  if (success) {
-    Operation op(this, window_server_,
-                 OperationType::SET_WINDOW_PREDEFINED_CURSOR);
-    window->SetPredefinedCursor(cursor_id);
+  bool success = access_policy_->CanSetCursorProperties(window);
+  if (!success) {
+    DVLOG(1) << "SetCursor failed (access denied)";
+    client()->OnChangeCompleted(change_id, false);
+    return;
   }
+
+  // If the cursor is custom, it must have valid frames.
+  if (cursor.cursor_type() == ui::CursorType::kCustom) {
+    if (cursor.cursor_frames().empty()) {
+      DVLOG(1) << "SetCursor failed (no frames with custom cursor)";
+      client()->OnChangeCompleted(change_id, false);
+      return;
+    }
+
+    for (const SkBitmap& bitmap : cursor.cursor_frames()) {
+      if (bitmap.drawsNothing()) {
+        DVLOG(1) << "SetCursor failed (cursor frame draws nothing)";
+        client()->OnChangeCompleted(change_id, false);
+        return;
+      }
+    }
+  }
+
+  Operation op(this, window_server_,
+               OperationType::SET_WINDOW_PREDEFINED_CURSOR);
+  window->SetCursor(std::move(cursor));
   client()->OnChangeCompleted(change_id, success);
 }
 
@@ -2232,11 +2259,11 @@
 }
 
 void WindowTree::WmSetNonClientCursor(uint32_t window_id,
-                                      mojom::CursorType cursor_id) {
+                                      ui::CursorData cursor) {
   DCHECK(window_manager_state_);
   ServerWindow* window = GetWindowByClientId(ClientWindowId(window_id));
   if (window) {
-    window->SetNonClientCursor(cursor_id);
+    window->SetNonClientCursor(std::move(cursor));
   } else {
     DVLOG(1) << "trying to update non-client cursor of invalid window";
   }
diff --git a/services/ui/ws/window_tree.h b/services/ui/ws/window_tree.h
index 3a2a779..0721fff 100644
--- a/services/ui/ws/window_tree.h
+++ b/services/ui/ws/window_tree.h
@@ -261,7 +261,7 @@
                                    float new_opacity,
                                    bool originated_change);
   void ProcessCursorChanged(const ServerWindow* window,
-                            mojom::CursorType cursor_id,
+                            const ui::CursorData& cursor,
                             bool originated_change);
   void ProcessFocusChanged(const ServerWindow* old_focused_window,
                            const ServerWindow* new_focused_window);
@@ -462,9 +462,9 @@
   void SetCanFocus(Id transport_window_id, bool can_focus) override;
   void SetEventTargetingPolicy(Id transport_window_id,
                                mojom::EventTargetingPolicy policy) override;
-  void SetPredefinedCursor(uint32_t change_id,
-                           Id transport_window_id,
-                           ui::mojom::CursorType cursor_id) override;
+  void SetCursor(uint32_t change_id,
+                 Id transport_window_id,
+                 ui::CursorData cursor) override;
   void SetWindowTextInputState(Id transport_window_id,
                                mojo::TextInputStatePtr state) override;
   void SetImeVisibility(Id transport_window_id,
@@ -521,8 +521,7 @@
   void WmRequestClose(Id transport_window_id) override;
   void WmSetFrameDecorationValues(
       mojom::FrameDecorationValuesPtr values) override;
-  void WmSetNonClientCursor(uint32_t window_id,
-                            mojom::CursorType cursor_id) override;
+  void WmSetNonClientCursor(uint32_t window_id, ui::CursorData cursor) override;
   void OnWmCreatedTopLevelWindow(uint32_t change_id,
                                  Id transport_window_id) override;
   void OnAcceleratorAck(
diff --git a/services/ui/ws/window_tree_client_unittest.cc b/services/ui/ws/window_tree_client_unittest.cc
index 602b3b9..d18cb29 100644
--- a/services/ui/ws/window_tree_client_unittest.cc
+++ b/services/ui/ws/window_tree_client_unittest.cc
@@ -23,6 +23,7 @@
 #include "services/ui/ws/ids.h"
 #include "services/ui/ws/test_change_tracker.h"
 #include "services/ui/ws/window_server_service_test_base.h"
+#include "ui/base/cursor/cursor.h"
 
 using mojo::InterfaceRequest;
 using service_manager::Service;
@@ -236,9 +237,9 @@
     return WaitForChangeCompleted(change_id);
   }
 
-  bool SetPredefinedCursor(Id window_id, mojom::CursorType cursor) {
+  bool SetCursor(Id window_id, const ui::CursorData& cursor) {
     const uint32_t change_id = GetAndAdvanceChangeId();
-    tree()->SetPredefinedCursor(change_id, window_id, cursor);
+    tree()->SetCursor(change_id, window_id, cursor);
     return WaitForChangeCompleted(change_id);
   }
 
@@ -385,9 +386,9 @@
   }
   // TODO(sky): add testing coverage.
   void OnWindowFocused(uint32_t focused_window_id) override {}
-  void OnWindowPredefinedCursorChanged(uint32_t window_id,
-                                       mojom::CursorType cursor_id) override {
-    tracker_.OnWindowPredefinedCursorChanged(window_id, cursor_id);
+  void OnWindowCursorChanged(uint32_t window_id,
+                             ui::CursorData cursor) override {
+    tracker_.OnWindowCursorChanged(window_id, cursor);
   }
 
   void OnDragDropStart(
@@ -1638,11 +1639,11 @@
   Id window_1_1 = BuildWindowId(client_id_1(), 1);
   changes2()->clear();
 
-  ASSERT_TRUE(
-      wt_client1()->SetPredefinedCursor(window_1_1, mojom::CursorType::kIBeam));
+  ASSERT_TRUE(wt_client1()->SetCursor(window_1_1,
+                                      ui::CursorData(ui::CursorType::kIBeam)));
   wt_client2_->WaitForChangeCount(1u);
 
-  EXPECT_EQ("CursorChanged id=" + IdToString(window_1_1) + " cursor_id=4",
+  EXPECT_EQ("CursorChanged id=" + IdToString(window_1_1) + " cursor_type=4",
             SingleChangeToDescription(*changes2()));
 }
 
diff --git a/services/ui/ws/window_tree_unittest.cc b/services/ui/ws/window_tree_unittest.cc
index 1d64d28..15a844a 100644
--- a/services/ui/ws/window_tree_unittest.cc
+++ b/services/ui/ws/window_tree_unittest.cc
@@ -31,6 +31,7 @@
 #include "services/ui/ws/window_server_delegate.h"
 #include "services/ui/ws/window_tree_binding.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/events/event.h"
 #include "ui/events/event_utils.h"
 #include "ui/gfx/geometry/rect.h"
@@ -129,8 +130,8 @@
   WindowTreeTest() {}
   ~WindowTreeTest() override {}
 
-  ui::mojom::CursorType cursor_id() {
-    return window_event_targeting_helper_.cursor();
+  ui::CursorType cursor_type() {
+    return window_event_targeting_helper_.cursor_type();
   }
   Display* display() { return window_event_targeting_helper_.display(); }
   TestWindowTreeClient* last_window_tree_client() {
@@ -475,11 +476,11 @@
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(21, 22));
 
   // Set the cursor on the parent as that is where the cursor is picked up from.
-  window->parent()->SetPredefinedCursor(mojom::CursorType::kIBeam);
+  window->parent()->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
 
   // Because the cursor is over the window when SetCursor was called, we should
   // have immediately changed the cursor.
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 }
 
 TEST_F(WindowTreeTest, CursorChangesWhenEnteringWindowWithDifferentCursor) {
@@ -492,11 +493,11 @@
   // inside.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5));
   // Set the cursor on the parent as that is where the cursor is picked up from.
-  window->parent()->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kPointer, cursor_id());
+  window->parent()->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kPointer, cursor_type());
 
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(21, 22));
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 }
 
 TEST_F(WindowTreeTest, TouchesDontChangeCursor) {
@@ -508,12 +509,12 @@
   // Let's create a pointer event outside the window and then move the pointer
   // inside.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5));
-  window->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kPointer, cursor_id());
+  window->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kPointer, cursor_type());
 
   // With a touch event, we shouldn't update the cursor.
   DispatchEventAndAckImmediately(CreatePointerDownEvent(21, 22));
-  EXPECT_EQ(mojom::CursorType::kPointer, cursor_id());
+  EXPECT_EQ(ui::CursorType::kPointer, cursor_type());
 }
 
 TEST_F(WindowTreeTest, DragOutsideWindow) {
@@ -526,25 +527,25 @@
   // change the cursor.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5));
   // Set the cursor on the parent as that is where the cursor is picked up from.
-  window->parent()->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kPointer, cursor_id());
+  window->parent()->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kPointer, cursor_type());
 
   // Move the pointer to the inside of the window
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(21, 22));
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 
   // Start the drag.
   DispatchEventAndAckImmediately(CreateMouseDownEvent(21, 22));
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 
   // Move the cursor (mouse is still down) outside the window.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5));
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 
   // Release the cursor. We should now adapt the cursor of the window
   // underneath the pointer.
   DispatchEventAndAckImmediately(CreateMouseUpEvent(5, 5));
-  EXPECT_EQ(mojom::CursorType::kPointer, cursor_id());
+  EXPECT_EQ(ui::CursorType::kPointer, cursor_type());
 }
 
 TEST_F(WindowTreeTest, ChangingWindowBoundsChangesCursor) {
@@ -556,17 +557,17 @@
   // Put the cursor just outside the bounds of the window.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(41, 41));
   // Sets the cursor on the root as that is where the cursor is picked up from.
-  window->parent()->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  EXPECT_EQ(mojom::CursorType::kPointer, cursor_id());
+  window->parent()->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  EXPECT_EQ(ui::CursorType::kPointer, cursor_type());
 
   // Expand the bounds of the window so they now include where the cursor now
   // is.
   window->SetBounds(gfx::Rect(20, 20, 25, 25));
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 
   // Contract the bounds again.
   window->SetBounds(gfx::Rect(20, 20, 20, 20));
-  EXPECT_EQ(mojom::CursorType::kPointer, cursor_id());
+  EXPECT_EQ(ui::CursorType::kPointer, cursor_type());
 }
 
 TEST_F(WindowTreeTest, WindowReorderingChangesCursor) {
@@ -583,14 +584,14 @@
       mojom::EventTargetingPolicy::TARGET_AND_DESCENDANTS);
   embed_window2->set_event_targeting_policy(
       mojom::EventTargetingPolicy::TARGET_AND_DESCENDANTS);
-  embed_window1->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  embed_window2->SetPredefinedCursor(mojom::CursorType::kCross);
+  embed_window1->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  embed_window2->SetCursor(ui::CursorData(ui::CursorType::kCross));
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5));
   // Cursor should match that of top-most window, which is |embed_window2|.
-  EXPECT_EQ(mojom::CursorType::kCross, cursor_id());
+  EXPECT_EQ(ui::CursorType::kCross, cursor_type());
   // Move |embed_window1| on top, cursor should now match it.
   embed_window1->parent()->StackChildAtTop(embed_window1);
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 }
 
 // Assertions around moving cursor between trees with roots.
@@ -608,9 +609,9 @@
       mojom::EventTargetingPolicy::TARGET_AND_DESCENDANTS);
   embed_window2->parent()->set_event_targeting_policy(
       mojom::EventTargetingPolicy::TARGET_AND_DESCENDANTS);
-  embed_window1->SetPredefinedCursor(mojom::CursorType::kIBeam);
-  embed_window2->SetPredefinedCursor(mojom::CursorType::kCross);
-  embed_window1->parent()->SetPredefinedCursor(mojom::CursorType::kCopy);
+  embed_window1->SetCursor(ui::CursorData(ui::CursorType::kIBeam));
+  embed_window2->SetCursor(ui::CursorData(ui::CursorType::kCross));
+  embed_window1->parent()->SetCursor(ui::CursorData(ui::CursorType::kCopy));
 
   // Create a child of |embed_window1|.
   ServerWindow* embed_window1_child = NewWindowInTreeWithParent(
@@ -621,15 +622,15 @@
 
   // Move mouse into |embed_window1|.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5));
-  EXPECT_EQ(mojom::CursorType::kIBeam, cursor_id());
+  EXPECT_EQ(ui::CursorType::kIBeam, cursor_type());
 
   // Move mouse into |embed_window2|.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(25, 25));
-  EXPECT_EQ(mojom::CursorType::kCross, cursor_id());
+  EXPECT_EQ(ui::CursorType::kCross, cursor_type());
 
   // Move mouse into area between, which should use cursor set on parent.
   DispatchEventAndAckImmediately(CreateMouseMoveEvent(15, 15));
-  EXPECT_EQ(mojom::CursorType::kCopy, cursor_id());
+  EXPECT_EQ(ui::CursorType::kCopy, cursor_type());
 }
 
 TEST_F(WindowTreeTest, EventAck) {
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 8fd5e78..b9dd2c71 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -273,20 +273,11 @@
 # LayoutNG - is a new layout system for Blink.
 
 ### external/wpt/css/CSS2/floats-clear
-#### Passed: 50 26%
-#### Skipped: 141
+#### Passed: 52 29%
+#### Skipped: 139
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/adjacent-floats-001.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-007.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-008.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-009.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-non-replaced-height-001.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-004.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-007.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-003.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-123.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-002.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-005.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-applies-to-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-applies-to-002.xht [ Skip ]
@@ -300,12 +291,14 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-applies-to-012.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-applies-to-013.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-applies-to-015.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-004.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-005.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-006.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-007.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-008.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/clear-float-009.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-006.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-applies-to-001.xht [ Skip ]
@@ -324,6 +317,7 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-applies-to-013.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-applies-to-014.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-applies-to-015.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-non-replaced-height-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-non-replaced-width-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-non-replaced-width-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-non-replaced-width-003.xht [ Skip ]
@@ -336,7 +330,9 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-003.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-004.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-006.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-height-007.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-width-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-width-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/float-replaced-width-003.xht [ Skip ]
@@ -350,6 +346,7 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floating-replaced-height-008.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-002.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-004.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-005.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-006.xht [ Skip ]
@@ -376,6 +373,7 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-113.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-117.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-120.xht [ Skip ]
+crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-123.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-126.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-127.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/floats-128.xht [ Skip ]
@@ -418,8 +416,8 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/margin-collapse-clear-015.xht [ Skip ]
 
 ### external/wpt/css/CSS2/floats
-#### Passed: 17 40%
-#### Skipped: 25
+#### Passed: 19 48%
+#### Skipped: 23
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-placement-vertical-001a.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-placement-vertical-001b.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-placement-vertical-001c.xht [ Skip ]
@@ -435,8 +433,6 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-007.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-bfc-001l.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-bfc-001r.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-bfc-002l.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-bfc-002r.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-bfc-003l.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-bfc-003r.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-001l.xht [ Skip ]
@@ -447,8 +443,8 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-003r.xht [ Skip ]
 
 ### external/wpt/css/CSS2/positioning
-#### Passed: 263 50%
-#### Skipped: 260
+#### Passed: 278 53%
+#### Skipped: 245
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/absolute-non-replaced-height-004.xht [ Skip ]
@@ -561,14 +557,10 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/bottom-applies-to-013.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/bottom-applies-to-014.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/bottom-applies-to-015.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/bottom-offset-001.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/bottom-offset-002.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/bottom-offset-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/bottom-offset-percentage-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/left-applies-to-009.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/left-applies-to-012.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/left-offset-001.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/left-offset-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/left-offset-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/left-offset-percentage-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-004.xht [ Skip ]
@@ -592,15 +584,9 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-fixed-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-fixed-004.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-fixed-005.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-fixed-007.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-003.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-004.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-014.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-015.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-016.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-017.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-019.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-020.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-021.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/position-relative-022.xht [ Skip ]
@@ -682,8 +668,6 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/right-applies-to-013.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/right-applies-to-014.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/right-applies-to-015.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/right-offset-001.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/right-offset-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/right-offset-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/right-offset-percentage-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-007.xht [ Skip ]
@@ -705,9 +689,6 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-103.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-104.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-113.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-offset-001.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-offset-002.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-offset-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/positioning/top-offset-percentage-001.xht [ Skip ]
 
 #### external/wpt/css/CSS2/abspos
@@ -718,8 +699,8 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/abspos/abspos-containing-block-initial-009a.xht [ Skip ]
 
 #### external/wpt/css/CSS2/normal-flow
-#### Passed: 430 61%
-#### Skipped: 278
+#### Passed: 492 70%
+#### Skipped: 216
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-formatting-context-height-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-formatting-context-height-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-formatting-contexts-008.xht [ Skip ]
@@ -797,32 +778,24 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-004.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-005.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-non-replaced-height-002.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-height-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-height-006.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-height-008.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-height-009.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-width-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-width-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-width-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-width-006.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-width-007.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-replaced-width-008.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-valign-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-block-zorder-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-non-replaced-height-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-non-replaced-height-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-non-replaced-width-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-non-replaced-width-002.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-height-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-height-006.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-height-008.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-height-009.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-003.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-006.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-008.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-009.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-011.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-012.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/inline-replaced-width-013.xht [ Skip ]
@@ -877,26 +850,8 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-height-applies-to-012.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-height-percentage-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-height-percentage-003.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-006.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-007.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-017.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-018.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-028.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-029.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-039.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-040.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-050.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-051.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-061.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-062.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-072.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-073.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-083.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-084.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-094.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-095.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-106.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-110.xht [ Skip ]
+crbug.com/635619  virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-110.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-applies-to-005.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-applies-to-006.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/max-width-applies-to-008.xht [ Skip ]
@@ -950,24 +905,6 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-height-104.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-height-106.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-height-applies-to-012.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-006.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-007.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-017.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-018.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-028.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-029.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-039.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-040.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-050.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-051.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-061.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-062.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-072.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-073.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-083.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-084.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-094.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-095.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-applies-to-005.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-applies-to-006.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/min-width-applies-to-008.xht [ Skip ]
@@ -977,24 +914,6 @@
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/replaced-intrinsic-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/replaced-intrinsic-002.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/root-box-001.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-006.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-007.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-017.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-018.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-028.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-029.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-039.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-040.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-050.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-051.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-061.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-062.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-072.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-073.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-083.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-084.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-094.xht [ Skip ]
-crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-095.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-applies-to-012.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-inherit-001.xht [ Skip ]
 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-percentage-001.xht [ Skip ]
@@ -2415,9 +2334,18 @@
 # This test fails with the stable release mode.
 crbug.com/694958 virtual/stable/http/tests/navigation/same-and-different-back.html [ Skip ]
 
-# These tests have console error messages whose order is not deterministic.
-crbug.com/679742 external/wpt/html/semantics/scripting-1/the-script-element/module/crossorigin.html [ Pass Failure ]
-crbug.com/679742 external/wpt/html/semantics/scripting-1/the-script-element/module/errorhandling.html [ Pass Failure ]
+# Failing because of module-related implementation/test issues and
+# lack of inline module script support.
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/crossorigin.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/errorhandling.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/imports.html [ Failure Crash ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/execorder.html [ Failure Timeout ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html [ Failure Crash ]
+
+# Inline module scripts are not yet supported.
+crbug.com/715369 external/wpt/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script.html [ Failure ]
+crbug.com/715369 fast/dom/HTMLScriptElement/module-script.html [ Failure ]
+crbug.com/715369 virtual/sharedarraybuffer/fast/dom/HTMLScriptElement/module-script.html [ Failure ]
 
 # This test has a failure console message with specific performance
 # numbers so a consistent baseline cannot be added. This test could be
@@ -2709,7 +2637,6 @@
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_with_class_object_specific_selector.html [ Failure ]
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/inherit_as_default_value_inherits_values_from_media_element.html [ Failure ]
 crbug.com/626703 external/wpt/pointerevents/pointerevent_click_during_capture-manual.html [ Timeout ]
-crbug.com/626703 [ Linux ] external/wpt/html/semantics/scripting-1/the-script-element/module/execorder.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/idle-callbacks/cancel-invoked.html [ Timeout ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/execorder-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/execorder-expected.txt
deleted file mode 100644
index 486dfeb..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/execorder-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS Unordered module script execution (parsed, unordered #1) 
-PASS Unordered module script execution (parsed, unordered #2) 
-PASS Unordered module script execution (dynamic, unordered #1) 
-PASS Unordered module script execution (dynamic, unordered #2) 
-FAIL Interlaced module/non-module script execution (parsed, async-ordered) assert_equals: Inline module-typed script element should have fired third expected 3 but got 1
-PASS Interlaced module/non-module script execution (dynamic, async-ordered) 
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/imports-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/imports-expected.txt
deleted file mode 100644
index 871ee18..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/imports-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL html-script-module-imports Uncaught SyntaxError: Unexpected token import
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script-expected.txt
deleted file mode 100644
index d7a598e..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL An external module script with nomodule must run Uncaught SyntaxError: Unexpected token import
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script-expected.txt
deleted file mode 100644
index 5629f50..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL An inline module script with nomodule must run Uncaught SyntaxError: Unexpected token import
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https-expected.txt
index 3f3e688..9eed3f6 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https-expected.txt
@@ -7,7 +7,7 @@
 PASS PaymentRequest interface: operation show() 
 PASS PaymentRequest interface: operation abort() 
 PASS PaymentRequest interface: operation canMakePayment() 
-FAIL PaymentRequest interface: attribute id assert_true: The prototype object must have a property "id" expected true got false
+PASS PaymentRequest interface: attribute id 
 PASS PaymentRequest interface: attribute shippingAddress 
 PASS PaymentRequest interface: attribute shippingOption 
 PASS PaymentRequest interface: attribute shippingType 
@@ -18,7 +18,7 @@
 PASS PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "show" with the proper type (0) 
 PASS PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "abort" with the proper type (1) 
 PASS PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "canMakePayment" with the proper type (2) 
-FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "id" with the proper type (3) assert_inherits: property "id" not found in prototype chain
+PASS PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "id" with the proper type (3) 
 PASS PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "shippingAddress" with the proper type (4) 
 PASS PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "shippingOption" with the proper type (5) 
 PASS PaymentRequest interface: new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "shippingType" with the proper type (6) 
@@ -45,7 +45,7 @@
 PASS PaymentResponse interface object name 
 FAIL PaymentResponse interface: existence and properties of interface prototype object assert_equals: class string of PaymentResponse.prototype expected "[object PaymentResponsePrototype]" but got "[object PaymentResponse]"
 PASS PaymentResponse interface: existence and properties of interface prototype object's "constructor" property 
-FAIL PaymentResponse interface: attribute requestId assert_true: The prototype object must have a property "requestId" expected true got false
+PASS PaymentResponse interface: attribute requestId 
 PASS PaymentResponse interface: attribute methodName 
 PASS PaymentResponse interface: attribute details 
 PASS PaymentResponse interface: attribute shippingAddress 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https.html
new file mode 100644
index 0000000..8e3c3446
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest identifier usage</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="iframe" allowpaymentrequest></iframe>
+<script>
+async_test((t) => {
+  onload = t.step_func_done(() => {
+    var request = new window[0].PaymentRequest([{supportedMethods: ['foo']}], {id: 'my_payment_id', total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
+    assert_equals(request.id, 'my_payment_id', 'Payment identifier is not reflected correctly in PaymentRequest.id');
+
+    request = new window[0].PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
+    assert_equals(request.id.length, 36, 'Generated payment identifier is not of correct length.');
+  });
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-response-id.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-response-id.html
new file mode 100644
index 0000000..3fd6bd1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-response-id.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>PaymentRequest identifier manual test</title>
+</head>
+<body>
+  <div id="contents">
+    <h1>PaymentRequest identifier manual test</h1>
+    <p>Perform the following steps:</p>
+    <ul>
+      <li>Press 'Buy'</li>
+      <li>In the payment dialog make sure a payment app is selected</li>
+      <li>In the payment dialog press 'Pay'</li>
+      <li>In the launched payment app perform steps to do the payment</li>
+      <li>The response will be processed and below should display 'my_payment_id'</li>
+    </ul>
+    <p>No payment will be processed.</p>
+    <p>Price: USD <strong>$55.00</strong></p>
+    <p><button onclick="onBuyClicked()">Buy</button></p>
+  </div>
+  <pre id="msg"></pre>
+  <script>
+    /**
+      * Initializes the payment request object.
+      * @return {PaymentRequest} The payment request object.
+      */
+     function buildPaymentRequest() {
+       if (!window.PaymentRequest) {
+         return null;
+       }
+
+       const supportedInstruments = [{
+         supportedMethods: ['https://android.com/pay'],
+         data: {
+           merchantName: 'Rouslan Solomakhin',
+           merchantId: '00184145120947117657',
+           allowedCardNetworks: ['AMEX', 'MASTERCARD', 'VISA', 'DISCOVER'],
+           paymentMethodTokenizationParameters: {
+             tokenizationType: 'GATEWAY_TOKEN',
+             parameters: {
+               'gateway': 'stripe',
+               'stripe:publishableKey': 'pk_live_lNk21zqKM2BENZENh3rzCUgo',
+               'stripe:version': '2016-07-06',
+             },
+           },
+         },
+       }, {
+         supportedMethods: ['basic-card'],
+         data: {
+           supportedNetworks: ['unionpay', 'visa', 'mastercard', 'amex', 'discover',
+             'diners', 'jcb', 'mir',
+           ],
+           supportedTypes: ['prepaid', 'debit', 'credit'],
+         },
+       }];
+
+       const details = {
+         id: 'my_payment_id',
+         total: {
+           label: 'Donation',
+           amount: {
+             currency: 'USD',
+             value: '55.00',
+           },
+         },
+         displayItems: [{
+           label: 'Original donation amount',
+           amount: {
+             currency: 'USD',
+             value: '65.00',
+           },
+         }, {
+           label: 'Friends and family discount',
+           amount: {
+             currency: 'USD',
+             value: '-10.00',
+           },
+         }],
+       };
+
+       let request = null;
+
+       try {
+         request = new PaymentRequest(supportedInstruments, details);
+         if (request.canMakePayment) {
+           request.canMakePayment().then(function(result) {
+             console.log(result ? 'Can make payment' : 'Cannot make payment');
+           }).catch(function(err) {
+             console.log(err);
+           });
+         }
+       } catch (e) {
+         console.log('Developer mistake: \'' + e + '\'');
+       }
+     
+       return request;
+     }
+
+     let request = buildPaymentRequest();
+
+     /**
+      * Launches payment request that does not require shipping.
+      */
+     function onBuyClicked() { // eslint-disable-line no-unused-vars
+       if (!window.PaymentRequest || !request) {
+         console.log('PaymentRequest API is not supported.');
+         return;
+       }
+
+       try {
+         request.show()
+           .then(function(instrumentResponse) {
+             window.setTimeout(function() {
+               instrumentResponse.complete('success')
+                 .then(function() {
+                    let element = document.createElement('pre');
+                    element.innerHTML = instrumentResponse.requestId;
+                    document.getElementById('msg').appendChild(element);
+                 })
+                 .catch(function(err) {
+                   console.log(err);
+                   request = buildPaymentRequest();
+                 });
+             }, 2000);
+           })
+           .catch(function(err) {
+             console.log(err);
+             request = buildPaymentRequest();
+           });
+       } catch (e) {
+         console.log('Developer mistake: \'' + e + '\'');
+         request = buildPaymentRequest();
+       }
+     }
+  </script>
+</body>
+</html>
+
diff --git a/third_party/WebKit/LayoutTests/inspector/components/parsed-url-expected.txt b/third_party/WebKit/LayoutTests/inspector/components/parsed-url-expected.txt
index 5d02f96d..076f2cc 100644
--- a/third_party/WebKit/LayoutTests/inspector/components/parsed-url-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector/components/parsed-url-expected.txt
@@ -150,4 +150,32 @@
   fragment: undefined
   folderPathComponents: /foo/bar////
   lastPathComponent: baz.html
+Splitting url: http://www.chromium.org
+  URL: http://www.chromium.org
+  Line: undefined
+  Column: undefined
+Splitting url: http://www.chromium.org:8000
+  URL: http://www.chromium.org:8000
+  Line: undefined
+  Column: undefined
+Splitting url: http://www.chromium.org:8000/
+  URL: http://www.chromium.org:8000/
+  Line: undefined
+  Column: undefined
+Splitting url: http://www.chromium.org:8000/foo.js:10
+  URL: http://www.chromium.org:8000/foo.js
+  Line: 9
+  Column: undefined
+Splitting url: http://www.chromium.org:8000/foo.js:10:20
+  URL: http://www.chromium.org:8000/foo.js
+  Line: 9
+  Column: 19
+Splitting url: http://www.chromium.org/foo.js:10
+  URL: http://www.chromium.org/foo.js
+  Line: 9
+  Column: undefined
+Splitting url: http://www.chromium.org/foo.js:10:20
+  URL: http://www.chromium.org/foo.js
+  Line: 9
+  Column: 19
 
diff --git a/third_party/WebKit/LayoutTests/inspector/components/parsed-url.html b/third_party/WebKit/LayoutTests/inspector/components/parsed-url.html
index 57098bd..d539d41 100644
--- a/third_party/WebKit/LayoutTests/inspector/components/parsed-url.html
+++ b/third_party/WebKit/LayoutTests/inspector/components/parsed-url.html
@@ -39,6 +39,23 @@
     parseAndDumpURL("http://example.com/foo////bar/baz.html");
     parseAndDumpURL("http://example.com/foo/bar/////baz.html");
 
+    function testSplitLineColumn(url) {
+        var result = Common.ParsedURL.splitLineAndColumn(url);
+
+        InspectorTest.addResult("Splitting url: " + url);
+        InspectorTest.addResult("  URL: " + result.url);
+        InspectorTest.addResult("  Line: " + result.lineNumber);
+        InspectorTest.addResult("  Column: " + result.columnNumber);
+    }
+
+    testSplitLineColumn("http://www.chromium.org");
+    testSplitLineColumn("http://www.chromium.org:8000");
+    testSplitLineColumn("http://www.chromium.org:8000/");
+    testSplitLineColumn("http://www.chromium.org:8000/foo.js:10");
+    testSplitLineColumn("http://www.chromium.org:8000/foo.js:10:20");
+    testSplitLineColumn("http://www.chromium.org/foo.js:10");
+    testSplitLineColumn("http://www.chromium.org/foo.js:10:20");
+
     InspectorTest.completeTest();
 }
 
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2-expected.png b/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2-expected.png
index c781870..a7fe526 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2-expected.png
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2.html b/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2.html
index aa972a0a..51020a7 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2.html
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/border-radius-repaint-2.html
@@ -3,13 +3,11 @@
 <head>
 <title>Repaint test for crbug.com/222851</title>
 <script src="resources/text-based-repaint.js" type="text/javascript"></script>
+<style>
+/* Avoid the noise caused by change of scrollbar existence. */
+html { overflow: hidden; }
+</style>
 <script>
-if (window.internals) {
-    // Avoid the noise caused by change of scrollbar existence.
-    internals.settings.setOverlayScrollbarsEnabled(true);
-    internals.settings.setMockScrollbarsEnabled(true);
-}
-
 function repaintTest() {
     document.getElementById('target').style.height = '2px';
 }
@@ -17,12 +15,12 @@
 </head>
 <body style="background-color: #3F3F3F;" onload="runRepaintAndPixelTest()">
   <div style="border-radius: 150px; background-color: #EFEFEF; width: 90%; padding: 5em; margin: 0 auto;">
-	<div style="border-radius: 5px; border: solid 3px #367CAF; width: 85%; margin: 0 auto;">
-	  <div class="notARealClass" style="background-color: #367CAF; height: 3em; cursor: pointer;">		
-	  </div>
-	  <div id="target" style="width: 100%; height: 550px;">
-	  </div>
-	</div>
+  <div style="border-radius: 5px; border: solid 3px #367CAF; width: 85%; margin: 0 auto;">
+    <div class="notARealClass" style="background-color: #367CAF; height: 3em; cursor: pointer;">
+    </div>
+    <div id="target" style="width: 100%; height: 550px;">
+    </div>
+  </div>
   </div>
 </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1-expected.html
index 9c01bec..847bad9 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1-expected.html
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1-expected.html
@@ -1,9 +1,6 @@
 <!DOCTYPE html>
-<script>
-if (window.internals) {
-    internals.settings.setOverlayScrollbarsEnabled(true);
-    internals.settings.setMockScrollbarsEnabled(true);
-}
-</script>
+<style>
+  html { overflow: hidden; }
+</style>
 <div style="width: 2000px; height: 2000px; background-color: blue">
 </div>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1.html b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1.html
index 3fd9ae72..fb0dac1 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1.html
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change1.html
@@ -1,10 +1,7 @@
 <!DOCTYPE html>
 <script src="resources/window-resize-repaint.js"></script>
-<script>
-if (window.internals) {
-    internals.settings.setOverlayScrollbarsEnabled(true);
-    internals.settings.setMockScrollbarsEnabled(true);
-}
-</script>
+<style>
+  html { overflow: hidden; }
+</style>
 <div style="width: 2000px; height: 2000px; background-color: blue">
 </div>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2-expected.html
index f8b1995..ba4be0f 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2-expected.html
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2-expected.html
@@ -1,10 +1,7 @@
 <!DOCTYPE html>
-<script>
-if (window.internals) {
-    internals.settings.setOverlayScrollbarsEnabled(true);
-    internals.settings.setMockScrollbarsEnabled(true);
-}
-</script>
+<style>
+  html { overflow: hidden; }
+</style>
 <div style="position: absolute; width: 2000px; height: 2000px; background-color: blue">
     <div style="width: 20%; height: 20%; background-color: yellow">
     </div>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2.html b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2.html
index f8ae3a8..76c2a2ed 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2.html
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/window-resize-no-layout-change2.html
@@ -1,11 +1,8 @@
 <!DOCTYPE html>
 <script src="resources/window-resize-repaint.js"></script>
-<script>
-if (window.internals) {
-    internals.settings.setOverlayScrollbarsEnabled(true);
-    internals.settings.setMockScrollbarsEnabled(true);
-}
-</script>
+<style>
+  html { overflow: hidden; }
+</style>
 <div style="position: absolute; width: 2000px; height: 2000px; background-color: blue">
     <div style="width: 20%; height: 20%; background-color: yellow">
     </div>
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
index 0e745ff..d9e555b 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
@@ -4716,6 +4716,7 @@
     method setManifest
 interface PaymentRequest : EventTarget
     attribute @@toStringTag
+    getter id
     getter onshippingaddresschange
     getter onshippingoptionchange
     getter shippingAddress
@@ -4738,6 +4739,7 @@
     getter payerEmail
     getter payerName
     getter payerPhone
+    getter requestId
     getter shippingAddress
     getter shippingOption
     method complete
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index 7b8356f..9865784 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -4723,6 +4723,7 @@
     method setManifest
 interface PaymentRequest : EventTarget
     attribute @@toStringTag
+    getter id
     getter onshippingaddresschange
     getter onshippingoptionchange
     getter shippingAddress
@@ -4745,6 +4746,7 @@
     getter payerEmail
     getter payerName
     getter payerPhone
+    getter requestId
     getter shippingAddress
     getter shippingOption
     method complete
diff --git a/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5 b/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
index e32263e0..68e408a 100644
--- a/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
+++ b/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
@@ -40,7 +40,6 @@
           "table-footer-group", "table-row", "table-column-group", "table-column", "table-cell", "table-caption", "-webkit-box",
           "-webkit-inline-box", "flex", "inline-flex", "grid", "inline-grid", "contents", "flow-root", "none",
       ],
-      has_custom_compare_and_copy: true,
     },
     {
       name: "InsideLink",
@@ -154,15 +153,14 @@
       has_custom_compare_and_copy: true,
     },
     // TODO(shend): vertical align is actually a CSS property, but since we don't support union fields
-    // which can be either a keyword or Length, this is generated as a nonproperty for now. Remove this
-    // once we can support union fields and groups.
+    // which can be either a keyword or Length, this is specified in this file for now. Remove this
+    // once we can support union fields.
     {
       name: "VerticalAlign",
       field_template: "storage_only",
       field_size: 4,
       default_value: "EVerticalAlign::kBaseline",
       type_name: "EVerticalAlign",
-      has_custom_compare_and_copy: true,
     },
     {
       name: "border",
diff --git a/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp b/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp
index eb3f00f3..b29ebec4 100644
--- a/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp
+++ b/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp
@@ -131,7 +131,7 @@
 
 void ModuleMapTestModulator::ResolveFetches() {
   for (const auto& test_request : test_requests_) {
-    ModuleScript* module_script = ModuleScript::Create(
+    ModuleScript* module_script = ModuleScript::CreateForTest(
         this, ScriptModule(), test_request->url, test_request->nonce,
         kParserInserted, WebURLRequest::kFetchCredentialsModeOmit);
     TaskRunner()->PostTask(
diff --git a/third_party/WebKit/Source/core/dom/ModuleScript.cpp b/third_party/WebKit/Source/core/dom/ModuleScript.cpp
index a0f6a396..6ad54434 100644
--- a/third_party/WebKit/Source/core/dom/ModuleScript.cpp
+++ b/third_party/WebKit/Source/core/dom/ModuleScript.cpp
@@ -10,6 +10,37 @@
 
 namespace blink {
 
+ModuleScript* ModuleScript::Create(
+    const String& source_text,
+    Modulator* modulator,
+    const KURL& base_url,
+    const String& nonce,
+    ParserDisposition parser_state,
+    WebURLRequest::FetchCredentialsMode credentials_mode,
+    AccessControlStatus access_control_status) {
+  // Step 1. Let script be a new module script that this algorithm will
+  // subsequently initialize.
+  // Step 2. Set script's settings object to the environment settings object
+  // provided.
+  // Note: "script's settings object" will be "modulator".
+
+  // Delegate to Modulator::compileModule to process Steps 3-6.
+  ScriptModule result = modulator->CompileModule(
+      source_text, base_url.GetString(), access_control_status);
+  // Step 6: "...return null, and abort these steps."
+  if (result.IsNull())
+    return nullptr;
+  // Step 7. Set script's module record to result.
+  // Step 8. Set script's base URL to the script base URL provided.
+  // Step 9. Set script's cryptographic nonce to the cryptographic nonce
+  // provided.
+  // Step 10. Set script's parser state to the parser state.
+  // Step 11. Set script's credentials mode to the credentials mode provided.
+  // Step 12. Return script.
+  return new ModuleScript(modulator, result, base_url, nonce, parser_state,
+                          credentials_mode);
+}
+
 void ModuleScript::SetInstantiationErrorAndClearRecord(ScriptValue error) {
   // Implements Step 7.1 of:
   // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure
diff --git a/third_party/WebKit/Source/core/dom/ModuleScript.h b/third_party/WebKit/Source/core/dom/ModuleScript.h
index df85c45..5ac8f9f 100644
--- a/third_party/WebKit/Source/core/dom/ModuleScript.h
+++ b/third_party/WebKit/Source/core/dom/ModuleScript.h
@@ -30,7 +30,16 @@
 // https://html.spec.whatwg.org/multipage/webappapis.html#module-script
 class CORE_EXPORT ModuleScript final : public Script, public TraceWrapperBase {
  public:
-  static ModuleScript* Create(
+  // https://html.spec.whatwg.org/#creating-a-module-script
+  static ModuleScript* Create(const String& source_text,
+                              Modulator*,
+                              const KURL& base_url,
+                              const String& nonce,
+                              ParserDisposition,
+                              WebURLRequest::FetchCredentialsMode,
+                              AccessControlStatus);
+
+  static ModuleScript* CreateForTest(
       Modulator* settings_object,
       ScriptModule record,
       const KURL& base_url,
diff --git a/third_party/WebKit/Source/core/dom/ScriptLoader.cpp b/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
index fb70a86..31c1d93 100644
--- a/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
+++ b/third_party/WebKit/Source/core/dom/ScriptLoader.cpp
@@ -26,6 +26,7 @@
 
 #include "bindings/core/v8/ScriptController.h"
 #include "bindings/core/v8/ScriptSourceCode.h"
+#include "bindings/core/v8/V8BindingForCore.h"
 #include "core/HTMLNames.h"
 #include "core/SVGNames.h"
 #include "core/dom/ClassicPendingScript.h"
@@ -34,6 +35,8 @@
 #include "core/dom/DocumentParserTiming.h"
 #include "core/dom/DocumentWriteIntervention.h"
 #include "core/dom/IgnoreDestructiveWriteCountIncrementer.h"
+#include "core/dom/Modulator.h"
+#include "core/dom/ModulePendingScript.h"
 #include "core/dom/Script.h"
 #include "core/dom/ScriptElementBase.h"
 #include "core/dom/ScriptRunner.h"
@@ -45,6 +48,8 @@
 #include "core/frame/csp/ContentSecurityPolicy.h"
 #include "core/html/imports/HTMLImport.h"
 #include "core/html/parser/HTMLParserIdioms.h"
+#include "core/inspector/ConsoleMessage.h"
+#include "core/loader/modulescript/ModuleScriptFetchRequest.h"
 #include "core/loader/resource/ScriptResource.h"
 #include "platform/WebFrameScheduler.h"
 #include "platform/loader/fetch/AccessControlStatus.h"
@@ -107,6 +112,7 @@
   visitor->Trace(element_);
   visitor->Trace(resource_);
   visitor->Trace(pending_script_);
+  visitor->Trace(module_tree_client_);
   PendingScriptClient::Trace(visitor);
 }
 
@@ -315,7 +321,7 @@
   CrossOriginAttributeValue cross_origin =
       GetCrossOriginAttributeValue(element_->CrossOriginAttributeValue());
 
-  // 16. will be handled below once module script support is implemented.
+  // 16. is handled below.
 
   // 17. "If the script element has a nonce attribute,
   //      then let cryptographic nonce be that attribute's value.
@@ -364,51 +370,82 @@
     }
 
     DCHECK(!resource_);
+    DCHECK(!module_tree_client_);
 
     // 21.6. "Switch on the script's type:"
-    // - "classic":
+    if (GetScriptType() == ScriptType::kClassic) {
+      // - "classic":
 
-    // 14. "If the script element has a charset attribute,
-    //      then let encoding be the result of
-    //      getting an encoding from the value of the charset attribute."
-    //     "If the script element does not have a charset attribute,
-    //      or if getting an encoding failed, let encoding
-    //      be the same as the encoding of the script element's node
-    //      document."
-    // TODO(hiroshige): Should we handle failure in getting an encoding?
-    String encoding;
-    if (!element_->CharsetAttributeValue().IsEmpty())
-      encoding = element_->CharsetAttributeValue();
-    else
-      encoding = element_document.characterSet();
+      // 14. "If the script element has a charset attribute,
+      //      then let encoding be the result of
+      //      getting an encoding from the value of the charset attribute."
+      //     "If the script element does not have a charset attribute,
+      //      or if getting an encoding failed, let encoding
+      //      be the same as the encoding of the script element's node
+      //      document."
+      // TODO(hiroshige): Should we handle failure in getting an encoding?
+      String encoding;
+      if (!element_->CharsetAttributeValue().IsEmpty())
+        encoding = element_->CharsetAttributeValue();
+      else
+        encoding = element_document.characterSet();
 
-    // Step 16 is skipped because "module script credentials" is not used
-    // for classic scripts.
+      // Step 16 is skipped because "module script credentials" is not used
+      // for classic scripts.
 
-    // 18. "If the script element has an integrity attribute,
-    //      then let integrity metadata be that attribute's value.
-    //      Otherwise, let integrity metadata be the empty string."
-    String integrity_attr = element_->IntegrityAttributeValue();
-    IntegrityMetadataSet integrity_metadata;
-    if (!integrity_attr.IsEmpty()) {
-      SubresourceIntegrity::ParseIntegrityAttribute(
-          integrity_attr, integrity_metadata, &element_document);
+      // 18. "If the script element has an integrity attribute,
+      //      then let integrity metadata be that attribute's value.
+      //      Otherwise, let integrity metadata be the empty string."
+      String integrity_attr = element_->IntegrityAttributeValue();
+      IntegrityMetadataSet integrity_metadata;
+      if (!integrity_attr.IsEmpty()) {
+        SubresourceIntegrity::ParseIntegrityAttribute(
+            integrity_attr, integrity_metadata, &element_document);
+      }
+
+      if (!FetchClassicScript(url, element_document.Fetcher(), nonce,
+                              integrity_metadata, parser_state, cross_origin,
+                              element_document.GetSecurityOrigin(), encoding)) {
+        // TODO(hiroshige): Make this asynchronous. Currently we fire the error
+        // event synchronously to keep the existing behavior.
+        DispatchErrorEvent();
+        return false;
+      }
+
+      DCHECK(resource_);
+      DCHECK(!module_tree_client_);
+    } else {
+      // - "module":
+
+      // Steps 14 and 18 are skipped because they are not used in module
+      // scripts.
+
+      // 16. "Let module script credentials mode be determined by switching
+      //      on CORS setting:"
+      WebURLRequest::FetchCredentialsMode credentials_mode =
+          WebURLRequest::kFetchCredentialsModeOmit;
+      switch (cross_origin) {
+        case kCrossOriginAttributeNotSet:
+          credentials_mode = WebURLRequest::kFetchCredentialsModeOmit;
+          break;
+        case kCrossOriginAttributeAnonymous:
+          credentials_mode = WebURLRequest::kFetchCredentialsModeSameOrigin;
+          break;
+        case kCrossOriginAttributeUseCredentials:
+          credentials_mode = WebURLRequest::kFetchCredentialsModeInclude;
+          break;
+      }
+
+      DCHECK(RuntimeEnabledFeatures::moduleScriptsEnabled());
+      Modulator* modulator = Modulator::From(
+          ToScriptStateForMainWorld(element_document.GetFrame()));
+      FetchModuleScriptTree(url, modulator, nonce, parser_state,
+                            credentials_mode);
+
+      DCHECK(!resource_);
+      DCHECK(module_tree_client_);
     }
 
-    if (!FetchClassicScript(url, element_document.Fetcher(), nonce,
-                            integrity_metadata, parser_state, cross_origin,
-                            element_document.GetSecurityOrigin(), encoding)) {
-      // TODO(hiroshige): Make this asynchronous. Currently we fire the error
-      // event synchronously to keep the existing behavior.
-      DispatchErrorEvent();
-      return false;
-    }
-
-    DCHECK(resource_);
-
-    // - "module":
-    // TODO(hiroshige): Implement this.
-
     // "When the chosen algorithm asynchronously completes, set
     //  the script's script to the result. At that time, the script is ready."
     // When the script is ready, PendingScriptClient::pendingScriptFinished()
@@ -421,32 +458,50 @@
 
   // 22. "If the element does not have a src content attribute,
   //      run these substeps:"
+  if (!element_->HasSourceAttribute()) {
+    // 22.1. "Let source text be the value of the text IDL attribute."
+    // This step is done later:
+    // - in ScriptLoader::pendingScript() (Step 23, 6th Clause),
+    //   as Element::textFromChildren() in ScriptLoader::scriptContent(),
+    // - in HTMLParserScriptRunner::processScriptElementInternal()
+    //   (Duplicated code of Step 23, 6th Clause),
+    //   as Element::textContent(),
+    // - in XMLDocumentParser::endElementNs() (Step 23, 5th Clause),
+    //   as Element::textFromChildren() in ScriptLoader::scriptContent(),
+    // - PendingScript::getSource() (Indirectly used via
+    //   HTMLParserScriptRunner::processScriptElementInternal(),
+    //   Step 23, 5th Clause),
+    //   as Element::textContent().
+    // TODO(hiroshige): Make them merged or consistent.
 
-  // 22.1. "Let source text be the value of the text IDL attribute."
-  // This step is done later:
-  // - in ScriptLoader::pendingScript() (Step 23, 6th Clause),
-  //   as Element::textFromChildren() in ScriptLoader::scriptContent(),
-  // - in HTMLParserScriptRunner::processScriptElementInternal()
-  //   (Duplicated code of Step 23, 6th Clause),
-  //   as Element::textContent(),
-  // - in XMLDocumentParser::endElementNs() (Step 23, 5th Clause),
-  //   as Element::textFromChildren() in ScriptLoader::scriptContent(),
-  // - PendingScript::getSource() (Indirectly used via
-  //   HTMLParserScriptRunner::processScriptElementInternal(),
-  //   Step 23, 5th Clause),
-  //   as Element::textContent().
-  // TODO(hiroshige): Make them merged or consistent.
+    // 22.2. "Switch on the script's type:"
+    switch (GetScriptType()) {
+      // - "classic":
+      case ScriptType::kClassic:
+        // TODO(hiroshige): Clarify how Step 22.2 is implemented for "classic".
+        break;
 
-  // 22.2. "Switch on the script's type:"
-  // TODO(hiroshige): Clarify how Step 22.2 is implemented for "classic".
-  // TODO(hiroshige): Implement Step 22.2 for "module".
+      // - "module":
+      case ScriptType::kModule:
+        // TODO(hiroshige): Implement inline module scripts.
+        element_document.AddConsoleMessage(ConsoleMessage::Create(
+            kJSMessageSource, kErrorMessageLevel,
+            "Inline module script is not yet supported",
+            SourceLocation::Create(element_document.Url().GetString(),
+                                   script_start_position.line_.OneBasedInt(),
+                                   script_start_position.column_.OneBasedInt(),
+                                   nullptr)));
+        return false;
+    }
+  }
 
   // [Intervention]
   // Since the asynchronous, low priority fetch for doc.written blocked
   // script is not for execution, return early from here. Watch for its
   // completion to be able to remove it from the memory cache.
-  if (document_write_intervention_ ==
-      DocumentWriteIntervention::kFetchDocWrittenScriptDeferIdle) {
+  if (GetScriptType() == ScriptType::kClassic &&
+      document_write_intervention_ ==
+          DocumentWriteIntervention::kFetchDocWrittenScriptDeferIdle) {
     pending_script_ = CreatePendingScript();
     pending_script_->WatchForLoad(this);
     return true;
@@ -467,9 +522,14 @@
   //    the element has a src attribute, and the element has a defer attribute,
   //    and the element has been flagged as "parser-inserted",
   //    and the element does not have an async attribute"
-  // TODO(hiroshige): Check the script's type and implement "module" case.
-  if (element_->HasSourceAttribute() && element_->DeferAttributeValue() &&
-      parser_inserted_ && !element_->AsyncAttributeValue()) {
+  // - "If the script's type is "module",
+  //    and the element has been flagged as "parser-inserted",
+  //    and the element does not have an async attribute"
+  if ((GetScriptType() == ScriptType::kClassic &&
+       element_->HasSourceAttribute() && element_->DeferAttributeValue() &&
+       parser_inserted_ && !element_->AsyncAttributeValue()) ||
+      (GetScriptType() == ScriptType::kModule && parser_inserted_ &&
+       !element_->AsyncAttributeValue())) {
     // This clause is implemented by the caller-side of prepareScript():
     // - HTMLParserScriptRunner::requestDeferredScript(), and
     // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
@@ -485,7 +545,8 @@
   //    and the element has been flagged as "parser-inserted",
   //    and the element does not have an async attribute"
   // TODO(hiroshige): Check the script's type.
-  if (element_->HasSourceAttribute() && parser_inserted_ &&
+  if (GetScriptType() == ScriptType::kClassic &&
+      element_->HasSourceAttribute() && parser_inserted_ &&
       !element_->AsyncAttributeValue()) {
     // This clause is implemented by the caller-side of prepareScript():
     // - HTMLParserScriptRunner::requestParsingBlockingScript()
@@ -509,7 +570,11 @@
   // Part of the condition check is done in
   // HTMLParserScriptRunner::processScriptElementInternal().
   // TODO(hiroshige): Clean up the split condition check.
-  if (!element_->HasSourceAttribute() && parser_inserted_ &&
+  // We check that the type is "classic" here, because according to the spec
+  // a "module" script doesn't reach the 5th Clause because the 4th Clause
+  // catches all "module" scripts.
+  if (GetScriptType() == ScriptType::kClassic &&
+      !element_->HasSourceAttribute() && parser_inserted_ &&
       !element_document.IsScriptExecutionReady()) {
     // The former part of this clause is
     // implemented by the caller-side of prepareScript():
@@ -527,9 +592,15 @@
   //    and the element has a src attribute,
   //    and the element does not have an async attribute,
   //    and the element does not have the "non-blocking" flag set"
+  // - "If the script's type is "module",
+  //    and the element does not have an async attribute,
+  //    and the element does not have the "non-blocking" flag set"
   // TODO(hiroshige): Check the script's type and implement "module" case.
-  if (element_->HasSourceAttribute() && !element_->AsyncAttributeValue() &&
-      !non_blocking_) {
+  if ((GetScriptType() == ScriptType::kClassic &&
+       element_->HasSourceAttribute() && !element_->AsyncAttributeValue() &&
+       !non_blocking_) ||
+      (GetScriptType() == ScriptType::kModule &&
+       !element_->AsyncAttributeValue() && !non_blocking_)) {
     // "Add the element to the end of the list of scripts that will execute
     // in order as soon as possible associated with the node document of the
     // script element at the time the prepare a script algorithm started."
@@ -550,8 +621,10 @@
 
   // 4th Clause:
   // - "If the script's type is "classic", and the element has a src attribute"
-  // TODO(hiroshige): Check the script's type and implement "module" case.
-  if (element_->HasSourceAttribute()) {
+  // - "If the script's type is "module""
+  if ((GetScriptType() == ScriptType::kClassic &&
+       element_->HasSourceAttribute()) ||
+      GetScriptType() == ScriptType::kModule) {
     // "The element must be added to the set of scripts that will execute
     //  as soon as possible of the node document of the script element at the
     //  time the prepare a script algorithm started."
@@ -582,6 +655,8 @@
 
   // This clause is executed only if the script's type is "classic"
   // and the element doesn't have a src attribute.
+  DCHECK_EQ(GetScriptType(), ScriptType::kClassic);
+  DCHECK(!is_external_script_);
 
   // Reset line numbering for nested writes.
   TextPosition position = element_document.IsInDocumentWrite()
@@ -669,9 +744,36 @@
   return true;
 }
 
+void ScriptLoader::FetchModuleScriptTree(
+    const KURL& url,
+    Modulator* modulator,
+    const String& nonce,
+    ParserDisposition parser_state,
+    WebURLRequest::FetchCredentialsMode credentials_mode) {
+  // https://html.spec.whatwg.org/#prepare-a-script
+  // 21.6, "module":
+  //     "Fetch a module script graph given url, settings, "script",
+  //      cryptographic nonce, parser state, and
+  //      module script credentials mode."
+  ModuleScriptFetchRequest module_request(url, nonce, parser_state,
+                                          credentials_mode);
+
+  module_tree_client_ = ModulePendingScriptTreeClient::Create();
+
+  modulator->FetchTree(module_request, module_tree_client_);
+}
+
 PendingScript* ScriptLoader::CreatePendingScript() {
-  CHECK(resource_);
-  return ClassicPendingScript::Create(element_, resource_);
+  switch (GetScriptType()) {
+    case ScriptType::kClassic:
+      CHECK(resource_);
+      return ClassicPendingScript::Create(element_, resource_);
+    case ScriptType::kModule:
+      CHECK(module_tree_client_);
+      return ModulePendingScript::Create(element_, module_tree_client_);
+  }
+  NOTREACHED();
+  return nullptr;
 }
 
 bool ScriptLoader::ExecuteScript(const Script* script) {
@@ -698,6 +800,7 @@
 // TODO(hiroshige): Move event dispatching code to doExecuteScript().
 bool ScriptLoader::DoExecuteScript(const Script* script) {
   DCHECK(already_started_);
+  CHECK_EQ(script->GetScriptType(), GetScriptType());
 
   if (script->IsEmpty())
     return true;
@@ -740,10 +843,13 @@
   //     or the script's type is module",
   //     then increment the ignore-destructive-writes counter of the
   //     script element's node document. Let neutralized doc be that Document."
-  // TODO(hiroshige): Implement "module" case.
   IgnoreDestructiveWriteCountIncrementer
       ignore_destructive_write_count_incrementer(
-          is_external_script_ || is_imported_script ? context_document : 0);
+          is_external_script_ ||
+                  script->GetScriptType() == ScriptType::kModule ||
+                  is_imported_script
+              ? context_document
+              : 0);
 
   // 4. "Let old script element be the value to which the script element's
   //     node document's currentScript object was most recently set."
@@ -754,18 +860,24 @@
   //    1. "If the script element's root is not a shadow root,
   //        then set the script element's node document's currentScript
   //        attribute to the script element. Otherwise, set it to null."
-  context_document->PushCurrentScript(element_.Get());
+  //    - "module":
+  //    1. "Set the script element's node document's currentScript attribute
+  //        to null."
+  ScriptElementBase* current_script = nullptr;
+  if (script->GetScriptType() == ScriptType::kClassic)
+    current_script = element_;
+  context_document->PushCurrentScript(current_script);
 
+  //    - "classic":
   //    2. "Run the classic script given by the script's script."
   // Note: This is where the script is compiled and actually executed.
-  script->RunScript(frame, element_->GetDocument().GetSecurityOrigin());
-
   //    - "module":
-  // TODO(hiroshige): Implement this.
+  //    2. "Run the module script given by the script's script."
+  script->RunScript(frame, element_->GetDocument().GetSecurityOrigin());
 
   // 6. "Set the script element's node document's currentScript attribute
   //     to old script element."
-  context_document->PopCurrentScript(element_.Get());
+  context_document->PopCurrentScript(current_script);
 
   return true;
 
@@ -791,11 +903,13 @@
       DispatchErrorEvent();
   }
   resource_ = nullptr;
+  module_tree_client_ = nullptr;
 }
 
 void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) {
   DCHECK(!will_be_parser_executed_);
   DCHECK_EQ(pending_script_, pending_script);
+  DCHECK_EQ(pending_script_->GetScriptType(), GetScriptType());
 
   // We do not need this script in the memory cache. The primary goals of
   // sending this fetch request are to let the third party server know
diff --git a/third_party/WebKit/Source/core/dom/ScriptLoader.h b/third_party/WebKit/Source/core/dom/ScriptLoader.h
index 1188afd7..862d0a57 100644
--- a/third_party/WebKit/Source/core/dom/ScriptLoader.h
+++ b/third_party/WebKit/Source/core/dom/ScriptLoader.h
@@ -39,6 +39,9 @@
 class ResourceFetcher;
 class ScriptResource;
 
+class Modulator;
+class ModulePendingScriptTreeClient;
+
 class CORE_EXPORT ScriptLoader : public GarbageCollectedFinalized<ScriptLoader>,
                                  public PendingScriptClient {
   USING_GARBAGE_COLLECTED_MIXIN(ScriptLoader);
@@ -75,7 +78,7 @@
   String ScriptContent() const;
 
   // Creates a PendingScript for external script whose fetch is started in
-  // FetchClassicScript().
+  // FetchClassicScript()/FetchModuleScriptTree().
   PendingScript* CreatePendingScript();
 
   // Returns false if and only if execution was blocked.
@@ -150,6 +153,12 @@
                           CrossOriginAttributeValue,
                           SecurityOrigin*,
                           const String& encoding);
+  // https://html.spec.whatwg.org/#fetch-a-module-script-tree
+  void FetchModuleScriptTree(const KURL&,
+                             Modulator*,
+                             const String& nonce,
+                             ParserDisposition,
+                             WebURLRequest::FetchCredentialsMode);
 
   bool DoExecuteScript(const Script*);
 
@@ -184,9 +193,6 @@
 
   // https://html.spec.whatwg.org/#concept-script-type
   // "It is determined when the script is prepared"
-  // TODO(hiroshige): Currently |script_type_| is set but ignored, and
-  // thus is handled as if it is a classic script even if type is "module"
-  // and module scripts is enabled.
   ScriptType script_type_ = ScriptType::kClassic;
 
   // https://html.spec.whatwg.org/#concept-script-external
@@ -219,6 +225,7 @@
   DocumentWriteIntervention document_write_intervention_;
 
   Member<PendingScript> pending_script_;
+  Member<ModulePendingScriptTreeClient> module_tree_client_;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/ScriptModuleResolverImplTest.cpp b/third_party/WebKit/Source/core/dom/ScriptModuleResolverImplTest.cpp
index 6921f7f7..e581c4b6 100644
--- a/third_party/WebKit/Source/core/dom/ScriptModuleResolverImplTest.cpp
+++ b/third_party/WebKit/Source/core/dom/ScriptModuleResolverImplTest.cpp
@@ -61,7 +61,7 @@
       scope.GetIsolate(), "import './target.js'; export const a = 42;",
       "referrer.js", kSharableCrossOrigin);
   KURL referrer_url(kParsedURLString, "https://example.com/referrer.js");
-  ModuleScript* referrer_module_script = ModuleScript::Create(
+  ModuleScript* referrer_module_script = ModuleScript::CreateForTest(
       modulator, referrer_record, referrer_url, "", kParserInserted,
       WebURLRequest::kFetchCredentialsModeOmit);
   // TODO(kouhei): moduleScript->setInstantiateSuccess(); once
@@ -76,8 +76,8 @@
                             "target.js", kSharableCrossOrigin);
   KURL url(kParsedURLString, "https://example.com/target.js");
   ModuleScript* module_script =
-      ModuleScript::Create(modulator, record, url, "", kParserInserted,
-                           WebURLRequest::kFetchCredentialsModeOmit);
+      ModuleScript::CreateForTest(modulator, record, url, "", kParserInserted,
+                                  WebURLRequest::kFetchCredentialsModeOmit);
   // TODO(kouhei): moduleScript->setInstantiateSuccess(); once
   // https://codereview.chromium.org/2782403002/ landed.
   return module_script;
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElement.h b/third_party/WebKit/Source/core/dom/custom/CustomElement.h
index 57984e8..5d74979 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElement.h
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElement.h
@@ -37,7 +37,7 @@
 
   static bool IsValidName(const AtomicString& name) {
     // This quickly rejects all common built-in element names.
-    if (name.Find('-', 1) == kNotFound)
+    if (name.find('-', 1) == kNotFound)
       return false;
 
     if (!IsASCIILower(name[0]))
diff --git a/third_party/WebKit/Source/core/dom/custom/V0CustomElement.cpp b/third_party/WebKit/Source/core/dom/custom/V0CustomElement.cpp
index 0a9ef55..778df42a 100644
--- a/third_party/WebKit/Source/core/dom/custom/V0CustomElement.cpp
+++ b/third_party/WebKit/Source/core/dom/custom/V0CustomElement.cpp
@@ -64,7 +64,7 @@
 }
 
 static inline bool IsValidNCName(const AtomicString& name) {
-  if (kNotFound != name.Find(':'))
+  if (kNotFound != name.find(':'))
     return false;
 
   if (!name.GetString().Is8Bit()) {
@@ -86,7 +86,7 @@
       kNotFound != EmbedderCustomElementNames().Find(name))
     return Document::IsValidName(name);
 
-  if ((valid_names & kStandardNames) && kNotFound != name.Find('-')) {
+  if ((valid_names & kStandardNames) && kNotFound != name.find('-')) {
     DEFINE_STATIC_LOCAL(Vector<AtomicString>, reserved_names, ());
     if (reserved_names.IsEmpty()) {
       // FIXME(crbug.com/426605): We should be able to remove this.
diff --git a/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp b/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp
index 03686dab..686f2ada 100644
--- a/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp
@@ -953,7 +953,8 @@
     OpacityMode opacity_mode) {
   if (ShouldUseDisplayList()) {
     auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
-        Size(), opacity_mode, context_->color_params());
+        Size(), RecordingImageBufferSurface::kAllowFallback, opacity_mode,
+        context_->color_params());
     if (surface->IsValid()) {
       CanvasMetrics::CountCanvasContextUsage(
           CanvasMetrics::kDisplayList2DCanvasImageBufferCreated);
diff --git a/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp b/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp
index 316d1a8..2d3cd32 100644
--- a/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp
+++ b/third_party/WebKit/Source/core/html/parser/HTMLParserScriptRunner.cpp
@@ -288,6 +288,7 @@
   DCHECK(script_loader);
   script_loader->SetFetchDocWrittenScriptDeferIdle();
   script_loader->PrepareScript(script_start_position);
+  CHECK_EQ(script_loader->GetScriptType(), ScriptType::kClassic);
 }
 
 void EmitWarningForDocWriteScripts(const String& url, Document& document) {
diff --git a/third_party/WebKit/Source/core/inspector/V8InspectorString.h b/third_party/WebKit/Source/core/inspector/V8InspectorString.h
index bb585cc..08bbb3c 100644
--- a/third_party/WebKit/Source/core/inspector/V8InspectorString.h
+++ b/third_party/WebKit/Source/core/inspector/V8InspectorString.h
@@ -12,6 +12,7 @@
 #include "platform/wtf/Assertions.h"
 #include "platform/wtf/text/StringBuilder.h"
 #include "platform/wtf/text/StringHash.h"
+#include "platform/wtf/text/StringToNumber.h"
 #include "platform/wtf/text/StringView.h"
 #include "platform/wtf/text/WTFString.h"
 #include "v8/include/v8-inspector.h"
@@ -44,6 +45,9 @@
   static String fromDouble(double number) {
     return Decimal::FromDouble(number).ToString();
   }
+  static double toDouble(const char* s, size_t len, bool* ok) {
+    return WTF::CharactersToDouble(reinterpret_cast<const LChar*>(s), len, ok);
+  }
   static size_t find(const String& s, const char* needle) {
     return s.Find(needle);
   }
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 8e58a02..62492de 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
@@ -49,6 +49,11 @@
   origin_point.block_offset += content_size;
   return origin_point;
 }
+
+inline bool IsObjectReplacementCharacter(UChar character) {
+  return character == kObjectReplacementCharacter;
+}
+
 }  // namespace
 
 NGInlineLayoutAlgorithm::NGInlineLayoutAlgorithm(
@@ -474,7 +479,12 @@
   baseline = LayoutUnit(baseline.Round());
 
   // Check if the line fits into the constraint space in block direction.
-  LayoutUnit line_bottom = baseline + line_box.Metrics().descent;
+  LayoutUnit line_bottom = baseline;
+
+  // See http://crrev.com/2840883002
+  if (!Node()->Text().IsAllSpecialCharacters<IsObjectReplacementCharacter>())
+    line_bottom += line_box.Metrics().descent;
+
   if (!container_builder_.Children().IsEmpty() &&
       ConstraintSpace().AvailableSize().block_size != NGSizeIndefinite &&
       line_bottom > ConstraintSpace().AvailableSize().block_size) {
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc
index 9dae0ce..90b6555 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc
@@ -416,10 +416,13 @@
   PositionFloats(curr_bfc_offset_.block_offset, curr_bfc_offset_.block_offset,
                  container_builder_.UnpositionedFloats(), tmp_space.Get());
 
+  NGLogicalOffset origin_offset = curr_bfc_offset_;
+  origin_offset.inline_offset += border_and_padding_.inline_start;
+
   // 2. Find an estimated layout opportunity for our fragment.
   NGLayoutOpportunity opportunity = FindLayoutOpportunityForFragment(
-      tmp_space->Exclusions().get(), child_space.AvailableSize(),
-      curr_bfc_offset_, curr_child_margins_, fragment);
+      tmp_space->Exclusions().get(), child_space.AvailableSize(), origin_offset,
+      curr_child_margins_, fragment);
 
   // 3. If the found opportunity lies on the same line with our estimated
   //    child's BFC offset then merge fragment's margins with the current
@@ -435,11 +438,14 @@
   PositionPendingFloats(curr_bfc_offset_.block_offset, &container_builder_,
                         MutableConstraintSpace());
 
+  origin_offset = curr_bfc_offset_;
+  origin_offset.inline_offset += border_and_padding_.inline_start;
+
   // 5. Find the final layout opportunity for the fragment after all pending
   // floats are positioned at the correct BFC block's offset.
   opportunity = FindLayoutOpportunityForFragment(
       MutableConstraintSpace()->Exclusions().get(), child_space.AvailableSize(),
-      curr_bfc_offset_, curr_child_margins_, fragment);
+      origin_offset, curr_child_margins_, fragment);
 
   curr_bfc_offset_ = opportunity.offset;
   return curr_bfc_offset_;
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_layout_opportunity_iterator.cc b/third_party/WebKit/Source/core/layout/ng/ng_layout_opportunity_iterator.cc
index dce977f6..5a7c894 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_layout_opportunity_iterator.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_layout_opportunity_iterator.cc
@@ -271,10 +271,10 @@
   NGLayoutOpportunity opportunity_candidate = opportunity_iter.Next();
   while (!opportunity_candidate.IsEmpty()) {
     opportunity = opportunity_candidate;
-    // Checking opportunity's block size is not necessary as a float cannot be
-    // positioned on top of another float inside of the same constraint space.
     auto fragment_inline_size = fragment.InlineSize() + margins.InlineSum();
-    if (opportunity.size.inline_size >= fragment_inline_size)
+    auto fragment_block_size = fragment.BlockSize() + margins.BlockSum();
+    if (opportunity.size.inline_size >= fragment_inline_size &&
+        opportunity.size.block_size >= fragment_block_size)
       break;
     opportunity_candidate = opportunity_iter.Next();
   }
diff --git a/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.cpp b/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.cpp
index 97e66fc..e6d6844b 100644
--- a/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.cpp
@@ -207,8 +207,8 @@
   // Step 9. Let module script be the result of creating a module script given
   // source text, module map settings object, response's url, cryptographic
   // nonce, parser state, and credentials mode.
-  module_script_ = CreateModuleScript(
-      source_text, GetResource()->GetResponse().Url(), modulator_, nonce_,
+  module_script_ = ModuleScript::Create(
+      source_text, modulator_, GetResource()->GetResponse().Url(), nonce_,
       parser_state_,
       GetResource()->GetResourceRequest().GetFetchCredentialsMode(),
       access_control_status);
@@ -216,38 +216,6 @@
   AdvanceState(State::kFinished);
 }
 
-// https://html.spec.whatwg.org/#creating-a-module-script
-ModuleScript* ModuleScriptLoader::CreateModuleScript(
-    const String& source_text,
-    const KURL& url,
-    Modulator* modulator,
-    const String& nonce,
-    ParserDisposition parser_state,
-    WebURLRequest::FetchCredentialsMode credentials_mode,
-    AccessControlStatus access_control_status) {
-  // Step 1. Let script be a new module script that this algorithm will
-  // subsequently initialize.
-  // Step 2. Set script's settings object to the environment settings object
-  // provided.
-  // Note: "script's settings object" will be "modulator".
-
-  // Delegate to Modulator::compileModule to process Steps 3-6.
-  ScriptModule result = modulator->CompileModule(source_text, url.GetString(),
-                                                 access_control_status);
-  // Step 6: "...return null, and abort these steps."
-  if (result.IsNull())
-    return nullptr;
-  // Step 7. Set script's module record to result.
-  // Step 8. Set script's base URL to the script base URL provided.
-  // Step 9. Set script's cryptographic nonce to the cryptographic nonce
-  // provided.
-  // Step 10. Set script's parser state to the parser state.
-  // Step 11. Set script's credentials mode to the credentials mode provided.
-  // Step 12. Return script.
-  return ModuleScript::Create(modulator, result, url, nonce, parser_state,
-                              credentials_mode);
-}
-
 DEFINE_TRACE(ModuleScriptLoader) {
   visitor->Trace(modulator_);
   visitor->Trace(module_script_);
diff --git a/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.h b/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.h
index 7115de85..c317983 100644
--- a/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.h
+++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoader.h
@@ -69,14 +69,6 @@
                      ModuleScriptLoaderRegistry*,
                      ModuleScriptLoaderClient*);
 
-  static ModuleScript* CreateModuleScript(const String& source_text,
-                                          const KURL&,
-                                          Modulator*,
-                                          const String& nonce,
-                                          ParserDisposition,
-                                          WebURLRequest::FetchCredentialsMode,
-                                          AccessControlStatus);
-
   void AdvanceState(State new_state);
 #if DCHECK_IS_ON()
   static const char* StateToString(State);
diff --git a/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp
index 80d66873..845e532 100644
--- a/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp
+++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp
@@ -76,9 +76,9 @@
     ScriptModule script_module = ScriptModule::Compile(
         script_state_->GetIsolate(), source_text.ToString(), url.GetString(),
         kSharableCrossOrigin);
-    ModuleScript* module_script =
-        ModuleScript::Create(this, script_module, url, "", kParserInserted,
-                             WebURLRequest::kFetchCredentialsModeOmit);
+    ModuleScript* module_script = ModuleScript::CreateForTest(
+        this, script_module, url, "", kParserInserted,
+        WebURLRequest::kFetchCredentialsModeOmit);
     auto result_request = dependency_module_requests_map_.insert(
         script_module, dependency_module_requests);
     EXPECT_TRUE(result_request.is_new_entry);
@@ -121,9 +121,9 @@
     ScriptModule script_module = ScriptModule::Compile(
         script_state_->GetIsolate(), "export default 'pineapples';",
         url.GetString(), kSharableCrossOrigin);
-    ModuleScript* module_script =
-        ModuleScript::Create(this, script_module, url, "", kParserInserted,
-                             WebURLRequest::kFetchCredentialsModeOmit);
+    ModuleScript* module_script = ModuleScript::CreateForTest(
+        this, script_module, url, "", kParserInserted,
+        WebURLRequest::kFetchCredentialsModeOmit);
     auto result_map = module_map_.insert(url, module_script);
     EXPECT_TRUE(result_map.is_new_entry);
 
diff --git a/third_party/WebKit/Source/core/style/ComputedStyle.cpp b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
index 8bb66a3e..7c4e644c 100644
--- a/third_party/WebKit/Source/core/style/ComputedStyle.cpp
+++ b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
@@ -338,8 +338,6 @@
   // See comments for each skipped flag below.
 
   // These are not generated in ComputedStyleBase
-  SetOriginalDisplay(other.OriginalDisplay());
-  SetVerticalAlign(other.VerticalAlign());
   SetHasViewportUnits(other.HasViewportUnits());
   SetHasRemUnitsInternal(other.HasRemUnits());
 
@@ -472,10 +470,6 @@
 bool ComputedStyle::NonInheritedEqual(const ComputedStyle& other) const {
   // compare everything except the pseudoStyle pointer
   return ComputedStyleBase::NonInheritedEqual(other) &&
-         OriginalDisplay() ==
-             other.OriginalDisplay() &&  // Not generated in ComputedStyleBase
-         VerticalAlign() == other.VerticalAlign() &&  // Not generated in
-                                                      // ComputedStyleBase
          box_data_ == other.box_data_ &&
          visual_data_ == other.visual_data_ &&
          rare_non_inherited_data_ == other.rare_non_inherited_data_ &&
diff --git a/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp b/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp
index f4f659d9..8c1bb05f 100644
--- a/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp
+++ b/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp
@@ -452,6 +452,7 @@
   ScriptLoader* script_loader =
       ScriptElementBase::FromElementIfPossible(e)->Loader();
   DCHECK(script_loader);
+  CHECK_EQ(script_loader->GetScriptType(), ScriptType::kClassic);
 
   if (error_occurred) {
     script_loader->DispatchErrorEvent();
@@ -1112,9 +1113,16 @@
   DCHECK(!pending_script_);
   requesting_script_ = true;
 
-  if (script_loader->PrepareScript(
-          script_start_position_,
-          ScriptLoader::kAllowLegacyTypeInTypeAttribute)) {
+  bool success = script_loader->PrepareScript(
+      script_start_position_, ScriptLoader::kAllowLegacyTypeInTypeAttribute);
+
+  if (script_loader->GetScriptType() != ScriptType::kClassic) {
+    // XMLDocumentParser does not support a module script, and thus ignores it.
+    success = false;
+    VLOG(0) << "Module scripts in XML documents are not supported.";
+  }
+
+  if (success) {
     // FIXME: Script execution should be shared between
     // the libxml2 and Qt XMLDocumentParser implementations.
 
diff --git a/third_party/WebKit/Source/devtools/front_end/common/ParsedURL.js b/third_party/WebKit/Source/devtools/front_end/common/ParsedURL.js
index a6629289..ba434c5 100644
--- a/third_party/WebKit/Source/devtools/front_end/common/ParsedURL.js
+++ b/third_party/WebKit/Source/devtools/front_end/common/ParsedURL.js
@@ -48,12 +48,12 @@
     var match = url.match(Common.ParsedURL._urlRegex());
     if (match) {
       this.isValid = true;
-      this.scheme = match[1].toLowerCase();
-      this.host = match[2];
-      this.port = match[3];
-      this.path = match[4] || '/';
-      this.queryParams = match[5] || '';
-      this.fragment = match[6];
+      this.scheme = match[2].toLowerCase();
+      this.host = match[3];
+      this.port = match[4];
+      this.path = match[5] || '/';
+      this.queryParams = match[6] || '';
+      this.fragment = match[7];
     } else {
       if (this.url.startsWith('data:')) {
         this.scheme = 'data';
@@ -97,12 +97,13 @@
     if (Common.ParsedURL._urlRegexInstance)
       return Common.ParsedURL._urlRegexInstance;
     // RegExp groups:
-    // 1 - scheme (using the RFC3986 grammar)
-    // 2 - hostname
-    // 3 - ?port
-    // 4 - ?path
-    // 5 - ?query
-    // 6 - ?fragment
+    // 1 - scheme, hostname, ?port
+    // 2 - scheme (using the RFC3986 grammar)
+    // 3 - hostname
+    // 4 - ?port
+    // 5 - ?path
+    // 6 - ?query
+    // 7 - ?fragment
     var schemeRegex = /([A-Za-z][A-Za-z0-9+.-]*):\/\//;
     var hostRegex = /([^\s\/:]*)/;
     var portRegex = /(?::([\d]+))?/;
@@ -111,7 +112,7 @@
     var fragmentRegex = /(?:#(.*))?/;
 
     Common.ParsedURL._urlRegexInstance = new RegExp(
-        '^' + schemeRegex.source + hostRegex.source + portRegex.source + pathRegex.source + queryRegex.source +
+        '^(' + schemeRegex.source + hostRegex.source + portRegex.source + ')' + pathRegex.source + queryRegex.source +
         fragmentRegex.source + '$');
     return Common.ParsedURL._urlRegexInstance;
   }
@@ -210,8 +211,17 @@
    * @return {!{url: string, lineNumber: (number|undefined), columnNumber: (number|undefined)}}
    */
   static splitLineAndColumn(string) {
+    // Only look for line and column numbers in the path to avoid matching port numbers.
+    var beforePathMatch = string.match(Common.ParsedURL._urlRegex());
+    var beforePath = '';
+    var pathAndAfter = string;
+    if (beforePathMatch) {
+      beforePath = beforePathMatch[1];
+      pathAndAfter = string.substring(beforePathMatch[1].length);
+    }
+
     var lineColumnRegEx = /(?::(\d+))?(?::(\d+))?$/;
-    var lineColumnMatch = lineColumnRegEx.exec(string);
+    var lineColumnMatch = lineColumnRegEx.exec(pathAndAfter);
     var lineNumber;
     var columnNumber;
     console.assert(lineColumnMatch);
@@ -227,7 +237,7 @@
     }
 
     return {
-      url: string.substring(0, string.length - lineColumnMatch[0].length),
+      url: beforePath + pathAndAfter.substring(0, pathAndAfter.length - lineColumnMatch[0].length),
       lineNumber: lineNumber,
       columnNumber: columnNumber
     };
diff --git a/third_party/WebKit/Source/devtools/front_end/quick_open/module.json b/third_party/WebKit/Source/devtools/front_end/quick_open/module.json
index 469f08d..58de963 100644
--- a/third_party/WebKit/Source/devtools/front_end/quick_open/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/quick_open/module.json
@@ -2,7 +2,7 @@
     "extensions": [
         {
             "type": "@QuickOpen.FilteredListWidget.Provider",
-            "title": "Run a command",
+            "title": "Run command",
             "prefix": ">",
             "className": "QuickOpen.CommandMenuProvider"
         },
@@ -29,7 +29,7 @@
         {
             "type": "@UI.ActionDelegate",
             "actionId": "quickOpen.show",
-            "title": "Go to file...",
+            "title": "Open file",
             "className": "QuickOpen.QuickOpen.ShowActionDelegate",
             "order": 100,
             "bindings": [
@@ -42,6 +42,11 @@
                     "shortcut": "Ctrl+P Ctrl+O"
                 }
             ]
+        },
+        {
+            "type": "context-menu-item",
+            "location": "mainMenu/navigate",
+            "actionId": "quickOpen.show"
         }
     ],
     "dependencies": [
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js b/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js
index 86fd83ac..84f38c1 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js
@@ -54,6 +54,7 @@
     this.textEditor.element.addEventListener('keydown', this._onKeyDown.bind(this), true);
     this.textEditor.element.addEventListener('keyup', this._onKeyUp.bind(this), true);
     this.textEditor.element.addEventListener('mousemove', this._onMouseMove.bind(this), false);
+    this.textEditor.element.addEventListener('mousedown', this._onMouseDown.bind(this), true);
     if (Runtime.experiments.isEnabled('continueToLocationMarkers')) {
       this.textEditor.element.addEventListener('wheel', event => {
         if (UI.KeyboardShortcut.eventHasCtrlOrMeta(event))
@@ -92,6 +93,8 @@
     this.onBindingChanged();
     Bindings.debuggerWorkspaceBinding.addEventListener(
         Bindings.DebuggerWorkspaceBinding.Events.SourceMappingChanged, this._onSourceMappingChanged, this);
+    /** @type {?Map<!Object, !Function>} */
+    this._continueToLocationDecorations = null;
   }
 
   /**
@@ -382,10 +385,12 @@
   }
 
   /**
-   * @param {!Event} event
+   * @param {!MouseEvent} event
    * @return {?UI.PopoverRequest}
    */
   _getPopoverRequest(event) {
+    if (UI.KeyboardShortcut.eventHasCtrlOrMeta(event))
+      return null;
     var target = UI.context.flavor(SDK.Target);
     var debuggerModel = target ? target.model(SDK.DebuggerModel) : null;
     if (!debuggerModel || !debuggerModel.isPaused())
@@ -488,10 +493,8 @@
       return;
     }
     if (UI.KeyboardShortcut.eventHasCtrlOrMeta(event) && this._executionLocation) {
-      if (!this._continueToLocationShown) {
+      if (!this._continueToLocationDecorations)
         this._showContinueToLocations();
-        this._continueToLocationShown = true;
-      }
     }
   }
 
@@ -500,11 +503,31 @@
    */
   _onMouseMove(event) {
     if (this._executionLocation && UI.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
-      if (!this._continueToLocationShown) {
+      if (!this._continueToLocationDecorations)
         this._showContinueToLocations();
-        this._continueToLocationShown = true;
-      }
+    }
+  }
+
+  /**
+   * @param {!MouseEvent} event
+   */
+  _onMouseDown(event) {
+    if (!this._executionLocation || !UI.KeyboardShortcut.eventHasCtrlOrMeta(event))
       return;
+    if (!this._continueToLocationDecorations)
+      return;
+    event.consume();
+    var textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y);
+    if (!textPosition)
+      return;
+    for (var decoration of this._continueToLocationDecorations.keys()) {
+      var range = decoration.find();
+      if (range.from.line !== textPosition.startLine || range.to.line !== textPosition.startLine)
+        continue;
+      if (range.from.ch <= textPosition.startColumn && textPosition.startColumn <= range.to.ch) {
+        this._continueToLocationDecorations.get(decoration)();
+        break;
+      }
     }
   }
 
@@ -514,10 +537,7 @@
   _onKeyUp(event) {
     if (UI.KeyboardShortcut.eventHasCtrlOrMeta(event))
       return;
-    if (!this._continueToLocationShown)
-      return;
     this._clearContinueToLocations();
-    this._continueToLocationShown = false;
   }
 
   /**
@@ -583,7 +603,7 @@
       setImmediate(() => {
         this._generateValuesInSource();
         if (Runtime.experiments.isEnabled('continueToLocationMarkers')) {
-          if (this._continueToLocationShown)
+          if (this._continueToLocationDecorations)
             this._showContinueToLocations();
         }
       });
@@ -616,78 +636,46 @@
   _showContinueToLocations() {
     if (!Runtime.experiments.isEnabled('continueToLocationMarkers'))
       return;
+    this._popoverHelper.hidePopover();
     var executionContext = UI.context.flavor(SDK.ExecutionContext);
     if (!executionContext)
       return;
     var callFrame = UI.context.flavor(SDK.DebuggerModel.CallFrame);
     if (!callFrame)
       return;
-    if (this._clearContinueToLocationsTimer) {
-      clearTimeout(this._clearContinueToLocationsTimer);
-      delete this._clearContinueToLocationsTimer;
-    }
     var localScope = callFrame.localScope();
     if (!localScope) {
-      this.textEditor.operation(clearExistingLocations.bind(this));
+      this._clearContinueToLocations();
       return;
     }
     var start = localScope.startLocation();
     var end = localScope.endLocation();
     var debuggerModel = callFrame.debuggerModel;
-    var executionLocation = callFrame.location();
     debuggerModel.getPossibleBreakpoints(start, end, true)
         .then(locations => this.textEditor.operation(renderLocations.bind(this, locations)));
+
     /**
      * @param {!Array<!SDK.DebuggerModel.BreakLocation>} locations
      * @this {Sources.JavaScriptSourceFrame}
      */
     function renderLocations(locations) {
-      clearExistingLocations.call(this);
+      this._clearContinueToLocations();
+      this._continueToLocationDecorations = new Map();
       for (var location of locations) {
-        var icon;
-        var isCurrent = location.lineNumber === executionLocation.lineNumber &&
-            location.columnNumber === executionLocation.columnNumber;
-        if (!isCurrent || (location.type !== SDK.DebuggerModel.BreakLocationType.Call &&
-                           location.type !== SDK.DebuggerModel.BreakLocationType.Return)) {
-          icon = UI.Icon.create('smallicon-green-arrow');
-          icon.addEventListener('click', location.continueToLocation.bind(location));
-        } else if (location.type === SDK.DebuggerModel.BreakLocationType.Call) {
-          icon = UI.Icon.create('smallicon-step-in');
-          icon.addEventListener('click', () => {
-            debuggerModel.scheduleStepIntoAsync();
-            debuggerModel.stepInto();
-          });
-        } else if (location.type === SDK.DebuggerModel.BreakLocationType.Return) {
-          icon = UI.Icon.create('smallicon-step-out');
-          icon.addEventListener('click', () => {
-            debuggerModel.stepOut();
-          });
-        }
-        icon.classList.add('cm-continue-to-location');
-        icon.addEventListener('mousemove', hidePopoverAndConsumeEvent.bind(this));
-        this.textEditor.addBookmark(
-            location.lineNumber, location.columnNumber, icon,
-            Sources.JavaScriptSourceFrame.continueToLocationDecorationSymbol);
+        var lineNumber = location.lineNumber;
+        var token = this.textEditor.tokenAtTextPosition(lineNumber, location.columnNumber);
+        if (!token || !token.type)
+          continue;
+        var line = this.textEditor.line(lineNumber);
+        var tokenContent = line.substring(token.startColumn, token.endColumn);
+        if (!this._isIdentifier(token.type) && (token.type !== 'js-keyword' || tokenContent !== 'this'))
+          continue;
+
+        var highlightRange = new TextUtils.TextRange(lineNumber, token.startColumn, lineNumber, token.endColumn - 1);
+        var decoration = this.textEditor.highlightRange(highlightRange, 'source-frame-continue-to-location');
+        this._continueToLocationDecorations.set(decoration, location.continueToLocation.bind(location));
       }
     }
-
-    /**
-     * @this {Sources.JavaScriptSourceFrame}
-     */
-    function clearExistingLocations() {
-      var bookmarks = this.textEditor.bookmarks(
-          this.textEditor.fullRange(), Sources.JavaScriptSourceFrame.continueToLocationDecorationSymbol);
-      bookmarks.map(bookmark => bookmark.clear());
-    }
-
-    /**
-     * @param {!Event} event
-     * @this {Sources.JavaScriptSourceFrame}
-     */
-    function hidePopoverAndConsumeEvent(event) {
-      event.consume(true);
-      this._popoverHelper.hidePopover();
-    }
   }
 
   /**
@@ -834,28 +822,32 @@
   }
 
   clearExecutionLine() {
-    if (this.loaded && this._executionLocation)
-      this.textEditor.clearExecutionLine();
-    delete this._executionLocation;
-    this._clearValueWidgetsTimer = setTimeout(this._clearValueWidgets.bind(this), 1000);
-    if (Runtime.experiments.isEnabled('continueToLocationMarkers'))
-      this._clearContinueToLocationsTimer = setTimeout(this._clearContinueToLocations.bind(this), 1000);
+    this.textEditor.operation(() => {
+      if (this.loaded && this._executionLocation)
+        this.textEditor.clearExecutionLine();
+      delete this._executionLocation;
+      this._clearValueWidgetsTimer = setTimeout(this._clearValueWidgets.bind(this), 1000);
+      this._clearContinueToLocations();
+    });
   }
 
   _clearValueWidgets() {
     delete this._clearValueWidgetsTimer;
-    for (var line of this._valueWidgets.keys())
-      this.textEditor.removeDecoration(this._valueWidgets.get(line), line);
-    this._valueWidgets.clear();
+    this.textEditor.operation(() => {
+      for (var line of this._valueWidgets.keys())
+        this.textEditor.removeDecoration(this._valueWidgets.get(line), line);
+      this._valueWidgets.clear();
+    });
   }
 
   _clearContinueToLocations() {
-    if (!Runtime.experiments.isEnabled('continueToLocationMarkers'))
+    if (!this._continueToLocationDecorations)
       return;
-    delete this._clearContinueToLocationsTimer;
-    var bookmarks = this.textEditor.bookmarks(
-        this.textEditor.fullRange(), Sources.JavaScriptSourceFrame.continueToLocationDecorationSymbol);
-    this.textEditor.operation(() => bookmarks.map(bookmark => bookmark.clear()));
+    this.textEditor.operation(() => {
+      for (var decoration of this._continueToLocationDecorations.keys())
+        this.textEditor.removeHighlight(decoration);
+      delete this._continueToLocationDecorations;
+    });
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/SourcesView.js b/third_party/WebKit/Source/devtools/front_end/sources/SourcesView.js
index 1736ddc..13db19a 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/SourcesView.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/SourcesView.js
@@ -26,12 +26,8 @@
     /** @type {!Map.<!Workspace.UISourceCode, !UI.Widget>} */
     this._sourceViewByUISourceCode = new Map();
 
-    var tabbedEditorPlaceholderText =
-        Host.isMac() ? Common.UIString('Hit \u2318+P to open a file') : Common.UIString('Hit Ctrl+P to open a file');
-    if (Runtime.experiments.isEnabled('persistence2'))
-      tabbedEditorPlaceholderText += '\n\n' + Common.UIString('Drop in a folder to add to workspace');
     this._editorContainer = new Sources.TabbedEditorContainer(
-        this, Common.settings.createLocalSetting('previouslyViewedFiles', []), tabbedEditorPlaceholderText);
+        this, Common.settings.createLocalSetting('previouslyViewedFiles', []), this._placeholderElement());
     this._editorContainer.show(this._searchableView.element);
     this._editorContainer.addEventListener(
         Sources.TabbedEditorContainer.Events.EditorSelected, this._editorSelected, this);
@@ -95,6 +91,28 @@
   }
 
   /**
+   * @return {!Element}
+   */
+  _placeholderElement() {
+    var shortcuts = [
+      {actionId: 'quickOpen.show', description: Common.UIString('Open file')},
+      {actionId: 'commandMenu.show', description: Common.UIString('Run command')}
+    ];
+
+    var element = createElementWithClass('span', 'tabbed-pane-placeholder');
+    for (var shortcut of shortcuts) {
+      var shortcutKeyText = UI.shortcutRegistry.shortcutTitleForAction(shortcut.actionId);
+      var row = element.createChild('div', 'tabbed-pane-placeholder-row');
+      row.createChild('div', 'tabbed-pane-placeholder-key').textContent = shortcutKeyText;
+      row.createChild('div', 'tabbed-pane-placeholder-value').textContent = shortcut.description;
+    }
+    if (Runtime.experiments.isEnabled('persistence2'))
+      element.createChild('div').textContent = Common.UIString('Drop in a folder to add to workspace');
+
+    return element;
+  }
+
+  /**
    * @return {!Map.<!Workspace.UISourceCode, number>}
    */
   static defaultUISourceCodeScores() {
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/TabbedEditorContainer.js b/third_party/WebKit/Source/devtools/front_end/sources/TabbedEditorContainer.js
index 051eaad..7906348 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/TabbedEditorContainer.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/TabbedEditorContainer.js
@@ -45,14 +45,14 @@
   /**
    * @param {!Sources.TabbedEditorContainerDelegate} delegate
    * @param {!Common.Setting} setting
-   * @param {string} placeholderText
+   * @param {!Element} placeholderElement
    */
-  constructor(delegate, setting, placeholderText) {
+  constructor(delegate, setting, placeholderElement) {
     super();
     this._delegate = delegate;
 
     this._tabbedPane = new UI.TabbedPane();
-    this._tabbedPane.setPlaceholderText(placeholderText);
+    this._tabbedPane.setPlaceholderElement(placeholderElement);
     this._tabbedPane.setTabDelegate(new Sources.EditorContainerTabDelegate(this));
 
     this._tabbedPane.setCloseableTabs(true);
diff --git a/third_party/WebKit/Source/devtools/front_end/text_editor/cmdevtools.css b/third_party/WebKit/Source/devtools/front_end/text_editor/cmdevtools.css
index 004fa99..f1a8984b 100644
--- a/third_party/WebKit/Source/devtools/front_end/text_editor/cmdevtools.css
+++ b/third_party/WebKit/Source/devtools/front_end/text_editor/cmdevtools.css
@@ -28,16 +28,39 @@
     background-color: rgb(255, 255, 194);
 }
 
+.CodeMirror .source-frame-eval-expression-start {
+    border-left-width: 1px;
+    margin-left: -1px;
+}
+
 .CodeMirror .source-frame-eval-expression-end {
     border-right-width: 1px;
     margin-right: -1px;
 }
 
-.CodeMirror .source-frame-eval-expression-start {
+.CodeMirror .source-frame-continue-to-location {
+    outline: 0;
+    border: 1px solid transparent;
+    border-left-width: 0;
+    border-right-width: 0;
+    background-color: rgb(230, 236, 255);
+    cursor: pointer;
+}
+
+.CodeMirror .source-frame-continue-to-location:hover {
+    border: 1px solid rgb(121, 141, 254);
+    background-color: rgb(171, 191, 254);
+}
+.CodeMirror .source-frame-continue-to-location-start {
     border-left-width: 1px;
     margin-left: -1px;
 }
 
+.CodeMirror .source-frame-continue-to-location-end {
+    border-right-width: 1px;
+    margin-right: -1px;
+}
+
 .CodeMirror-readonly .CodeMirror-cursor {
     display: none;
 }
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/Popover.js b/third_party/WebKit/Source/devtools/front_end/ui/Popover.js
index 6dd7e1ac..c5994a7a 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/Popover.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/Popover.js
@@ -34,7 +34,7 @@
 UI.PopoverHelper = class {
   /**
    * @param {!Element} container
-   * @param {function(!Event):?UI.PopoverRequest} getRequest
+   * @param {function(!MouseEvent):?UI.PopoverRequest} getRequest
    */
   constructor(container, getRequest) {
     this._disableOnClick = false;
@@ -93,7 +93,7 @@
 
     this._startHidePopoverTimer(0);
     this._stopShowPopoverTimer();
-    this._startShowPopoverTimer(event, 0);
+    this._startShowPopoverTimer(/** @type {!MouseEvent} */ (event), 0);
   }
 
   /**
@@ -108,7 +108,8 @@
     this._stopShowPopoverTimer();
     if (event.which && this._disableOnClick)
       return;
-    this._startShowPopoverTimer(event, this.isPopoverVisible() ? this._showTimeout * 0.6 : this._showTimeout);
+    this._startShowPopoverTimer(
+        /** @type {!MouseEvent} */ (event), this.isPopoverVisible() ? this._showTimeout * 0.6 : this._showTimeout);
   }
 
   /**
@@ -154,7 +155,7 @@
   }
 
   /**
-   * @param {!Event} event
+   * @param {!MouseEvent} event
    * @param {number} timeout
    */
   _startShowPopoverTimer(event, timeout) {
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/TabbedPane.js b/third_party/WebKit/Source/devtools/front_end/ui/TabbedPane.js
index a578b774..81bbe25 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/TabbedPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/TabbedPane.js
@@ -489,10 +489,15 @@
   }
 
   /**
-   * @param {string} text
+   * @param {!Element} element
    */
-  setPlaceholderText(text) {
-    this._noTabsMessage = text;
+  setPlaceholderElement(element) {
+    this._placeholderElement = element;
+
+    if (this._placeholderContainerElement) {
+      this._placeholderContainerElement.removeChildren();
+      this._placeholderContainerElement.appendChild(element);
+    }
   }
 
   _innerUpdateTabElements() {
@@ -501,15 +506,15 @@
 
     if (!this._tabs.length) {
       this._contentElement.classList.add('has-no-tabs');
-      if (this._noTabsMessage && !this._noTabsMessageElement) {
-        this._noTabsMessageElement = this._contentElement.createChild('div', 'tabbed-pane-placeholder fill');
-        this._noTabsMessageElement.textContent = this._noTabsMessage;
+      if (this._placeholderElement && !this._placeholderContainerElement) {
+        this._placeholderContainerElement = this._contentElement.createChild('div', 'tabbed-pane-placeholder fill');
+        this._placeholderContainerElement.appendChild(this._placeholderElement);
       }
     } else {
       this._contentElement.classList.remove('has-no-tabs');
-      if (this._noTabsMessageElement) {
-        this._noTabsMessageElement.remove();
-        delete this._noTabsMessageElement;
+      if (this._placeholderContainerElement) {
+        this._placeholderContainerElement.remove();
+        delete this._placeholderContainerElement;
       }
     }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/tabbedPane.css b/third_party/WebKit/Source/devtools/front_end/ui/tabbedPane.css
index ab6f320..da591de1 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/tabbedPane.css
+++ b/third_party/WebKit/Source/devtools/front_end/ui/tabbedPane.css
@@ -50,7 +50,27 @@
     text-align: center;
     margin-top: 20px;
     text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0;
-    white-space: pre;
+    line-height: 28px;
+    overflow: hidden;
+}
+
+.tabbed-pane-placeholder-row {
+    display: flex;
+    white-space: nowrap;
+}
+
+.tabbed-pane-placeholder-key {
+    flex: 1;
+    text-align: right;
+    padding-right: 14px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.tabbed-pane-placeholder-value {
+    flex: 1;
+    text-align: left;
+    padding-left: 14px;
 }
 
 .tabbed-pane-header {
diff --git a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp
index 3489a30..22cd88c7 100644
--- a/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp
+++ b/third_party/WebKit/Source/modules/canvas2d/CanvasRenderingContext2DTest.cpp
@@ -519,8 +519,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionByDefault) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -530,8 +530,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderOverdrawLimit) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -548,8 +548,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverOverdrawLimit) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -566,8 +566,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderImageSizeRatioLimit) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -600,8 +600,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverImageSizeRatioLimit) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -636,8 +636,8 @@
 TEST_F(CanvasRenderingContext2DTest,
        NoLayerPromotionUnderExpensivePathPointCount) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -660,8 +660,8 @@
 TEST_F(CanvasRenderingContext2DTest,
        LayerPromotionOverExpensivePathPointCount) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -683,8 +683,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, LayerPromotionWhenPathIsConcave) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -705,8 +705,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionWithRectangleClip) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -721,8 +721,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, LayerPromotionWithComplexClip) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -744,8 +744,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, LayerPromotionWithBlurredShadow) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -763,8 +763,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionWithSharpShadow) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -778,8 +778,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, NoFallbackWithSmallState) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -798,8 +798,8 @@
 
 TEST_F(CanvasRenderingContext2DTest, FallbackWithLargeState) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -822,8 +822,8 @@
   // does not support pixel geometry settings.
   // See: crbug.com/583809
   CreateContext(kOpaque);
-  auto surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
@@ -835,8 +835,8 @@
 TEST_F(CanvasRenderingContext2DTest,
        NonOpaqueDisplayListDoesNotFallBackForText) {
   CreateContext(kNonOpaque);
-  std::unique_ptr<RecordingImageBufferSurface> surface =
-      WTF::MakeUnique<RecordingImageBufferSurface>(IntSize(10, 10), kNonOpaque);
+  auto surface = WTF::MakeUnique<RecordingImageBufferSurface>(
+      IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback, kNonOpaque);
   auto* surface_ptr = surface.get();
   CanvasElement().CreateImageBufferUsingSurfaceForTesting(std::move(surface));
 
diff --git a/third_party/WebKit/Source/modules/csspaint/CSSPaintDefinition.cpp b/third_party/WebKit/Source/modules/csspaint/CSSPaintDefinition.cpp
index 4bbff95..25bbd22 100644
--- a/third_party/WebKit/Source/modules/csspaint/CSSPaintDefinition.cpp
+++ b/third_party/WebKit/Source/modules/csspaint/CSSPaintDefinition.cpp
@@ -91,7 +91,8 @@
 
   PaintRenderingContext2D* rendering_context = PaintRenderingContext2D::Create(
       ImageBuffer::Create(WTF::WrapUnique(new RecordingImageBufferSurface(
-          size, has_alpha_ ? kNonOpaque : kOpaque))),
+          size, RecordingImageBufferSurface::kDisallowFallback,
+          has_alpha_ ? kNonOpaque : kOpaque))),
       has_alpha_, zoom);
   PaintSize* paint_size = PaintSize::Create(specified_size);
   StylePropertyMapReadonly* style_map =
diff --git a/third_party/WebKit/Source/modules/indexeddb/IDBObjectStore.cpp b/third_party/WebKit/Source/modules/indexeddb/IDBObjectStore.cpp
index e535711..c8784980 100644
--- a/third_party/WebKit/Source/modules/indexeddb/IDBObjectStore.cpp
+++ b/third_party/WebKit/Source/modules/indexeddb/IDBObjectStore.cpp
@@ -506,6 +506,8 @@
   serialized_value->ToWireBytes(wire_bytes);
   RefPtr<SharedBuffer> value_buffer = SharedBuffer::AdoptVector(wire_bytes);
 
+  request->StorePutOperationBlobs(serialized_value->BlobDataHandles());
+
   BackendDB()->Put(transaction_->Id(), Id(), WebData(value_buffer), blob_info,
                    key, static_cast<WebIDBPutMode>(put_mode),
                    request->CreateWebCallbacks().release(), index_ids,
diff --git a/third_party/WebKit/Source/modules/indexeddb/IDBRequest.cpp b/third_party/WebKit/Source/modules/indexeddb/IDBRequest.cpp
index bea4fbb..1ddd3180 100644
--- a/third_party/WebKit/Source/modules/indexeddb/IDBRequest.cpp
+++ b/third_party/WebKit/Source/modules/indexeddb/IDBRequest.cpp
@@ -241,6 +241,7 @@
 
 void IDBRequest::OnError(DOMException* error) {
   IDB_TRACE("IDBRequest::onError()");
+  ClearPutOperationBlobs();
   if (!ShouldEnqueueEvent())
     return;
 
@@ -289,6 +290,7 @@
 
 void IDBRequest::OnSuccess(IDBKey* idb_key) {
   IDB_TRACE("IDBRequest::onSuccess(IDBKey)");
+  ClearPutOperationBlobs();
   if (!ShouldEnqueueEvent())
     return;
 
@@ -360,6 +362,7 @@
 void IDBRequest::OnSuccessInternal(IDBAny* result) {
   DCHECK(GetExecutionContext());
   DCHECK(!pending_cursor_);
+  DCHECK(transit_blob_handles_.IsEmpty());
   SetResult(result);
   EnqueueEvent(Event::Create(EventTypeNames::success));
 }
diff --git a/third_party/WebKit/Source/modules/indexeddb/IDBRequest.h b/third_party/WebKit/Source/modules/indexeddb/IDBRequest.h
index 4f221aeb..69d4f1395 100644
--- a/third_party/WebKit/Source/modules/indexeddb/IDBRequest.h
+++ b/third_party/WebKit/Source/modules/indexeddb/IDBRequest.h
@@ -29,6 +29,8 @@
 #ifndef IDBRequest_h
 #define IDBRequest_h
 
+#include <memory>
+
 #include "bindings/core/v8/ActiveScriptWrappable.h"
 #include "bindings/core/v8/ScriptState.h"
 #include "bindings/core/v8/ScriptValue.h"
@@ -43,10 +45,10 @@
 #include "modules/indexeddb/IndexedDB.h"
 #include "platform/blob/BlobData.h"
 #include "platform/heap/Handle.h"
+#include "platform/wtf/HashMap.h"
 #include "public/platform/WebBlobInfo.h"
 #include "public/platform/modules/indexeddb/WebIDBCursor.h"
 #include "public/platform/modules/indexeddb/WebIDBTypes.h"
-#include <memory>
 
 namespace blink {
 
@@ -141,6 +143,11 @@
 
   IDBCursor* GetResultCursor() const;
 
+  void StorePutOperationBlobs(
+      HashMap<String, RefPtr<BlobDataHandle>> blob_handles) {
+    transit_blob_handles_ = std::move(blob_handles);
+  }
+
  protected:
   IDBRequest(ScriptState*, IDBAny* source, IDBTransaction*);
   void EnqueueEvent(Event*);
@@ -168,6 +175,8 @@
   void AckReceivedBlobs(const IDBValue*);
   void AckReceivedBlobs(const Vector<RefPtr<IDBValue>>&);
 
+  void ClearPutOperationBlobs() { transit_blob_handles_.clear(); }
+
   Member<IDBAny> source_;
   Member<IDBAny> result_;
   Member<DOMException> error_;
@@ -187,6 +196,8 @@
   Member<IDBKey> cursor_primary_key_;
   RefPtr<IDBValue> cursor_value_;
 
+  HashMap<String, RefPtr<BlobDataHandle>> transit_blob_handles_;
+
   bool did_fire_upgrade_needed_event_ = false;
   bool prevent_propagation_ = false;
   bool result_dirty_ = true;
diff --git a/third_party/WebKit/Source/modules/payments/PaymentDetailsInit.idl b/third_party/WebKit/Source/modules/payments/PaymentDetailsInit.idl
index 1f9ddb6..9a784e38 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentDetailsInit.idl
+++ b/third_party/WebKit/Source/modules/payments/PaymentDetailsInit.idl
@@ -5,5 +5,6 @@
 // https://w3c.github.io/browser-payment-api/#paymentdetailsinit-dictionary
 
 dictionary PaymentDetailsInit : PaymentDetailsBase {
+    DOMString id;
     required PaymentItem total;
 };
diff --git a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp
index 1c2bbb0..d245f988 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp
+++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp
@@ -39,6 +39,7 @@
 #include "modules/payments/PaymentsValidators.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "platform/RuntimeEnabledFeatures.h"
+#include "platform/UUID.h"
 #include "platform/mojo/MojoHelper.h"
 #include "platform/wtf/HashSet.h"
 #include "public/platform/InterfaceProvider.h"
@@ -536,6 +537,11 @@
     return;
   }
 
+  if (input.hasId())
+    output->id = input.id();
+  else
+    output->id = CreateCanonicalUUIDString();
+
   ValidateAndConvertTotal(input.total(), output->total, exception_state);
 }
 
@@ -880,6 +886,8 @@
   if (exception_state.HadException())
     return;
 
+  id_ = validated_details->id;
+
   if (options_.requestShipping())
     shipping_type_ = GetValidShippingType(options_.shippingType());
 
@@ -975,7 +983,7 @@
 
   complete_timer_.StartOneShot(kCompleteTimeoutSeconds, BLINK_FROM_HERE);
 
-  show_resolver_->Resolve(new PaymentResponse(std::move(response), this));
+  show_resolver_->Resolve(new PaymentResponse(std::move(response), this, id_));
 
   // Do not close the mojo connection here. The merchant website should call
   // PaymentResponse::complete(String), which will be forwarded over the mojo
diff --git a/third_party/WebKit/Source/modules/payments/PaymentRequest.h b/third_party/WebKit/Source/modules/payments/PaymentRequest.h
index f3c1319..a36b56b 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentRequest.h
+++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.h
@@ -61,6 +61,7 @@
   ScriptPromise show(ScriptState*);
   ScriptPromise abort(ScriptState*);
 
+  const String& id() const { return id_; }
   PaymentAddress* getShippingAddress() const { return shipping_address_.Get(); }
   const String& shippingOption() const { return shipping_option_; }
   const String& shippingType() const { return shipping_type_; }
@@ -116,6 +117,7 @@
 
   PaymentOptions options_;
   Member<PaymentAddress> shipping_address_;
+  String id_;
   String shipping_option_;
   String shipping_type_;
   Member<ScriptPromiseResolver> show_resolver_;
diff --git a/third_party/WebKit/Source/modules/payments/PaymentRequest.idl b/third_party/WebKit/Source/modules/payments/PaymentRequest.idl
index e3e06d2..50e698e 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentRequest.idl
+++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.idl
@@ -17,6 +17,7 @@
     [CallWith=ScriptState] Promise<void> abort();
     [CallWith=ScriptState, RuntimeEnabled=CanMakePayment] Promise<boolean> canMakePayment();
 
+    readonly attribute DOMString id;
     [ImplementedAs=getShippingAddress] readonly attribute PaymentAddress? shippingAddress;
     readonly attribute DOMString? shippingOption;
     readonly attribute PaymentShippingType? shippingType;
diff --git a/third_party/WebKit/Source/modules/payments/PaymentRequestTest.cpp b/third_party/WebKit/Source/modules/payments/PaymentRequestTest.cpp
index b52a250..c7d0851a 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentRequestTest.cpp
+++ b/third_party/WebKit/Source/modules/payments/PaymentRequestTest.cpp
@@ -631,5 +631,19 @@
   EXPECT_TRUE(request->shippingOption().IsNull());
 }
 
+TEST(PaymentRequestTest, DetailsIdIsSet) {
+  V8TestingScope scope;
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentDetailsInit details;
+  details.setTotal(BuildPaymentItemForTest());
+  details.setId("my_payment_id");
+
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(), details,
+      scope.GetExceptionState());
+
+  EXPECT_EQ("my_payment_id", request->id());
+}
+
 }  // namespace
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/payments/PaymentResponse.cpp b/third_party/WebKit/Source/modules/payments/PaymentResponse.cpp
index 379ecc9..f28abe3 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentResponse.cpp
+++ b/third_party/WebKit/Source/modules/payments/PaymentResponse.cpp
@@ -15,8 +15,10 @@
 
 PaymentResponse::PaymentResponse(
     payments::mojom::blink::PaymentResponsePtr response,
-    PaymentCompleter* payment_completer)
-    : method_name_(response->method_name),
+    PaymentCompleter* payment_completer,
+    const String& requestId)
+    : requestId_(requestId),
+      method_name_(response->method_name),
       stringified_details_(response->stringified_details),
       shipping_address_(
           response->shipping_address
@@ -34,6 +36,7 @@
 
 ScriptValue PaymentResponse::toJSONForBinding(ScriptState* script_state) const {
   V8ObjectBuilder result(script_state);
+  result.AddString("requestId", requestId());
   result.AddString("methodName", methodName());
   result.Add("details", details(script_state, ASSERT_NO_EXCEPTION));
 
diff --git a/third_party/WebKit/Source/modules/payments/PaymentResponse.h b/third_party/WebKit/Source/modules/payments/PaymentResponse.h
index f3a713f..e105768 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentResponse.h
+++ b/third_party/WebKit/Source/modules/payments/PaymentResponse.h
@@ -30,11 +30,13 @@
 
  public:
   PaymentResponse(payments::mojom::blink::PaymentResponsePtr,
-                  PaymentCompleter*);
+                  PaymentCompleter*,
+                  const String& requestId);
   virtual ~PaymentResponse();
 
   ScriptValue toJSONForBinding(ScriptState*) const;
 
+  const String& requestId() const { return requestId_; }
   const String& methodName() const { return method_name_; }
   ScriptValue details(ScriptState*, ExceptionState&) const;
   PaymentAddress* shippingAddress() const { return shipping_address_.Get(); }
@@ -48,6 +50,7 @@
   DECLARE_TRACE();
 
  private:
+  String requestId_;
   String method_name_;
   String stringified_details_;
   Member<PaymentAddress> shipping_address_;
diff --git a/third_party/WebKit/Source/modules/payments/PaymentResponse.idl b/third_party/WebKit/Source/modules/payments/PaymentResponse.idl
index 71284189..9d21cb9 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentResponse.idl
+++ b/third_party/WebKit/Source/modules/payments/PaymentResponse.idl
@@ -17,6 +17,7 @@
 ] interface PaymentResponse {
     serializer = {attribute};
 
+    readonly attribute DOMString requestId;
     readonly attribute DOMString methodName;
     [RuntimeEnabled=PaymentRequestPayerName] readonly attribute DOMString? payerName;
     readonly attribute DOMString? payerEmail;
diff --git a/third_party/WebKit/Source/modules/payments/PaymentResponseTest.cpp b/third_party/WebKit/Source/modules/payments/PaymentResponseTest.cpp
index b8fea86..e3984bc 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentResponseTest.cpp
+++ b/third_party/WebKit/Source/modules/payments/PaymentResponseTest.cpp
@@ -55,7 +55,7 @@
   MockPaymentCompleter* complete_callback = new MockPaymentCompleter;
 
   PaymentResponse* output =
-      new PaymentResponse(std::move(input), complete_callback);
+      new PaymentResponse(std::move(input), complete_callback, "");
 
   EXPECT_EQ("foo", output->methodName());
   EXPECT_EQ("standardShippingOption", output->shippingOption());
@@ -85,7 +85,7 @@
   input->stringified_details = "transactionId";
   MockPaymentCompleter* complete_callback = new MockPaymentCompleter;
   PaymentResponse* output =
-      new PaymentResponse(std::move(input), complete_callback);
+      new PaymentResponse(std::move(input), complete_callback, "");
 
   ScriptValue details =
       output->details(scope.GetScriptState(), scope.GetExceptionState());
@@ -101,7 +101,7 @@
   input->stringified_details = "{\"transactionId\": 123}";
   MockPaymentCompleter* complete_callback = new MockPaymentCompleter;
   PaymentResponse* output =
-      new PaymentResponse(std::move(input), complete_callback);
+      new PaymentResponse(std::move(input), complete_callback, "");
 
   EXPECT_CALL(*complete_callback,
               Complete(scope.GetScriptState(), PaymentCompleter::kSuccess));
@@ -117,7 +117,7 @@
   input->stringified_details = "{\"transactionId\": 123}";
   MockPaymentCompleter* complete_callback = new MockPaymentCompleter;
   PaymentResponse* output =
-      new PaymentResponse(std::move(input), complete_callback);
+      new PaymentResponse(std::move(input), complete_callback, "");
 
   EXPECT_CALL(*complete_callback,
               Complete(scope.GetScriptState(), PaymentCompleter::kFail));
@@ -144,7 +144,7 @@
   input->shipping_address->address_line.push_back("First floor");
 
   PaymentResponse* output =
-      new PaymentResponse(std::move(input), new MockPaymentCompleter);
+      new PaymentResponse(std::move(input), new MockPaymentCompleter, "");
   ScriptValue json_object = output->toJSONForBinding(scope.GetScriptState());
   EXPECT_TRUE(json_object.IsObject());
 
@@ -154,7 +154,8 @@
           .ToLocalChecked(),
       kDoNotExternalize);
   String expected =
-      "{\"methodName\":\"foo\",\"details\":{\"transactionId\":123},"
+      "{\"requestId\":\"\",\"methodName\":\"foo\",\"details\":{"
+      "\"transactionId\":123},"
       "\"shippingAddress\":{\"country\":\"US\",\"addressLine\":[\"340 Main "
       "St\","
       "\"BIN1\",\"First "
diff --git a/third_party/WebKit/Source/platform/BUILD.gn b/third_party/WebKit/Source/platform/BUILD.gn
index dff63a1..31e7f16d 100644
--- a/third_party/WebKit/Source/platform/BUILD.gn
+++ b/third_party/WebKit/Source/platform/BUILD.gn
@@ -2207,6 +2207,11 @@
 
     # TODO(toyoshim): Remove Platform dependency and move to loader/BUILD.gn
     "loader/fetch/ResourceResponseTest.cpp",
+
+    # TODO(hongchan): Platform::Current()->CreateThread() returns nullptr
+    # without adding the test file into this section. Remove platform dependency
+    # when creating a valid thread becomes possible in the Blink unit test.
+    "audio/PushPullFIFOMultithreadTest.cpp",
   ]
 
   configs += [
diff --git a/third_party/WebKit/Source/platform/audio/AudioDestination.cpp b/third_party/WebKit/Source/platform/audio/AudioDestination.cpp
index ff053c2..e71d5ec 100644
--- a/third_party/WebKit/Source/platform/audio/AudioDestination.cpp
+++ b/third_party/WebKit/Source/platform/audio/AudioDestination.cpp
@@ -29,7 +29,9 @@
 #include "platform/audio/AudioDestination.h"
 
 #include <memory>
+#include "platform/CrossThreadFunctional.h"
 #include "platform/Histogram.h"
+#include "platform/WebTaskRunner.h"
 #include "platform/audio/AudioUtilities.h"
 #include "platform/audio/PushPullFIFO.h"
 #include "platform/weborigin/SecurityOrigin.h"
@@ -37,6 +39,7 @@
 #include "public/platform/Platform.h"
 #include "public/platform/WebAudioLatencyHint.h"
 #include "public/platform/WebSecurityOrigin.h"
+#include "public/platform/WebThread.h"
 
 namespace blink {
 
@@ -64,14 +67,16 @@
                                    PassRefPtr<SecurityOrigin> security_origin)
     : number_of_output_channels_(number_of_output_channels),
       is_playing_(false),
-      callback_(callback),
+      rendering_thread_(WTF::WrapUnique(
+          Platform::Current()->CreateThread("WebAudio Rendering Thread"))),
+      fifo_(WTF::WrapUnique(
+          new PushPullFIFO(number_of_output_channels, kFIFOSize))),
       output_bus_(AudioBus::Create(number_of_output_channels,
                                    AudioUtilities::kRenderQuantumFrames,
                                    false)),
       render_bus_(AudioBus::Create(number_of_output_channels,
                                    AudioUtilities::kRenderQuantumFrames)),
-      fifo_(WTF::WrapUnique(
-          new PushPullFIFO(number_of_output_channels, kFIFOSize))),
+      callback_(callback),
       frames_elapsed_(0) {
   // Create WebAudioDevice. blink::WebAudioDevice is designed to support the
   // local input (e.g. loopback from OS audio system), but Chromium's media
@@ -97,6 +102,9 @@
                               double delay,
                               double delay_timestamp,
                               size_t prior_frames_skipped) {
+  // This method is called by AudioDeviceThread.
+  DCHECK(!IsRenderingThread());
+
   CHECK_EQ(destination_data.size(), number_of_output_channels_);
   CHECK_EQ(number_of_frames, callback_buffer_size_);
 
@@ -106,25 +114,36 @@
   if (!fifo_ || fifo_->length() < number_of_frames)
     return;
 
-  frames_elapsed_ -= std::min(frames_elapsed_, prior_frames_skipped);
-  double output_position =
-      frames_elapsed_ / static_cast<double>(web_audio_device_->SampleRate()) -
-      delay;
-  output_position_.position = output_position;
-  output_position_.timestamp = delay_timestamp;
-  output_position_received_timestamp_ = base::TimeTicks::Now();
-
   // Associate the destination data array with the output bus then fill the
   // FIFO.
   for (unsigned i = 0; i < number_of_output_channels_; ++i)
     output_bus_->SetChannelMemory(i, destination_data[i], number_of_frames);
 
-  // Number of frames to render via WebAudio graph. |framesToRender > 0| means
-  // the frames in FIFO is not enough to fulfill the requested frames from the
-  // audio device.
-  size_t frames_to_render = number_of_frames > fifo_->FramesAvailable()
-                                ? number_of_frames - fifo_->FramesAvailable()
-                                : 0;
+  size_t frames_to_render = fifo_->Pull(output_bus_.Get(), number_of_frames);
+
+  rendering_thread_->GetWebTaskRunner()->PostTask(
+      BLINK_FROM_HERE,
+      CrossThreadBind(&AudioDestination::RequestRenderOnWebThread,
+                      CrossThreadUnretained(this),
+                      number_of_frames, frames_to_render,
+                      delay, delay_timestamp, prior_frames_skipped));
+}
+
+void AudioDestination::RequestRenderOnWebThread(size_t frames_requested,
+                                                size_t frames_to_render,
+                                                double delay,
+                                                double delay_timestamp,
+                                                size_t prior_frames_skipped) {
+  // This method is called by WebThread.
+  DCHECK(IsRenderingThread());
+
+  frames_elapsed_ -= std::min(frames_elapsed_, prior_frames_skipped);
+  AudioIOPosition output_position;
+  output_position.position =
+      frames_elapsed_ / static_cast<double>(web_audio_device_->SampleRate()) -
+      delay;
+  output_position.timestamp = delay_timestamp;
+  base::TimeTicks received_timestamp = base::TimeTicks::Now();
 
   for (size_t pushed_frames = 0; pushed_frames < frames_to_render;
        pushed_frames += AudioUtilities::kRenderQuantumFrames) {
@@ -132,27 +151,23 @@
     // we do not want output position to get stuck so we promote it
     // using the elapsed time from the moment it was initially obtained.
     if (callback_buffer_size_ > AudioUtilities::kRenderQuantumFrames * 2) {
-      double delta =
-          (base::TimeTicks::Now() - output_position_received_timestamp_)
-              .InSecondsF();
-      output_position_.position += delta;
-      output_position_.timestamp += delta;
+      double delta = (base::TimeTicks::Now() - received_timestamp).InSecondsF();
+      output_position.position += delta;
+      output_position.timestamp += delta;
     }
 
     // Some implementations give only rough estimation of |delay| so
     // we might have negative estimation |outputPosition| value.
-    if (output_position_.position < 0.0)
-      output_position_.position = 0.0;
+    if (output_position.position < 0.0)
+      output_position.position = 0.0;
 
     // Process WebAudio graph and push the rendered output to FIFO.
     callback_.Render(nullptr, render_bus_.Get(),
-                     AudioUtilities::kRenderQuantumFrames, output_position_);
+                     AudioUtilities::kRenderQuantumFrames, output_position);
     fifo_->Push(render_bus_.Get());
   }
 
-  fifo_->Pull(output_bus_.Get(), number_of_frames);
-
-  frames_elapsed_ += number_of_frames;
+  frames_elapsed_ += frames_requested;
 }
 
 void AudioDestination::Start() {
@@ -204,4 +219,9 @@
   return is_buffer_size_valid;
 }
 
+bool AudioDestination::IsRenderingThread() {
+  return static_cast<ThreadIdentifier>(rendering_thread_->ThreadId()) ==
+         CurrentThread();
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/audio/AudioDestination.h b/third_party/WebKit/Source/platform/audio/AudioDestination.h
index 6bf30f3..64797f22 100644
--- a/third_party/WebKit/Source/platform/audio/AudioDestination.h
+++ b/third_party/WebKit/Source/platform/audio/AudioDestination.h
@@ -43,6 +43,7 @@
 class PushPullFIFO;
 class SecurityOrigin;
 class WebAudioLatencyHint;
+class WebThread;
 
 // The AudioDestination class is an audio sink interface between the media
 // renderer and the Blink's WebAudio module. It has a FIFO to adapt the
@@ -73,6 +74,14 @@
               double delay_timestamp,
               size_t prior_frames_skipped) override;
 
+  // The actual render request to the WebAudio destination node. This triggers
+  // the WebAudio rendering pipe line on the web thread.
+  void RequestRenderOnWebThread(size_t frames_requested,
+                                size_t frames_to_render,
+                                double delay,
+                                double delay_timestamp,
+                                size_t prior_frames_skipped);
+
   virtual void Start();
   virtual void Stop();
 
@@ -90,32 +99,38 @@
   static unsigned long MaxChannelCount();
 
  private:
-  std::unique_ptr<WebAudioDevice> web_audio_device_;
-  unsigned number_of_output_channels_;
-  size_t callback_buffer_size_;
-  bool is_playing_;
-
-  // The render callback function of WebAudio engine. (i.e. DestinationNode)
-  AudioIOCallback& callback_;
-
-  // To pass the data from FIFO to the audio device callback.
-  RefPtr<AudioBus> output_bus_;
-
-  // To push the rendered result from WebAudio graph into the FIFO.
-  RefPtr<AudioBus> render_bus_;
-
-  // Resolves the buffer size mismatch between the WebAudio engine and
-  // the callback function from the actual audio device.
-  std::unique_ptr<PushPullFIFO> fifo_;
-
-  size_t frames_elapsed_;
-  AudioIOPosition output_position_;
-  base::TimeTicks output_position_received_timestamp_;
-
   // Check if the buffer size chosen by the WebAudioDevice is too large.
   bool CheckBufferSize();
 
   size_t HardwareBufferSize();
+
+  bool IsRenderingThread();
+
+  std::unique_ptr<WebAudioDevice> web_audio_device_;
+  const unsigned number_of_output_channels_;
+  size_t callback_buffer_size_;
+  bool is_playing_;
+
+  // Rendering thread for WebAudio graph.
+  std::unique_ptr<WebThread> rendering_thread_;
+
+  // Accessed by both threads: resolves the buffer size mismatch between the
+  // WebAudio engine and the callback function from the actual audio device.
+  std::unique_ptr<PushPullFIFO> fifo_;
+
+  // Accessed by device thread: to pass the data from FIFO to the device.
+  RefPtr<AudioBus> output_bus_;
+
+  // Accessed by rendering thread: to push the rendered result from WebAudio
+  // graph into the FIFO.
+  RefPtr<AudioBus> render_bus_;
+
+  // Accessed by rendering thread: the render callback function of WebAudio
+  // engine. (i.e. DestinationNode)
+  AudioIOCallback& callback_;
+
+  // Accessed by rendering thread.
+  size_t frames_elapsed_;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/audio/PushPullFIFO.cpp b/third_party/WebKit/Source/platform/audio/PushPullFIFO.cpp
index fdfafd7a..7d18c6e 100644
--- a/third_party/WebKit/Source/platform/audio/PushPullFIFO.cpp
+++ b/third_party/WebKit/Source/platform/audio/PushPullFIFO.cpp
@@ -19,21 +19,18 @@
 const size_t PushPullFIFO::kMaxFIFOLength = 65536;
 
 PushPullFIFO::PushPullFIFO(unsigned number_of_channels, size_t fifo_length)
-    : fifo_length_(fifo_length),
-      frames_available_(0),
-      index_read_(0),
-      index_write_(0),
-      overflow_count_(0),
-      underflow_count_(0) {
+    : fifo_length_(fifo_length) {
   CHECK_LE(fifo_length_, kMaxFIFOLength);
   fifo_bus_ = AudioBus::Create(number_of_channels, fifo_length_);
 }
 
 PushPullFIFO::~PushPullFIFO() {}
 
-// Push the data from |inputBus| to FIFO. The size of push is determined by
-// the length of |inputBus|.
+// Push the data from |input_bus| to FIFO. The size of push is determined by
+// the length of |input_bus|.
 void PushPullFIFO::Push(const AudioBus* input_bus) {
+  MutexLocker locker(lock_);
+
   CHECK(input_bus);
   CHECK_EQ(input_bus->length(), AudioUtilities::kRenderQuantumFrames);
   SECURITY_CHECK(input_bus->length() <= fifo_length_);
@@ -61,8 +58,8 @@
   // Update the write index; wrap it around if necessary.
   index_write_ = (index_write_ + input_bus_length) % fifo_length_;
 
-  // In case of overflow, move the |indexRead| to the updated |indexWrite| to
-  // avoid reading overwritten frames by the next pull.
+  // In case of overflow, move the |index_read_| to the updated |index_write_|
+  // to avoid reading overwritten frames by the next pull.
   if (input_bus_length > fifo_length_ - frames_available_) {
     index_read_ = index_write_;
     if (++overflow_count_ < kMaxMessagesToLog) {
@@ -80,16 +77,18 @@
   DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
 }
 
-// Pull the data out of FIFO to |outputBus|. If remaining frame in the FIFO
+// Pull the data out of FIFO to |output_bus|. If remaining frame in the FIFO
 // is less than the frames to pull, provides remaining frame plus the silence.
-void PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) {
+size_t PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) {
+  MutexLocker locker(lock_);
+
 #if OS(ANDROID)
   if (!output_bus) {
     // Log when outputBus or FIFO object is invalid. (crbug.com/692423)
     LOG(WARNING) << "[WebAudio/PushPullFIFO::pull <" << static_cast<void*>(this)
                  << ">] |outputBus| is invalid.";
     // Silently return to avoid crash.
-    return;
+    return 0;
   }
 
   // The following checks are in place to catch the inexplicable crash.
@@ -110,6 +109,7 @@
                  << " >= " << fifo_length_ << ")";
   }
 #endif
+
   CHECK(output_bus);
   SECURITY_CHECK(frames_requested <= output_bus->length());
   SECURITY_CHECK(frames_requested <= fifo_length_);
@@ -162,10 +162,16 @@
   // Update the number of frames in FIFO.
   frames_available_ -= frames_to_fill;
   DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
+
+  // |frames_requested > frames_available_| means the frames in FIFO is not
+  // enough to fulfill the requested frames from the audio device.
+  return frames_requested > frames_available_
+      ? frames_requested - frames_available_
+      : 0;
 }
 
 const PushPullFIFOStateForTest PushPullFIFO::GetStateForTest() const {
-  return {length(),     NumberOfChannels(), FramesAvailable(), index_read_,
+  return {length(),     NumberOfChannels(), frames_available_, index_read_,
           index_write_, overflow_count_,    underflow_count_};
 }
 
diff --git a/third_party/WebKit/Source/platform/audio/PushPullFIFO.h b/third_party/WebKit/Source/platform/audio/PushPullFIFO.h
index 447ee221..017d89683 100644
--- a/third_party/WebKit/Source/platform/audio/PushPullFIFO.h
+++ b/third_party/WebKit/Source/platform/audio/PushPullFIFO.h
@@ -7,6 +7,9 @@
 
 #include "platform/audio/AudioBus.h"
 #include "platform/wtf/Allocator.h"
+#include "platform/wtf/Functional.h"
+#include "platform/wtf/Threading.h"
+#include "platform/wtf/ThreadingPrimitives.h"
 #include "public/platform/WebCommon.h"
 
 namespace blink {
@@ -26,6 +29,14 @@
 // Blink-WebAudio and the renderer. The renderer's hardware callback buffer size
 // varies on the platform, but the WebAudio always renders 128 frames (render
 // quantum, RQ) thus FIFO is needed to handle the general case.
+//
+// Note that this object is concurrently accessed by two threads; WebAudio
+// rendering thread (WebThread) in Blink and the audio device thread
+// (AudioDeviceThread) from the media renderer. The push/pull operations touch
+// most of variables in the class (index_write_, index_read_, frames_available_,
+// and fifo_Bus_) so the thread safety must be handled with care.
+//
+// TODO(hongchan): add a unit test for multi-thread access.
 class BLINK_PLATFORM_EXPORT PushPullFIFO {
   USING_FAST_MALLOC(PushPullFIFO);
   WTF_MAKE_NONCOPYABLE(PushPullFIFO);
@@ -34,50 +45,53 @@
   // Maximum FIFO length. (512 render quanta)
   static const size_t kMaxFIFOLength;
 
-  // |fifoLength| cannot exceed |kMaxFIFOLength|. Otherwise it crashes.
+  // |fifo_length| cannot exceed |kMaxFIFOLength|. Otherwise it crashes.
   explicit PushPullFIFO(unsigned number_of_channels, size_t fifo_length);
   ~PushPullFIFO();
 
   // Pushes the rendered frames by WebAudio engine.
-  //  - The |inputBus| length is 128 frames (1 render quantum), fixed.
+  //  - The |input_bus| length is 128 frames (1 render quantum), fixed.
   //  - In case of overflow (FIFO full while push), the existing frames in FIFO
-  //    will be overwritten and |indexRead| will be forcibly moved to
-  //    |indexWrite| to avoid reading overwritten frames.
+  //    will be overwritten and |index_read_| will be forcibly moved to
+  //    |index_write_| to avoid reading overwritten frames.
   void Push(const AudioBus* input_bus);
 
-  // Pulling |framesRequested| by the audio device thread.
-  //  - If |framesRequested| is bigger than the length of |outputBus|, it
+  // Pulls |frames_requested| by the audio device thread and returns the actual
+  // number of frames to be rendered by the source. (i.e. WebAudio graph)
+  //  - If |frames_requested| is bigger than the length of |output_bus|, it
   //    violates SECURITY_CHECK().
-  //  - If |framesRequested| is bigger than FIFO length, it violates
+  //  - If |frames_requested| is bigger than FIFO length, it violates
   //    SECURITY_CHECK().
   //  - In case of underflow (FIFO empty while pull), the remaining space in the
   //    requested output bus will be filled with silence. Thus it will fulfill
   //    the request from the consumer without causing error, but with a glitch.
-  void Pull(AudioBus* output_bus, size_t frames_requested);
+  size_t Pull(AudioBus* output_bus, size_t frames_requested);
 
-  size_t FramesAvailable() const { return frames_available_; }
   size_t length() const { return fifo_length_; }
   unsigned NumberOfChannels() const { return fifo_bus_->NumberOfChannels(); }
-  AudioBus* Bus() const { return fifo_bus_.Get(); }
 
-  // For unit test. Get the current configuration that consists of FIFO length,
-  // number of channels, read/write index position and under/overflow count.
+  // TODO(hongchan): For single thread unit test only. Consider refactoring.
+  AudioBus* GetFIFOBusForTest() const { return fifo_bus_.Get(); }
+
+  // For single thread unit test only. Get the current configuration that
+  // consists of FIFO length, number of channels, read/write index position and
+  // under/overflow count.
   const PushPullFIFOStateForTest GetStateForTest() const;
 
  private:
   // The size of the FIFO.
   const size_t fifo_length_ = 0;
 
-  RefPtr<AudioBus> fifo_bus_;
+  unsigned overflow_count_ = 0;
+  unsigned underflow_count_ = 0;
 
+  // This lock protects variables below.
+  Mutex lock_;
   // The number of frames in the FIFO actually available for pulling.
-  size_t frames_available_;
-
-  size_t index_read_;
-  size_t index_write_;
-
-  unsigned overflow_count_;
-  unsigned underflow_count_;
+  size_t frames_available_ = 0;
+  size_t index_read_ = 0;
+  size_t index_write_ = 0;
+  RefPtr<AudioBus> fifo_bus_;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/audio/PushPullFIFOMultithreadTest.cpp b/third_party/WebKit/Source/platform/audio/PushPullFIFOMultithreadTest.cpp
new file mode 100644
index 0000000..24b0fa91
--- /dev/null
+++ b/third_party/WebKit/Source/platform/audio/PushPullFIFOMultithreadTest.cpp
@@ -0,0 +1,235 @@
+// 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 "platform/audio/PushPullFIFO.h"
+
+#include <memory>
+#include <vector>
+#include "platform/CrossThreadFunctional.h"
+#include "platform/WaitableEvent.h"
+#include "platform/WebTaskRunner.h"
+#include "platform/audio/AudioUtilities.h"
+#include "platform/testing/UnitTestHelpers.h"
+#include "platform/wtf/Functional.h"
+#include "platform/wtf/PtrUtil.h"
+#include "public/platform/Platform.h"
+#include "public/platform/WebThread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+namespace {
+
+// To forcibly stop the message loop.
+// TODO(hongchan): move this hack into Test class when the solution is found.
+void FinishTest() {
+  LOG(INFO) << "FinishTest";
+  testing::ExitRunLoop();
+}
+
+// To wait for spawned threads to finish their tasks.
+// TODO(hongchan): move this hack into Test class when the solution is found.
+void HoldTestForDuration(double duration_ms) {
+  LOG(INFO) << "HoldTestForDuration";
+  Platform::Current()->CurrentThread()->GetWebTaskRunner()->PostDelayedTask(
+      BLINK_FROM_HERE,
+      WTF::Bind(&FinishTest),
+      duration_ms);
+  testing::EnterRunLoop();
+}
+
+// Base FIFOClient with an extra thread for looping and jitter control. The
+// child class must define a specific task to run on the thread.
+class FIFOClient {
+ public:
+  FIFOClient(PushPullFIFO* fifo, size_t bus_length, size_t jitter_range_ms)
+      : fifo_(fifo),
+        bus_(AudioBus::Create(fifo->NumberOfChannels(), bus_length)),
+        client_thread_(WTF::WrapUnique(
+            Platform::Current()->CreateThread("client thread"))),
+        jitter_range_ms_(jitter_range_ms) {}
+
+  void Start(double duration_ms, double interval_ms) {
+    duration_ms_ = duration_ms;
+    interval_ms_ = interval_ms;
+    client_thread_->GetWebTaskRunner()->PostTask(
+        BLINK_FROM_HERE,
+        CrossThreadBind(&FIFOClient::RunTaskOnOwnThread,
+                        CrossThreadUnretained(this)));
+  }
+
+  virtual void Stop(int callback_counter) = 0;
+  virtual void RunTask() = 0;
+
+  void Pull(size_t frames_to_pull) {
+    fifo_->Pull(bus_.Get(), frames_to_pull);
+  }
+
+  void Push() {
+    fifo_->Push(bus_.Get());
+  }
+
+ private:
+  void RunTaskOnOwnThread() {
+    double interval_with_jitter = interval_ms_
+        + (static_cast<double>(std::rand()) / RAND_MAX) * jitter_range_ms_;
+    elapsed_ms_ += interval_with_jitter;
+    ++counter_;
+    RunTask();
+    if (elapsed_ms_ < duration_ms_) {
+      client_thread_->GetWebTaskRunner()->PostDelayedTask(
+              BLINK_FROM_HERE,
+              CrossThreadBind(&FIFOClient::RunTaskOnOwnThread,
+                              CrossThreadUnretained(this)),
+              interval_with_jitter);
+    } else {
+      Stop(counter_);
+    }
+  }
+
+  PushPullFIFO* fifo_;
+  RefPtr<AudioBus> bus_;
+  std::unique_ptr<WebThread> client_thread_;
+
+  // Test duration.
+  double duration_ms_;
+
+  // Interval between each callback.
+  double interval_ms_;
+
+  // Jitter added to the regular pushing/pulling interval.
+  // (where j is 0 < j < jitter_range_ms)
+  double jitter_range_ms_;
+
+  // Elapsed test duration.
+  double elapsed_ms_ = 0;
+
+  // Counter variable for the total number of callbacks invoked.
+  int counter_ = 0;
+};
+
+// FIFO-pulling client (consumer). This mimics the audio device thread.
+// |frames_to_pull| is variable.
+class PullClient : public FIFOClient {
+ public:
+  PullClient(PushPullFIFO* fifo, size_t frames_to_pull, double jitter_range_ms)
+      : FIFOClient(fifo, frames_to_pull, jitter_range_ms),
+        frames_to_pull_(frames_to_pull) {
+  }
+
+  void RunTask() override {
+    Pull(frames_to_pull_);
+  }
+
+  void Stop(int callback_counter) override {
+    LOG(INFO) << "PullClient stopped. (" << callback_counter << " calls)";
+  }
+
+ private:
+  size_t frames_to_pull_;
+};
+
+// FIFO-pushing client (producer). This mimics the WebAudio rendering thread.
+// The frames to push are static as 128 frames.
+class PushClient : public FIFOClient {
+ public:
+  PushClient(PushPullFIFO* fifo, size_t frames_to_push, double jitter_range_ms)
+      : FIFOClient(fifo, frames_to_push, jitter_range_ms) {}
+
+  void RunTask() override {
+    Push();
+  }
+
+  void Stop(int callback_counter) override {
+    LOG(INFO) << "PushClient stopped. (" << callback_counter << " calls)";
+  }
+};
+
+struct FIFOSmokeTestParam {
+  const double sample_rate;
+  const unsigned number_of_channels;
+  const size_t fifo_length;
+  const double test_duration_ms;
+  // Buffer size for pulling. Equivalent of |callback_buffer_size|.
+  const size_t pull_buffer_size;
+  // Jitter range for the pulling interval.
+  const double pull_jitter_range_ms;
+  // Buffer size for pushing. Equivalent of WebAudio render quantum.
+  const size_t push_buffer_size;
+  // Jitter range for the pushing interval.
+  const double push_jitter_range_ms;
+};
+
+class PushPullFIFOSmokeTest
+    : public ::testing::TestWithParam<FIFOSmokeTestParam> {};
+
+TEST_P(PushPullFIFOSmokeTest, SmokeTests) {
+  const FIFOSmokeTestParam param = GetParam();
+  const double sample_rate = param.sample_rate * 4;
+
+  const double pull_interval_ms =
+      param.pull_buffer_size / sample_rate * 1000;
+  const double push_interval_ms =
+      param.push_buffer_size / sample_rate * 1000;
+
+  std::unique_ptr<PushPullFIFO> test_fifo = WTF::WrapUnique(
+      new PushPullFIFO(param.number_of_channels, param.fifo_length));
+  std::unique_ptr<PullClient> pull_client = WTF::WrapUnique(new PullClient(
+      test_fifo.get(), param.pull_buffer_size, param.pull_jitter_range_ms));
+  std::unique_ptr<PushClient> push_client = WTF::WrapUnique(new PushClient(
+      test_fifo.get(), param.push_buffer_size, param.push_jitter_range_ms));
+
+  LOG(INFO) << "PushPullFIFOSmokeTest - Start";
+
+  pull_client->Start(param.test_duration_ms, pull_interval_ms);
+  push_client->Start(param.test_duration_ms, push_interval_ms);
+
+  // If the operation does not cause a crash for the test period, it's passed.
+  // Also give a bit more time to finish the tear-down process.
+  HoldTestForDuration(param.test_duration_ms + 150);
+}
+
+FIFOSmokeTestParam smoke_test_params[] = {
+  // Test case 0 (OSX): 256 Pull, 128 Push, Minimal jitter.
+  // WebThread's priority is lower than the device thread, so its jitter range
+  // is slightly bigger than the other.
+  {48000, 2, 8192, 1000, 256, 1, 128, 2},
+
+  // Test case 1 (Windows): 441 Pull, 128 Push. Moderate Jitter.
+  // Windows' audio callback is known to be ~10ms and UMA data shows the
+  // evidence for it. The jitter range was determined speculatively.
+  {44100, 2, 8192, 1000, 441, 2, 128, 3},
+
+  // Test case 2 (Ubuntu/Linux): 512 Pull, 128 Push. Unstable callback, but
+  // fast CPU. A typical configuration for Ubuntu + PulseAudio setup.
+  // PulseAudio's callback is known to be rather unstable.
+  {48000, 2, 8192, 1000, 512, 8, 128, 1},
+
+  // Test case 3 (Android-Reference): 512 Pull, 128 Push. Similar to Linux, but
+  // low profile CPU.
+  {44100, 2, 8192, 1000, 512, 8, 128, 3},
+
+  // Test case 4 (Android-ExternalA): 441 Pull, 128 Push. Extreme jitter with
+  // low profile CPU.
+  {44100, 2, 8192, 1000, 441, 24, 128, 8},
+
+  // Test case 5 (Android-ExternalB): 5768 Pull, 128 Push. Huge callback with
+  // large jitter. Low profile CPU.
+  {44100, 2, 8192, 1000, 5768, 120, 128, 12},
+
+  // Test case 6 (User-specified buffer size): 960 Pull, 128 Push. Minimal
+  // Jitter. 960 frames = 20ms at 48KHz.
+  {48000, 2, 8192, 1000, 960, 1, 128, 1},
+
+  // Test case 7 (Longer test duration): 256 Pull, 128 Push. 10 seconds.
+  {48000, 2, 8192, 10000, 256, 0, 128, 1}
+};
+
+INSTANTIATE_TEST_CASE_P(PushPullFIFOSmokeTest,
+                        PushPullFIFOSmokeTest,
+                        ::testing::ValuesIn(smoke_test_params));
+
+}  // namespace
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/platform/audio/PushPullFIFOTest.cpp b/third_party/WebKit/Source/platform/audio/PushPullFIFOTest.cpp
index 9351b42..c0d1b77 100644
--- a/third_party/WebKit/Source/platform/audio/PushPullFIFOTest.cpp
+++ b/third_party/WebKit/Source/platform/audio/PushPullFIFOTest.cpp
@@ -6,51 +6,57 @@
 
 #include <memory>
 #include <vector>
+#include "platform/CrossThreadFunctional.h"
+#include "platform/WebTaskRunner.h"
 #include "platform/audio/AudioUtilities.h"
 #include "platform/testing/TestingPlatformSupport.h"
+#include "platform/wtf/Functional.h"
 #include "platform/wtf/PtrUtil.h"
+#include "public/platform/Platform.h"
+#include "public/platform/WebThread.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace blink {
 
 namespace {
 
-// Check the basic contract of FIFO.
+// Check the basic contract of FIFO. This test only covers the single thread
+// scenario.
 TEST(PushPullFIFOBasicTest, BasicTests) {
   // This suppresses the multi-thread warning for GTest. Potently it increases
   // the test execution time, but this specific test is very short and simple.
   ::testing::FLAGS_gtest_death_test_style = "threadsafe";
 
   // FIFO length exceeding the maximum length allowed will cause crash.
-  // i.e.) m_fifoLength <= kMaxFIFOLength
+  // i.e.) fifo_length_ <= kMaxFIFOLength
   EXPECT_DEATH(new PushPullFIFO(2, PushPullFIFO::kMaxFIFOLength + 1), "");
 
   std::unique_ptr<PushPullFIFO> test_fifo =
       WTF::WrapUnique(new PushPullFIFO(2, 1024));
 
   // The input bus length must be |AudioUtilities::kRenderQuantumFrames|.
-  // i.e.) inputBus->length() == kRenderQuantumFrames
-  RefPtr<AudioBus> input_bus_of129_frames =
+  // i.e.) input_bus->length() == kRenderQuantumFrames
+  RefPtr<AudioBus> input_bus_129_frames =
       AudioBus::Create(2, AudioUtilities::kRenderQuantumFrames + 1);
-  EXPECT_DEATH(test_fifo->Push(input_bus_of129_frames.Get()), "");
-  RefPtr<AudioBus> input_bus_of127_frames =
+  EXPECT_DEATH(test_fifo->Push(input_bus_129_frames.Get()), "");
+  RefPtr<AudioBus> input_bus_127_frames =
       AudioBus::Create(2, AudioUtilities::kRenderQuantumFrames - 1);
-  EXPECT_DEATH(test_fifo->Push(input_bus_of127_frames.Get()), "");
+  EXPECT_DEATH(test_fifo->Push(input_bus_127_frames.Get()), "");
 
   // Pull request frames cannot exceed the length of output bus.
-  // i.e.) framesRequested <= outputBus->length()
-  RefPtr<AudioBus> output_bus_of512_frames = AudioBus::Create(2, 512);
-  EXPECT_DEATH(test_fifo->Pull(output_bus_of512_frames.Get(), 513), "");
+  // i.e.) frames_requested <= output_bus->length()
+  RefPtr<AudioBus> output_bus_512_frames = AudioBus::Create(2, 512);
+  EXPECT_DEATH(test_fifo->Pull(output_bus_512_frames.Get(), 513), "");
 
   // Pull request frames cannot exceed the length of FIFO.
-  // i.e.) framesRequested <= m_fifoLength
-  RefPtr<AudioBus> output_bus_of1025_frames = AudioBus::Create(2, 1025);
-  EXPECT_DEATH(test_fifo->Pull(output_bus_of1025_frames.Get(), 1025), "");
+  // i.e.) frames_requested <= fifo_length_
+  RefPtr<AudioBus> output_bus_1025_frames = AudioBus::Create(2, 1025);
+  EXPECT_DEATH(test_fifo->Pull(output_bus_1025_frames.Get(), 1025), "");
 }
 
 // Fills each AudioChannel in an AudioBus with a series of linearly increasing
-// values starting from |startingValue| and incrementing by 1. Then return value
-// will be |startingValue| + |bus_length|.
+// values starting from |starting_value| and incrementing by 1. Then return
+// value will be |starting_value| + |bus_length|.
 size_t FillBusWithLinearRamp(AudioBus* target_bus, size_t starting_value) {
   for (unsigned c = 0; c < target_bus->NumberOfChannels(); ++c) {
     float* bus_channel = target_bus->Channel(c)->MutableData();
@@ -169,7 +175,8 @@
 
   // Verify in-FIFO samples.
   for (const auto& sample : expected_state.fifo_samples) {
-    EXPECT_TRUE(VerifyBusValueAtIndex(fifo->Bus(), sample.index, sample.value));
+    EXPECT_TRUE(VerifyBusValueAtIndex(fifo->GetFIFOBusForTest(),
+                                      sample.index, sample.value));
   }
 
   // Verify samples from the most recent output bus.
diff --git a/third_party/WebKit/Source/platform/graphics/ImageBuffer.cpp b/third_party/WebKit/Source/platform/graphics/ImageBuffer.cpp
index 23e4a518..da3a88f 100644
--- a/third_party/WebKit/Source/platform/graphics/ImageBuffer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/ImageBuffer.cpp
@@ -501,8 +501,8 @@
   // Create and configure a recording (unaccelerated) surface.
   std::unique_ptr<ImageBufferSurface> surface =
       WTF::WrapUnique(new RecordingImageBufferSurface(
-          surface_->size(), surface_->GetOpacityMode(),
-          surface_->color_params()));
+          surface_->size(), RecordingImageBufferSurface::kAllowFallback,
+          surface_->GetOpacityMode(), surface_->color_params()));
   SetSurface(std::move(surface));
 }
 
diff --git a/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.cpp b/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.cpp
index 11f0991..5315caa 100644
--- a/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.cpp
+++ b/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.cpp
@@ -20,9 +20,11 @@
 
 RecordingImageBufferSurface::RecordingImageBufferSurface(
     const IntSize& size,
+    AllowFallback allow_fallback,
     OpacityMode opacity_mode,
     const CanvasColorParams& color_params)
     : ImageBufferSurface(size, opacity_mode, color_params),
+      allow_fallback_(allow_fallback),
       image_buffer_(0),
       current_frame_pixel_count_(0),
       previous_frame_pixel_count_(0),
@@ -77,6 +79,7 @@
 
 void RecordingImageBufferSurface::FallBackToRasterCanvas(
     FallbackReason reason) {
+  DCHECK(allow_fallback_ == kAllowFallback);
   CHECK(reason != kFallbackReasonUnknown);
 
   if (fallback_surface_) {
@@ -315,8 +318,9 @@
     return false;
   }
 
-  if (current_frame_->getRecordingCanvas()->getSaveCount() - 1 >
-      ExpensiveCanvasHeuristicParameters::kExpensiveRecordingStackDepth) {
+  if (allow_fallback_ == kAllowFallback &&
+      current_frame_->getRecordingCanvas()->getSaveCount() - 1 >
+          ExpensiveCanvasHeuristicParameters::kExpensiveRecordingStackDepth) {
     // (getSaveCount() decremented to account  for the intial recording canvas
     // save frame.)
     *fallback_reason = kFallbackReasonRunawayStateStack;
diff --git a/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.h b/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.h
index 8ed5756c8..19938f7 100644
--- a/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.h
+++ b/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurface.h
@@ -23,14 +23,16 @@
   USING_FAST_MALLOC(RecordingImageBufferSurface);
 
  public:
-  // If the fallbackFactory is null the buffer surface should only be used
-  // for one frame and should not be used for any operations which need a
-  // raster surface, (i.e. writePixels).
-  // Only #getRecord should be used to access the resulting frame.
-  RecordingImageBufferSurface(
-      const IntSize&,
-      OpacityMode = kNonOpaque,
-      const CanvasColorParams& = CanvasColorParams());
+  enum AllowFallback : bool { kDisallowFallback, kAllowFallback };
+
+  // If |AllowFallback| is kDisallowFallback the buffer surface should only be
+  // used for one frame and should not be used for any operations which need a
+  // raster surface, (i.e. WritePixels()).
+  // Only GetRecord() should be used to access the resulting frame.
+  RecordingImageBufferSurface(const IntSize&,
+                              AllowFallback,
+                              OpacityMode = kNonOpaque,
+                              const CanvasColorParams& = CanvasColorParams());
   ~RecordingImageBufferSurface() override;
 
   // Implementation of ImageBufferSurface interfaces
@@ -106,6 +108,7 @@
   bool FinalizeFrameInternal(FallbackReason*);
   int ApproximateOpCount();
 
+  const AllowFallback allow_fallback_;
   std::unique_ptr<PaintRecorder> current_frame_;
   sk_sp<PaintRecord> previous_frame_;
   std::unique_ptr<UnacceleratedImageBufferSurface> fallback_surface_;
diff --git a/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurfaceTest.cpp b/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurfaceTest.cpp
index e4d44dc..3b4b653 100644
--- a/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurfaceTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/RecordingImageBufferSurfaceTest.cpp
@@ -29,7 +29,8 @@
  protected:
   RecordingImageBufferSurfaceTest() {
     auto test_surface = WTF::MakeUnique<RecordingImageBufferSurface>(
-        IntSize(10, 10), kNonOpaque);
+        IntSize(10, 10), RecordingImageBufferSurface::kAllowFallback,
+        kNonOpaque);
     test_surface_ = test_surface.get();
     // We create an ImageBuffer in order for the |test_surface| to be
     // properly initialized with a GraphicsContext
diff --git a/third_party/WebKit/Source/platform/wtf/text/AtomicString.h b/third_party/WebKit/Source/platform/wtf/text/AtomicString.h
index 1aa0d61..ecdb99fe 100644
--- a/third_party/WebKit/Source/platform/wtf/text/AtomicString.h
+++ b/third_party/WebKit/Source/platform/wtf/text/AtomicString.h
@@ -98,14 +98,14 @@
   UChar operator[](unsigned i) const { return string_[i]; }
 
   // Find characters.
-  size_t Find(UChar c, unsigned start = 0) const {
+  size_t find(UChar c, unsigned start = 0) const {
     return string_.find(c, start);
   }
-  size_t Find(LChar c, unsigned start = 0) const {
+  size_t find(LChar c, unsigned start = 0) const {
     return string_.find(c, start);
   }
-  size_t Find(char c, unsigned start = 0) const {
-    return Find(static_cast<LChar>(c), start);
+  size_t find(char c, unsigned start = 0) const {
+    return find(static_cast<LChar>(c), start);
   }
   size_t Find(CharacterMatchFunctionPtr match_function,
               unsigned start = 0) const {
@@ -132,7 +132,7 @@
     return string_.FindIgnoringASCIICase(value, start);
   }
 
-  bool Contains(char c) const { return Find(c) != kNotFound; }
+  bool Contains(char c) const { return find(c) != kNotFound; }
   bool Contains(
       const StringView& value,
       TextCaseSensitivity case_sensitivity = kTextCaseSensitive) const {
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
index 62c523a..8328a63 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
@@ -1097,9 +1097,8 @@
 
     def _shorten_filename(self, filename):
         finder = WebKitFinder(self._port.host.filesystem)
-        # TODO(tkent): Can we use path_from_layout_tests() instead?
-        if filename.startswith(finder.path_from_webkit_base()):
-            return self._port.host.filesystem.relpath(filename, finder.path_from_webkit_base())
+        if filename.startswith(finder.path_from_chromium_base()):
+            return self._port.host.filesystem.relpath(filename, finder.path_from_chromium_base())
         return filename
 
     def _report_warnings(self):
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
index c8aea18..9ffc1a2 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
@@ -387,6 +387,12 @@
         self.assertEqual(expectations.get_expectations(test_name1), set([PASS, FAIL, TIMEOUT]))
         self.assertEqual(expectations.get_expectations(test_name2), set([CRASH]))
 
+    def test_shorten_filename(self):
+        expectations = TestExpectations(self._port, self.get_basic_tests())
+        self.assertEquals(expectations._shorten_filename('/out-of-checkout/TestExpectations'),
+                          '/out-of-checkout/TestExpectations')
+        self.assertEquals(expectations._shorten_filename('/mock-checkout/third_party/WebKit/LayoutTests/TestExpectations'),
+                          'third_party/WebKit/LayoutTests/TestExpectations')
 
 class SkippedTests(Base):
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
index b4f0616..a77e180d1 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
@@ -67,7 +67,7 @@
 
         # TODO(qyearsley): Simplify this to use LocalWPT.fetch when csswg-test
         # is merged into web-platform-tests (crbug.com/706118).
-        temp_repo_path = self.finder.path_from_webkit_base(dest_dir_name)
+        temp_repo_path = self.finder.path_from_layout_tests(dest_dir_name)
         _log.info('Cloning repo: %s', repo_url)
         _log.info('Local path: %s', temp_repo_path)
         self.run(['git', 'clone', repo_url, temp_repo_path])
@@ -139,8 +139,9 @@
             _log.warning('Checkout has local commits; aborting. Use --allow-local-commits to allow this.')
             return False
 
-        if self.fs.exists(self.finder.path_from_webkit_base(WPT_DEST_NAME)):
-            _log.warning('WebKit/%s exists; aborting.', WPT_DEST_NAME)
+        temp_repo_path = self.finder.path_from_layout_tests(WPT_DEST_NAME)
+        if self.fs.exists(temp_repo_path):
+            _log.warning('%s exists; aborting.', temp_repo_path)
             return False
 
         return True
@@ -162,7 +163,7 @@
 
     def clean_up_temp_repo(self, temp_repo_path):
         _log.info('Deleting temp repo directory %s.', temp_repo_path)
-        self.rmtree(temp_repo_path)
+        self.fs.rmtree(temp_repo_path)
 
     def _copy_resources(self):
         """Copies resources from wpt to LayoutTests/resources.
@@ -248,7 +249,7 @@
             basename != 'OWNERS')
         files_to_delete = self.fs.files_under(dest_path, file_filter=should_remove)
         for subpath in files_to_delete:
-            self.remove('LayoutTests', 'external', subpath)
+            self.remove(self.finder.path_from_layout_tests('external', subpath))
 
     def _commit_changes(self, commit_message):
         _log.info('Committing changes.')
@@ -286,7 +287,7 @@
     def run(self, cmd, exit_on_failure=True, cwd=None, stdin=''):
         _log.debug('Running command: %s', ' '.join(cmd))
 
-        cwd = cwd or self.finder.webkit_base()
+        cwd = cwd or self.finder.path_from_layout_tests()
         proc = self.executive.popen(cmd, stdout=self.executive.PIPE, stderr=self.executive.PIPE, stdin=self.executive.PIPE, cwd=cwd)
         out, err = proc.communicate(stdin)
         if proc.returncode or self.verbose:
@@ -311,16 +312,10 @@
         _log.debug('cp %s %s', source, destination)
         self.fs.copyfile(source, destination)
 
-    def remove(self, *comps):
-        dest = self.finder.path_from_webkit_base(*comps)
+    def remove(self, dest):
         _log.debug('rm %s', dest)
         self.fs.remove(dest)
 
-    def rmtree(self, *comps):
-        dest = self.finder.path_from_webkit_base(*comps)
-        _log.debug('rm -fr %s', dest)
-        self.fs.rmtree(dest)
-
     def do_auto_update(self):
         """Attempts to upload a CL, make any required adjustments, and commit.
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
index 2a3f3c5..db22c11 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
@@ -25,11 +25,11 @@
         self.assertEqual(return_code, 0)
         self.assertLog([
             'INFO: Cloning repo: https://chromium.googlesource.com/external/w3c/web-platform-tests.git\n',
-            'INFO: Local path: /mock-checkout/third_party/WebKit/wpt\n',
+            'INFO: Local path: /mock-checkout/third_party/WebKit/LayoutTests/wpt\n',
             'INFO: There were exportable but not-yet-exported commits:\n',
             'INFO:   https://chromium.googlesource.com/chromium/src/+/deadbeef\n',
             'INFO: Aborting import to prevent clobbering these commits.\n',
-            'INFO: Deleting temp repo directory /mock-checkout/third_party/WebKit/wpt.\n',
+            'INFO: Deleting temp repo directory /mock-checkout/third_party/WebKit/LayoutTests/wpt.\n',
         ])
 
     def test_update_test_expectations(self):
diff --git a/third_party/closure_compiler/externs/networking_private.js b/third_party/closure_compiler/externs/networking_private.js
index e911ff8..f8fc26f5 100644
--- a/third_party/closure_compiler/externs/networking_private.js
+++ b/third_party/closure_compiler/externs/networking_private.js
@@ -767,6 +767,7 @@
  * @typedef {{
  *   BatteryPercentage: (number|undefined),
  *   Carrier: (string|undefined),
+ *   HasConnectedToHost: boolean,
  *   SignalStrength: (number|undefined)
  * }}
  * @see https://developer.chrome.com/extensions/networkingPrivate#type-TetherProperties
@@ -775,16 +776,6 @@
 
 /**
  * @typedef {{
- *   BatteryPercentage: (!chrome.networkingPrivate.ManagedLong|undefined),
- *   Carrier: (!chrome.networkingPrivate.ManagedDOMString|undefined),
- *   SignalStrength: (!chrome.networkingPrivate.ManagedLong|undefined)
- * }}
- * @see https://developer.chrome.com/extensions/networkingPrivate#type-ManagedTetherProperties
- */
-chrome.networkingPrivate.ManagedTetherProperties;
-
-/**
- * @typedef {{
  *   AutoConnect: (boolean|undefined),
  *   Host: (string|undefined),
  *   IPsec: (!chrome.networkingPrivate.IPSecProperties|undefined),
@@ -968,7 +959,7 @@
  *   StaticIPConfig: (!chrome.networkingPrivate.ManagedIPConfigProperties|undefined),
  *   SavedIPConfig: (!chrome.networkingPrivate.IPConfigProperties|undefined),
  *   Source: (string|undefined),
- *   Tether: (!chrome.networkingPrivate.ManagedTetherProperties|undefined),
+ *   Tether: (!chrome.networkingPrivate.TetherProperties|undefined),
  *   Type: !chrome.networkingPrivate.NetworkType,
  *   VPN: (!chrome.networkingPrivate.ManagedVPNProperties|undefined),
  *   WiFi: (!chrome.networkingPrivate.ManagedWiFiProperties|undefined),
diff --git a/third_party/custom_tabs_client/BUILD.gn b/third_party/custom_tabs_client/BUILD.gn
index f98ff51..dd0a56c3d 100644
--- a/third_party/custom_tabs_client/BUILD.gn
+++ b/third_party/custom_tabs_client/BUILD.gn
@@ -43,6 +43,8 @@
 
 android_library("custom_tabs_support_java") {
   java_files = [
+    "src/customtabs/src/android/support/customtabs/browseractions/BrowserActionsIntent.java",
+    "src/customtabs/src/android/support/customtabs/browseractions/BrowserActionItem.java",
     "src/customtabs/src/android/support/customtabs/CustomTabsCallback.java",
     "src/customtabs/src/android/support/customtabs/CustomTabsClient.java",
     "src/customtabs/src/android/support/customtabs/CustomTabsIntent.java",
diff --git a/third_party/custom_tabs_client/README.chromium b/third_party/custom_tabs_client/README.chromium
index 1714401f..fac3ac31 100644
--- a/third_party/custom_tabs_client/README.chromium
+++ b/third_party/custom_tabs_client/README.chromium
@@ -13,4 +13,7 @@
 management. Also inside demos there is another application that launches
 custom tabs in different modes.
 
+The example applicaton also presents how to use Browser Actions, including
+creating request intent and adding custom items.
+
 Local Modifications: none
diff --git a/third_party/inspector_protocol/README.chromium b/third_party/inspector_protocol/README.chromium
index f3fd10d..b1863f9 100644
--- a/third_party/inspector_protocol/README.chromium
+++ b/third_party/inspector_protocol/README.chromium
@@ -2,7 +2,7 @@
 Short Name: inspector_protocol
 URL: https://chromium.googlesource.com/deps/inspector_protocol/
 Version: 0
-Revision: 13768f332cacb5170d64b8fcce1887b78fec2d86
+Revision: efefa86c3183d307f0a0e53bf568fe57c5b58849
 License: BSD
 License File: LICENSE
 Security Critical: no
diff --git a/third_party/inspector_protocol/lib/Parser_cpp.template b/third_party/inspector_protocol/lib/Parser_cpp.template
index 4bf6beb..f3dde5a 100644
--- a/third_party/inspector_protocol/lib/Parser_cpp.template
+++ b/third_party/inspector_protocol/lib/Parser_cpp.template
@@ -51,19 +51,13 @@
         buffer.push_back(static_cast<char>(characters[i]));
     }
     buffer.push_back('\0');
-    char* endptr;
-    double result = std::strtod(buffer.data(), &endptr);
-    *ok = !(*endptr);
-    return result;
+    return StringUtil::toDouble(buffer.data(), length, ok);
 }
 
 double charactersToDouble(const uint8_t* characters, size_t length, bool* ok)
 {
     std::string buffer(reinterpret_cast<const char*>(characters), length);
-    char* endptr;
-    double result = std::strtod(buffer.data(), &endptr);
-    *ok = !(*endptr);
-    return result;
+    return StringUtil::toDouble(buffer.data(), length, ok);
 }
 
 template<typename Char>
diff --git a/third_party/netty-tcnative/BUILD.gn b/third_party/netty-tcnative/BUILD.gn
index 9fecf9c..521a81a 100644
--- a/third_party/netty-tcnative/BUILD.gn
+++ b/third_party/netty-tcnative/BUILD.gn
@@ -11,37 +11,15 @@
 shared_library("netty-tcnative-so") {
   output_name = "netty-tcnative"
   sources = [
-    "src/c/address.c",
     "src/c/bb.c",
-    "src/c/dir.c",
     "src/c/error.c",
-    "src/c/file.c",
-    "src/c/info.c",
     "src/c/jnilib.c",
-    "src/c/lock.c",
-    "src/c/misc.c",
-    "src/c/mmap.c",
-    "src/c/multicast.c",
-    "src/c/network.c",
-    "src/c/os.c",
-    "src/c/os_unix_system.c",
-    "src/c/os_unix_uxpipe.c",
-    "src/c/poll.c",
-    "src/c/pool.c",
-    "src/c/proc.c",
-    "src/c/shm.c",
+    "src/c/native_constants.c",
     "src/c/ssl.c",
     "src/c/ssl_private.h",
     "src/c/sslcontext.c",
-    "src/c/sslinfo.c",
-    "src/c/sslnetwork.c",
     "src/c/sslutils.c",
-    "src/c/stdlib.c",
     "src/c/tcn.h",
-    "src/c/tcn_api.h",
-    "src/c/tcn_version.h",
-    "src/c/thread.c",
-    "src/c/user.c",
   ]
   include_dirs = [ "../apache-portable-runtime/src/include" ]
   defines = [ "HAVE_OPENSSL" ]
@@ -56,47 +34,14 @@
 # Builds the Java part of netty-tcnative library.
 android_library("netty-tcnative_java") {
   java_files = [
-    "src/java/src/org/apache/tomcat/Apr.java",
-    "src/java/src/org/apache/tomcat/jni/Address.java",
-    "src/java/src/org/apache/tomcat/jni/BIOCallback.java",
-    "src/java/src/org/apache/tomcat/jni/Buffer.java",
-    "src/java/src/org/apache/tomcat/jni/CertificateVerifier.java",
-    "src/java/src/org/apache/tomcat/jni/Directory.java",
-    "src/java/src/org/apache/tomcat/jni/Error.java",
-    "src/java/src/org/apache/tomcat/jni/FileInfo.java",
-    "src/java/src/org/apache/tomcat/jni/File.java",
-    "src/java/src/org/apache/tomcat/jni/Global.java",
-    "src/java/src/org/apache/tomcat/jni/Library.java",
-    "src/java/src/org/apache/tomcat/jni/LibraryNotFoundError.java",
-    "src/java/src/org/apache/tomcat/jni/Local.java",
-    "src/java/src/org/apache/tomcat/jni/Lock.java",
-    "src/java/src/org/apache/tomcat/jni/Mmap.java",
-    "src/java/src/org/apache/tomcat/jni/Multicast.java",
-    "src/java/src/org/apache/tomcat/jni/OS.java",
-    "src/java/src/org/apache/tomcat/jni/PasswordCallback.java",
-    "src/java/src/org/apache/tomcat/jni/Poll.java",
-    "src/java/src/org/apache/tomcat/jni/PoolCallback.java",
-    "src/java/src/org/apache/tomcat/jni/Pool.java",
-    "src/java/src/org/apache/tomcat/jni/Procattr.java",
-    "src/java/src/org/apache/tomcat/jni/ProcErrorCallback.java",
-    "src/java/src/org/apache/tomcat/jni/Proc.java",
-    "src/java/src/org/apache/tomcat/jni/Registry.java",
-    "src/java/src/org/apache/tomcat/jni/SessionTicketKey.java",
-    "src/java/src/org/apache/tomcat/jni/Shm.java",
-    "src/java/src/org/apache/tomcat/jni/Sockaddr.java",
-    "src/java/src/org/apache/tomcat/jni/socket/AprSocketContext.java",
-    "src/java/src/org/apache/tomcat/jni/socket/AprSocket.java",
-    "src/java/src/org/apache/tomcat/jni/socket/HostInfo.java",
-    "src/java/src/org/apache/tomcat/jni/Socket.java",
-    "src/java/src/org/apache/tomcat/jni/SSLContext.java",
-    "src/java/src/org/apache/tomcat/jni/SSLExt.java",
-    "src/java/src/org/apache/tomcat/jni/SSL.java",
-    "src/java/src/org/apache/tomcat/jni/SSLSocket.java",
-    "src/java/src/org/apache/tomcat/jni/Status.java",
-    "src/java/src/org/apache/tomcat/jni/Stdlib.java",
-    "src/java/src/org/apache/tomcat/jni/Thread.java",
-    "src/java/src/org/apache/tomcat/jni/Time.java",
-    "src/java/src/org/apache/tomcat/jni/User.java",
+    "src/java/io/netty/internal/tcnative/Buffer.java",
+    "src/java/io/netty/internal/tcnative/CertificateRequestedCallback.java",
+    "src/java/io/netty/internal/tcnative/CertificateVerifier.java",
+    "src/java/io/netty/internal/tcnative/Library.java",
+    "src/java/io/netty/internal/tcnative/NativeStaticallyReferencedJniMethods.java",
+    "src/java/io/netty/internal/tcnative/SessionTicketKey.java",
+    "src/java/io/netty/internal/tcnative/SSL.java",
+    "src/java/io/netty/internal/tcnative/SSLContext.java",
   ]
   run_findbugs_override = false
   deps = [
diff --git a/third_party/netty-tcnative/README.chromium b/third_party/netty-tcnative/README.chromium
index 0cc63a1f..377ab58 100644
--- a/third_party/netty-tcnative/README.chromium
+++ b/third_party/netty-tcnative/README.chromium
@@ -1,10 +1,9 @@
 Name: Tomcat Native Fork for Netty
 Short Name: netty-tcnative
-URL: https://github.com/netty/netty-tcnative
-SHA: 856865181ca38c07b7d2be619903ee98f6f77a23 netty-tcnative-1.1.33.zip
-Version: 1.1.33
-Date: October 13, 2015
-Revision: 2aa47be27783ec31086ca9881402f845543de4e6
+URL: https://github.com/netty/netty-tcnative.git
+Version: 2.0.0.Final
+Date: March 9, 2017
+Revision: 28d9d70090f1b18927f4554621648cc1922d6e05
 License: Apache 2.0
 License File: NOT_SHIPPED
 Security Critical: no
@@ -21,161 +20,16 @@
 
 Local Modifications:
 
-diff -ruN ./original/src/main/c/ssl.c ./src/third_party/netty-tcnative/src/c/ssl.c
---- ./original/src/main/c/ssl.c	2015-10-13 08:36:59.000000000 -0400
-+++ ./src/third_party/netty-tcnative/src/c/ssl.c	2016-01-04 10:18:31.729765992 -0500
-@@ -1821,7 +1821,7 @@
-     verify = SSL_VERIFY_NONE;
-
-     UNREFERENCED(o);
--    TCN_ASSERT(ctx != 0);
-+    TCN_ASSERT(c->ctx != 0);
-     c->verify_mode = level;
-
-     if (c->verify_mode == SSL_CVERIFY_UNSET)
-
-diff --git a/c/ssl.c b/c/ssl.c
-index 89e6cad..97c7982 100644
---- a/c/ssl.c
-+++ b/c/ssl.c
-@@ -231,26 +231,38 @@ static const jint supported_ssl_opts = 0
-
- static int ssl_tmp_key_init_rsa(int bits, int idx)
- {
--#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(OPENSSL_USE_DEPRECATED)
--    if (!(SSL_temp_keys[idx] =
--          RSA_generate_key(bits, RSA_F4, NULL, NULL))) {
-+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-+    return 0;
-+#else
-+
- #ifdef OPENSSL_FIPS
--        /**
--         * With FIPS mode short RSA keys cannot be
--         * generated.
--         */
--        if (bits < 1024)
--            return 0;
--        else
--#endif
--        return 1;
--    }
--    else {
-+    /**
-+     * Short RSA keys cannot be generated in FIPS mode.
-+     */
-+    if (bits < 1024)
-         return 0;
--    }
--#else
--    return 0;
- #endif
-+
-+    BIGNUM *e = BN_new();
-+    RSA *rsa = RSA_new();
-+    int ret = 1;
-+
-+    if (e == NULL ||
-+        rsa == NULL ||
-+        !BN_set_word(e, RSA_F4) ||
-+        RSA_generate_key_ex(rsa, bits, e, NULL) != 1) {
-+        goto err;
-+    }
-+
-+    SSL_temp_keys[idx] = rsa;
-+    rsa = NULL;
-+    ret = 0;
-+
-+err:
-+    BN_free(e);
-+    RSA_free(rsa);
-+    return ret;
-+#endif  /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
- }
-
- static int ssl_tmp_key_init_dh(int bits, int idx)
-@@ -610,45 +622,6 @@ int SSL_rand_seed(const char *file)
-     return RAND_status();
- }
-
--static int ssl_rand_make(const char *file, int len, int base64)
--{
--    int r;
--    int num = len;
--    BIO *out = NULL;
--
--    out = BIO_new(BIO_s_file());
--    if (out == NULL)
--        return 0;
--    if ((r = BIO_write_filename(out, (char *)file)) < 0) {
--        BIO_free_all(out);
--        return 0;
--    }
--    if (base64) {
--        BIO *b64 = BIO_new(BIO_f_base64());
--        if (b64 == NULL) {
--            BIO_free_all(out);
--            return 0;
--        }
--        out = BIO_push(b64, out);
--    }
--    while (num > 0) {
--        unsigned char buf[4096];
--        int len = num;
--        if (len > sizeof(buf))
--            len = sizeof(buf);
--        r = RAND_bytes(buf, len);
--        if (r <= 0) {
--            BIO_free_all(out);
--            return 0;
--        }
--        BIO_write(out, buf, len);
--        num -= len;
--    }
--    r = BIO_flush(out);
--    BIO_free_all(out);
--    return r > 0 ? 1 : 0;
--}
--
- TCN_IMPLEMENT_CALL(jint, SSL, initialize)(TCN_STDARGS, jstring engine)
- {
-     int r = 0;
-@@ -785,17 +758,6 @@ TCN_IMPLEMENT_CALL(jboolean, SSL, randSave)(TCN_STDARGS, jstring file)
-     return r ? JNI_TRUE : JNI_FALSE;
- }
-
--TCN_IMPLEMENT_CALL(jboolean, SSL, randMake)(TCN_STDARGS, jstring file,
--                                            jint length, jboolean base64)
--{
--    TCN_ALLOC_CSTRING(file);
--    int r;
--    UNREFERENCED(o);
--    r = ssl_rand_make(J2S(file), length, base64);
--    TCN_FREE_CSTRING(file);
--    return r ? JNI_TRUE : JNI_FALSE;
--}
--
- TCN_IMPLEMENT_CALL(void, SSL, randSet)(TCN_STDARGS, jstring file)
- {
-     TCN_ALLOC_CSTRING(file);
-
 diff --git a/c/sslcontext.c b/c/sslcontext.c
-index 925ca2a..78afe61 100644
+index 5668298..25bfb6e 100644
 --- a/c/sslcontext.c
 +++ b/c/sslcontext.c
-@@ -1464,7 +1464,11 @@ static const char* authentication_method(const SSL* ssl) {
-         case SSL2_VERSION:
-             return SSL_TXT_RSA;
-         default:
-+#if defined(OPENSSL_IS_BORINGSSL)
-+            return cipher_authentication_method(SSL_get_pending_cipher(ssl));
-+#else
-             return cipher_authentication_method(ssl->s3->tmp.new_cipher);
-+#endif
-         }
-     }
- }
+@@ -1178,7 +1178,7 @@ static int SSL_cert_verify(X509_STORE_CTX *ctx, void *arg) {
+     tcn_ssl_ctxt_t *c = SSL_get_app_data2(ssl);
+     TCN_ASSERT(c != NULL);
+     tcn_ssl_verify_config_t* verify_config = SSL_get_app_data4(ssl);
+-    TCN_ASSERT(verify_confg != NULL);
++    TCN_ASSERT(verify_config != NULL);
 
-
-025da0aad4f9c2fdeebb64bcebf11bbf2c12a2bd and
-fd68c837b156ddb4b054e03d99a401e93068b34d were backported from upstream.
+     // Get a stack of all certs in the chain
+     STACK_OF(X509) *sk = ctx->untrusted;
diff --git a/third_party/netty4/README.chromium b/third_party/netty4/README.chromium
index 5cce515..947623a7 100644
--- a/third_party/netty4/README.chromium
+++ b/third_party/netty4/README.chromium
@@ -1,9 +1,9 @@
 Name: Netty
 Short Name: netty
 URL: http://netty.io/
-SHA: f40598a04aae5fa4b24810f30aaaf4a61c9c4385  netty-4.1.0.Beta8.tar.bz2
-Version: 4.1.0.Beta8
-Date: November 10, 2015
+SHA: 3b0025e08168eebc97b232fef333a716dc4d42bd  netty-4.1.9.Final.tar.bz2
+Version: 4.1.9.Final
+Date: March 10, 2017
 License: Apache 2.0
 License File: NOT_SHIPPED
 Security Critical: no
@@ -16,13 +16,4 @@
 simplifies and streamlines network programming such as TCP and UDP socket server.
 
 Local Modifications:
-Replaced netty-all jar files with the nightly build version
-and deleted all 4.1.0.Beta8 version jars.
-
-URL: https://oss.sonatype.org/content/repositories/snapshots/io/netty/netty-all/4.1.0.CR1-SNAPSHOT/
-SHA: 2748f46eca4216a08e75dd9ce618f61ed067c4f5 netty-all-4.1.0.CR1-20160111.120759-50-sources.jar
-Date: January 11, 2016
-
-URL: https://oss.sonatype.org/content/repositories/snapshots/io/netty/netty-all/4.1.0.CR1-SNAPSHOT/
-SHA: 16cc4addd84c7fb3444e6d79f7d8cef74adefc7d netty-all-4.1.0.CR1-20160111.120759-50.jar
-Date: January 11, 2016
+None
diff --git a/third_party/netty4/netty4.gni b/third_party/netty4/netty4.gni
index bb17fc6d..27e045df 100644
--- a/third_party/netty4/netty4.gni
+++ b/third_party/netty4/netty4.gni
@@ -4,4 +4,5 @@
 
 # Defines location of netty4 jar file.
 
-NETTY4_JAR_FILE = "//third_party/netty4/src/jar/all-in-one/netty-all-4.1.0.CR1-20160111.120759-50.jar"
+NETTY4_JAR_FILE =
+    "//third_party/netty4/src/jar/all-in-one/netty-all-4.1.9.Final.jar"
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index a9336494..74b3d8b 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -722,7 +722,7 @@
     ],
 
     'android_cronet_data_reduction_proxy_release_bot_minimal_symbols_arm_no_neon': [
-      'android', 'cronet', 'data_reduction_proxy', 'release_bot', 'minimal_symbols', 'arm_no_neon',
+      'android', 'cronet', 'release_bot', 'minimal_symbols', 'arm_no_neon',
     ],
 
     'android_cronet_debug_static_bot_arm64': [
@@ -738,7 +738,7 @@
     ],
 
     'android_cronet_data_reduction_proxy_release_bot_minimal_symbols_arm_no_neon': [
-      'android', 'cronet', 'data_reduction_proxy', 'release_bot', 'minimal_symbols', 'arm_no_neon',
+      'android', 'cronet', 'release_bot', 'minimal_symbols', 'arm_no_neon',
     ],
 
     'android_cronet_debug_static_bot_arm64': [
@@ -1654,10 +1654,6 @@
       'cros_passthrough': True,
     },
 
-    'data_reduction_proxy': {
-      'gn_args': 'cronet_enable_data_reduction_proxy_support=true',
-    },
-
     'dcheck_always_on': {
       'gn_args': 'dcheck_always_on=true',
     },
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 12b8f38..4913a17 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -9498,7 +9498,7 @@
 </histogram>
 
 <histogram name="Cryptohome.DircryptoMigrationEndStatus"
-    enum="DircryptoMigrationStartStatus">
+    enum="DircryptoMigrationEndStatus">
   <owner>dspaid@chromium.org</owner>
   <summary>
     The status of the user home directory migration from ecryptfs to
@@ -9528,6 +9528,14 @@
   </summary>
 </histogram>
 
+<histogram name="Cryptohome.HomedirEncryptionType" enum="HomedirEncryptionType">
+  <owner>dspaid@chromium.org</owner>
+  <summary>
+    The encryption type used for a user's cryptohome directory.  This is logged
+    each time the cryptohome is mounted.
+  </summary>
+</histogram>
+
 <histogram name="Cryptohome.MigrationToGaiaId"
     enum="CryptohomeMigrationToGaiaId">
   <owner>alemate@chromium.org</owner>
@@ -20821,6 +20829,11 @@
   </summary>
 </histogram>
 
+<histogram name="Feedback.RequestSource" enum="FeedbackSource">
+  <owner>afakhry@chromium.org</owner>
+  <summary>Records the source that requested showing the feedback app.</summary>
+</histogram>
+
 <histogram name="FileBrowser.CloudImport.UserAction"
     enum="CloudImportUserAction">
   <owner>smckay@google.com</owner>
@@ -97389,6 +97402,17 @@
   <int value="1970" label="GATTServerDisconnectedEvent"/>
 </enum>
 
+<enum name="FeedbackSource" type="int">
+  <int value="0" label="Arc App"/>
+  <int value="1" label="Ash"/>
+  <int value="2" label="Browser Command"/>
+  <int value="3" label="MD Settings About Page"/>
+  <int value="4" label="Old Settings About Page"/>
+  <int value="5" label="Profile Error Dialog"/>
+  <int value="6" label="SadTab Page"/>
+  <int value="7" label="Supervised User Interstitial"/>
+</enum>
+
 <enum name="FetchRequestMode" type="int">
   <int value="0" label="SameOrigin"/>
   <int value="1" label="NoCORS"/>
@@ -100048,6 +100072,11 @@
   </int>
 </enum>
 
+<enum name="HomedirEncryptionType" type="int">
+  <int value="1" label="Ecryptfs"/>
+  <int value="2" label="Ext4 Dir Encryption"/>
+</enum>
+
 <enum name="HotwordAvailability" type="int">
   <int value="0" label="Unavailable -- reason may be unknown"/>
   <int value="1" label="Available"/>
@@ -119324,6 +119353,20 @@
   <affected-histogram name="Autofill.SaveCreditCardPrompt"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="AutofillSaveCreditCardPromptPreviousUserDecision"
+    separator=".">
+  <suffix name="PreviouslyAccepted"
+      label="User had previously accepted save credit card prompt"/>
+  <suffix name="PreviouslyDenied"
+      label="User had previously denied save credit card prompt"/>
+  <affected-histogram name="Autofill.CreditCardInfoBar.Local"/>
+  <affected-histogram name="Autofill.CreditCardInfoBar.Server"/>
+  <affected-histogram name="Autofill.SaveCreditCardPrompt.Local.FirstShow"/>
+  <affected-histogram name="Autofill.SaveCreditCardPrompt.Local.Reshows"/>
+  <affected-histogram name="Autofill.SaveCreditCardPrompt.Upload.FirstShow"/>
+  <affected-histogram name="Autofill.SaveCreditCardPrompt.Upload.Reshows"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="AutofillSaveCreditCardPromptShow" separator=".">
   <suffix name="FirstShow"
       label="first time prompt is shown for a single credit card submit"/>
diff --git a/ui/aura/client/aura_constants.cc b/ui/aura/client/aura_constants.cc
index 1edf5a5d..e1b69559 100644
--- a/ui/aura/client/aura_constants.cc
+++ b/ui/aura/client/aura_constants.cc
@@ -32,6 +32,7 @@
 
 DEFINE_UI_CLASS_PROPERTY_KEY(
      bool, kAccessibilityFocusFallsbackToWidgetKey, true);
+DEFINE_UI_CLASS_PROPERTY_KEY(bool, kActivateOnPointerKey, true);
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kAlwaysOnTopKey, false);
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kAnimationsDisabledKey, false);
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::ImageSkia, kAppIconKey, nullptr);
diff --git a/ui/aura/client/aura_constants.h b/ui/aura/client/aura_constants.h
index bcfab7f..b67a543f0 100644
--- a/ui/aura/client/aura_constants.h
+++ b/ui/aura/client/aura_constants.h
@@ -36,6 +36,11 @@
 AURA_EXPORT extern const WindowProperty<bool>* const
     kAccessibilityFocusFallsbackToWidgetKey;
 
+// A property key to store whether activation on pointer event is enabled or
+// not. The default value is true, which means windows are activated when a
+// pointer down event occurs on them.
+AURA_EXPORT extern const WindowProperty<bool>* const kActivateOnPointerKey;
+
 // A property key to store always-on-top flag.
 AURA_EXPORT extern const WindowProperty<bool>* const kAlwaysOnTopKey;
 
diff --git a/ui/aura/mus/in_flight_change.cc b/ui/aura/mus/in_flight_change.cc
index 844d3f8..d806f5b 100644
--- a/ui/aura/mus/in_flight_change.cc
+++ b/ui/aura/mus/in_flight_change.cc
@@ -181,24 +181,22 @@
   window()->SetPropertyFromServer(property_name_, revert_value_.get());
 }
 
-// InFlightPredefinedCursorChange ---------------------------------------------
+// InFlightCursorChange ----------------------------------------------------
 
-InFlightPredefinedCursorChange::InFlightPredefinedCursorChange(
-    WindowMus* window,
-    ui::mojom::CursorType revert_value)
-    : InFlightChange(window, ChangeType::PREDEFINED_CURSOR),
+InFlightCursorChange::InFlightCursorChange(WindowMus* window,
+                                           const ui::CursorData& revert_value)
+    : InFlightChange(window, ChangeType::CURSOR),
       revert_cursor_(revert_value) {}
 
-InFlightPredefinedCursorChange::~InFlightPredefinedCursorChange() {}
+InFlightCursorChange::~InFlightCursorChange() {}
 
-void InFlightPredefinedCursorChange::SetRevertValueFrom(
-    const InFlightChange& change) {
+void InFlightCursorChange::SetRevertValueFrom(const InFlightChange& change) {
   revert_cursor_ =
-      static_cast<const InFlightPredefinedCursorChange&>(change).revert_cursor_;
+      static_cast<const InFlightCursorChange&>(change).revert_cursor_;
 }
 
-void InFlightPredefinedCursorChange::Revert() {
-  window()->SetPredefinedCursorFromServer(revert_cursor_);
+void InFlightCursorChange::Revert() {
+  window()->SetCursorFromServer(revert_cursor_);
 }
 
 // InFlightVisibleChange -------------------------------------------------------
diff --git a/ui/aura/mus/in_flight_change.h b/ui/aura/mus/in_flight_change.h
index 50920e9..738237f8 100644
--- a/ui/aura/mus/in_flight_change.h
+++ b/ui/aura/mus/in_flight_change.h
@@ -16,6 +16,7 @@
 #include "base/optional.h"
 #include "cc/surfaces/local_surface_id.h"
 #include "ui/aura/window_observer.h"
+#include "ui/base/cursor/cursor_data.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -46,7 +47,7 @@
   NEW_TOP_LEVEL_WINDOW,
   NEW_WINDOW,
   OPACITY,
-  PREDEFINED_CURSOR,
+  CURSOR,
   PROPERTY,
   REMOVE_CHILD,
   REMOVE_TRANSIENT_WINDOW_FROM_PARENT,
@@ -271,20 +272,19 @@
   DISALLOW_COPY_AND_ASSIGN(InFlightPropertyChange);
 };
 
-class InFlightPredefinedCursorChange : public InFlightChange {
+class InFlightCursorChange : public InFlightChange {
  public:
-  InFlightPredefinedCursorChange(WindowMus* window,
-                                 ui::mojom::CursorType revert_value);
-  ~InFlightPredefinedCursorChange() override;
+  InFlightCursorChange(WindowMus* window, const ui::CursorData& revert_value);
+  ~InFlightCursorChange() override;
 
   // InFlightChange:
   void SetRevertValueFrom(const InFlightChange& change) override;
   void Revert() override;
 
  private:
-  ui::mojom::CursorType revert_cursor_;
+  ui::CursorData revert_cursor_;
 
-  DISALLOW_COPY_AND_ASSIGN(InFlightPredefinedCursorChange);
+  DISALLOW_COPY_AND_ASSIGN(InFlightCursorChange);
 };
 
 class InFlightVisibleChange : public InFlightChange {
diff --git a/ui/aura/mus/window_manager_delegate.h b/ui/aura/mus/window_manager_delegate.h
index 0441963..917fe539 100644
--- a/ui/aura/mus/window_manager_delegate.h
+++ b/ui/aura/mus/window_manager_delegate.h
@@ -48,7 +48,7 @@
   virtual void SetFrameDecorationValues(
       ui::mojom::FrameDecorationValuesPtr values) = 0;
   virtual void SetNonClientCursor(Window* window,
-                                  ui::mojom::CursorType non_client_cursor) = 0;
+                                  const ui::CursorData& non_client_cursor) = 0;
 
   virtual void AddAccelerators(
       std::vector<ui::mojom::WmAcceleratorPtr> accelerators,
diff --git a/ui/aura/mus/window_mus.h b/ui/aura/mus/window_mus.h
index 40e8b4a..af7692c 100644
--- a/ui/aura/mus/window_mus.h
+++ b/ui/aura/mus/window_mus.h
@@ -81,7 +81,7 @@
       const base::Optional<cc::LocalSurfaceId>& local_surface_id) = 0;
   virtual void SetVisibleFromServer(bool visible) = 0;
   virtual void SetOpacityFromServer(float opacity) = 0;
-  virtual void SetPredefinedCursorFromServer(ui::mojom::CursorType cursor) = 0;
+  virtual void SetCursorFromServer(const ui::CursorData& cursor) = 0;
   virtual void SetPropertyFromServer(const std::string& property_name,
                                      const std::vector<uint8_t>* data) = 0;
   virtual void SetFrameSinkIdFromServer(
diff --git a/ui/aura/mus/window_port_mus.cc b/ui/aura/mus/window_port_mus.cc
index d5cec711..4c605a0 100644
--- a/ui/aura/mus/window_port_mus.cc
+++ b/ui/aura/mus/window_port_mus.cc
@@ -69,12 +69,12 @@
   window_tree_client_->SetImeVisibility(this, visible, std::move(state));
 }
 
-void WindowPortMus::SetPredefinedCursor(ui::mojom::CursorType cursor_id) {
-  if (cursor_id == predefined_cursor_)
+void WindowPortMus::SetCursor(const ui::CursorData& cursor) {
+  if (cursor_.IsSameAs(cursor))
     return;
 
-  window_tree_client_->SetPredefinedCursor(this, predefined_cursor_, cursor_id);
-  predefined_cursor_ = cursor_id;
+  window_tree_client_->SetCursor(this, cursor_, cursor);
+  cursor_ = cursor;
 }
 
 void WindowPortMus::SetEventTargetingPolicy(
@@ -270,11 +270,10 @@
   window_->layer()->SetOpacity(opacity);
 }
 
-void WindowPortMus::SetPredefinedCursorFromServer(
-    ui::mojom::CursorType cursor) {
+void WindowPortMus::SetCursorFromServer(const ui::CursorData& cursor) {
   // As this does nothing more than set the cursor we don't need to use
   // ServerChange.
-  predefined_cursor_ = cursor;
+  cursor_ = cursor;
 }
 
 void WindowPortMus::SetPropertyFromServer(
diff --git a/ui/aura/mus/window_port_mus.h b/ui/aura/mus/window_port_mus.h
index ec0a6ac2..8c102005 100644
--- a/ui/aura/mus/window_port_mus.h
+++ b/ui/aura/mus/window_port_mus.h
@@ -61,8 +61,8 @@
   void SetTextInputState(mojo::TextInputStatePtr state);
   void SetImeVisibility(bool visible, mojo::TextInputStatePtr state);
 
-  ui::mojom::CursorType predefined_cursor() const { return predefined_cursor_; }
-  void SetPredefinedCursor(ui::mojom::CursorType cursor_id);
+  const ui::CursorData& cursor() const { return cursor_; }
+  void SetCursor(const ui::CursorData& cursor);
 
   // Sets the EventTargetingPolicy, default is TARGET_AND_DESCENDANTS.
   void SetEventTargetingPolicy(ui::mojom::EventTargetingPolicy policy);
@@ -224,7 +224,7 @@
       const base::Optional<cc::LocalSurfaceId>& local_surface_id) override;
   void SetVisibleFromServer(bool visible) override;
   void SetOpacityFromServer(float opacity) override;
-  void SetPredefinedCursorFromServer(ui::mojom::CursorType cursor) override;
+  void SetCursorFromServer(const ui::CursorData& cursor) override;
   void SetPropertyFromServer(
       const std::string& property_name,
       const std::vector<uint8_t>* property_data) override;
@@ -286,7 +286,7 @@
   cc::LocalSurfaceIdAllocator local_surface_id_allocator_;
   gfx::Size last_surface_size_;
 
-  ui::mojom::CursorType predefined_cursor_ = ui::mojom::CursorType::kNull;
+  ui::CursorData cursor_;
 
   DISALLOW_COPY_AND_ASSIGN(WindowPortMus);
 };
diff --git a/ui/aura/mus/window_tree_client.cc b/ui/aura/mus/window_tree_client.cc
index d272bad..52b15cb 100644
--- a/ui/aura/mus/window_tree_client.cc
+++ b/ui/aura/mus/window_tree_client.cc
@@ -302,14 +302,14 @@
   tree_->SetCanFocus(WindowMus::Get(window)->server_id(), can_focus);
 }
 
-void WindowTreeClient::SetPredefinedCursor(WindowMus* window,
-                                           ui::mojom::CursorType old_cursor,
-                                           ui::mojom::CursorType new_cursor) {
+void WindowTreeClient::SetCursor(WindowMus* window,
+                                 const ui::CursorData& old_cursor,
+                                 const ui::CursorData& new_cursor) {
   DCHECK(tree_);
 
   const uint32_t change_id = ScheduleInFlightChange(
-      base::MakeUnique<InFlightPredefinedCursorChange>(window, old_cursor));
-  tree_->SetPredefinedCursor(change_id, window->server_id(), new_cursor);
+      base::MakeUnique<InFlightCursorChange>(window, old_cursor));
+  tree_->SetCursor(change_id, window->server_id(), new_cursor);
 }
 
 void WindowTreeClient::SetWindowTextInputState(WindowMus* window,
@@ -1341,18 +1341,17 @@
   focus_synchronizer_->SetFocusFromServer(focused_window);
 }
 
-void WindowTreeClient::OnWindowPredefinedCursorChanged(
-    Id window_id,
-    ui::mojom::CursorType cursor) {
+void WindowTreeClient::OnWindowCursorChanged(Id window_id,
+                                             ui::CursorData cursor) {
   WindowMus* window = GetWindowByServerId(window_id);
   if (!window)
     return;
 
-  InFlightPredefinedCursorChange new_change(window, cursor);
+  InFlightCursorChange new_change(window, cursor);
   if (ApplyServerChangeToExistingInFlightChange(new_change))
     return;
 
-  window->SetPredefinedCursorFromServer(cursor);
+  window->SetCursorFromServer(cursor);
 }
 
 void WindowTreeClient::OnWindowSurfaceChanged(
@@ -1770,10 +1769,10 @@
 }
 
 void WindowTreeClient::SetNonClientCursor(Window* window,
-                                          ui::mojom::CursorType cursor_id) {
+                                          const ui::CursorData& cursor) {
   if (window_manager_client_) {
     window_manager_client_->WmSetNonClientCursor(
-        WindowMus::Get(window)->server_id(), cursor_id);
+        WindowMus::Get(window)->server_id(), cursor);
   }
 }
 
diff --git a/ui/aura/mus/window_tree_client.h b/ui/aura/mus/window_tree_client.h
index 57616e9..0e5a550 100644
--- a/ui/aura/mus/window_tree_client.h
+++ b/ui/aura/mus/window_tree_client.h
@@ -130,9 +130,9 @@
   void SetCanAcceptDrops(WindowMus* window, bool can_accept_drops);
   void SetEventTargetingPolicy(WindowMus* window,
                                ui::mojom::EventTargetingPolicy policy);
-  void SetPredefinedCursor(WindowMus* window,
-                           ui::mojom::CursorType old_cursor,
-                           ui::mojom::CursorType new_cursor);
+  void SetCursor(WindowMus* window,
+                 const ui::CursorData& old_cursor,
+                 const ui::CursorData& new_cursor);
   void SetWindowTextInputState(WindowMus* window,
                                mojo::TextInputStatePtr state);
   void SetImeVisibility(WindowMus* window,
@@ -384,8 +384,7 @@
                               uint32_t window_id,
                               int64_t display_id) override;
   void OnWindowFocused(Id focused_window_id) override;
-  void OnWindowPredefinedCursorChanged(Id window_id,
-                                       ui::mojom::CursorType cursor) override;
+  void OnWindowCursorChanged(Id window_id, ui::CursorData cursor) override;
   void OnWindowSurfaceChanged(Id window_id,
                               const cc::SurfaceInfo& surface_info) override;
   void OnDragDropStart(
@@ -466,7 +465,7 @@
   void SetFrameDecorationValues(
       ui::mojom::FrameDecorationValuesPtr values) override;
   void SetNonClientCursor(Window* window,
-                          ui::mojom::CursorType cursor_id) override;
+                          const ui::CursorData& cursor) override;
   void AddAccelerators(std::vector<ui::mojom::WmAcceleratorPtr> accelerators,
                        const base::Callback<void(bool)>& callback) override;
   void RemoveAccelerator(uint32_t id) override;
diff --git a/ui/aura/test/mus/test_window_manager_client.cc b/ui/aura/test/mus/test_window_manager_client.cc
index 02fec597..713c3cc 100644
--- a/ui/aura/test/mus/test_window_manager_client.cc
+++ b/ui/aura/test/mus/test_window_manager_client.cc
@@ -53,9 +53,9 @@
 void TestWindowManagerClient::WmSetFrameDecorationValues(
     ui::mojom::FrameDecorationValuesPtr values) {}
 
-void TestWindowManagerClient::WmSetNonClientCursor(
-    uint32_t window_id,
-    ui::mojom::CursorType cursor_id) {}
+void TestWindowManagerClient::WmSetNonClientCursor(uint32_t window_id,
+                                                   ui::CursorData cursor_data) {
+}
 
 void TestWindowManagerClient::OnWmCreatedTopLevelWindow(
     uint32_t change_id,
diff --git a/ui/aura/test/mus/test_window_manager_client.h b/ui/aura/test/mus/test_window_manager_client.h
index a68bb39..e46d314 100644
--- a/ui/aura/test/mus/test_window_manager_client.h
+++ b/ui/aura/test/mus/test_window_manager_client.h
@@ -47,7 +47,7 @@
   void WmSetFrameDecorationValues(
       ui::mojom::FrameDecorationValuesPtr values) override;
   void WmSetNonClientCursor(uint32_t window_id,
-                            ui::mojom::CursorType cursor_id) override;
+                            ui::CursorData cursor_data) override;
   void OnWmCreatedTopLevelWindow(uint32_t change_id,
                                  Id transport_window_id) override;
   void OnAcceleratorAck(
diff --git a/ui/aura/test/mus/test_window_tree.cc b/ui/aura/test/mus/test_window_tree.cc
index 603d61a..fdf553f 100644
--- a/ui/aura/test/mus/test_window_tree.cc
+++ b/ui/aura/test/mus/test_window_tree.cc
@@ -254,9 +254,9 @@
     uint32_t window_id,
     ui::mojom::EventTargetingPolicy policy) {}
 
-void TestWindowTree::SetPredefinedCursor(uint32_t change_id,
-                                         uint32_t window_id,
-                                         ui::mojom::CursorType cursor_id) {
+void TestWindowTree::SetCursor(uint32_t change_id,
+                               Id transport_window_id,
+                               ui::CursorData cursor_data) {
   OnChangeReceived(change_id);
 }
 
diff --git a/ui/aura/test/mus/test_window_tree.h b/ui/aura/test/mus/test_window_tree.h
index 916e8a1d..5f7bc7c 100644
--- a/ui/aura/test/mus/test_window_tree.h
+++ b/ui/aura/test/mus/test_window_tree.h
@@ -175,9 +175,9 @@
   void SetCanFocus(uint32_t window_id, bool can_focus) override;
   void SetEventTargetingPolicy(uint32_t window_id,
                                ui::mojom::EventTargetingPolicy policy) override;
-  void SetPredefinedCursor(uint32_t change_id,
-                           uint32_t window_id,
-                           ui::mojom::CursorType cursor_id) override;
+  void SetCursor(uint32_t change_id,
+                 Id transport_window_id,
+                 ui::CursorData cursor_data) override;
   void SetWindowTextInputState(uint32_t window_id,
                                mojo::TextInputStatePtr state) override;
   void SetImeVisibility(uint32_t window_id,
diff --git a/ui/base/cursor/cursor_data.cc b/ui/base/cursor/cursor_data.cc
index c49400e..6c39f5a 100644
--- a/ui/base/cursor/cursor_data.cc
+++ b/ui/base/cursor/cursor_data.cc
@@ -30,10 +30,14 @@
 
 CursorData::CursorData(const CursorData& cursor) = default;
 
+CursorData::CursorData(CursorData&& cursor) = default;
+
 CursorData::~CursorData() {}
 
 CursorData& CursorData::operator=(const CursorData& cursor) = default;
 
+CursorData& CursorData::operator=(CursorData&& cursor) = default;
+
 bool CursorData::IsType(CursorType cursor_type) const {
   return cursor_type_ == cursor_type;
 }
diff --git a/ui/base/cursor/cursor_data.h b/ui/base/cursor/cursor_data.h
index c3879692d..6b3f5d2 100644
--- a/ui/base/cursor/cursor_data.h
+++ b/ui/base/cursor/cursor_data.h
@@ -45,9 +45,11 @@
              float scale_factor,
              const base::TimeDelta& frame_delay);
   CursorData(const CursorData& cursor);
+  CursorData(CursorData&& cursor);
   ~CursorData();
 
   CursorData& operator=(const CursorData& cursor);
+  CursorData& operator=(CursorData&& cursor);
 
   CursorType cursor_type() const { return cursor_type_; }
   const base::TimeDelta& frame_delay() const { return frame_delay_; }
diff --git a/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js b/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
index 544b6600..2955630 100644
--- a/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
@@ -548,6 +548,15 @@
   var shareEntries;
   var taskId = this.fileOperationManager_.generateTaskId();
 
+  var item = new ProgressCenterItem();
+  item.id = taskId;
+  if (toMove) {
+    item.message = strf('MOVE_ITEMS_REMAINING', sourceURLs.length);
+  } else {
+    item.message = strf('COPY_ITEMS_REMAINING', sourceURLs.length);
+  }
+  this.progressCenter_.updateItem(item);
+
   FileTransferController.URLsToEntriesWithAccess(sourceURLs)
       .then(
           /**
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index a5ad059..1d054ac 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -35,6 +35,10 @@
 #include "ui/wm/core/window_util.h"
 #include "ui/wm/public/activation_client.h"
 
+#if defined(USE_OZONE)
+#include "ui/base/cursor/ozone/cursor_data_factory_ozone.h"
+#endif
+
 namespace views {
 
 namespace {
@@ -131,8 +135,21 @@
 
   void SetCursor(gfx::NativeCursor cursor,
                  wm::NativeCursorManagerDelegate* delegate) override {
-    aura::WindowPortMus::Get(window_)->SetPredefinedCursor(
-        ui::mojom::CursorType(cursor.native_type()));
+    ui::CursorData mojo_cursor;
+    if (cursor.platform()) {
+#if defined(USE_OZONE)
+      mojo_cursor =
+          ui::CursorDataFactoryOzone::GetCursorData(cursor.platform());
+#else
+      NOTIMPLEMENTED()
+          << "Can't pass native platform cursors on non-ozone platforms";
+      mojo_cursor = ui::CursorData(ui::CursorType::kPointer);
+#endif
+    } else {
+      mojo_cursor = ui::CursorData(cursor.native_type());
+    }
+
+    aura::WindowPortMus::Get(window_)->SetCursor(mojo_cursor);
     delegate->CommitCursor(cursor);
   }
 
@@ -143,8 +160,8 @@
     if (visible) {
       SetCursor(delegate->GetCursor(), delegate);
     } else {
-      aura::WindowPortMus::Get(window_)->SetPredefinedCursor(
-          ui::mojom::CursorType::kNone);
+      aura::WindowPortMus::Get(window_)->SetCursor(
+          ui::CursorData(ui::CursorType::kNone));
     }
   }
 
diff --git a/ui/wm/core/base_focus_rules.cc b/ui/wm/core/base_focus_rules.cc
index e38aa2e5..70086b2 100644
--- a/ui/wm/core/base_focus_rules.cc
+++ b/ui/wm/core/base_focus_rules.cc
@@ -16,7 +16,7 @@
 aura::Window* GetFocusedWindow(aura::Window* context) {
   aura::client::FocusClient* focus_client =
       aura::client::GetFocusClient(context);
-  return focus_client ? focus_client->GetFocusedWindow() : NULL;
+  return focus_client ? focus_client->GetFocusedWindow() : nullptr;
 }
 
 }  // namespace
@@ -24,11 +24,9 @@
 ////////////////////////////////////////////////////////////////////////////////
 // BaseFocusRules, protected:
 
-BaseFocusRules::BaseFocusRules() {
-}
+BaseFocusRules::BaseFocusRules() = default;
 
-BaseFocusRules::~BaseFocusRules() {
-}
+BaseFocusRules::~BaseFocusRules() = default;
 
 bool BaseFocusRules::IsWindowConsideredVisibleForActivation(
     aura::Window* window) const {
@@ -78,7 +76,8 @@
   return !GetModalTransient(window);
 }
 
-bool BaseFocusRules::CanFocusWindow(aura::Window* window) const {
+bool BaseFocusRules::CanFocusWindow(aura::Window* window,
+                                    const ui::Event* event) const {
   // It is possible to focus a NULL window, it is equivalent to clearing focus.
   if (!window)
     return true;
@@ -101,7 +100,7 @@
     parent = parent->parent();
     child = child->parent();
   }
-  return NULL;
+  return nullptr;
 }
 
 aura::Window* BaseFocusRules::GetActivatableWindow(aura::Window* window) const {
@@ -133,11 +132,11 @@
     parent = parent->parent();
     child = child->parent();
   }
-  return NULL;
+  return nullptr;
 }
 
 aura::Window* BaseFocusRules::GetFocusableWindow(aura::Window* window) const {
-  if (CanFocusWindow(window))
+  if (CanFocusWindow(window, nullptr))
     return window;
 
   // |window| may be in a hierarchy that is non-activatable, in which case we
@@ -150,7 +149,7 @@
     if (toplevel)
       activatable = GetNextActivatableWindow(toplevel);
     if (!activatable)
-      return NULL;
+      return nullptr;
   }
 
   if (!activatable->Contains(window)) {
@@ -161,7 +160,7 @@
     return activatable->Contains(focused) ? focused : activatable;
   }
 
-  while (window && !CanFocusWindow(window))
+  while (window && !CanFocusWindow(window, nullptr))
     window = window->parent();
   return window;
 }
@@ -172,7 +171,7 @@
 
   // Can be called from the RootWindow's destruction, which has a NULL parent.
   if (!ignore->parent())
-    return NULL;
+    return nullptr;
 
   // In the basic scenarios handled by BasicFocusRules, the pool of activatable
   // windows is limited to the |ignore|'s siblings.
@@ -188,7 +187,7 @@
     if (CanActivateWindow(cur))
       return cur;
   }
-  return NULL;
+  return nullptr;
 }
 
 }  // namespace wm
diff --git a/ui/wm/core/base_focus_rules.h b/ui/wm/core/base_focus_rules.h
index 0e84e29..990a368 100644
--- a/ui/wm/core/base_focus_rules.h
+++ b/ui/wm/core/base_focus_rules.h
@@ -28,7 +28,8 @@
   // Overridden from FocusRules:
   bool IsToplevelWindow(aura::Window* window) const override;
   bool CanActivateWindow(aura::Window* window) const override;
-  bool CanFocusWindow(aura::Window* window) const override;
+  bool CanFocusWindow(aura::Window* window,
+                      const ui::Event* event) const override;
   aura::Window* GetToplevelWindow(aura::Window* window) const override;
   aura::Window* GetActivatableWindow(aura::Window* window) const override;
   aura::Window* GetFocusableWindow(aura::Window* window) const override;
diff --git a/ui/wm/core/focus_controller.cc b/ui/wm/core/focus_controller.cc
index 230de21..68b6d80 100644
--- a/ui/wm/core/focus_controller.cc
+++ b/ui/wm/core/focus_controller.cc
@@ -38,8 +38,8 @@
 // FocusController, public:
 
 FocusController::FocusController(FocusRules* rules)
-    : active_window_(NULL),
-      focused_window_(NULL),
+    : active_window_(nullptr),
+      focused_window_(nullptr),
       updating_focus_(false),
       updating_activation_(false),
       rules_(rules),
@@ -47,8 +47,7 @@
   DCHECK(rules);
 }
 
-FocusController::~FocusController() {
-}
+FocusController::~FocusController() = default;
 
 ////////////////////////////////////////////////////////////////////////////////
 // FocusController, aura::client::ActivationClient implementation:
@@ -127,7 +126,8 @@
 
 void FocusController::OnMouseEvent(ui::MouseEvent* event) {
   if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled())
-    WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target()));
+    WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target()),
+                                event);
 }
 
 void FocusController::OnScrollEvent(ui::ScrollEvent* event) {
@@ -140,7 +140,8 @@
   if (event->type() == ui::ET_GESTURE_BEGIN &&
       event->details().touch_points() == 1 &&
       !event->handled()) {
-    WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target()));
+    WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target()),
+                                event);
   }
 }
 
@@ -196,7 +197,7 @@
   // that the rules could redirect activation activation and/or focus.
   aura::Window* focusable = rules_->GetFocusableWindow(window);
   aura::Window* activatable =
-      focusable ? rules_->GetActivatableWindow(focusable) : NULL;
+      focusable ? rules_->GetActivatableWindow(focusable) : nullptr;
 
   // We need valid focusable/activatable windows in the event we're not clearing
   // focus. "Clearing focus" is inferred by whether or not |window| passed to
@@ -224,7 +225,7 @@
 void FocusController::SetFocusedWindow(aura::Window* window) {
   if (updating_focus_ || window == focused_window_)
     return;
-  DCHECK(rules_->CanFocusWindow(window));
+  DCHECK(rules_->CanFocusWindow(window, nullptr));
   if (window)
     DCHECK_EQ(window, rules_->GetFocusableWindow(window));
 
@@ -247,7 +248,7 @@
   for (auto& observer : focus_observers_) {
     observer.OnWindowFocused(
         focused_window_,
-        window_tracker.Contains(lost_focus) ? lost_focus : NULL);
+        window_tracker.Contains(lost_focus) ? lost_focus : nullptr);
   }
   if (window_tracker.Contains(lost_focus)) {
     aura::client::FocusChangeObserver* observer =
@@ -260,7 +261,7 @@
   if (observer) {
     observer->OnWindowFocused(
         focused_window_,
-        window_tracker.Contains(lost_focus) ? lost_focus : NULL);
+        window_tracker.Contains(lost_focus) ? lost_focus : nullptr);
   }
 }
 
@@ -300,7 +301,7 @@
   if (active_window_)
     StackActiveWindow();
 
-  aura::client::ActivationChangeObserver* observer = NULL;
+  aura::client::ActivationChangeObserver* observer = nullptr;
   if (window_tracker.Contains(lost_activation)) {
     observer = aura::client::GetActivationChangeObserver(lost_activation);
     if (observer)
@@ -310,12 +311,12 @@
   if (observer) {
     observer->OnWindowActivated(
         reason, active_window_,
-        window_tracker.Contains(lost_activation) ? lost_activation : NULL);
+        window_tracker.Contains(lost_activation) ? lost_activation : nullptr);
   }
   for (auto& observer : activation_observers_) {
     observer.OnWindowActivated(
         reason, active_window_,
-        window_tracker.Contains(lost_activation) ? lost_activation : NULL);
+        window_tracker.Contains(lost_activation) ? lost_activation : nullptr);
   }
 }
 
@@ -338,7 +339,7 @@
     aura::Window* next_activatable = rules_->GetNextActivatableWindow(window);
     SetActiveWindow(aura::client::ActivationChangeObserver::ActivationReason::
                         WINDOW_DISPOSITION_CHANGED,
-                    NULL, next_activatable);
+                    nullptr, next_activatable);
     if (!(active_window_ && active_window_->Contains(focused_window_)))
       SetFocusedWindow(next_activatable);
   } else if (window->Contains(focused_window_)) {
@@ -347,11 +348,12 @@
   }
 }
 
-void FocusController::WindowFocusedFromInputEvent(aura::Window* window) {
+void FocusController::WindowFocusedFromInputEvent(aura::Window* window,
+                                                  const ui::Event* event) {
   // Only focus |window| if it or any of its parents can be focused. Otherwise
   // FocusWindow() will focus the topmost window, which may not be the
   // currently focused one.
-  if (rules_->CanFocusWindow(GetToplevelWindow(window))) {
+  if (rules_->CanFocusWindow(GetToplevelWindow(window), event)) {
     FocusAndActivateWindow(
         aura::client::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
         window);
diff --git a/ui/wm/core/focus_controller.h b/ui/wm/core/focus_controller.h
index 66b2b8d..c59003f0 100644
--- a/ui/wm/core/focus_controller.h
+++ b/ui/wm/core/focus_controller.h
@@ -116,7 +116,8 @@
   // Called when an attempt is made to focus or activate a window via an input
   // event targeted at that window. Rules determine the best focusable window
   // for the input window.
-  void WindowFocusedFromInputEvent(aura::Window* window);
+  void WindowFocusedFromInputEvent(aura::Window* window,
+                                   const ui::Event* event);
 
   aura::Window* active_window_;
   aura::Window* focused_window_;
diff --git a/ui/wm/core/focus_controller_unittest.cc b/ui/wm/core/focus_controller_unittest.cc
index 110a47c8..169c01c32 100644
--- a/ui/wm/core/focus_controller_unittest.cc
+++ b/ui/wm/core/focus_controller_unittest.cc
@@ -335,9 +335,11 @@
         CanFocusOrActivate(window) || window->Contains(focus_restriction_);
     return can_activate ? BaseFocusRules::CanActivateWindow(window) : false;
   }
-  bool CanFocusWindow(aura::Window* window) const override {
-    return CanFocusOrActivate(window) ?
-        BaseFocusRules::CanFocusWindow(window) : false;
+  bool CanFocusWindow(aura::Window* window,
+                      const ui::Event* event) const override {
+    return CanFocusOrActivate(window)
+               ? BaseFocusRules::CanFocusWindow(window, event)
+               : false;
   }
   aura::Window* GetActivatableWindow(aura::Window* window) const override {
     return BaseFocusRules::GetActivatableWindow(
diff --git a/ui/wm/core/focus_rules.h b/ui/wm/core/focus_rules.h
index cb9a4ddb..b8d0874 100644
--- a/ui/wm/core/focus_rules.h
+++ b/ui/wm/core/focus_rules.h
@@ -11,6 +11,10 @@
 class Window;
 }
 
+namespace ui {
+class Event;
+}
+
 namespace wm {
 
 // Implemented by an object that establishes the rules about what can be
@@ -26,9 +30,11 @@
   virtual bool IsToplevelWindow(aura::Window* window) const = 0;
   // Returns true if |window| can be activated or focused.
   virtual bool CanActivateWindow(aura::Window* window) const = 0;
-  // For CanFocusWindow(), NULL is supported, because NULL is a valid focusable
-  // window (in the case of clearing focus).
-  virtual bool CanFocusWindow(aura::Window* window) const = 0;
+  // For CanFocusWindow(), NULL window is supported, because NULL is a valid
+  // focusable window (in the case of clearing focus).
+  // If |event| is non-null it is the event triggering the focus change.
+  virtual bool CanFocusWindow(aura::Window* window,
+                              const ui::Event* event) const = 0;
 
   // Returns the toplevel window containing |window|. Not all toplevel windows
   // are activatable, call GetActivatableWindow() instead to return the