diff --git a/BUILD.gn b/BUILD.gn
index bd6786e..7c9522e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -284,6 +284,7 @@
       "//services:service_junit_tests",
       "//testing/android/junit:junit_unit_tests",
       "//third_party/android_async_task:android_async_task_java",
+      "//third_party/catapult/devil",
       "//third_party/errorprone:chromium_errorprone",
       "//third_party/smhasher:murmurhash3",
       "//tools/android:android_tools",
diff --git a/DEPS b/DEPS
index feb40e2b..901fbd31 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'fb49909acafba5e031b90a265a6ce059cda85019',
+  'skia_revision': '8744405448b9402e1368aebd321c4f555543301a',
   # 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': '65ef03f8a0fb9bd4f2cfa9afe60ba312cbb3ea08',
+  'v8_revision': '71ba1026417321cbdf2bd0f9728a11622a9715e3',
   # 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.
@@ -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': 'b4eb70c319cb0f38d1a7764e7d9fb56dc96f8bcf',
+  'catapult_revision': '3db1a306cf177f6977aee7ba79d6df325b99d052',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/apps/app_restore_service_browsertest.cc b/apps/app_restore_service_browsertest.cc
index 9946a8a87..28788566 100644
--- a/apps/app_restore_service_browsertest.cc
+++ b/apps/app_restore_service_browsertest.cc
@@ -11,6 +11,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/test/test_utils.h"
+#include "extensions/browser/api/file_system/saved_file_entry.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/common/extension.h"
@@ -20,6 +21,7 @@
 using extensions::ExtensionPrefs;
 using extensions::ExtensionSystem;
 using extensions::FileSystemChooseEntryFunction;
+using extensions::SavedFileEntry;
 
 // TODO(benwells): Move PlatformAppBrowserTest to apps namespace in apps
 // component.
diff --git a/apps/saved_files_service.cc b/apps/saved_files_service.cc
index 14e25ff..c2f01d7 100644
--- a/apps/saved_files_service.cc
+++ b/apps/saved_files_service.cc
@@ -16,6 +16,7 @@
 #include "base/value_conversions.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/notification_service.h"
+#include "extensions/browser/api/file_system/saved_file_entry.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_system.h"
@@ -30,6 +31,7 @@
 using extensions::Extension;
 using extensions::ExtensionHost;
 using extensions::ExtensionPrefs;
+using extensions::SavedFileEntry;
 
 namespace {
 
@@ -140,17 +142,6 @@
 
 }  // namespace
 
-SavedFileEntry::SavedFileEntry() : is_directory(false), sequence_number(0) {}
-
-SavedFileEntry::SavedFileEntry(const std::string& id,
-                               const base::FilePath& path,
-                               bool is_directory,
-                               int sequence_number)
-    : id(id),
-      path(path),
-      is_directory(is_directory),
-      sequence_number(sequence_number) {}
-
 class SavedFilesService::SavedFiles {
  public:
   SavedFiles(content::BrowserContext* context, const std::string& extension_id);
diff --git a/apps/saved_files_service.h b/apps/saved_files_service.h
index 927f22b..e414e3e 100644
--- a/apps/saved_files_service.h
+++ b/apps/saved_files_service.h
@@ -16,6 +16,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/api/file_system/saved_files_service_interface.h"
 
 namespace content {
 class BrowserContext;
@@ -28,38 +29,15 @@
 
 namespace extensions {
 class Extension;
+struct SavedFileEntry;
 }
 
 namespace apps {
 
-// Represents a file entry that a user has given an app permission to
-// access. Will be persisted to disk (in the Preferences file), so should remain
-// serializable.
-struct SavedFileEntry {
-  SavedFileEntry();
-
-  SavedFileEntry(const std::string& id,
-                 const base::FilePath& path,
-                 bool is_directory,
-                 int sequence_number);
-
-  // The opaque id of this file entry.
-  std::string id;
-
-  // The path to a file entry that the app had permission to access.
-  base::FilePath path;
-
-  // Whether or not the entry refers to a directory.
-  bool is_directory;
-
-  // The sequence number in the LRU of the file entry. The value 0 indicates
-  // that the entry is not in the LRU.
-  int sequence_number;
-};
-
 // Tracks the files that apps have retained access to both while running and
 // when suspended.
-class SavedFilesService : public KeyedService,
+class SavedFilesService : public extensions::SavedFilesServiceInterface,
+                          public KeyedService,
                           public content::NotificationObserver {
  public:
   explicit SavedFilesService(content::BrowserContext* context);
@@ -67,31 +45,21 @@
 
   static SavedFilesService* Get(content::BrowserContext* context);
 
-  // Registers a file entry with the saved files service, making it eligible to
-  // be put into the queue. File entries that are in the retained files queue at
-  // object construction are automatically registered.
+  // extensions::SavedFilesServiceInterface:
   void RegisterFileEntry(const std::string& extension_id,
                          const std::string& id,
                          const base::FilePath& file_path,
-                         bool is_directory);
-
-  // If the file with |id| is not in the queue of files to be retained
-  // permanently, adds the file to the back of the queue, evicting the least
-  // recently used entry at the front of the queue if it is full. If it is
-  // already present, moves it to the back of the queue. The |id| must have been
-  // registered.
-  void EnqueueFileEntry(const std::string& extension_id, const std::string& id);
-
-  // Returns whether the file entry with the given |id| has been registered.
-  bool IsRegistered(const std::string& extension_id, const std::string& id);
-
-  // Gets a borrowed pointer to the file entry with the specified |id|. Returns
-  // NULL if the file entry has not been registered.
-  const SavedFileEntry* GetFileEntry(const std::string& extension_id,
-                                     const std::string& id);
+                         bool is_directory) override;
+  void EnqueueFileEntry(const std::string& extension_id,
+                        const std::string& id) override;
+  bool IsRegistered(const std::string& extension_id,
+                    const std::string& id) override;
+  const extensions::SavedFileEntry* GetFileEntry(
+      const std::string& extension_id,
+      const std::string& id) override;
 
   // Returns all registered file entries.
-  std::vector<SavedFileEntry> GetAllFileEntries(
+  std::vector<extensions::SavedFileEntry> GetAllFileEntries(
       const std::string& extension_id);
 
   // Clears all retained files if the app does not have the
diff --git a/apps/saved_files_service_unittest.cc b/apps/saved_files_service_unittest.cc
index fe215c6..3d7c373c 100644
--- a/apps/saved_files_service_unittest.cc
+++ b/apps/saved_files_service_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/values.h"
 #include "chrome/browser/extensions/test_extension_environment.h"
 #include "chrome/test/base/testing_profile.h"
+#include "extensions/browser/api/file_system/saved_file_entry.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
@@ -22,8 +23,8 @@
     expression;                \
   } while (0)
 
-using apps::SavedFileEntry;
 using apps::SavedFilesService;
+using extensions::SavedFileEntry;
 
 namespace {
 
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index 948fb78a..9812cb8 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -11,8 +11,10 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shell.h"
+#include "ash/shell_port.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/test_app_list_view_presenter_impl.h"
+#include "ash/wm/maximize_mode/maximize_mode_controller.h"
 #include "ash/wm/window_util.h"
 #include "base/command_line.h"
 #include "base/macros.h"
@@ -34,6 +36,15 @@
   return display::Screen::GetScreen()->GetPrimaryDisplay().id();
 }
 
+void SetShelfAlignment(ShelfAlignment alignment) {
+  test::AshTestBase::GetPrimaryShelf()->SetAlignment(alignment);
+}
+
+void EnableMaximizeMode(bool enable) {
+  Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager(
+      enable);
+}
+
 }  // namespace
 
 class AppListPresenterDelegateTest : public test::AshTestBase,
@@ -192,11 +203,260 @@
   EXPECT_GE(app_list_view_top, kMinimalAppListMargin);
 }
 
+// Tests that the app list initializes in fullscreen with side shelf alignment
+// and that the state transitions via text input act properly.
+TEST_F(AppListPresenterDelegateTest, SideShelfAlignmentTextStateTransitions) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  SetShelfAlignment(ShelfAlignment::SHELF_ALIGNMENT_LEFT);
+
+  // Open the app list with side shelf alignment, then check that it is in
+  // fullscreen mode.
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_TRUE(app_list->is_fullscreen());
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+
+  // Enter text in the searchbox, the app list should transition to fullscreen
+  // search.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_SEARCH);
+
+  // Delete the text in the searchbox, the app list should transition to
+  // fullscreen all apps.
+  generator.PressKey(ui::KeyboardCode::VKEY_BACK, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+}
+
+// Tests that the app list initializes in peeking with bottom shelf alignment
+// and that the state transitions via text input act properly.
+TEST_F(AppListPresenterDelegateTest, BottomShelfAlignmentTextStateTransitions) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_FALSE(app_list->is_fullscreen());
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::PEEKING);
+
+  // Enter text in the searchbox, this should transition the app list to half
+  // state.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::HALF);
+
+  // Empty the searchbox, this should transition the app list to it's previous
+  // state.
+  generator.PressKey(ui::KeyboardCode::VKEY_BACK, 0);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::PEEKING);
+}
+
+// Tests that the app list initializes in fullscreen with maximize mode active
+// and that the state transitions via text input act properly.
+TEST_F(AppListPresenterDelegateTest, MaximizeModeTextStateTransitions) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  EnableMaximizeMode(true);
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+
+  // Enter text in the searchbox, the app list should transition to fullscreen
+  // search.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_SEARCH);
+
+  // Delete the text in the searchbox, the app list should transition to
+  // fullscreen all apps. generator.PressKey(ui::KeyboardCode::VKEY_BACK, 0);
+  generator.PressKey(ui::KeyboardCode::VKEY_BACK, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+}
+
+// Tests that the app list state responds correctly to maximize mode being
+// enabled while the app list is being shown.
+TEST_F(AppListPresenterDelegateTest,
+       PeekingToFullscreenWhenMaximizeModeIsActive) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::PEEKING);
+  // Enable maximize mode, this should force the app list to switch to the
+  // fullscreen equivalent of the current state.
+  EnableMaximizeMode(true);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+  // Disable maximize mode, the state of the app list should not change.
+  EnableMaximizeMode(false);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+  // Enter text in the searchbox, the app list should transition to fullscreen
+  // search.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_SEARCH);
+
+  // Delete the text in the searchbox, the app list should transition to
+  // fullscreen all apps. generator.PressKey(ui::KeyboardCode::VKEY_BACK, 0);
+  generator.PressKey(ui::KeyboardCode::VKEY_BACK, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+}
+
+// Tests that the app list state responds correctly to maximize mode being
+// enabled while the app list is being shown with half launcher.
+TEST_F(AppListPresenterDelegateTest, HalfToFullscreenWhenMaximizeModeIsActive) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::PEEKING);
+
+  // Enter text in the search box to transition to half app list.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::HALF);
+
+  // Enable maximize mode and force the app list to transition to the fullscreen
+  // equivalent of the current state.
+  EnableMaximizeMode(true);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_SEARCH);
+  generator.PressKey(ui::KeyboardCode::VKEY_BACK, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+}
+
+// Tests that the app list view handles drag properly in laptop mode.
+TEST_F(AppListPresenterDelegateTest, AppListViewDragHandler) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::PEEKING);
+
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  // Execute a slow short upwards drag this should fail to transition the app
+  // list.
+  int top_of_app_list = app_list_presenter_impl()
+                            ->GetView()
+                            ->GetWidget()
+                            ->GetWindowBoundsInScreen()
+                            .y();
+  generator.GestureScrollSequence(gfx::Point(0, top_of_app_list + 20),
+                                  gfx::Point(0, top_of_app_list - 20),
+                                  base::TimeDelta::FromMilliseconds(500), 50);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::PEEKING);
+
+  // Execute a long upwards drag, this should transition the app list.
+  generator.GestureScrollSequence(gfx::Point(10, top_of_app_list + 20),
+                                  gfx::Point(10, 10),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+
+  // Execute a short downward drag, this should fail to transition the app list.
+  generator.GestureScrollSequence(gfx::Point(10, 10), gfx::Point(10, 100),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+
+  // Execute a long downward drag, this should transition the app list.
+  generator.GestureScrollSequence(gfx::Point(10, 10), gfx::Point(10, 900),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::PEEKING);
+
+  // Transition to fullscreen.
+  generator.GestureScrollSequence(gfx::Point(10, top_of_app_list + 20),
+                                  gfx::Point(10, 10),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+
+  // Enter text to transition to |FULLSCREEN_SEARCH|.
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_SEARCH);
+
+  // Execute a short downward drag, this should fail to close the app list.
+  generator.GestureScrollSequence(gfx::Point(10, 10), gfx::Point(10, 100),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_SEARCH);
+
+  // Execute a long downward drag, this should close the app list.
+  generator.GestureScrollSequence(gfx::Point(10, 10), gfx::Point(10, 900),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::CLOSED);
+}
+
+// Tests that the app list view handles drag properly in maximize mode.
+TEST_F(AppListPresenterDelegateTest,
+       AppListViewDragHandlerMaximizeModeFromAllApps) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  EnableMaximizeMode(true);
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  // Drag down.
+  generator.GestureScrollSequence(gfx::Point(0, 0), gfx::Point(0, 720),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::CLOSED);
+}
+
+// Tests that the state of the app list changes properly with drag input from
+// fullscreen search.
+TEST_F(AppListPresenterDelegateTest,
+       AppListViewDragHandlerMaximizeModeFromSearch) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  // Reset the app list.
+  EnableFullscreenAppList();
+  EnableMaximizeMode(true);
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_ALL_APPS);
+  // Type in the search box to transition to |FULLSCREEN_SEARCH|.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(),
+            app_list::AppListView::FULLSCREEN_SEARCH);
+  // Drag down, this should close the app list.
+  generator.GestureScrollSequence(gfx::Point(0, 0), gfx::Point(0, 720),
+                                  base::TimeDelta::FromMilliseconds(100), 10);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::CLOSED);
+}
+
+// Tests that the bottom shelf background is hidden when the app list is shown
+// in laptop mode.
+TEST_F(AppListPresenterDelegateTest,
+       ShelfBackgroundIsHiddenWhenAppListIsShown) {
+  EnableFullscreenAppList();
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  ShelfLayoutManager* shelf_layout_manager =
+      Shelf::ForWindow(Shell::GetRootWindowForDisplayId(GetPrimaryDisplayId()))
+          ->shelf_layout_manager();
+  EXPECT_TRUE(shelf_layout_manager->GetShelfBackgroundType() ==
+              ShelfBackgroundType::SHELF_BACKGROUND_DEFAULT);
+}
+
 // Tests that the peeking app list closes if the user taps outside its
 // bounds.
 TEST_F(AppListPresenterDelegateTest, TapAndClickOutsideClosesPeekingAppList) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
   EnableFullscreenAppList();
-
   app_list_presenter_impl()->Show(GetPrimaryDisplayId());
   EXPECT_TRUE(app_list_presenter_impl()->GetTargetVisibility());
   ui::test::EventGenerator& generator = GetEventGenerator();
@@ -262,4 +522,51 @@
             SHELF_BACKGROUND_DEFAULT);
 }
 
+// Tests that the half app list closes if the user taps outside its bounds.
+TEST_F(AppListPresenterDelegateTest, TapAndClickOutsideClosesHalfAppList) {
+  // TODO(newcomer): Investigate mash failures crbug.com/726838
+  EnableFullscreenAppList();
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  // Transition to half app list by entering text.
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  app_list::AppListView* app_list = app_list_presenter_impl()->GetView();
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::HALF);
+
+  // Grab the bounds of the search box,
+  // which is guaranteed to be inside the app list.
+  gfx::Point tap_point = app_list_presenter_impl()
+                             ->GetView()
+                             ->search_box_widget()
+                             ->GetContentsView()
+                             ->GetBoundsInScreen()
+                             .CenterPoint();
+
+  // Tapping inside the bounds doesn't close the app list.
+  generator.GestureTapAt(tap_point);
+  EXPECT_TRUE(app_list_presenter_impl()->GetTargetVisibility());
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::HALF);
+
+  // Clicking inside the bounds doesn't close the app list.
+  generator.MoveMouseTo(tap_point);
+  generator.ClickLeftButton();
+  EXPECT_TRUE(app_list_presenter_impl()->IsVisible());
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::HALF);
+
+  // Tapping outside the bounds closes the app list.
+  generator.GestureTapAt(gfx::Point(10, 10));
+  EXPECT_FALSE(app_list_presenter_impl()->IsVisible());
+
+  // Reset the app list to half state.
+  app_list_presenter_impl()->Show(GetPrimaryDisplayId());
+  generator.PressKey(ui::KeyboardCode::VKEY_0, 0);
+  EXPECT_EQ(app_list->app_list_state(), app_list::AppListView::HALF);
+
+  // Clicking outside the bounds closes the app list.
+  generator.MoveMouseTo(gfx::Point(10, 10));
+  generator.ClickLeftButton();
+  EXPECT_FALSE(app_list_presenter_impl()->IsVisible());
+}
+
 }  // namespace ash
diff --git a/ash/mus/BUILD.gn b/ash/mus/BUILD.gn
index 72de32ac..d55c14e 100644
--- a/ash/mus/BUILD.gn
+++ b/ash/mus/BUILD.gn
@@ -194,6 +194,7 @@
   sources = [
     "app_launch_unittest.cc",
     "bridge/shell_port_mash_test_api.h",
+    "display_synchronizer_unittest.cc",
     "non_client_frame_controller_unittest.cc",
     "top_level_window_factory_unittest.cc",
     "window_manager_unittest.cc",
diff --git a/ash/mus/display_synchronizer.cc b/ash/mus/display_synchronizer.cc
index f948e2f..ca00f7c 100644
--- a/ash/mus/display_synchronizer.cc
+++ b/ash/mus/display_synchronizer.cc
@@ -15,10 +15,12 @@
     aura::WindowManagerClient* window_manager_client)
     : window_manager_client_(window_manager_client) {
   Shell::Get()->window_tree_host_manager()->AddObserver(this);
+  Shell::Get()->display_manager()->AddObserver(this);
   SendDisplayConfigurationToServer();
 }
 
 DisplaySynchronizer::~DisplaySynchronizer() {
+  Shell::Get()->display_manager()->RemoveObserver(this);
   Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
 }
 
@@ -44,6 +46,8 @@
   window_manager_client_->SetDisplayConfiguration(
       displays, std::move(metrics),
       WindowTreeHostManager::GetPrimaryDisplayId());
+
+  sent_initial_config_ = true;
 }
 
 void DisplaySynchronizer::OnDisplaysInitialized() {
@@ -54,4 +58,15 @@
   SendDisplayConfigurationToServer();
 }
 
+void DisplaySynchronizer::OnDisplayMetricsChanged(
+    const display::Display& display,
+    uint32_t changed_metrics) {
+  // Changing only the work area doesn't trigger
+  // OnDisplayConfigurationChanged().
+  // Wait for the initial config before sending anything as initial display
+  // creation may trigger numerous calls to OnDisplayConfigurationChanged().
+  if (sent_initial_config_ && changed_metrics == DISPLAY_METRIC_WORK_AREA)
+    SendDisplayConfigurationToServer();
+}
+
 }  // namespace ash
diff --git a/ash/mus/display_synchronizer.h b/ash/mus/display_synchronizer.h
index 791fffe3..3428cc53 100644
--- a/ash/mus/display_synchronizer.h
+++ b/ash/mus/display_synchronizer.h
@@ -7,6 +7,7 @@
 
 #include "ash/display/window_tree_host_manager.h"
 #include "base/macros.h"
+#include "ui/display/display_observer.h"
 
 namespace aura {
 class WindowManagerClient;
@@ -17,7 +18,8 @@
 // DisplaySynchronizer keeps the display state in mus in sync with ash's display
 // state. As ash controls the overall display state this synchronization is one
 // way (from ash to mus).
-class DisplaySynchronizer : public ash::WindowTreeHostManager::Observer {
+class DisplaySynchronizer : public WindowTreeHostManager::Observer,
+                            public display::DisplayObserver {
  public:
   explicit DisplaySynchronizer(
       aura::WindowManagerClient* window_manager_client);
@@ -30,8 +32,14 @@
   void OnDisplaysInitialized() override;
   void OnDisplayConfigurationChanged() override;
 
+  // display::DisplayObserver:
+  void OnDisplayMetricsChanged(const display::Display& display,
+                               uint32_t changed_metrics) override;
+
   aura::WindowManagerClient* window_manager_client_;
 
+  bool sent_initial_config_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(DisplaySynchronizer);
 };
 
diff --git a/ash/mus/display_synchronizer_unittest.cc b/ash/mus/display_synchronizer_unittest.cc
new file mode 100644
index 0000000..7a146e63
--- /dev/null
+++ b/ash/mus/display_synchronizer_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/ash_test_helper.h"
+#include "ui/aura/test/mus/test_window_manager_client.h"
+#include "ui/aura/test/mus/test_window_tree_client_setup.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/gfx/geometry/insets.h"
+
+namespace ash {
+namespace mus {
+
+using DisplaySynchronizerTest = test::AshTestBase;
+
+TEST_F(DisplaySynchronizerTest, ChangingWorkAreaNotifesServer) {
+  aura::TestWindowManagerClient* test_window_manager_client =
+      ash_test_helper()
+          ->window_tree_client_setup()
+          ->test_window_manager_client();
+  const size_t initial_count =
+      test_window_manager_client->GetChangeCountForType(
+          aura::WindowManagerClientChangeType::SET_DISPLAY_CONFIGURATION);
+  gfx::Insets work_area_insets =
+      display::Screen::GetScreen()->GetPrimaryDisplay().GetWorkAreaInsets();
+  work_area_insets += gfx::Insets(1);
+  Shell::Get()->SetDisplayWorkAreaInsets(Shell::GetPrimaryRootWindow(),
+                                         work_area_insets);
+  EXPECT_EQ(
+      initial_count + 1,
+      test_window_manager_client->GetChangeCountForType(
+          aura::WindowManagerClientChangeType::SET_DISPLAY_CONFIGURATION));
+}
+
+TEST_F(DisplaySynchronizerTest, AddingDisplayNotifies) {
+  aura::TestWindowManagerClient* test_window_manager_client =
+      ash_test_helper()
+          ->window_tree_client_setup()
+          ->test_window_manager_client();
+  const size_t initial_count =
+      test_window_manager_client->GetChangeCountForType(
+          aura::WindowManagerClientChangeType::SET_DISPLAY_CONFIGURATION);
+  UpdateDisplay("400x400,400x400");
+  // Multiple calls may be sent, so we only check the count changes.
+  EXPECT_NE(
+      initial_count,
+      test_window_manager_client->GetChangeCountForType(
+          aura::WindowManagerClientChangeType::SET_DISPLAY_CONFIGURATION));
+}
+
+}  // namespace mus
+}  // namespace ash
diff --git a/build/android/gradle/generate_gradle.py b/build/android/gradle/generate_gradle.py
index 6269bfb..e7bf827 100755
--- a/build/android/gradle/generate_gradle.py
+++ b/build/android/gradle/generate_gradle.py
@@ -44,6 +44,7 @@
     # because it has resources as deps of android_apk() rather than using an
     #  android_library() intermediate target.
     # '//android_webview:system_webview_apk',
+    '//android_webview/test/embedded_test_server:aw_net_test_support_apk',
     '//android_webview/test:webview_instrumentation_apk',
     '//android_webview/test:webview_instrumentation_test_apk',
     '//base:base_junit_tests',
diff --git a/build/android/pylib/utils/logging_utils.py b/build/android/pylib/utils/logging_utils.py
index 41c61338..29c67cb 100644
--- a/build/android/pylib/utils/logging_utils.py
+++ b/build/android/pylib/utils/logging_utils.py
@@ -22,7 +22,7 @@
     logging.DEBUG: colorama.Fore.CYAN,
     logging.WARNING: colorama.Fore.YELLOW,
     logging.ERROR: colorama.Fore.RED,
-    logging.CRITICAL: colorama.Back.RED + colorama.Style.BRIGHT,
+    logging.CRITICAL: colorama.Back.RED,
   }
 
   def __init__(self, wrapped_formatter=None):
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 3753483..8574607 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -189,7 +189,10 @@
 def ProcessCommonOptions(args):
   """Processes and handles all common options."""
   run_tests_helper.SetLogLevel(args.verbose_count, add_handler=False)
-  handler = logging_utils.ColorStreamHandler()
+  if args.verbose_count > 0:
+    handler = logging_utils.ColorStreamHandler()
+  else:
+    handler = logging.StreamHandler(sys.stdout)
   handler.setFormatter(run_tests_helper.CustomFormatter())
   logging.getLogger().addHandler(handler)
 
diff --git a/build/secondary/third_party/catapult/devil/BUILD.gn b/build/secondary/third_party/catapult/devil/BUILD.gn
new file mode 100644
index 0000000..50e6050c
--- /dev/null
+++ b/build/secondary/third_party/catapult/devil/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//testing/android/empty_apk/empty_apk.gni")
+
+empty_apk("empty_system_webview_apk") {
+  package_name = "com.android.webview"
+  apk_name = "EmptySystemWebView"
+}
+
+group("devil") {
+  testonly = true
+  deps = [
+    ":empty_system_webview_apk",
+    "//tools/android/forwarder2",
+    "//tools/android/md5sum",
+  ]
+}
diff --git a/build/toolchain/mac/compile_xcassets.py b/build/toolchain/mac/compile_xcassets.py
index 7e2dbca..1a6e0c6 100644
--- a/build/toolchain/mac/compile_xcassets.py
+++ b/build/toolchain/mac/compile_xcassets.py
@@ -68,6 +68,8 @@
       continue
     if 'Error Domain=IBMessageChannelErrorDomain Code=4' in line:
       continue
+    if 'Attempting to remove the stale service in order to add' in line:
+      continue
     sys.stderr.write(stdout)
     sys.exit(1)
 
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 0ffd466..bb6312a 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -778,6 +778,7 @@
     "paint/discardable_image_map_unittest.cc",
     "paint/display_item_list_unittest.cc",
     "paint/paint_op_buffer_unittest.cc",
+    "paint/solid_color_analyzer_unittest.cc",
     "quads/draw_polygon_unittest.cc",
     "quads/draw_quad_unittest.cc",
     "quads/nine_patch_generator_unittest.cc",
diff --git a/cc/benchmarks/rasterize_and_record_benchmark_impl.cc b/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
index 203f770..3052425 100644
--- a/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
+++ b/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
@@ -45,8 +45,10 @@
                    base::TimeDelta::FromMilliseconds(kTimeLimitMillis),
                    kTimeCheckInterval);
     SkColor color = SK_ColorTRANSPARENT;
-    *is_solid_color = raster_source->PerformSolidColorAnalysis(
-        content_rect, contents_scale, &color);
+    gfx::Rect layer_rect =
+        gfx::ScaleToEnclosingRect(content_rect, 1.f / contents_scale);
+    *is_solid_color =
+        raster_source->PerformSolidColorAnalysis(layer_rect, &color);
 
     do {
       SkBitmap bitmap;
diff --git a/cc/layers/recording_source.cc b/cc/layers/recording_source.cc
index e48e0ce..06defb1 100644
--- a/cc/layers/recording_source.cc
+++ b/cc/layers/recording_source.cc
@@ -12,6 +12,7 @@
 #include "cc/base/region.h"
 #include "cc/layers/content_layer_client.h"
 #include "cc/paint/display_item_list.h"
+#include "cc/paint/solid_color_analyzer.h"
 #include "cc/raster/raster_source.h"
 #include "skia/ext/analysis_canvas.h"
 
@@ -142,15 +143,14 @@
   is_solid_color_ = false;
   solid_color_ = SK_ColorTRANSPARENT;
 
+  // TODO(vmpstr): We can probably remove this check.
   if (!display_list_->ShouldBeAnalyzedForSolidColor())
     return;
 
   TRACE_EVENT1("cc", "RecordingSource::DetermineIfSolidColor", "opcount",
                display_list_->op_count());
-  gfx::Size layer_size = GetSize();
-  skia::AnalysisCanvas canvas(layer_size.width(), layer_size.height());
-  display_list_->Raster(&canvas);
-  is_solid_color_ = canvas.GetColorIfSolid(&solid_color_);
+  is_solid_color_ =
+      display_list_->GetColorIfSolidInRect(gfx::Rect(GetSize()), &solid_color_);
 }
 
 }  // namespace cc
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index baa9458a..57de616 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -35,6 +35,8 @@
     "record_paint_canvas.h",
     "skia_paint_canvas.cc",
     "skia_paint_canvas.h",
+    "solid_color_analyzer.cc",
+    "solid_color_analyzer.h",
   ]
 
   defines = [ "CC_PAINT_IMPLEMENTATION=1" ]
diff --git a/cc/paint/display_item_list.cc b/cc/paint/display_item_list.cc
index a79d886c..8945e5a 100644
--- a/cc/paint/display_item_list.cc
+++ b/cc/paint/display_item_list.cc
@@ -15,6 +15,7 @@
 #include "cc/base/render_surface_filters.h"
 #include "cc/debug/picture_debug_util.h"
 #include "cc/paint/discardable_image_store.h"
+#include "cc/paint/solid_color_analyzer.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkPictureRecorder.h"
 #include "ui/gfx/geometry/rect.h"
@@ -200,4 +201,23 @@
   return record;
 }
 
+bool DisplayItemList::GetColorIfSolidInRect(const gfx::Rect& rect,
+                                            SkColor* color) {
+  std::vector<size_t>* indices_to_use = nullptr;
+  std::vector<size_t> indices;
+  if (!rect.Contains(rtree_.GetBounds())) {
+    indices = rtree_.Search(rect);
+    indices_to_use = &indices;
+  }
+
+  base::Optional<SkColor> solid_color =
+      SolidColorAnalyzer::DetermineIfSolidColor(&paint_op_buffer_, rect,
+                                                indices_to_use);
+  if (solid_color) {
+    *color = *solid_color;
+    return true;
+  }
+  return false;
+}
+
 }  // namespace cc
diff --git a/cc/paint/display_item_list.h b/cc/paint/display_item_list.h
index fe3bdc7..e419e04 100644
--- a/cc/paint/display_item_list.h
+++ b/cc/paint/display_item_list.h
@@ -138,6 +138,8 @@
   // an empty state.
   sk_sp<PaintRecord> ReleaseAsRecord();
 
+  bool GetColorIfSolidInRect(const gfx::Rect& rect, SkColor* color);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, AsValueWithNoOps);
   FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, AsValueWithOps);
diff --git a/cc/paint/paint_flags.h b/cc/paint/paint_flags.h
index 07e579a..6954892d 100644
--- a/cc/paint/paint_flags.h
+++ b/cc/paint/paint_flags.h
@@ -27,6 +27,7 @@
     kStroke_Style = SkPaint::kStroke_Style,
     kStrokeAndFill_Style = SkPaint::kStrokeAndFill_Style,
   };
+  ALWAYS_INLINE bool nothingToDraw() const { return paint_.nothingToDraw(); }
   ALWAYS_INLINE Style getStyle() const {
     return static_cast<Style>(paint_.getStyle());
   }
diff --git a/cc/paint/solid_color_analyzer.cc b/cc/paint/solid_color_analyzer.cc
new file mode 100644
index 0000000..9aac466a
--- /dev/null
+++ b/cc/paint/solid_color_analyzer.cc
@@ -0,0 +1,255 @@
+// 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 "cc/paint/solid_color_analyzer.h"
+
+#include "base/trace_event/trace_event.h"
+#include "cc/paint/paint_op_buffer.h"
+#include "third_party/skia/include/utils/SkNoDrawCanvas.h"
+
+namespace cc {
+namespace {
+const int kMaxOpsToAnalyze = 1;
+
+bool ActsLikeClear(SkBlendMode mode, unsigned src_alpha) {
+  switch (mode) {
+    case SkBlendMode::kClear:
+      return true;
+    case SkBlendMode::kSrc:
+    case SkBlendMode::kSrcIn:
+    case SkBlendMode::kDstIn:
+    case SkBlendMode::kSrcOut:
+    case SkBlendMode::kDstATop:
+      return src_alpha == 0;
+    case SkBlendMode::kDstOut:
+      return src_alpha == 0xFF;
+    default:
+      return false;
+  }
+}
+
+bool IsSolidColor(SkColor color, SkBlendMode blendmode) {
+  return SkColorGetA(color) == 255 &&
+         (blendmode == SkBlendMode::kSrc || blendmode == SkBlendMode::kSrcOver);
+}
+
+bool IsSolidColorPaint(const PaintFlags& flags) {
+  SkBlendMode blendmode = flags.getBlendMode();
+
+  // Paint is solid color if the following holds:
+  // - Alpha is 1.0, style is fill, and there are no special effects
+  // - Xfer mode is either kSrc or kSrcOver (kSrcOver is equivalent
+  //   to kSrc if source alpha is 1.0, which is already checked).
+  return IsSolidColor(flags.getColor(), blendmode) && !flags.HasShader() &&
+         !flags.getLooper() && !flags.getMaskFilter() &&
+         !flags.getColorFilter() && !flags.getImageFilter() &&
+         flags.getStyle() == PaintFlags::kFill_Style;
+}
+
+// Returns true if the specified drawn_rect will cover the entire canvas, and
+// that the canvas is not clipped (i.e. it covers ALL of the canvas).
+bool IsFullQuad(const SkCanvas& canvas, const SkRect& drawn_rect) {
+  if (!canvas.isClipRect())
+    return false;
+
+  SkIRect clip_irect;
+  if (!canvas.getDeviceClipBounds(&clip_irect))
+    return false;
+
+  // if the clip is smaller than the canvas, we're partly clipped, so abort.
+  if (!clip_irect.contains(SkIRect::MakeSize(canvas.getBaseLayerSize())))
+    return false;
+
+  const SkMatrix& matrix = canvas.getTotalMatrix();
+  // If the transform results in a non-axis aligned
+  // rect, then be conservative and return false.
+  if (!matrix.rectStaysRect())
+    return false;
+
+  SkRect device_rect;
+  matrix.mapRect(&device_rect, drawn_rect);
+  SkRect clip_rect;
+  clip_rect.set(clip_irect);
+  return device_rect.contains(clip_rect);
+}
+
+void CheckIfSolidColor(const SkCanvas& canvas,
+                       SkColor color,
+                       SkBlendMode blendmode,
+                       bool* is_solid_color,
+                       bool* is_transparent,
+                       SkColor* out_color) {
+  SkRect rect;
+  if (!canvas.getLocalClipBounds(&rect)) {
+    *is_transparent = false;
+    *is_solid_color = false;
+    return;
+  }
+
+  bool does_cover_canvas = IsFullQuad(canvas, rect);
+  uint8_t alpha = SkColorGetA(color);
+  if (does_cover_canvas && ActsLikeClear(blendmode, alpha))
+    *is_transparent = true;
+  else if (alpha != 0 || blendmode != SkBlendMode::kSrc)
+    *is_transparent = false;
+
+  if (does_cover_canvas && IsSolidColor(color, blendmode)) {
+    *is_solid_color = true;
+    *out_color = color;
+  } else {
+    *is_solid_color = false;
+  }
+}
+
+void CheckIfSolidRect(const SkCanvas& canvas,
+                      const SkRect& rect,
+                      const PaintFlags& flags,
+                      bool* is_solid_color,
+                      bool* is_transparent,
+                      SkColor* color) {
+  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
+               "SolidColorAnalyzer::HandleDrawRect");
+  if (flags.nothingToDraw())
+    return;
+
+  bool does_cover_canvas = IsFullQuad(canvas, rect);
+  SkBlendMode blendmode = flags.getBlendMode();
+  if (does_cover_canvas && ActsLikeClear(blendmode, flags.getAlpha()))
+    *is_transparent = true;
+  else if (flags.getAlpha() != 0 || blendmode != SkBlendMode::kSrc)
+    *is_transparent = false;
+
+  if (does_cover_canvas && IsSolidColorPaint(flags)) {
+    *is_solid_color = true;
+    *color = flags.getColor();
+  } else {
+    *is_solid_color = false;
+  }
+}
+
+}  // namespace
+
+base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor(
+    const PaintOpBuffer* buffer,
+    const gfx::Rect& rect,
+    const std::vector<size_t>* indices) {
+  if (buffer->size() == 0 || (indices && indices->empty()))
+    return SK_ColorTRANSPARENT;
+
+  bool is_solid = false;
+  bool is_transparent = true;
+  SkColor color = SK_ColorTRANSPARENT;
+
+  struct Frame {
+    Frame() = default;
+    Frame(PaintOpBuffer::Iterator iter,
+          const SkMatrix& original_ctm,
+          int save_count)
+        : iter(iter), original_ctm(original_ctm), save_count(save_count) {}
+
+    PaintOpBuffer::Iterator iter;
+    const SkMatrix original_ctm;
+    int save_count = 0;
+  };
+
+  SkNoDrawCanvas canvas(rect.width(), rect.height());
+  canvas.translate(-rect.x(), -rect.y());
+  canvas.clipRect(gfx::RectToSkRect(rect), SkClipOp::kIntersect, false);
+
+  std::vector<Frame> stack;
+  // We expect to see at least one DrawRecordOp because of the way items are
+  // constructed. Reserve this to 2, and go from there.
+  stack.reserve(2);
+  stack.emplace_back(PaintOpBuffer::Iterator(buffer, indices),
+                     canvas.getTotalMatrix(), canvas.getSaveCount());
+
+  int num_ops = 0;
+  while (!stack.empty()) {
+    auto& frame = stack.back();
+    if (!frame.iter) {
+      canvas.restoreToCount(frame.save_count);
+      stack.pop_back();
+      if (!stack.empty())
+        ++stack.back().iter;
+      continue;
+    }
+
+    const PaintOp* op = *frame.iter;
+    const SkMatrix& original_ctm = frame.original_ctm;
+    switch (op->GetType()) {
+      case PaintOpType::DrawRecord: {
+        const DrawRecordOp* record_op = static_cast<const DrawRecordOp*>(op);
+        stack.emplace_back(PaintOpBuffer::Iterator(record_op->record.get()),
+                           canvas.getTotalMatrix(), canvas.getSaveCount());
+        continue;
+      }
+
+      // Any of the following ops result in non solid content.
+      case PaintOpType::DrawArc:
+      case PaintOpType::DrawCircle:
+      case PaintOpType::DrawDRRect:
+      case PaintOpType::DrawImage:
+      case PaintOpType::DrawImageRect:
+      case PaintOpType::DrawIRect:
+      case PaintOpType::DrawLine:
+      case PaintOpType::DrawOval:
+      case PaintOpType::DrawPath:
+      case PaintOpType::DrawPosText:
+      case PaintOpType::DrawRRect:
+      case PaintOpType::DrawText:
+      case PaintOpType::DrawTextBlob:
+      // Anything that has to do a save layer is probably not solid. As it will
+      // likely need more than one draw op.
+      // TODO(vmpstr): We could investigate handling these.
+      case PaintOpType::SaveLayer:
+      case PaintOpType::SaveLayerAlpha:
+      // Complex clips will probably result in non solid color as it might not
+      // cover the canvas.
+      // TODO(vmpstr): We could investigate handling these.
+      case PaintOpType::ClipPath:
+      case PaintOpType::ClipRRect:
+        return base::nullopt;
+
+      case PaintOpType::DrawRect: {
+        if (++num_ops > kMaxOpsToAnalyze)
+          return base::nullopt;
+        const DrawRectOp* rect_op = static_cast<const DrawRectOp*>(op);
+        CheckIfSolidRect(canvas, rect_op->rect, rect_op->flags, &is_solid,
+                         &is_transparent, &color);
+        break;
+      }
+      case PaintOpType::DrawColor: {
+        if (++num_ops > kMaxOpsToAnalyze)
+          return base::nullopt;
+        const DrawColorOp* color_op = static_cast<const DrawColorOp*>(op);
+        CheckIfSolidColor(canvas, color_op->color, color_op->mode, &is_solid,
+                          &is_transparent, &color);
+        break;
+      }
+
+      // The rest of the ops should only affect our state canvas.
+      case PaintOpType::Annotate:
+      case PaintOpType::ClipRect:
+      case PaintOpType::Concat:
+      case PaintOpType::Scale:
+      case PaintOpType::SetMatrix:
+      case PaintOpType::Restore:
+      case PaintOpType::Rotate:
+      case PaintOpType::Save:
+      case PaintOpType::Translate:
+      case PaintOpType::Noop:
+        op->Raster(&canvas, original_ctm);
+        break;
+    }
+    ++frame.iter;
+  }
+
+  if (is_transparent)
+    return SK_ColorTRANSPARENT;
+  if (is_solid)
+    return color;
+  return base::nullopt;
+}
+
+}  // namespace cc
diff --git a/cc/paint/solid_color_analyzer.h b/cc/paint/solid_color_analyzer.h
new file mode 100644
index 0000000..f0d9f26
--- /dev/null
+++ b/cc/paint/solid_color_analyzer.h
@@ -0,0 +1,30 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_PAINT_SOLID_COLOR_ANALYZER_H_
+#define CC_PAINT_SOLID_COLOR_ANALYZER_H_
+
+#include <vector>
+
+#include "base/optional.h"
+#include "cc/paint/paint_export.h"
+#include "cc/paint/paint_flags.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+
+class CC_PAINT_EXPORT SolidColorAnalyzer {
+ public:
+  SolidColorAnalyzer() = delete;
+
+  static base::Optional<SkColor> DetermineIfSolidColor(
+      const PaintOpBuffer* buffer,
+      const gfx::Rect& rect,
+      const std::vector<size_t>* indices = nullptr);
+};
+
+}  // namespace cc
+
+#endif  // CC_PAINT_SOLID_COLOR_ANALYZER_H_
diff --git a/cc/paint/solid_color_analyzer_unittest.cc b/cc/paint/solid_color_analyzer_unittest.cc
new file mode 100644
index 0000000..a56d107
--- /dev/null
+++ b/cc/paint/solid_color_analyzer_unittest.cc
@@ -0,0 +1,250 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/paint/solid_color_analyzer.h"
+#include "base/optional.h"
+#include "cc/paint/record_paint_canvas.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/effects/SkOffsetImageFilter.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+namespace {
+
+class SolidColorAnalyzerTest : public testing::Test {
+ public:
+  void SetUp() override {}
+
+  void TearDown() override {
+    canvas_.reset();
+    buffer_.Reset();
+  }
+
+  void Initialize(const gfx::Rect& rect = gfx::Rect(0, 0, 100, 100)) {
+    canvas_.emplace(&buffer_, gfx::RectToSkRect(rect));
+    rect_ = rect;
+  }
+  RecordPaintCanvas* canvas() { return &*canvas_; }
+  PaintOpBuffer* paint_op_buffer() { return &buffer_; }
+
+  bool IsSolidColor() {
+    auto color =
+        SolidColorAnalyzer::DetermineIfSolidColor(&buffer_, rect_, nullptr);
+    return !!color;
+  }
+
+  SkColor GetColor() const {
+    auto color =
+        SolidColorAnalyzer::DetermineIfSolidColor(&buffer_, rect_, nullptr);
+    EXPECT_TRUE(color);
+    return color ? *color : SK_ColorTRANSPARENT;
+  }
+
+ private:
+  gfx::Rect rect_;
+  PaintOpBuffer buffer_;
+  base::Optional<RecordPaintCanvas> canvas_;
+  base::Optional<SolidColorAnalyzer> analyzer_;
+};
+
+TEST_F(SolidColorAnalyzerTest, Empty) {
+  Initialize();
+  EXPECT_EQ(SK_ColorTRANSPARENT, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, ClearTransparent) {
+  Initialize();
+  SkColor color = SkColorSetARGB(0, 12, 34, 56);
+  canvas()->clear(color);
+  EXPECT_EQ(SK_ColorTRANSPARENT, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, ClearSolid) {
+  Initialize();
+  SkColor color = SkColorSetARGB(255, 65, 43, 21);
+  canvas()->clear(color);
+  EXPECT_EQ(color, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, ClearTranslucent) {
+  Initialize();
+  SkColor color = SkColorSetARGB(128, 11, 22, 33);
+  canvas()->clear(color);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawColor) {
+  Initialize();
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  canvas()->drawColor(color);
+  EXPECT_EQ(color, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawOval) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  canvas()->drawOval(SkRect::MakeWH(100, 100), flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawBitmap) {
+  Initialize();
+  SkBitmap bitmap;
+  bitmap.allocN32Pixels(16, 16);
+  canvas()->drawBitmap(bitmap, 0, 0, nullptr);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRect) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->clipRect(rect, SkClipOp::kIntersect, false);
+  canvas()->drawRect(rect, flags);
+  EXPECT_EQ(color, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectClipped) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->clipRect(SkRect::MakeWH(50, 50), SkClipOp::kIntersect, false);
+  canvas()->drawRect(rect, flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectWithTranslateNotSolid) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  SkRect rect = SkRect::MakeWH(100, 100);
+  canvas()->translate(1, 1);
+  canvas()->drawRect(rect, flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectWithTranslateSolid) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  SkRect rect = SkRect::MakeWH(101, 101);
+  canvas()->translate(1, 1);
+  canvas()->drawRect(rect, flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, TwoOpsNotSolid) {
+  Initialize();
+  SkColor color = SkColorSetARGB(255, 65, 43, 21);
+  canvas()->clear(color);
+  canvas()->clear(color);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectBlendModeClear) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  flags.setBlendMode(SkBlendMode::kClear);
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->drawRect(rect, flags);
+  EXPECT_EQ(SK_ColorTRANSPARENT, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectBlendModeSrcOver) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  flags.setBlendMode(SkBlendMode::kSrcOver);
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->drawRect(rect, flags);
+  EXPECT_EQ(color, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectRotated) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->rotate(50);
+  canvas()->drawRect(rect, flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectScaledNotSolid) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->scale(0.1f, 0.1f);
+  canvas()->drawRect(rect, flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectScaledSolid) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  SkRect rect = SkRect::MakeWH(10, 10);
+  canvas()->scale(10, 10);
+  canvas()->drawRect(rect, flags);
+  EXPECT_EQ(color, GetColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectFilterPaint) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+  flags.setImageFilter(SkOffsetImageFilter::Make(10, 10, nullptr));
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->drawRect(rect, flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, DrawRectClipPath) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+
+  SkPath path;
+  path.moveTo(0, 0);
+  path.lineTo(128, 50);
+  path.lineTo(255, 0);
+  path.lineTo(255, 255);
+  path.lineTo(0, 255);
+
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->clipPath(path, SkClipOp::kIntersect);
+  canvas()->drawRect(rect, flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+TEST_F(SolidColorAnalyzerTest, SaveLayer) {
+  Initialize();
+  PaintFlags flags;
+  SkColor color = SkColorSetARGB(255, 11, 22, 33);
+  flags.setColor(color);
+
+  SkRect rect = SkRect::MakeWH(200, 200);
+  canvas()->saveLayer(&rect, &flags);
+  EXPECT_FALSE(IsSolidColor());
+}
+
+}  // namespace
+}  // namespace cc
diff --git a/cc/raster/raster_source.cc b/cc/raster/raster_source.cc
index 9294cd6..124d5c5 100644
--- a/cc/raster/raster_source.cc
+++ b/cc/raster/raster_source.cc
@@ -237,21 +237,12 @@
   return display_list_->BytesUsed() + painter_reported_memory_usage_;
 }
 
-bool RasterSource::PerformSolidColorAnalysis(const gfx::Rect& content_rect,
-                                             float contents_scale,
+bool RasterSource::PerformSolidColorAnalysis(gfx::Rect layer_rect,
                                              SkColor* color) const {
   TRACE_EVENT0("cc", "RasterSource::PerformSolidColorAnalysis");
 
-  gfx::Rect layer_rect =
-      gfx::ScaleToEnclosingRect(content_rect, 1.f / contents_scale);
-
   layer_rect.Intersect(gfx::Rect(size_));
-  skia::AnalysisCanvas canvas(layer_rect.width(), layer_rect.height());
-  canvas.translate(-layer_rect.x(), -layer_rect.y());
-  // Note that because no color conversion is applied to solid color analysis,
-  // the resulting solid color will be known to be sRGB.
-  RasterCommon(&canvas, &canvas);
-  return canvas.GetColorIfSolid(color);
+  return display_list_->GetColorIfSolidInRect(layer_rect, color);
 }
 
 void RasterSource::GetDiscardableImagesInRect(
diff --git a/cc/raster/raster_source.h b/cc/raster/raster_source.h
index 92e429b..8479de1a 100644
--- a/cc/raster/raster_source.h
+++ b/cc/raster/raster_source.h
@@ -89,9 +89,7 @@
 
   // Returns whether the given rect at given scale is of solid color in
   // this raster source, as well as the solid color value.
-  bool PerformSolidColorAnalysis(const gfx::Rect& content_rect,
-                                 float contents_scale,
-                                 SkColor* color) const;
+  bool PerformSolidColorAnalysis(gfx::Rect content_rect, SkColor* color) const;
 
   // Returns true iff the whole raster source is of solid color.
   bool IsSolidColor() const;
diff --git a/cc/raster/raster_source_unittest.cc b/cc/raster/raster_source_unittest.cc
index 5cb06aaf..c75fcb8 100644
--- a/cc/raster/raster_source_unittest.cc
+++ b/cc/raster/raster_source_unittest.cc
@@ -53,7 +53,7 @@
   for (int y = 0; y <= 300; y += 100) {
     for (int x = 0; x <= 300; x += 100) {
       gfx::Rect rect(x, y, 100, 100);
-      is_solid_color = raster->PerformSolidColorAnalysis(rect, 1.f, &color);
+      is_solid_color = raster->PerformSolidColorAnalysis(rect, &color);
       EXPECT_TRUE(is_solid_color) << rect.ToString();
       EXPECT_EQ(solid_color, color) << rect.ToString();
     }
@@ -68,124 +68,35 @@
 
   color = SK_ColorTRANSPARENT;
   is_solid_color =
-      raster->PerformSolidColorAnalysis(gfx::Rect(0, 0, 100, 100), 1.f, &color);
+      raster->PerformSolidColorAnalysis(gfx::Rect(0, 0, 100, 100), &color);
   EXPECT_FALSE(is_solid_color);
 
   color = SK_ColorTRANSPARENT;
-  is_solid_color = raster->PerformSolidColorAnalysis(
-      gfx::Rect(100, 0, 100, 100), 1.f, &color);
+  is_solid_color =
+      raster->PerformSolidColorAnalysis(gfx::Rect(100, 0, 100, 100), &color);
   EXPECT_TRUE(is_solid_color);
   EXPECT_EQ(solid_color, color);
 
   // Boundaries should be clipped.
   color = SK_ColorTRANSPARENT;
-  is_solid_color = raster->PerformSolidColorAnalysis(
-      gfx::Rect(350, 0, 100, 100), 1.f, &color);
+  is_solid_color =
+      raster->PerformSolidColorAnalysis(gfx::Rect(350, 0, 100, 100), &color);
   EXPECT_TRUE(is_solid_color);
   EXPECT_EQ(solid_color, color);
 
   color = SK_ColorTRANSPARENT;
-  is_solid_color = raster->PerformSolidColorAnalysis(
-      gfx::Rect(0, 350, 100, 100), 1.f, &color);
+  is_solid_color =
+      raster->PerformSolidColorAnalysis(gfx::Rect(0, 350, 100, 100), &color);
   EXPECT_TRUE(is_solid_color);
   EXPECT_EQ(solid_color, color);
 
   color = SK_ColorTRANSPARENT;
-  is_solid_color = raster->PerformSolidColorAnalysis(
-      gfx::Rect(350, 350, 100, 100), 1.f, &color);
+  is_solid_color =
+      raster->PerformSolidColorAnalysis(gfx::Rect(350, 350, 100, 100), &color);
   EXPECT_TRUE(is_solid_color);
   EXPECT_EQ(solid_color, color);
 }
 
-TEST(RasterSourceTest, AnalyzeIsSolidScaled) {
-  gfx::Size layer_bounds(400, 400);
-
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
-
-  SkColor solid_color = SkColorSetARGB(255, 12, 23, 34);
-  SkColor color = SK_ColorTRANSPARENT;
-  PaintFlags solid_flags;
-  bool is_solid_color = false;
-  solid_flags.setColor(solid_color);
-
-  SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67);
-  PaintFlags non_solid_flags;
-  non_solid_flags.setColor(non_solid_color);
-
-  recording_source->add_draw_rect_with_flags(gfx::Rect(0, 0, 400, 400),
-                                             solid_flags);
-  recording_source->Rerecord();
-
-  scoped_refptr<RasterSource> raster =
-      RasterSource::CreateFromRecordingSource(recording_source.get(), false);
-
-  // Ensure everything is solid.
-  for (int y = 0; y <= 30; y += 10) {
-    for (int x = 0; x <= 30; x += 10) {
-      gfx::Rect rect(x, y, 10, 10);
-      is_solid_color = raster->PerformSolidColorAnalysis(rect, 0.1f, &color);
-      EXPECT_TRUE(is_solid_color) << rect.ToString();
-      EXPECT_EQ(color, solid_color) << rect.ToString();
-    }
-  }
-
-  // Add one non-solid pixel and recreate the raster source.
-  recording_source->add_draw_rect_with_flags(gfx::Rect(50, 50, 1, 1),
-                                             non_solid_flags);
-  recording_source->Rerecord();
-  raster =
-      RasterSource::CreateFromRecordingSource(recording_source.get(), false);
-
-  color = SK_ColorTRANSPARENT;
-  is_solid_color =
-      raster->PerformSolidColorAnalysis(gfx::Rect(0, 0, 10, 10), 0.1f, &color);
-  EXPECT_FALSE(is_solid_color);
-
-  color = SK_ColorTRANSPARENT;
-  is_solid_color =
-      raster->PerformSolidColorAnalysis(gfx::Rect(10, 0, 10, 10), 0.1f, &color);
-  EXPECT_TRUE(is_solid_color);
-  EXPECT_EQ(color, solid_color);
-
-  // Boundaries should be clipped.
-  color = SK_ColorTRANSPARENT;
-  is_solid_color =
-      raster->PerformSolidColorAnalysis(gfx::Rect(35, 0, 10, 10), 0.1f, &color);
-  EXPECT_TRUE(is_solid_color);
-  EXPECT_EQ(color, solid_color);
-
-  color = SK_ColorTRANSPARENT;
-  is_solid_color =
-      raster->PerformSolidColorAnalysis(gfx::Rect(0, 35, 10, 10), 0.1f, &color);
-  EXPECT_TRUE(is_solid_color);
-  EXPECT_EQ(color, solid_color);
-
-  color = SK_ColorTRANSPARENT;
-  is_solid_color = raster->PerformSolidColorAnalysis(gfx::Rect(35, 35, 10, 10),
-                                                     0.1f, &color);
-  EXPECT_TRUE(is_solid_color);
-  EXPECT_EQ(color, solid_color);
-}
-
-TEST(RasterSourceTest, AnalyzeIsSolidEmpty) {
-  gfx::Size layer_bounds(400, 400);
-
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
-  recording_source->Rerecord();
-
-  scoped_refptr<RasterSource> raster =
-      RasterSource::CreateFromRecordingSource(recording_source.get(), false);
-
-  SkColor color = SK_ColorTRANSPARENT;
-  bool is_solid_color =
-      raster->PerformSolidColorAnalysis(gfx::Rect(0, 0, 400, 400), 1.f, &color);
-
-  EXPECT_TRUE(is_solid_color);
-  EXPECT_EQ(color, SkColorSetARGB(0, 0, 0, 0));
-}
-
 TEST(RasterSourceTest, PixelRefIteratorDiscardableRefsOneTile) {
   gfx::Size layer_bounds(512, 512);
 
diff --git a/cc/tiles/tile_manager.cc b/cc/tiles/tile_manager.cc
index 2e78129..d9a680d6 100644
--- a/cc/tiles/tile_manager.cc
+++ b/cc/tiles/tile_manager.cc
@@ -685,11 +685,9 @@
       // canvas which is reset between tiles.
       tile->set_solid_color_analysis_performed(true);
       SkColor color = SK_ColorTRANSPARENT;
-      gfx::RectF layer_rect = tile->raster_transform().InverseMapRect(
-          gfx::RectF(tile->content_rect()));
       bool is_solid_color =
           prioritized_tile.raster_source()->PerformSolidColorAnalysis(
-              gfx::ToEnclosingRect(layer_rect), 1.f, &color);
+              tile->enclosing_layer_rect(), &color);
       if (is_solid_color) {
         tile->draw_info().set_solid_color(color);
         client_->NotifyTileStateChanged(tile);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
index 6d265c65..f74c4f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
@@ -78,6 +78,7 @@
 
     private boolean mSearchEnginePromoCompleted;
     private boolean mSearchEnginePromoShownThisSession;
+    private boolean mSearchEnginePromoCheckedThisSession;
 
     // LocaleManager is a singleton and it should not have strong reference to UI objects.
     // SnackbarManager is owned by ChromeActivity and is not null as long as the activity is alive.
@@ -244,8 +245,15 @@
             final Activity activity, final @Nullable Callback<Boolean> onSearchEngineFinalized) {
         assert TemplateUrlService.getInstance().isLoaded();
 
+        final Callback<Boolean> finalizeInternalCallback = new Callback<Boolean>() {
+            @Override
+            public void onResult(Boolean result) {
+                if (result != null && result) mSearchEnginePromoCheckedThisSession = true;
+                if (onSearchEngineFinalized != null) onSearchEngineFinalized.onResult(result);
+            }
+        };
         if (TemplateUrlService.getInstance().isDefaultSearchManaged()) {
-            if (onSearchEngineFinalized != null) onSearchEngineFinalized.onResult(true);
+            finalizeInternalCallback.onResult(true);
             return;
         }
 
@@ -253,14 +261,14 @@
         Callable<PromoDialog> dialogCreator;
         switch (shouldShow) {
             case SEARCH_ENGINE_PROMO_DONT_SHOW:
-                if (onSearchEngineFinalized != null) onSearchEngineFinalized.onResult(true);
+                finalizeInternalCallback.onResult(true);
                 return;
             case SEARCH_ENGINE_PROMO_SHOW_SOGOU:
                 dialogCreator = new Callable<PromoDialog>() {
                     @Override
                     public PromoDialog call() throws Exception {
                         return new SogouPromoDialog(
-                                activity, LocaleManager.this, onSearchEngineFinalized);
+                                activity, LocaleManager.this, finalizeInternalCallback);
                     }
                 };
                 break;
@@ -270,20 +278,20 @@
                     @Override
                     public PromoDialog call() throws Exception {
                         return new DefaultSearchEnginePromoDialog(
-                                activity, shouldShow, onSearchEngineFinalized);
+                                activity, shouldShow, finalizeInternalCallback);
                     }
                 };
                 break;
             default:
                 assert false;
-                if (onSearchEngineFinalized != null) onSearchEngineFinalized.onResult(true);
+                finalizeInternalCallback.onResult(true);
                 return;
         }
 
         // If the activity has been destroyed by the time the TemplateUrlService has
         // loaded, then do not attempt to show the dialog.
         if (ApplicationStatus.getStateForActivity(activity) == ActivityState.DESTROYED) {
-            if (onSearchEngineFinalized != null) onSearchEngineFinalized.onResult(false);
+            finalizeInternalCallback.onResult(false);
             return;
         }
 
@@ -466,7 +474,7 @@
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
         }
-        return state == SEARCH_ENGINE_PROMO_SHOULD_CHECK;
+        return !mSearchEnginePromoCheckedThisSession && state == SEARCH_ENGINE_PROMO_SHOULD_CHECK;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
index a64514d..26e30c3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.tab;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.R;
@@ -25,10 +24,6 @@
  * Class that controls navigations and allows to intercept them. It is used on Android to 'convert'
  * certain navigations to Intents to 3rd party applications and to "pause" navigations when data use
  * tracking has ended.
- * Note the Intent is often created together with a new empty tab which then shoud be closed
- * immediately. This task of closing tab should be done in an asynchrnous fashion by posting
- * the task onto UI thread again to avoid accessing the WebContents and the associated objects
- * afterwards since they will have been destroyed as well. see https://crbug.com/732260.
  */
 public class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
     private final Tab mTab;
@@ -248,14 +243,7 @@
                 // crbug.com/487938.
                 mTab.getActivity().moveTaskToBack(false);
             }
-            // Defer closing a tab (and the associated WebContents) till the navigation
-            // request and the throttle finishes the job with it.
-            ThreadUtils.postOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mTab.getTabModelSelector().closeTab(mTab);
-                }
-            });
+            mTab.getTabModelSelector().closeTab(mTab);
         } else if (mTab.getTabRedirectHandler().isOnNavigation()) {
             int lastCommittedEntryIndexBeforeNavigation = mTab.getTabRedirectHandler()
                     .getLastCommittedEntryIndexBeforeStartingNavigation();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShell.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShell.java
index ace20d0..e691062 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShell.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShell.java
@@ -17,7 +17,7 @@
      * Performs native VrShell initialization.
      */
     void initializeNative(
-            Tab currentTab, boolean forWebVr, boolean webVrAutopresented, boolean inCct);
+            Tab currentTab, boolean forWebVr, boolean webVrAutopresentationExpected, boolean inCct);
 
     /**
      * Pauses VrShell.
@@ -38,7 +38,7 @@
      * Sets whether we're presenting WebVR content or not.
      */
     // TODO: Refactor needed. See crbug.com/735169.
-    void setWebVrModeEnabled(boolean enabled, boolean autoPresented, boolean showToast);
+    void setWebVrModeEnabled(boolean enabled, boolean showToast);
 
     /**
      * Returns true if we're presenting WebVR content.
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 8f5cbecf..4b44c09 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
@@ -580,14 +580,18 @@
             return false;
         }
 
-        if (mListeningForWebVrActivateBeforePause && !mRequestedWebVr) {
+        // If the page is listening for vrdisplayactivate we assume it wants to request
+        // presentation. Go into WebVR mode tentatively. If the page doesn't request presentation
+        // in the vrdisplayactivate handler we will exit presentation later. Note that in the
+        // case of autopresentation, we don't want to enter WebVR mode so that we can show the
+        // splash screen. In this case, we enter WebVR mode when the site requests presentation.
+        boolean tentativeWebVrMode =
+                mListeningForWebVrActivateBeforePause && !mRequestedWebVr && !mAutopresentWebVr;
+        if (tentativeWebVrMode) {
             nativeDisplayActivate(mNativeVrShellDelegate);
         }
 
-        // If the page is listening for vrdisplayactivate we assume it wants to request
-        // presentation. Go into WebVR mode tentatively. If the page doesn't request presentation
-        // in the vrdisplayactivate handler we will exit presentation later.
-        enterVr(mListeningForWebVrActivateBeforePause && !mRequestedWebVr);
+        enterVr(tentativeWebVrMode);
 
         // The user has successfully completed a DON flow.
         RecordUserAction.record("VR.DON");
@@ -648,11 +652,10 @@
         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
 
         addVrViews();
-        boolean webVrMode = mRequestedWebVr || tentativeWebVrMode;
+        boolean webVrMode = mRequestedWebVr || tentativeWebVrMode && !mAutopresentWebVr;
         mVrShell.initializeNative(mActivity.getActivityTab(), webVrMode, mAutopresentWebVr,
                 mActivity instanceof CustomTabActivity);
-        mVrShell.setWebVrModeEnabled(webVrMode, mAutopresentWebVr, false);
-        mAutopresentWebVr = false;
+        mVrShell.setWebVrModeEnabled(webVrMode, false);
 
         // We're entering VR, but not in WebVr mode.
         mVrBrowserUsed = !webVrMode;
@@ -676,6 +679,7 @@
         // we're not in vr.
         assert !mInVr;
         mAutopresentWebVr = true;
+        mDonSucceeded = true;
     }
 
     /**
@@ -748,7 +752,7 @@
         mRequestedWebVr = true;
         switch (enterVrInternal()) {
             case ENTER_VR_NOT_NECESSARY:
-                mVrShell.setWebVrModeEnabled(true, mAutopresentWebVr, true);
+                mVrShell.setWebVrModeEnabled(true, true);
                 maybeSetPresentResult(true);
                 break;
             case ENTER_VR_CANCELLED:
@@ -762,6 +766,7 @@
             default:
                 Log.e(TAG, "Unexpected enum.");
         }
+        mAutopresentWebVr = false;
     }
 
     /**
@@ -803,7 +808,7 @@
         } else {
             mVrBrowserUsed = true;
             mAutopresentWebVr = false;
-            mVrShell.setWebVrModeEnabled(false, false, false);
+            mVrShell.setWebVrModeEnabled(false, false);
         }
         return true;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java
index 6b69793..a6c2d01 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java
@@ -5,8 +5,12 @@
 package org.chromium.chrome.browser.vr_shell;
 
 import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Point;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
 import android.os.StrictMode;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -20,10 +24,12 @@
 import com.google.vr.ndk.base.AndroidCompat;
 import com.google.vr.ndk.base.GvrLayout;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.NativePage;
@@ -209,8 +215,7 @@
             }
 
             @Override
-            public void onWebContentsSwapped(
-                    Tab tab, boolean didStartLoad, boolean didFinishLoad) {
+            public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) {
                 onContentChanged(tab);
             }
 
@@ -310,14 +315,39 @@
         addView(mRenderToSurfaceLayoutParent);
     }
 
+    private void setSplashScreenIcon() {
+        new AsyncTask<Void, Void, Bitmap>() {
+            @Override
+            protected Bitmap doInBackground(Void... params) {
+                Drawable drawable = ApiCompatibilityUtils.getDrawable(
+                        mActivity.getResources(), R.mipmap.app_icon);
+                if (drawable instanceof BitmapDrawable) {
+                    BitmapDrawable bd = (BitmapDrawable) drawable;
+                    return bd.getBitmap();
+                }
+                assert false : "The drawable was not a bitmap drawable as expected";
+                return null;
+            }
+            @Override
+            protected void onPostExecute(Bitmap bitmap) {
+                nativeSetSplashScreenIcon(mNativeVrShell, bitmap);
+            }
+        }
+                .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+    }
+
     @Override
-    public void initializeNative(
-            Tab currentTab, boolean forWebVr, boolean webVrAutopresented, boolean inCct) {
+    public void initializeNative(Tab currentTab, boolean forWebVr,
+            boolean webVrAutopresentationExpected, boolean inCct) {
         mContentVrWindowAndroid = new VrWindowAndroid(mActivity, mContentVirtualDisplay);
         mNativeVrShell = nativeInit(mDelegate, mContentVrWindowAndroid.getNativePointer(), forWebVr,
-                webVrAutopresented, inCct, getGvrApi().getNativeGvrContext(),
+                webVrAutopresentationExpected, inCct, getGvrApi().getNativeGvrContext(),
                 mReprojectedRendering);
 
+        // We need to set the icon bitmap from here because we can't read the app icon from native
+        // code.
+        setSplashScreenIcon();
+
         // Set the UI and content sizes before we load the UI.
         updateWebVrDisplaySize(forWebVr);
 
@@ -538,10 +568,9 @@
     }
 
     @Override
-    public void setWebVrModeEnabled(boolean enabled, boolean autoPresented, boolean showToast) {
+    public void setWebVrModeEnabled(boolean enabled, boolean showToast) {
         mContentVrWindowAndroid.setVSyncPaused(enabled);
-        nativeSetWebVrMode(mNativeVrShell, enabled, autoPresented, showToast);
-
+        nativeSetWebVrMode(mNativeVrShell, enabled, showToast);
         updateWebVrDisplaySize(enabled);
     }
 
@@ -706,9 +735,10 @@
     }
 
     private native long nativeInit(VrShellDelegate delegate, long nativeWindowAndroid,
-            boolean forWebVR, boolean webVRAutopresented, boolean inCct, long gvrApi,
+            boolean forWebVR, boolean webVrAutopresentationExpected, boolean inCct, long gvrApi,
             boolean reprojectedRendering);
     private native void nativeSetSurface(long nativeVrShell, Surface surface);
+    private native void nativeSetSplashScreenIcon(long nativeVrShell, Bitmap bitmap);
     private native void nativeSwapContents(
             long nativeVrShell, WebContents webContents, MotionEventSynthesizer eventSynthesizer);
     private native void nativeDestroy(long nativeVrShell);
@@ -720,8 +750,7 @@
             long nativeVrShell, WebContents webContents, int width, int height);
     private native void nativeContentPhysicalBoundsChanged(long nativeVrShell, int width,
             int height, float dpr);
-    private native void nativeSetWebVrMode(
-            long nativeVrShell, boolean enabled, boolean autoPresented, boolean showToast);
+    private native void nativeSetWebVrMode(long nativeVrShell, boolean enabled, boolean showToast);
     private native boolean nativeGetWebVrMode(long nativeVrShell);
     private native void nativeOnTabListCreated(long nativeVrShell, Tab[] mainTabs,
             Tab[] incognitoTabs);
diff --git a/chrome/browser/android/vr_shell/BUILD.gn b/chrome/browser/android/vr_shell/BUILD.gn
index 36f0663..d4e01f1 100644
--- a/chrome/browser/android/vr_shell/BUILD.gn
+++ b/chrome/browser/android/vr_shell/BUILD.gn
@@ -48,6 +48,8 @@
     "textures/presentation_toast_texture.h",
     "textures/render_text_wrapper.cc",
     "textures/render_text_wrapper.h",
+    "textures/splash_screen_icon_texture.cc",
+    "textures/splash_screen_icon_texture.h",
     "textures/system_indicator_texture.cc",
     "textures/system_indicator_texture.h",
     "textures/ui_texture.cc",
@@ -71,6 +73,8 @@
     "ui_elements/presentation_toast.h",
     "ui_elements/screen_dimmer.cc",
     "ui_elements/screen_dimmer.h",
+    "ui_elements/splash_screen_icon.cc",
+    "ui_elements/splash_screen_icon.h",
     "ui_elements/system_indicator.cc",
     "ui_elements/system_indicator.h",
     "ui_elements/textured_element.cc",
diff --git a/chrome/browser/android/vr_shell/color_scheme.cc b/chrome/browser/android/vr_shell/color_scheme.cc
index 01f1941..5545ecd 100644
--- a/chrome/browser/android/vr_shell/color_scheme.cc
+++ b/chrome/browser/android/vr_shell/color_scheme.cc
@@ -67,6 +67,7 @@
   normal_scheme.disabled = 0x33333333;
   normal_scheme.dimmer_inner = 0xCC0D0D0D;
   normal_scheme.dimmer_outer = 0xE6000000;
+  normal_scheme.splash_screen_background = SK_ColorBLACK;
 
   kColorSchemes[ColorScheme::kModeFullscreen] =
       kColorSchemes[ColorScheme::kModeNormal];
@@ -121,6 +122,7 @@
   incognito_scheme.prompt_button_background_hover = 0xFF8C8C8C;
   incognito_scheme.prompt_button_background_down = 0xE6FFFFFF;
   incognito_scheme.disabled = 0x33E6E6E6;
+  incognito_scheme.splash_screen_background = SK_ColorBLACK;
 
   initialized = true;
 }
diff --git a/chrome/browser/android/vr_shell/color_scheme.h b/chrome/browser/android/vr_shell/color_scheme.h
index c943841..b6516343 100644
--- a/chrome/browser/android/vr_shell/color_scheme.h
+++ b/chrome/browser/android/vr_shell/color_scheme.h
@@ -78,6 +78,9 @@
   // Screen dimmer colors.
   SkColor dimmer_outer;
   SkColor dimmer_inner;
+
+  // Splash screen colors.
+  SkColor splash_screen_background;
 };
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.cc b/chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.cc
new file mode 100644
index 0000000..6bc62a4
--- /dev/null
+++ b/chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.cc
@@ -0,0 +1,38 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.h"
+
+#include "ui/gfx/canvas.h"
+
+namespace vr_shell {
+
+SplashScreenIconTexture::SplashScreenIconTexture() = default;
+
+SplashScreenIconTexture::~SplashScreenIconTexture() = default;
+
+void SplashScreenIconTexture::SetSplashScreenIconBitmap(
+    const SkBitmap& bitmap) {
+  splash_screen_icon_ = SkImage::MakeFromBitmap(bitmap);
+  set_dirty();
+}
+
+void SplashScreenIconTexture::Draw(SkCanvas* sk_canvas,
+                                   const gfx::Size& texture_size) {
+  size_.set_width(texture_size.width());
+  size_.set_height(texture_size.height());
+
+  sk_canvas->drawImage(splash_screen_icon_, 0, 0);
+}
+
+gfx::Size SplashScreenIconTexture::GetPreferredTextureSize(
+    int maximum_width) const {
+  return gfx::Size(maximum_width, maximum_width);
+}
+
+gfx::SizeF SplashScreenIconTexture::GetDrawnSize() const {
+  return size_;
+}
+
+}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.h b/chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.h
new file mode 100644
index 0000000..d2a4959
--- /dev/null
+++ b/chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.h
@@ -0,0 +1,34 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_SPLASH_SCREEN_ICON_TEXTURE_H_
+#define CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_SPLASH_SCREEN_ICON_TEXTURE_H_
+
+#include "base/macros.h"
+#include "chrome/browser/android/vr_shell/textures/ui_texture.h"
+#include "third_party/skia/include/core/SkImage.h"
+
+namespace vr_shell {
+
+class SplashScreenIconTexture : public UiTexture {
+ public:
+  SplashScreenIconTexture();
+  ~SplashScreenIconTexture() override;
+  gfx::Size GetPreferredTextureSize(int width) const override;
+  gfx::SizeF GetDrawnSize() const override;
+
+  void SetSplashScreenIconBitmap(const SkBitmap& bitmap);
+
+ private:
+  void Draw(SkCanvas* sk_canvas, const gfx::Size& texture_size) override;
+
+  sk_sp<SkImage> splash_screen_icon_;
+  gfx::SizeF size_;
+
+  DISALLOW_COPY_AND_ASSIGN(SplashScreenIconTexture);
+};
+
+}  // namespace vr_shell
+
+#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_TEXTURES_SPLASH_SCREEN_ICON_TEXTURE_H_
diff --git a/chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.cc b/chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.cc
new file mode 100644
index 0000000..dc67480
--- /dev/null
+++ b/chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.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 "chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.h"
+
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/android/vr_shell/textures/splash_screen_icon_texture.h"
+
+namespace vr_shell {
+
+SplashScreenIcon::SplashScreenIcon(int preferred_width)
+    : TexturedElement(preferred_width),
+      texture_(base::MakeUnique<SplashScreenIconTexture>()) {}
+
+SplashScreenIcon::~SplashScreenIcon() = default;
+
+void SplashScreenIcon::SetSplashScreenIconBitmap(const SkBitmap& bitmap) {
+  texture_->SetSplashScreenIconBitmap(bitmap);
+  UpdateTexture();
+}
+
+UiTexture* SplashScreenIcon::GetTexture() const {
+  return texture_.get();
+}
+
+}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.h b/chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.h
new file mode 100644
index 0000000..feaf6be
--- /dev/null
+++ b/chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.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 CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_SPLASH_SCREEN_ICON_H_
+#define CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_SPLASH_SCREEN_ICON_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/android/vr_shell/ui_elements/textured_element.h"
+
+class SkBitmap;
+
+namespace vr_shell {
+
+class SplashScreenIconTexture;
+
+class SplashScreenIcon : public TexturedElement {
+ public:
+  explicit SplashScreenIcon(int preferred_width);
+  ~SplashScreenIcon() override;
+
+  void SetSplashScreenIconBitmap(const SkBitmap& bitmap);
+
+ private:
+  UiTexture* GetTexture() const override;
+
+  std::unique_ptr<SplashScreenIconTexture> texture_;
+
+  DISALLOW_COPY_AND_ASSIGN(SplashScreenIcon);
+};
+
+}  // namespace vr_shell
+
+#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_UI_ELEMENTS_SPLASH_SCREEN_ICON_H_
diff --git a/chrome/browser/android/vr_shell/ui_elements/ui_element_debug_id.h b/chrome/browser/android/vr_shell/ui_elements/ui_element_debug_id.h
index 9f1a13e..26c2f6a7 100644
--- a/chrome/browser/android/vr_shell/ui_elements/ui_element_debug_id.h
+++ b/chrome/browser/android/vr_shell/ui_elements/ui_element_debug_id.h
@@ -30,6 +30,7 @@
   kTransientUrlBar,
   kLocationAccessIndicator,
   kPresentationToast,
+  kSplashScreenIcon,
 };
 
 }  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/ui_interface.h b/chrome/browser/android/vr_shell/ui_interface.h
index 1a0f0c6..f8904642 100644
--- a/chrome/browser/android/vr_shell/ui_interface.h
+++ b/chrome/browser/android/vr_shell/ui_interface.h
@@ -8,6 +8,7 @@
 #include "components/security_state/core/security_state.h"
 
 class GURL;
+class SkBitmap;
 
 namespace vr_shell {
 
@@ -25,9 +26,7 @@
 
   virtual ~UiInterface() {}
 
-  virtual void SetWebVrMode(bool enabled,
-                            bool auto_presented,
-                            bool show_toast) = 0;
+  virtual void SetWebVrMode(bool enabled, bool show_toast) = 0;
   virtual void SetURL(const GURL& url) = 0;
   virtual void SetFullscreen(bool enabled) = 0;
   virtual void SetSecurityInfo(security_state::SecurityLevel level,
@@ -42,6 +41,7 @@
   virtual void SetVideoCapturingIndicator(bool enabled) = 0;
   virtual void SetScreenCapturingIndicator(bool enabled) = 0;
   virtual void SetAudioCapturingIndicator(bool enabled) = 0;
+  virtual void SetSplashScreenIcon(const SkBitmap& bitmap) = 0;
 
   // Tab handling.
   virtual void InitTabList() {}
diff --git a/chrome/browser/android/vr_shell/ui_scene.cc b/chrome/browser/android/vr_shell/ui_scene.cc
index 1df212c..5956b75 100644
--- a/chrome/browser/android/vr_shell/ui_scene.cc
+++ b/chrome/browser/android/vr_shell/ui_scene.cc
@@ -188,7 +188,9 @@
 }
 
 SkColor UiScene::GetWorldBackgroundColor() const {
-  return ColorScheme::GetColorScheme(mode_).world_background;
+  return showing_splash_screen_
+             ? ColorScheme::GetColorScheme(mode_).splash_screen_background
+             : ColorScheme::GetColorScheme(mode_).world_background;
 }
 
 void UiScene::SetBackgroundDistance(float distance) {
@@ -215,6 +217,10 @@
   is_prompting_to_exit_ = prompting;
 }
 
+void UiScene::set_showing_splash_screen(bool showing) {
+  showing_splash_screen_ = showing;
+}
+
 const std::vector<std::unique_ptr<UiElement>>& UiScene::GetUiElements() const {
   return ui_elements_;
 }
diff --git a/chrome/browser/android/vr_shell/ui_scene.h b/chrome/browser/android/vr_shell/ui_scene.h
index df949d2c..12bf9df 100644
--- a/chrome/browser/android/vr_shell/ui_scene.h
+++ b/chrome/browser/android/vr_shell/ui_scene.h
@@ -80,8 +80,10 @@
 
   bool is_exiting() const { return is_exiting_; }
   void set_is_exiting();
-  bool is_prompting_to_exit() { return is_prompting_to_exit_; }
+  bool is_prompting_to_exit() const { return is_prompting_to_exit_; }
   void set_is_prompting_to_exit(bool prompting);
+  bool showing_splash_screen() const { return showing_splash_screen_; }
+  void set_showing_splash_screen(bool showing);
 
   void OnGLInitialized();
 
@@ -98,6 +100,7 @@
   bool gl_initialized_ = false;
   bool is_exiting_ = false;
   bool is_prompting_to_exit_ = false;
+  bool showing_splash_screen_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(UiScene);
 };
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager.cc b/chrome/browser/android/vr_shell/ui_scene_manager.cc
index 22580916..5bc2200 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager.cc
+++ b/chrome/browser/android/vr_shell/ui_scene_manager.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/android/vr_shell/ui_elements/permanent_security_warning.h"
 #include "chrome/browser/android/vr_shell/ui_elements/presentation_toast.h"
 #include "chrome/browser/android/vr_shell/ui_elements/screen_dimmer.h"
+#include "chrome/browser/android/vr_shell/ui_elements/splash_screen_icon.h"
 #include "chrome/browser/android/vr_shell/ui_elements/system_indicator.h"
 #include "chrome/browser/android/vr_shell/ui_elements/transient_security_warning.h"
 #include "chrome/browser/android/vr_shell/ui_elements/transient_url_bar.h"
@@ -89,6 +90,15 @@
 static constexpr float kToastHeight = 0.16 * kToastDistance;
 static constexpr int kToastTimeoutSeconds = kTransientUrlBarTimeoutSeconds;
 
+static constexpr float kSplashScreenDistance = 1;
+static constexpr float kSplashScreenIconDMM = 0.12;
+static constexpr float kSplashScreenIconHeight =
+    kSplashScreenIconDMM * kSplashScreenDistance;
+static constexpr float kSplashScreenIconWidth =
+    kSplashScreenIconDMM * kSplashScreenDistance;
+static constexpr float kSplashScreenIconVerticalOffset =
+    0.2 * kSplashScreenDistance;
+
 static constexpr float kCloseButtonDistance = 2.4;
 static constexpr float kCloseButtonHeight =
     kUrlBarHeightDMM * kCloseButtonDistance;
@@ -122,13 +132,14 @@
                                UiScene* scene,
                                bool in_cct,
                                bool in_web_vr,
-                               bool web_vr_autopresented)
+                               bool web_vr_autopresentation_expected)
     : browser_(browser),
       scene_(scene),
       in_cct_(in_cct),
       web_vr_mode_(in_web_vr),
-      web_vr_autopresented_(web_vr_autopresented),
+      web_vr_autopresentation_expected_(web_vr_autopresentation_expected),
       weak_ptr_factory_(this) {
+  CreateSplashScreen();
   CreateBackground();
   CreateContentQuad();
   CreateSecurityWarnings();
@@ -142,7 +153,6 @@
 
   ConfigureScene();
   ConfigureSecurityWarnings();
-  ConfigureTransientUrlBar();
 }
 
 UiSceneManager::~UiSceneManager() {}
@@ -276,6 +286,20 @@
                                 -kBackgroundDistanceMultiplier);
 }
 
+void UiSceneManager::CreateSplashScreen() {
+  // Chrome icon.
+  std::unique_ptr<SplashScreenIcon> icon =
+      base::MakeUnique<SplashScreenIcon>(256);
+  icon->set_debug_id(kSplashScreenIcon);
+  icon->set_id(AllocateId());
+  icon->set_hit_testable(false);
+  icon->set_size({kSplashScreenIconWidth, kSplashScreenIconHeight, 1.0});
+  icon->set_translation(
+      {0, kSplashScreenIconVerticalOffset, -kSplashScreenDistance});
+  splash_screen_icon_ = icon.get();
+  scene_->AddUiElement(std::move(icon));
+}
+
 void UiSceneManager::CreateBackground() {
   std::unique_ptr<UiElement> element;
 
@@ -423,49 +447,55 @@
   return weak_ptr_factory_.GetWeakPtr();
 }
 
-void UiSceneManager::SetWebVrMode(bool web_vr,
-                                  bool auto_presented,
-                                  bool show_toast) {
-  if (web_vr_mode_ == web_vr && web_vr_autopresented_ == auto_presented &&
-      web_vr_show_toast_ == show_toast) {
+void UiSceneManager::SetWebVrMode(bool web_vr, bool show_toast) {
+  if (web_vr_mode_ == web_vr && web_vr_show_toast_ == show_toast) {
     return;
   }
   web_vr_mode_ = web_vr;
-  web_vr_autopresented_ = auto_presented;
+  ConfigureTransientUrlBar();
+  scene_->set_showing_splash_screen(false);
+  web_vr_autopresentation_expected_ = false;
   web_vr_show_toast_ = show_toast;
   toast_state_ = SET_FOR_WEB_VR;
   ConfigureScene();
   ConfigureSecurityWarnings();
-  ConfigureTransientUrlBar();
   ConfigurePresentationToast();
 }
 
 void UiSceneManager::ConfigureScene() {
+  // Splash screen.
+  scene_->set_showing_splash_screen(web_vr_autopresentation_expected_);
+  splash_screen_icon_->SetEnabled(!web_vr_mode_ &&
+                                  web_vr_autopresentation_expected_);
+
+  // Exit warning.
   exit_warning_->SetEnabled(scene_->is_exiting());
   screen_dimmer_->SetEnabled(scene_->is_exiting());
 
+  bool browsing_mode = !web_vr_mode_ && !scene_->showing_splash_screen();
+
   // Controls (URL bar, loading progress, etc).
-  bool controls_visible = !web_vr_mode_ && !fullscreen_;
+  bool controls_visible = browsing_mode && !fullscreen_;
   for (UiElement* element : control_elements_) {
     element->SetEnabled(controls_visible && !scene_->is_prompting_to_exit());
   }
 
   // Close button is a special control element that needs to be hidden when in
   // WebVR, but it needs to be visible when in cct or fullscreen.
-  close_button_->SetEnabled(!web_vr_mode_ && (fullscreen_ || in_cct_));
+  close_button_->SetEnabled(browsing_mode && (fullscreen_ || in_cct_));
 
   // Content elements.
   for (UiElement* element : content_elements_) {
-    element->SetEnabled(!web_vr_mode_ && !scene_->is_prompting_to_exit());
+    element->SetEnabled(browsing_mode && !scene_->is_prompting_to_exit());
   }
 
   // Background elements.
   for (UiElement* element : background_elements_) {
-    element->SetEnabled(!web_vr_mode_);
+    element->SetEnabled(browsing_mode);
   }
 
   // Exit prompt.
-  bool showExitPrompt = !web_vr_mode_ && scene_->is_prompting_to_exit();
+  bool showExitPrompt = browsing_mode && scene_->is_prompting_to_exit();
   exit_prompt_->SetEnabled(showExitPrompt);
   exit_prompt_backplane_->SetEnabled(showExitPrompt);
 
@@ -518,6 +548,11 @@
   floor_->set_grid_color(color_scheme().floor_grid);
 }
 
+void UiSceneManager::SetSplashScreenIcon(const SkBitmap& bitmap) {
+  splash_screen_icon_->SetSplashScreenIconBitmap(bitmap);
+  ConfigureScene();
+}
+
 void UiSceneManager::SetAudioCapturingIndicator(bool enabled) {
   audio_capturing_ = enabled;
   ConfigureIndicators();
@@ -647,7 +682,7 @@
 }
 
 void UiSceneManager::ConfigureTransientUrlBar() {
-  bool enabled = web_vr_mode_ && web_vr_autopresented_;
+  bool enabled = web_vr_mode_ && web_vr_autopresentation_expected_;
   transient_url_bar_->set_visible(enabled);
   if (enabled) {
     transient_url_bar_timer_.Start(
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager.h b/chrome/browser/android/vr_shell/ui_scene_manager.h
index a330af6..105bc2a 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager.h
+++ b/chrome/browser/android/vr_shell/ui_scene_manager.h
@@ -13,10 +13,12 @@
 #include "chrome/browser/android/vr_shell/color_scheme.h"
 #include "chrome/browser/android/vr_shell/ui_interface.h"
 #include "chrome/browser/android/vr_shell/ui_unsupported_mode.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace vr_shell {
 
 class LoadingIndicator;
+class SplashScreenIcon;
 class TransientUrlBar;
 class UiBrowserInterface;
 class UiElement;
@@ -29,7 +31,7 @@
                  UiScene* scene,
                  bool in_cct,
                  bool in_web_vr,
-                 bool web_vr_autopresented);
+                 bool web_vr_autopresentation_expected);
   ~UiSceneManager();
 
   base::WeakPtr<UiSceneManager> GetWeakPtr();
@@ -38,7 +40,7 @@
   void SetIncognito(bool incognito);
   void SetURL(const GURL& gurl);
   void SetWebVrSecureOrigin(bool secure);
-  void SetWebVrMode(bool web_vr, bool auto_presented, bool show_toast);
+  void SetWebVrMode(bool web_vr, bool show_toast);
   void SetSecurityInfo(security_state::SecurityLevel level, bool malware);
   void SetLoading(bool loading);
   void SetLoadProgress(float progress);
@@ -47,6 +49,7 @@
   void SetScreenCapturingIndicator(bool enabled);
   void SetAudioCapturingIndicator(bool enabled);
   void SetLocationAccessIndicator(bool enabled);
+  void SetSplashScreenIcon(const SkBitmap& bitmap);
 
   // These methods are currently stubbed.
   void SetHistoryButtonsEnabled(bool can_go_back, bool can_go_forward);
@@ -68,6 +71,7 @@
   void CreateSecurityWarnings();
   void CreateSystemIndicators();
   void CreateContentQuad();
+  void CreateSplashScreen();
   void CreateBackground();
   void CreateUrlBar();
   void CreateTransientUrlBar();
@@ -115,6 +119,7 @@
   UiElement* ceiling_ = nullptr;
   UiElement* floor_ = nullptr;
   UiElement* close_button_ = nullptr;
+  SplashScreenIcon* splash_screen_icon_ = nullptr;
   UrlBar* url_bar_ = nullptr;
   TransientUrlBar* transient_url_bar_ = nullptr;
   LoadingIndicator* loading_indicator_ = nullptr;
@@ -123,8 +128,8 @@
 
   bool in_cct_;
   bool web_vr_mode_;
-  bool web_vr_autopresented_ = false;
   bool web_vr_show_toast_ = false;
+  bool web_vr_autopresentation_expected_ = false;
   bool secure_origin_ = false;
   bool fullscreen_ = false;
   bool incognito_ = false;
diff --git a/chrome/browser/android/vr_shell/ui_scene_manager_unittest.cc b/chrome/browser/android/vr_shell/ui_scene_manager_unittest.cc
index 38b8241..93d6988c 100644
--- a/chrome/browser/android/vr_shell/ui_scene_manager_unittest.cc
+++ b/chrome/browser/android/vr_shell/ui_scene_manager_unittest.cc
@@ -6,7 +6,8 @@
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
-#include "base/test/scoped_task_environment.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/scoped_mock_time_message_loop_task_runner.h"
 #include "chrome/browser/android/vr_shell/ui_browser_interface.h"
 #include "chrome/browser/android/vr_shell/ui_elements/ui_element.h"
 #include "chrome/browser/android/vr_shell/ui_elements/ui_element_debug_id.h"
@@ -37,7 +38,6 @@
     kContentQuad, kBackplane, kCeiling, kFloor, kUrlBar};
 std::set<UiElementDebugId> kElementsVisibleWithExitPrompt = {
     kExitPrompt, kExitPromptBackplane, kCeiling, kFloor};
-
 }  // namespace
 
 class UiSceneManagerTest : public testing::Test {
@@ -69,7 +69,7 @@
   void MakeAutoPresentedManager() {
     scene_ = base::MakeUnique<UiScene>();
     manager_ = base::MakeUnique<UiSceneManager>(
-        browser_.get(), scene_.get(), kNotInCct, kInWebVr, kAutopresented);
+        browser_.get(), scene_.get(), kNotInCct, kNotInWebVr, kAutopresented);
   }
 
   bool IsVisible(UiElementDebugId debug_id) {
@@ -102,7 +102,7 @@
     return true;
   }
 
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::MessageLoop message_loop_;
   std::unique_ptr<MockBrowserInterface> browser_;
   std::unique_ptr<UiScene> scene_;
   std::unique_ptr<UiSceneManager> manager_;
@@ -132,7 +132,7 @@
   EXPECT_TRUE(IsVisible(kWebVrPermanentHttpSecurityWarning));
   EXPECT_TRUE(IsVisible(kWebVrTransientHttpSecurityWarning));
 
-  manager_->SetWebVrMode(false, false, false);
+  manager_->SetWebVrMode(false, false);
   EXPECT_FALSE(IsVisible(kWebVrPermanentHttpSecurityWarning));
   EXPECT_FALSE(IsVisible(kWebVrTransientHttpSecurityWarning));
 }
@@ -143,11 +143,26 @@
   EXPECT_FALSE(IsVisible(kWebVrPermanentHttpSecurityWarning));
   EXPECT_FALSE(IsVisible(kWebVrTransientHttpSecurityWarning));
 
-  manager_->SetWebVrMode(true, false, false);
+  manager_->SetWebVrMode(true, false);
   EXPECT_TRUE(IsVisible(kWebVrPermanentHttpSecurityWarning));
   EXPECT_TRUE(IsVisible(kWebVrTransientHttpSecurityWarning));
 }
 
+TEST_F(UiSceneManagerTest, WebVrTransientWarningTimesOut) {
+  base::ScopedMockTimeMessageLoopTaskRunner task_runner_;
+
+  MakeManager(kNotInCct, kInWebVr);
+  EXPECT_TRUE(IsVisible(kWebVrTransientHttpSecurityWarning));
+
+  // Note: Fast-forwarding appears broken in conjunction with restarting timers.
+  // In this test, we can fast-forward by the appropriate time interval, but in
+  // other cases (until the bug is addressed), we could work around the problem
+  // by using FastForwardUntilNoTasksRemain() instead.
+  // See http://crbug.com/736558.
+  task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(31));
+  EXPECT_FALSE(IsVisible(kWebVrTransientHttpSecurityWarning));
+}
+
 TEST_F(UiSceneManagerTest, ToastVisibility) {
   // Tests toast not showing when directly entering VR though WebVR
   // presentation.
@@ -160,19 +175,19 @@
   manager_->SetFullscreen(true);
   EXPECT_TRUE(IsVisible(kPresentationToast));
 
-  manager_->SetWebVrMode(true, false, true);
+  manager_->SetWebVrMode(true, true);
   EXPECT_TRUE(IsVisible(kPresentationToast));
 
-  manager_->SetWebVrMode(false, false, false);
+  manager_->SetWebVrMode(false, false);
   EXPECT_FALSE(IsVisible(kPresentationToast));
 
   manager_->SetFullscreen(false);
   EXPECT_FALSE(IsVisible(kPresentationToast));
 
-  manager_->SetWebVrMode(true, false, false);
+  manager_->SetWebVrMode(true, false);
   EXPECT_FALSE(IsVisible(kPresentationToast));
 
-  manager_->SetWebVrMode(false, false, true);
+  manager_->SetWebVrMode(false, true);
   EXPECT_TRUE(IsVisible(kPresentationToast));
 }
 
@@ -194,7 +209,7 @@
   // Button should not be visible when in WebVR.
   MakeManager(kInCct, kInWebVr);
   EXPECT_FALSE(IsVisible(kCloseButton));
-  manager_->SetWebVrMode(false, false, false);
+  manager_->SetWebVrMode(false, false);
   EXPECT_TRUE(IsVisible(kCloseButton));
 
   // Button should be visible in Cct across transistions in fullscreen.
@@ -261,23 +276,17 @@
   }
 }
 
-TEST_F(UiSceneManagerTest, WebVrAutopresentedInitially) {
-  MakeAutoPresentedManager();
-  manager_->SetWebVrSecureOrigin(true);
-  VerifyElementsVisible("Autopresented",
-                        std::set<UiElementDebugId>{kTransientUrlBar});
-}
-
 TEST_F(UiSceneManagerTest, WebVrAutopresented) {
-  MakeManager(kNotInCct, kNotInWebVr);
+  MakeAutoPresentedManager();
 
   manager_->SetWebVrSecureOrigin(true);
 
-  // Initial state.
-  VerifyElementsVisible("Initial", kElementsVisibleInBrowsing);
+  // Initially, we should only show the splash screen.
+  VerifyElementsVisible("Initial",
+                        std::set<UiElementDebugId>{kSplashScreenIcon});
 
   // Enter WebVR with autopresentation.
-  manager_->SetWebVrMode(true, true, false);
+  manager_->SetWebVrMode(true, false);
 
   VerifyElementsVisible("Autopresented",
                         std::set<UiElementDebugId>{kTransientUrlBar});
@@ -370,7 +379,7 @@
   manager_->SetLocationAccessIndicator(true);
 
   // Transition to WebVR mode
-  manager_->SetWebVrMode(true, false, false);
+  manager_->SetWebVrMode(true, false);
   manager_->SetWebVrSecureOrigin(true);
 
   // All elements should be hidden.
@@ -393,9 +402,9 @@
   EXPECT_TRUE(VerifyVisibility(indicators, true));
 
   // Go into non-browser modes and make sure all indicators are hidden.
-  manager_->SetWebVrMode(true, false, false);
+  manager_->SetWebVrMode(true, false);
   EXPECT_TRUE(VerifyVisibility(indicators, false));
-  manager_->SetWebVrMode(false, false, false);
+  manager_->SetWebVrMode(false, false);
   manager_->SetFullscreen(true);
   EXPECT_TRUE(VerifyVisibility(indicators, false));
   manager_->SetFullscreen(false);
diff --git a/chrome/browser/android/vr_shell/vr_gl_thread.cc b/chrome/browser/android/vr_shell/vr_gl_thread.cc
index b1fc9138..d814a04 100644
--- a/chrome/browser/android/vr_shell/vr_gl_thread.cc
+++ b/chrome/browser/android/vr_shell/vr_gl_thread.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/android/vr_shell/vr_input_manager.h"
 #include "chrome/browser/android/vr_shell/vr_shell.h"
 #include "chrome/browser/android/vr_shell/vr_shell_gl.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace vr_shell {
 
@@ -20,7 +21,7 @@
     scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
     gvr_context* gvr_api,
     bool initially_web_vr,
-    bool web_vr_autopresented,
+    bool web_vr_autopresentation_expected,
     bool in_cct,
     bool reprojected_rendering,
     bool daydream_support)
@@ -29,7 +30,7 @@
       main_thread_task_runner_(std::move(main_thread_task_runner)),
       gvr_api_(gvr_api),
       initially_web_vr_(initially_web_vr),
-      web_vr_autopresented_(web_vr_autopresented),
+      web_vr_autopresentation_expected_(web_vr_autopresentation_expected),
       in_cct_(in_cct),
       reprojected_rendering_(reprojected_rendering),
       daydream_support_(daydream_support) {}
@@ -44,7 +45,8 @@
                                              reprojected_rendering_,
                                              daydream_support_, scene_.get());
   scene_manager_ = base::MakeUnique<UiSceneManager>(
-      this, scene_.get(), in_cct_, initially_web_vr_, web_vr_autopresented_);
+      this, scene_.get(), in_cct_, initially_web_vr_,
+      web_vr_autopresentation_expected_);
 
   weak_vr_shell_gl_ = vr_shell_gl_->GetWeakPtr();
   weak_scene_manager_ = scene_manager_->GetWeakPtr();
@@ -191,13 +193,11 @@
                                                 weak_scene_manager_, gurl));
 }
 
-void VrGLThread::SetWebVrMode(bool enabled,
-                              bool auto_presented,
-                              bool show_toast) {
+void VrGLThread::SetWebVrMode(bool enabled, bool show_toast) {
   WaitUntilThreadStarted();
   task_runner()->PostTask(
       FROM_HERE, base::Bind(&UiSceneManager::SetWebVrMode, weak_scene_manager_,
-                            enabled, auto_presented, show_toast));
+                            enabled, show_toast));
 }
 
 void VrGLThread::SetWebVrSecureOrigin(bool secure) {
@@ -231,6 +231,13 @@
                                                 weak_scene_manager_));
 }
 
+void VrGLThread::SetSplashScreenIcon(const SkBitmap& bitmap) {
+  WaitUntilThreadStarted();
+  task_runner()->PostTask(FROM_HERE,
+                          base::Bind(&UiSceneManager::SetSplashScreenIcon,
+                                     weak_scene_manager_, bitmap));
+}
+
 void VrGLThread::CleanUp() {
   scene_manager_.reset();
   vr_shell_gl_.reset();
diff --git a/chrome/browser/android/vr_shell/vr_gl_thread.h b/chrome/browser/android/vr_shell/vr_gl_thread.h
index 95ff30ee..8e3b89d 100644
--- a/chrome/browser/android/vr_shell/vr_gl_thread.h
+++ b/chrome/browser/android/vr_shell/vr_gl_thread.h
@@ -35,7 +35,7 @@
       scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
       gvr_context* gvr_api,
       bool initially_web_vr,
-      bool web_vr_autopresented,
+      bool web_vr_autopresentation_expected,
       bool in_cct,
       bool reprojected_rendering,
       bool daydream_support);
@@ -78,14 +78,13 @@
   void SetSecurityInfo(security_state::SecurityLevel level,
                        bool malware) override;
   void SetURL(const GURL& gurl) override;
-  void SetWebVrMode(bool enabled,
-                    bool auto_presented,
-                    bool show_toast) override;
+  void SetWebVrMode(bool enabled, bool show_toast) override;
   void SetWebVrSecureOrigin(bool secure) override;
   void SetVideoCapturingIndicator(bool enabled) override;
   void SetScreenCapturingIndicator(bool enabled) override;
   void SetAudioCapturingIndicator(bool enabled) override;
   void SetIsExiting() override;
+  void SetSplashScreenIcon(const SkBitmap& bitmap) override;
 
  protected:
   void Init() override;
@@ -104,7 +103,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
   gvr_context* gvr_api_;
   bool initially_web_vr_;
-  bool web_vr_autopresented_;
+  bool web_vr_autopresentation_expected_;
   bool in_cct_;
   bool reprojected_rendering_;
   bool daydream_support_;
diff --git a/chrome/browser/android/vr_shell/vr_shell.cc b/chrome/browser/android/vr_shell/vr_shell.cc
index b881c09..97878d9 100644
--- a/chrome/browser/android/vr_shell/vr_shell.cc
+++ b/chrome/browser/android/vr_shell/vr_shell.cc
@@ -56,6 +56,7 @@
 #include "ui/base/page_transition_types.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
+#include "ui/gfx/android/java_bitmap.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/native_widget_types.h"
@@ -94,7 +95,7 @@
                  jobject obj,
                  ui::WindowAndroid* window,
                  bool for_web_vr,
-                 bool web_vr_autopresented,
+                 bool web_vr_autopresentation_expected,
                  bool in_cct,
                  VrShellDelegate* delegate,
                  gvr_context* gvr_api,
@@ -113,8 +114,8 @@
 
   gl_thread_ = base::MakeUnique<VrGLThread>(
       weak_ptr_factory_.GetWeakPtr(), main_thread_task_runner_, gvr_api,
-      for_web_vr, web_vr_autopresented, in_cct, reprojected_rendering_,
-      HasDaydreamSupport(env));
+      for_web_vr, web_vr_autopresentation_expected, in_cct,
+      reprojected_rendering_, HasDaydreamSupport(env));
   ui_ = gl_thread_.get();
 
   base::Thread::Options options(base::MessageLoop::TYPE_DEFAULT, 0);
@@ -122,6 +123,13 @@
   gl_thread_->StartWithOptions(options);
 }
 
+void VrShell::SetSplashScreenIcon(JNIEnv* env,
+                                  const JavaParamRef<jobject>& obj,
+                                  const JavaParamRef<jobject>& bitmap) {
+  ui_->SetSplashScreenIcon(
+      gfx::CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(bitmap)));
+}
+
 void VrShell::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
   delete this;
 }
@@ -337,7 +345,6 @@
 void VrShell::SetWebVrMode(JNIEnv* env,
                            const JavaParamRef<jobject>& obj,
                            bool enabled,
-                           bool auto_presented,
                            bool show_toast) {
   webvr_mode_ = enabled;
   if (metrics_helper_)
@@ -345,7 +352,7 @@
   WaitForGlThread();
   PostToGlThread(FROM_HERE, base::Bind(&VrShellGl::SetWebVrMode,
                                        gl_thread_->GetVrShellGl(), enabled));
-  ui_->SetWebVrMode(enabled, auto_presented, show_toast);
+  ui_->SetWebVrMode(enabled, show_toast);
 }
 
 void VrShell::OnFullscreenChanged(bool enabled) {
@@ -708,13 +715,13 @@
            const JavaParamRef<jobject>& delegate,
            jlong window_android,
            jboolean for_web_vr,
-           jboolean web_vr_autopresented,
+           jboolean web_vr_autopresentation_expected,
            jboolean in_cct,
            jlong gvr_api,
            jboolean reprojected_rendering) {
   return reinterpret_cast<intptr_t>(new VrShell(
       env, obj, reinterpret_cast<ui::WindowAndroid*>(window_android),
-      for_web_vr, web_vr_autopresented, in_cct,
+      for_web_vr, web_vr_autopresentation_expected, in_cct,
       VrShellDelegate::GetNativeVrShellDelegate(env, delegate),
       reinterpret_cast<gvr_context*>(gvr_api), reprojected_rendering));
 }
diff --git a/chrome/browser/android/vr_shell/vr_shell.h b/chrome/browser/android/vr_shell/vr_shell.h
index c675a37..26d1c49 100644
--- a/chrome/browser/android/vr_shell/vr_shell.h
+++ b/chrome/browser/android/vr_shell/vr_shell.h
@@ -68,7 +68,7 @@
           jobject obj,
           ui::WindowAndroid* window,
           bool for_web_vr,
-          bool web_vr_autopresented,
+          bool web_vr_autopresentation_expected,
           bool in_cct,
           VrShellDelegate* delegate,
           gvr_context* gvr_api,
@@ -86,13 +86,15 @@
                       bool touched);
   void OnPause(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
   void OnResume(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
+  void SetSplashScreenIcon(JNIEnv* env,
+                           const base::android::JavaParamRef<jobject>& obj,
+                           const base::android::JavaParamRef<jobject>& bitmap);
   void SetSurface(JNIEnv* env,
                   const base::android::JavaParamRef<jobject>& obj,
                   const base::android::JavaParamRef<jobject>& surface);
   void SetWebVrMode(JNIEnv* env,
                     const base::android::JavaParamRef<jobject>& obj,
                     bool enabled,
-                    bool auto_presented,
                     bool show_toast);
   bool GetWebVrMode(JNIEnv* env,
                     const base::android::JavaParamRef<jobject>& obj);
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.cc b/chrome/browser/android/vr_shell/vr_shell_gl.cc
index c0a97bd..aa961325 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.cc
@@ -1194,7 +1194,9 @@
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   }
   std::vector<const UiElement*> elements = scene_->GetWorldElements();
-  const bool draw_reticle = !(scene_->is_exiting() || ShouldDrawWebVr());
+  const bool draw_reticle =
+      !(scene_->is_exiting() || scene_->showing_splash_screen() ||
+        ShouldDrawWebVr());
   DrawUiView(head_pose, elements, render_size_primary_,
              kViewportListPrimaryOffset, draw_reticle);
 }
diff --git a/chrome/browser/apps/app_url_redirector.cc b/chrome/browser/apps/app_url_redirector.cc
index e5ec867..46321f2e 100644
--- a/chrome/browser/apps/app_url_redirector.cc
+++ b/chrome/browser/apps/app_url_redirector.cc
@@ -113,7 +113,8 @@
           new navigation_interception::InterceptNavigationThrottle(
               handle,
               base::Bind(&LaunchAppWithUrl,
-                         scoped_refptr<const Extension>(*iter), handler->id)));
+                         scoped_refptr<const Extension>(*iter), handler->id),
+              true));
     }
   }
 
diff --git a/chrome/browser/browsing_data/downloads_counter_browsertest.cc b/chrome/browser/browsing_data/downloads_counter_browsertest.cc
index 04b0e18..e451143b 100644
--- a/chrome/browser/browsing_data/downloads_counter_browsertest.cc
+++ b/chrome/browser/browsing_data/downloads_counter_browsertest.cc
@@ -338,7 +338,13 @@
 }
 
 // Tests that the counter takes time ranges into account.
-IN_PROC_BROWSER_TEST_F(DownloadsCounterTest, TimeRanges) {
+// Flaky on Mac (crbug.com/736820)
+#if defined(OS_MACOSX)
+#define MAYBE_TimeRanges DISABLED_TimeRanges
+#else
+#define MAYBE_TimeRanges TimeRanges
+#endif
+IN_PROC_BROWSER_TEST_F(DownloadsCounterTest, MAYBE_TimeRanges) {
   AddDownload();
   AddDownload();  // 2 items
 
diff --git a/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc b/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc
index 620e32c..8d04694 100644
--- a/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc
+++ b/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/apps/app_info_dialog.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/api/device_permissions_manager.h"
+#include "extensions/browser/api/file_system/saved_file_entry.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -66,10 +67,10 @@
   std::vector<base::FilePath> retained_file_paths;
   if (extension->permissions_data()->HasAPIPermission(
           APIPermission::kFileSystem)) {
-    std::vector<apps::SavedFileEntry> retained_file_entries =
-        apps::SavedFilesService::Get(profile_)
-            ->GetAllFileEntries(extension_id_);
-    for (const apps::SavedFileEntry& entry : retained_file_entries)
+    std::vector<SavedFileEntry> retained_file_entries =
+        apps::SavedFilesService::Get(profile_)->GetAllFileEntries(
+            extension_id_);
+    for (const SavedFileEntry& entry : retained_file_entries)
       retained_file_paths.push_back(entry.path);
   }
   std::vector<base::string16> retained_device_messages;
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc
index 8d5c628b..9ca75b7f 100644
--- a/chrome/browser/extensions/api/file_system/file_system_api.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_api.cc
@@ -40,6 +40,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/api/file_handlers/app_file_handler_util.h"
+#include "extensions/browser/api/file_system/saved_file_entry.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/browser/extension_prefs.h"
@@ -75,7 +76,6 @@
 #include "url/url_constants.h"
 #endif
 
-using apps::SavedFileEntry;
 using apps::SavedFilesService;
 using storage::IsolatedContext;
 
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
index 19dd525..abd1da1 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_paths.h"
+#include "extensions/browser/api/file_system/saved_file_entry.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
@@ -615,8 +616,9 @@
       &test_file);
   ASSERT_TRUE(RunPlatformAppTest(
       "api_test/file_system/retain_entry")) << message_;
-  std::vector<apps::SavedFileEntry> file_entries = apps::SavedFilesService::Get(
-      profile())->GetAllFileEntries(GetSingleLoadedExtension()->id());
+  std::vector<SavedFileEntry> file_entries =
+      apps::SavedFilesService::Get(profile())->GetAllFileEntries(
+          GetSingleLoadedExtension()->id());
   ASSERT_EQ(1u, file_entries.size());
   EXPECT_EQ(test_file, file_entries[0].path);
   EXPECT_EQ(1, file_entries[0].sequence_number);
@@ -631,8 +633,9 @@
       &test_directory);
   ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/retain_directory"))
       << message_;
-  std::vector<apps::SavedFileEntry> file_entries = apps::SavedFilesService::Get(
-      profile())->GetAllFileEntries(GetSingleLoadedExtension()->id());
+  std::vector<SavedFileEntry> file_entries =
+      apps::SavedFilesService::Get(profile())->GetAllFileEntries(
+          GetSingleLoadedExtension()->id());
   ASSERT_EQ(1u, file_entries.size());
   EXPECT_EQ(test_directory, file_entries[0].path);
   EXPECT_EQ(1, file_entries[0].sequence_number);
diff --git a/chrome/browser/plugins/flash_download_interception.cc b/chrome/browser/plugins/flash_download_interception.cc
index f9a7b44..b4f6678 100644
--- a/chrome/browser/plugins/flash_download_interception.cc
+++ b/chrome/browser/plugins/flash_download_interception.cc
@@ -159,5 +159,5 @@
   }
 
   return base::MakeUnique<navigation_interception::InterceptNavigationThrottle>(
-      handle, base::Bind(&InterceptNavigation, source_url));
+      handle, base::Bind(&InterceptNavigation, source_url), true);
 }
diff --git a/chrome/browser/predictors/loading_test_util.cc b/chrome/browser/predictors/loading_test_util.cc
index b740a7e..ef41145f 100644
--- a/chrome/browser/predictors/loading_test_util.cc
+++ b/chrome/browser/predictors/loading_test_util.cc
@@ -207,6 +207,19 @@
   return prediction;
 }
 
+PreconnectPrediction CreatePreconnectPrediction(
+    std::string host,
+    bool is_redirected,
+    std::vector<GURL> preconnect_origins,
+    std::vector<GURL> preresolve_hosts) {
+  PreconnectPrediction prediction;
+  prediction.host = host;
+  prediction.is_redirected = is_redirected;
+  prediction.preconnect_origins = preconnect_origins;
+  prediction.preresolve_hosts = preresolve_hosts;
+  return prediction;
+}
+
 void PopulateTestConfig(LoadingPredictorConfig* config, bool small_db) {
   if (small_db) {
     config->max_urls_to_track = 3;
@@ -372,6 +385,21 @@
   return os << navigation_id.tab_id << "," << navigation_id.main_frame_url;
 }
 
+std::ostream& operator<<(std::ostream& os,
+                         const PreconnectPrediction& prediction) {
+  os << "[" << prediction.host << "," << prediction.is_redirected << "]"
+     << std::endl;
+
+  os << "Preconnect:" << std::endl;
+  for (const auto& url : prediction.preconnect_origins)
+    os << "\t\t" << url << std::endl;
+
+  os << "Preresolve:" << std::endl;
+  for (const auto& url : prediction.preresolve_hosts)
+    os << "\t\t" << url << std::endl;
+  return os;
+}
+
 bool operator==(const PrefetchData& lhs, const PrefetchData& rhs) {
   bool equal = lhs.primary_key() == rhs.primary_key() &&
                lhs.resources_size() == rhs.resources_size();
@@ -460,6 +488,13 @@
          lhs.accessed_network() == rhs.accessed_network();
 }
 
+bool operator==(const PreconnectPrediction& lhs,
+                const PreconnectPrediction& rhs) {
+  return lhs.is_redirected == rhs.is_redirected && lhs.host == rhs.host &&
+         lhs.preconnect_origins == rhs.preconnect_origins &&
+         lhs.preresolve_hosts == rhs.preresolve_hosts;
+}
+
 }  // namespace predictors
 
 namespace precache {
diff --git a/chrome/browser/predictors/loading_test_util.h b/chrome/browser/predictors/loading_test_util.h
index 2d6e171..2fe23ac 100644
--- a/chrome/browser/predictors/loading_test_util.h
+++ b/chrome/browser/predictors/loading_test_util.h
@@ -104,6 +104,12 @@
     const std::string& main_frame_key,
     std::vector<GURL> subresource_urls);
 
+PreconnectPrediction CreatePreconnectPrediction(
+    std::string host,
+    bool is_redirected,
+    std::vector<GURL> preconnect_urls,
+    std::vector<GURL> preresolve_urls);
+
 void PopulateTestConfig(LoadingPredictorConfig* config, bool small_db = true);
 
 scoped_refptr<net::HttpResponseHeaders> MakeResponseHeaders(
@@ -184,6 +190,8 @@
 
 std::ostream& operator<<(std::ostream& os, const OriginData& data);
 std::ostream& operator<<(std::ostream& os, const OriginStat& redirect);
+std::ostream& operator<<(std::ostream& os,
+                         const PreconnectPrediction& prediction);
 
 bool operator==(const PrefetchData& lhs, const PrefetchData& rhs);
 bool operator==(const ResourceData& lhs, const ResourceData& rhs);
@@ -195,6 +203,8 @@
                 const ResourcePrefetchPredictor::URLRequestSummary& rhs);
 bool operator==(const OriginData& lhs, const OriginData& rhs);
 bool operator==(const OriginStat& lhs, const OriginStat& rhs);
+bool operator==(const PreconnectPrediction& lhs,
+                const PreconnectPrediction& rhs);
 
 }  // namespace predictors
 
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.cc b/chrome/browser/predictors/resource_prefetch_predictor.cc
index 2923f9c..5d3b5114 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor.cc
@@ -54,6 +54,8 @@
 const size_t kMaxManifestByteSize = 16 * 1024;
 const size_t kNumSampleHosts = 50;
 const size_t kReportReadinessThreshold = 50;
+const float kMinOriginConfidenceToTriggerPreconnect = 0.75;
+const float kMinOriginConfidenceToTriggerPreresolve = 0.2;
 
 // For reporting events of interest that are not tied to any navigation.
 enum ReportingEvent {
@@ -181,6 +183,11 @@
 
 }  // namespace internal
 
+PreconnectPrediction::PreconnectPrediction() = default;
+PreconnectPrediction::PreconnectPrediction(
+    const PreconnectPrediction& prediction) = default;
+PreconnectPrediction::~PreconnectPrediction() = default;
+
 ////////////////////////////////////////////////////////////////////////////////
 // ResourcePrefetchPredictor static functions.
 
@@ -715,6 +722,41 @@
   return false;
 }
 
+bool ResourcePrefetchPredictor::PredictPreconnectOrigins(
+    const GURL& url,
+    PreconnectPrediction* prediction) const {
+  DCHECK(prediction);
+  DCHECK(prediction->preconnect_origins.empty());
+  DCHECK(prediction->preresolve_hosts.empty());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (initialization_state_ != INITIALIZED)
+    return false;
+
+  std::string host = url.host();
+  std::string redirect_endpoint;
+  if (!GetRedirectEndpoint(host, *host_redirect_data_, &redirect_endpoint))
+    return false;
+
+  OriginData data;
+  if (!origin_data_->TryGetData(redirect_endpoint, &data))
+    return false;
+
+  prediction->host = redirect_endpoint;
+  prediction->is_redirected = (host != redirect_endpoint);
+  for (const OriginStat& origin : data.origins()) {
+    float confidence = static_cast<float>(origin.number_of_hits()) /
+                       (origin.number_of_hits() + origin.number_of_misses());
+    if (confidence > kMinOriginConfidenceToTriggerPreconnect) {
+      prediction->preconnect_origins.emplace_back(origin.origin());
+    } else if (confidence > kMinOriginConfidenceToTriggerPreresolve) {
+      prediction->preresolve_hosts.emplace_back(origin.origin());
+    }
+  }
+
+  return !prediction->preconnect_origins.empty() ||
+         !prediction->preresolve_hosts.empty();
+}
+
 bool ResourcePrefetchPredictor::PopulatePrefetcherRequest(
     const std::string& main_frame_key,
     const PrefetchDataMap& resource_data,
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.h b/chrome/browser/predictors/resource_prefetch_predictor.h
index fb5c2ba..28df24d 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.h
+++ b/chrome/browser/predictors/resource_prefetch_predictor.h
@@ -68,6 +68,17 @@
 class ResourcePrefetcherManager;
 class LoadingStatsCollector;
 
+struct PreconnectPrediction {
+  PreconnectPrediction();
+  PreconnectPrediction(const PreconnectPrediction& other);
+  ~PreconnectPrediction();
+
+  bool is_redirected;
+  std::string host;
+  std::vector<GURL> preconnect_origins;
+  std::vector<GURL> preresolve_hosts;
+};
+
 // Contains logic for learning what can be prefetched and for kicking off
 // speculative prefetching.
 // - The class is a profile keyed service owned by the profile.
@@ -254,6 +265,12 @@
   virtual bool GetPrefetchData(const GURL& main_frame_url,
                                Prediction* prediction) const;
 
+  // Returns true iff there is OriginData that can be used for a |url| and fills
+  // |prediction| with origins and hosts that need to be preconnected and
+  // preresolved respectively.
+  virtual bool PredictPreconnectOrigins(const GURL& url,
+                                        PreconnectPrediction* prediction) const;
+
  private:
   // 'LoadingPredictorObserver' calls the below functions to inform the
   // predictor of main frame and resource requests. Should only be called if the
@@ -314,6 +331,8 @@
   FRIEND_TEST_ALL_PREFIXES(ResourcePrefetchPredictorTest, GetRedirectEndpoint);
   FRIEND_TEST_ALL_PREFIXES(ResourcePrefetchPredictorTest, GetPrefetchData);
   FRIEND_TEST_ALL_PREFIXES(ResourcePrefetchPredictorTest,
+                           TestPredictPreconnectOrigins);
+  FRIEND_TEST_ALL_PREFIXES(ResourcePrefetchPredictorTest,
                            TestPrecisionRecallHistograms);
   FRIEND_TEST_ALL_PREFIXES(ResourcePrefetchPredictorTest,
                            TestPrefetchingDurationHistogram);
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
index 9c6fa37..1b00760f 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
@@ -1632,6 +1632,61 @@
   EXPECT_THAT(urls, UnorderedElementsAre(GURL(font_url)));
 }
 
+TEST_F(ResourcePrefetchPredictorTest, TestPredictPreconnectOrigins) {
+  const GURL main_frame_url("http://google.com/?query=cats");
+  auto prediction = base::MakeUnique<PreconnectPrediction>();
+  // No prefetch data.
+  EXPECT_FALSE(
+      predictor_->PredictPreconnectOrigins(main_frame_url, prediction.get()));
+
+  const char* cdn_origin = "https://cdn%d.google.com";
+  auto gen_origin = [cdn_origin](int n) {
+    return base::StringPrintf(cdn_origin, n);
+  };
+
+  // Add origins associated with the main frame host.
+  OriginData google = CreateOriginData("google.com");
+  InitializeOriginStat(google.add_origins(), gen_origin(1), 10, 0, 0, 1.0, true,
+                       true);  // High confidence - preconnect.
+  InitializeOriginStat(google.add_origins(), gen_origin(2), 10, 5, 0, 2.0, true,
+                       true);  // Medium confidence - preresolve.
+  InitializeOriginStat(google.add_origins(), gen_origin(3), 1, 10, 10, 3.0,
+                       true, true);  // Low confidence - ignore.
+  predictor_->origin_data_->UpdateData(google.host(), google);
+
+  prediction = base::MakeUnique<PreconnectPrediction>();
+  EXPECT_TRUE(
+      predictor_->PredictPreconnectOrigins(main_frame_url, prediction.get()));
+  EXPECT_EQ(*prediction, CreatePreconnectPrediction("google.com", false,
+                                                    {GURL(gen_origin(1))},
+                                                    {GURL(gen_origin(2))}));
+
+  // Add a redirect.
+  RedirectData redirect = CreateRedirectData("google.com", 3);
+  InitializeRedirectStat(redirect.add_redirect_endpoints(), "www.google.com",
+                         10, 0, 0);
+  predictor_->host_redirect_data_->UpdateData(redirect.primary_key(), redirect);
+
+  // Prediction failed: no data associated with the redirect endpoint.
+  prediction = base::MakeUnique<PreconnectPrediction>();
+  EXPECT_FALSE(
+      predictor_->PredictPreconnectOrigins(main_frame_url, prediction.get()));
+
+  // Add a resource associated with the redirect endpoint.
+  OriginData www_google = CreateOriginData("www.google.com", 4);
+  InitializeOriginStat(www_google.add_origins(), gen_origin(4), 10, 0, 0, 1.0,
+                       true,
+                       true);  // High confidence - preconnect.
+  predictor_->origin_data_->UpdateData(www_google.host(), www_google);
+
+  prediction = base::MakeUnique<PreconnectPrediction>();
+  EXPECT_TRUE(
+      predictor_->PredictPreconnectOrigins(main_frame_url, prediction.get()));
+  EXPECT_EQ(*prediction, CreatePreconnectPrediction("www.google.com", true,
+                                                    {GURL(gen_origin(4))},
+                                                    std::vector<GURL>()));
+}
+
 TEST_F(ResourcePrefetchPredictorTest, TestRecordFirstContentfulPaint) {
   auto res1_time = base::TimeTicks::FromInternalValue(1);
   auto res2_time = base::TimeTicks::FromInternalValue(2);
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc
index aad28a88..8bcdac8 100644
--- a/chrome/browser/safe_browsing/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -56,11 +56,11 @@
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/common/safebrowsing_switches.h"
+#include "components/safe_browsing/common/utils.h"
 #include "components/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_item.h"
 #include "content/public/browser/page_navigator.h"
-#include "crypto/sha2.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/escape.h"
 #include "net/base/load_flags.h"
@@ -107,15 +107,6 @@
                             WHITELIST_TYPE_MAX);
 }
 
-}  // namespace
-
-const char DownloadProtectionService::kDownloadRequestUrl[] =
-    "https://sb-ssl.google.com/safebrowsing/clientreport/download";
-
-const void* const DownloadProtectionService::kDownloadPingTokenKey
-    = &kDownloadPingTokenKey;
-
-namespace {
 void RecordFileExtensionType(const std::string& metric_name,
                              const base::FilePath& file) {
   UMA_HISTOGRAM_SPARSE_SLOWLY(
@@ -166,6 +157,12 @@
 
 }  // namespace
 
+const char DownloadProtectionService::kDownloadRequestUrl[] =
+    "https://sb-ssl.google.com/safebrowsing/clientreport/download";
+
+const void* const DownloadProtectionService::kDownloadPingTokenKey =
+    &kDownloadPingTokenKey;
+
 // SafeBrowsing::Client class used to lookup the bad binary URL list.
 
 class DownloadUrlSBClient
@@ -954,16 +951,7 @@
     if (type_ == ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE)
       return url.GetOrigin().spec();
 
-    std::string spec = url.spec();
-    if (url.SchemeIs(url::kDataScheme)) {
-      size_t comma_pos = spec.find(',');
-      if (comma_pos != std::string::npos && comma_pos != spec.size() - 1) {
-        std::string hash_value = crypto::SHA256HashString(spec);
-        spec.erase(comma_pos + 1);
-        spec += base::HexEncode(hash_value.data(), hash_value.size());
-      }
-    }
-    return spec;
+    return ShortURLForReporting(url);
   }
 
   void SendRequest() {
@@ -1034,6 +1022,9 @@
         !referrer_chain_data->GetReferrerChain()->empty()) {
       request.mutable_referrer_chain()->Swap(
           referrer_chain_data->GetReferrerChain());
+      if (type_ == ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE)
+        SafeBrowsingNavigationObserverManager::SanitizeReferrerChain(
+            request.mutable_referrer_chain());
     }
 
     if (archive_is_valid_ != ArchiveValid::UNSET)
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
index 816f7ea..346b630a 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
@@ -42,6 +42,7 @@
     "navigation_observer_multi_frame_tests.html";
 const char kRedirectURL[] =
     "/safe_browsing/download_protection/navigation_observer/redirect.html";
+// Please update |kShortDataURL| too if you're changing |kDownloadDataURL|.
 const char kDownloadDataURL[] =
     "data:application/octet-stream;base64,a2poYWxrc2hkbGtoYXNka2xoYXNsa2RoYWxra"
     "GtoYWxza2hka2xzamFoZGxramhhc2xka2hhc2xrZGgKYXNrZGpoa2FzZGpoYWtzaGRrYXNoZGt"
@@ -49,6 +50,14 @@
     "mFoZGtoYXNrZGhhc2tka2hrYXNkCjg3MzQ2ODEyNzQ2OGtqc2hka2FoZHNrZGhraApha3NqZGt"
     "hc2Roa3NkaGthc2hka2FzaGtkaAohISomXkAqJl4qYWhpZGFzeWRpeWlhc1xcb1wKa2Fqc2Roa"
     "2FzaGRrYXNoZGsKYWtzamRoc2tkaAplbmQK";
+// Short data url is computed by keeping the prefix of |kDownloadDataURL| up to
+// the first ",", and appending the hash (SHA256) of entire |kDownloadDataURL|.
+// e.g.,
+// $echo -n <kDownloadDataURL> | sha256sum |
+//  awk '{print "data:application/octet-stream;base64,"toupper($1)}'
+const char kShortDataURL[] =
+    "data:application/octet-stream;base64,4A19A03B1EF9D2C3061C5B87BF7D0BE05998D"
+    "A5F6BA693B6759B47EEA211D246";
 const char kIframeDirectDownloadURL[] =
     "/safe_browsing/download_protection/navigation_observer/iframe.html";
 const char kIframeRetargetingURL[] =
@@ -1077,6 +1086,7 @@
   GURL initial_url = embedded_test_server()->GetURL(kSingleFrameTestURL);
   ClickTestLink("new_tab_download_with_data_url", 2, initial_url);
   GURL download_url = GURL(kDownloadDataURL);
+  GURL short_download_url = GURL(kShortDataURL);
   GURL blank_url = GURL(url::kAboutBlankURL);
   std::string test_server_ip(embedded_test_server()->host_port_pair().host());
   auto* nav_list = navigation_event_list();
@@ -1126,7 +1136,7 @@
   ReferrerChain referrer_chain;
   IdentifyReferrerChainForDownload(GetDownload(), &referrer_chain);
   ASSERT_EQ(3, referrer_chain.size());
-  VerifyReferrerChainEntry(download_url,                   // url
+  VerifyReferrerChainEntry(short_download_url,             // url
                            GURL(),                         // main_frame_url
                            ReferrerChainEntry::EVENT_URL,  // type
                            "",                             // ip_address
@@ -1974,4 +1984,38 @@
                            std::vector<GURL>(),  // server redirects
                            referrer_chain.Get(0));
 }
+
+IN_PROC_BROWSER_TEST_F(SBNavigationObserverBrowserTest,
+                       VerifySanitizeReferrerChain) {
+  GURL initial_url = embedded_test_server()->GetURL(kSingleFrameTestURL);
+  GURL test_server_origin = embedded_test_server()->base_url().GetOrigin();
+  ClickTestLink("direct_download", 1, initial_url);
+  std::string test_server_ip(embedded_test_server()->host_port_pair().host());
+  auto* nav_list = navigation_event_list();
+  ASSERT_TRUE(nav_list);
+  ASSERT_EQ(2U, nav_list->Size());
+  ReferrerChain referrer_chain;
+  IdentifyReferrerChainForDownload(GetDownload(), &referrer_chain);
+  SafeBrowsingNavigationObserverManager::SanitizeReferrerChain(&referrer_chain);
+  ASSERT_EQ(2, referrer_chain.size());
+  VerifyReferrerChainEntry(test_server_origin,             // url
+                           GURL(),                         // main_frame_url
+                           ReferrerChainEntry::EVENT_URL,  // type
+                           test_server_ip,                 // ip_address
+                           test_server_origin,             // referrer_url
+                           GURL(),               // referrer_main_frame_url
+                           false,                // is_retargeting
+                           std::vector<GURL>(),  // server redirects
+                           referrer_chain.Get(0));
+  VerifyReferrerChainEntry(test_server_origin,                // url
+                           GURL(),                            // main_frame_url
+                           ReferrerChainEntry::LANDING_PAGE,  // type
+                           test_server_ip,                    // ip_address
+                           GURL(),                            // referrer_url
+                           GURL(),               // referrer_main_frame_url
+                           false,                // is_retargeting
+                           std::vector<GURL>(),  // server redirects
+                           referrer_chain.Get(1));
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
index 54699090a..dbbbcd4 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/common/utils.h"
 #include "content/public/browser/navigation_details.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -59,6 +60,10 @@
   }
 }
 
+std::string GetOrigin(const std::string& url) {
+  return GURL(url).GetOrigin().spec();
+}
+
 }  // namespace
 
 // The expiration period of a user gesture. Any user gesture that happened 1.0
@@ -219,6 +224,38 @@
              ->navigation_observer_manager();
 }
 
+// static
+void SafeBrowsingNavigationObserverManager::SanitizeReferrerChain(
+    ReferrerChain* referrer_chain) {
+  for (int i = 0; i < referrer_chain->size(); i++) {
+    ReferrerChainEntry* entry = referrer_chain->Mutable(i);
+    ReferrerChainEntry entry_copy(*entry);
+    entry->Clear();
+    if (entry_copy.has_url())
+      entry->set_url(GetOrigin(entry_copy.url()));
+    if (entry_copy.has_main_frame_url())
+      entry->set_main_frame_url(GetOrigin(entry_copy.main_frame_url()));
+    entry->set_type(entry_copy.type());
+    for (int j = 0; j < entry_copy.ip_addresses_size(); j++)
+      entry->add_ip_addresses(entry_copy.ip_addresses(j));
+    if (entry_copy.has_referrer_url())
+      entry->set_referrer_url(GetOrigin(entry_copy.referrer_url()));
+    if (entry_copy.has_referrer_main_frame_url())
+      entry->set_referrer_main_frame_url(
+          GetOrigin(entry_copy.referrer_main_frame_url()));
+    entry->set_is_retargeting(entry_copy.is_retargeting());
+    entry->set_navigation_time_msec(entry_copy.navigation_time_msec());
+    for (int j = 0; j < entry_copy.server_redirect_chain_size(); j++) {
+      ReferrerChainEntry::ServerRedirect* server_redirect_entry =
+          entry->add_server_redirect_chain();
+      if (entry_copy.server_redirect_chain(j).has_url()) {
+        server_redirect_entry->set_url(
+            GetOrigin(entry_copy.server_redirect_chain(j).url()));
+      }
+    }
+  }
+}
+
 SafeBrowsingNavigationObserverManager::SafeBrowsingNavigationObserverManager()
     : navigation_event_list_(kNavigationRecordMaxSize) {
 
@@ -486,10 +523,11 @@
   std::unique_ptr<ReferrerChainEntry> referrer_chain_entry =
       base::MakeUnique<ReferrerChainEntry>();
   const GURL destination_url = nav_event->GetDestinationUrl();
-  referrer_chain_entry->set_url(destination_url.spec());
+  referrer_chain_entry->set_url(ShortURLForReporting(destination_url));
   if (destination_main_frame_url.is_valid() &&
       destination_url != destination_main_frame_url)
-    referrer_chain_entry->set_main_frame_url(destination_main_frame_url.spec());
+    referrer_chain_entry->set_main_frame_url(
+        ShortURLForReporting(destination_main_frame_url));
   referrer_chain_entry->set_type(type);
   auto ip_it = host_to_ip_map_.find(destination_url.host());
   if (ip_it != host_to_ip_map_.end()) {
@@ -500,12 +538,13 @@
   // Since we only track navigation to landing referrer, we will not log the
   // referrer of the landing referrer page.
   if (type != ReferrerChainEntry::LANDING_REFERRER) {
-    referrer_chain_entry->set_referrer_url(nav_event->source_url.spec());
+    referrer_chain_entry->set_referrer_url(
+        ShortURLForReporting(nav_event->source_url));
     // Only set |referrer_main_frame_url| if it is diff from |referrer_url|.
     if (nav_event->source_main_frame_url.is_valid() &&
         nav_event->source_url != nav_event->source_main_frame_url) {
       referrer_chain_entry->set_referrer_main_frame_url(
-          nav_event->source_main_frame_url.spec());
+          ShortURLForReporting(nav_event->source_main_frame_url));
     }
   }
   referrer_chain_entry->set_is_retargeting(nav_event->source_tab_id !=
@@ -517,10 +556,11 @@
     // url.
     ReferrerChainEntry::ServerRedirect* server_redirect =
         referrer_chain_entry->add_server_redirect_chain();
-    server_redirect->set_url(nav_event->original_request_url.spec());
+    server_redirect->set_url(
+        ShortURLForReporting(nav_event->original_request_url));
     for (const GURL& redirect : nav_event->server_redirect_urls) {
       server_redirect = referrer_chain_entry->add_server_redirect_chain();
-      server_redirect->set_url(redirect.spec());
+      server_redirect->set_url(ShortURLForReporting(redirect));
     }
   }
   referrer_chain->Add()->Swap(referrer_chain_entry.get());
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
index ded29a5..473cb52 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
@@ -122,6 +122,9 @@
   // initialized.
   static bool IsEnabledAndReady(Profile* profile);
 
+  // Sanitize referrer chain by only keeping origin information of all URLs.
+  static void SanitizeReferrerChain(ReferrerChain* referrer_chain);
+
   SafeBrowsingNavigationObserverManager();
 
   // Adds |nav_event| to |navigation_event_list_|. Object pointed to by
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index ce52e9bc..bcf223ed 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3332,6 +3332,10 @@
       "app_list/search/app_result.h",
       "app_list/search/app_search_provider.cc",
       "app_list/search/app_search_provider.h",
+      "app_list/search/arc/arc_playstore_search_provider.cc",
+      "app_list/search/arc/arc_playstore_search_provider.h",
+      "app_list/search/arc/arc_playstore_search_result.cc",
+      "app_list/search/arc/arc_playstore_search_result.h",
       "app_list/search/common/json_response_fetcher.cc",
       "app_list/search/common/json_response_fetcher.h",
       "app_list/search/common/url_icon_source.cc",
@@ -3413,6 +3417,8 @@
         "app_list/arc/arc_package_syncable_service.h",
         "app_list/arc/arc_package_syncable_service_factory.cc",
         "app_list/arc/arc_package_syncable_service_factory.h",
+        "app_list/arc/arc_playstore_app_context_menu.cc",
+        "app_list/arc/arc_playstore_app_context_menu.h",
         "app_list/search/arc_app_result.cc",
         "app_list/search/arc_app_result.h",
         "ash/launcher/arc_app_deferred_launcher_controller.cc",
@@ -3434,7 +3440,10 @@
         "views/arc_app_dialog_view.cc",
       ]
     }
-    deps += [ "//ui/app_list" ]
+    deps += [
+      "//ui/app_list",
+      "//ui/app_list/vector_icons",
+    ]
   } else {
     sources += [
       "app_list/app_list_service.h",
diff --git a/chrome/browser/ui/app_list/app_context_menu.h b/chrome/browser/ui/app_list/app_context_menu.h
index 24ab832..c6de4ed 100644
--- a/chrome/browser/ui/app_list/app_context_menu.h
+++ b/chrome/browser/ui/app_list/app_context_menu.h
@@ -31,6 +31,7 @@
     REMOVE_FROM_FOLDER,
     MENU_NEW_WINDOW,
     MENU_NEW_INCOGNITO_WINDOW,
+    INSTALL,
     // Order matters in USE_LAUNCH_TYPE_* and must match the LaunchType enum.
     USE_LAUNCH_TYPE_COMMAND_START = 200,
     USE_LAUNCH_TYPE_PINNED = USE_LAUNCH_TYPE_COMMAND_START,
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
index e190e603..4da996d 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
@@ -777,9 +777,8 @@
 }
 
 void ArcAppListPrefs::OnInstanceReady() {
-  // Init() is also available at version 0.
   arc::mojom::AppInstance* app_instance =
-      ARC_GET_INSTANCE_FOR_METHOD(app_instance_holder_, RefreshAppList);
+      ARC_GET_INSTANCE_FOR_METHOD(app_instance_holder_, Init);
 
   // Note, sync_service_ may be nullptr in testing.
   sync_service_ = arc::ArcPackageSyncableService::Get(profile_);
@@ -793,7 +792,6 @@
   arc::mojom::AppHostPtr host_proxy;
   binding_.Bind(mojo::MakeRequest(&host_proxy));
   app_instance->Init(std::move(host_proxy));
-  app_instance->RefreshAppList();
 }
 
 void ArcAppListPrefs::OnInstanceClosed() {
@@ -1397,11 +1395,6 @@
       RemovePackageFromPrefs(prefs_, package_name);
   }
 
-  // TODO(lgcheng@) File http://b/31944261. Remove the flag after Android side
-  // cleanup.
-  if (package_list_initial_refreshed_)
-    return;
-
   package_list_initial_refreshed_ = true;
   for (auto& observer : observer_list_)
     observer.OnPackageListInitialRefreshed();
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h
index f9efdec..d849fb5c7 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h
+++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h
@@ -439,7 +439,7 @@
   bool is_initialized_ = false;
   // True if apps were restored.
   bool apps_restored_ = false;
-  // True is ARC package list has been refreshed once.
+  // True is ARC package list has been successfully refreshed.
   bool package_list_initial_refreshed_ = false;
   // Play Store does not have publicly available observers for default app
   // installations. This timeout is for validating default app availability.
diff --git a/chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.cc b/chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.cc
new file mode 100644
index 0000000..4a92b361
--- /dev/null
+++ b/chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.cc
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.h"
+
+#include <string>
+
+#include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
+#include "chrome/grit/generated_resources.h"
+
+ArcPlayStoreAppContextMenu::ArcPlayStoreAppContextMenu(
+    app_list::AppContextMenuDelegate* delegate,
+    Profile* profile,
+    AppListControllerDelegate* controller)
+    : app_list::AppContextMenu(delegate, profile, std::string(), controller) {}
+
+ArcPlayStoreAppContextMenu::~ArcPlayStoreAppContextMenu() = default;
+
+void ArcPlayStoreAppContextMenu::BuildMenu(ui::SimpleMenuModel* menu_model) {
+  // App Info item.
+  menu_model->AddItemWithStringId(INSTALL, IDS_APP_CONTEXT_MENU_INSTALL_ARC);
+}
+
+bool ArcPlayStoreAppContextMenu::IsCommandIdEnabled(int command_id) const {
+  switch (command_id) {
+    case INSTALL:
+      return true;
+    default:
+      return app_list::AppContextMenu::IsCommandIdEnabled(command_id);
+  }
+}
+
+void ArcPlayStoreAppContextMenu::ExecuteCommand(int command_id,
+                                                int event_flags) {
+  switch (command_id) {
+    case INSTALL:
+      delegate()->ExecuteLaunchCommand(event_flags);
+      break;
+    default:
+      app_list::AppContextMenu::ExecuteCommand(command_id, event_flags);
+  }
+}
diff --git a/chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.h b/chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.h
new file mode 100644
index 0000000..78ee7899
--- /dev/null
+++ b/chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.h
@@ -0,0 +1,35 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_APP_LIST_ARC_ARC_PLAYSTORE_APP_CONTEXT_MENU_H_
+#define CHROME_BROWSER_UI_APP_LIST_ARC_ARC_PLAYSTORE_APP_CONTEXT_MENU_H_
+
+#include "chrome/browser/ui/app_list/app_context_menu.h"
+
+class AppListControllerDelegate;
+class Profile;
+
+namespace app_list {
+class AppContextMenuDelegate;
+}
+
+class ArcPlayStoreAppContextMenu : public app_list::AppContextMenu {
+ public:
+  ArcPlayStoreAppContextMenu(app_list::AppContextMenuDelegate* delegate,
+                             Profile* profile,
+                             AppListControllerDelegate* controller);
+  ~ArcPlayStoreAppContextMenu() override;
+
+  // AppListContextMenu overrides:
+  void BuildMenu(ui::SimpleMenuModel* menu_model) override;
+
+  // ui::SimpleMenuModel::Delegate overrides:
+  void ExecuteCommand(int command_id, int event_flags) override;
+  bool IsCommandIdEnabled(int command_id) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ArcPlayStoreAppContextMenu);
+};
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_ARC_ARC_PLAYSTORE_APP_CONTEXT_MENU_H_
diff --git a/chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.cc b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.cc
new file mode 100644
index 0000000..0c3c0275
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.h"
+
+#include <utility>
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
+#include "chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.h"
+#include "components/arc/arc_bridge_service.h"
+#include "components/arc/arc_service_manager.h"
+
+namespace app_list {
+
+ArcPlayStoreSearchProvider::ArcPlayStoreSearchProvider(
+    int max_results,
+    Profile* profile,
+    AppListControllerDelegate* list_controller)
+    : max_results_(max_results),
+      profile_(profile),
+      list_controller_(list_controller),
+      weak_ptr_factory_(this) {}
+
+ArcPlayStoreSearchProvider::~ArcPlayStoreSearchProvider() = default;
+
+void ArcPlayStoreSearchProvider::Start(bool is_voice_query,
+                                       const base::string16& query) {
+  // TODO(crbug.com/736027): should search for voice?
+  arc::mojom::AppInstance* app_instance =
+      arc::ArcServiceManager::Get()
+          ? ARC_GET_INSTANCE_FOR_METHOD(
+                arc::ArcServiceManager::Get()->arc_bridge_service()->app(),
+                GetRecentAndSuggestedAppsFromPlayStore)
+          : nullptr;
+
+  if (app_instance == nullptr)
+    return;
+
+  ClearResults();
+  app_instance->GetRecentAndSuggestedAppsFromPlayStore(
+      UTF16ToUTF8(query), max_results_,
+      base::Bind(&ArcPlayStoreSearchProvider::OnResults,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ArcPlayStoreSearchProvider::Stop() {}
+
+void ArcPlayStoreSearchProvider::OnResults(
+    std::vector<arc::mojom::AppDiscoveryResultPtr> results) {
+  for (auto& result : results) {
+    Add(base::MakeUnique<ArcPlayStoreSearchResult>(std::move(result), profile_,
+                                                   list_controller_));
+  }
+}
+
+}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.h b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.h
new file mode 100644
index 0000000..40ca9ee5
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.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 CHROME_BROWSER_UI_APP_LIST_SEARCH_ARC_ARC_PLAYSTORE_SEARCH_PROVIDER_H_
+#define CHROME_BROWSER_UI_APP_LIST_SEARCH_ARC_ARC_PLAYSTORE_SEARCH_PROVIDER_H_
+
+#include <memory>
+#include <vector>
+
+#include "components/arc/common/app.mojom.h"
+#include "ui/app_list/search_provider.h"
+
+class Profile;
+class AppListControllerDelegate;
+
+namespace app_list {
+
+class ArcPlayStoreSearchProvider : public SearchProvider {
+ public:
+  ArcPlayStoreSearchProvider(int max_results,
+                             Profile* profile,
+                             AppListControllerDelegate* list_controller);
+  ~ArcPlayStoreSearchProvider() override;
+
+ protected:
+  // app_list::SearchProvider overrides:
+  void Start(bool is_voice_query, const base::string16& query) override;
+  void Stop() override;
+
+ private:
+  void OnResults(std::vector<arc::mojom::AppDiscoveryResultPtr> results);
+
+  const int max_results_;
+  // |profile_| is owned by ProfileInfo.
+  Profile* const profile_;
+  // list_controller_ is owned by AppListServiceAsh and lives
+  // until the service finishes.
+  AppListControllerDelegate* const list_controller_;
+  base::WeakPtrFactory<ArcPlayStoreSearchProvider> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArcPlayStoreSearchProvider);
+};
+
+}  // namespace app_list
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_ARC_ARC_PLAYSTORE_SEARCH_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.cc b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.cc
new file mode 100644
index 0000000..98013dd
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.cc
@@ -0,0 +1,194 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.h"
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/image_decoder.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/app_list/arc/arc_playstore_app_context_menu.h"
+#include "chrome/grit/component_extension_resources.h"
+#include "components/arc/arc_bridge_service.h"
+#include "components/arc/arc_service_manager.h"
+#include "components/arc/common/app.mojom.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/app_list/app_list_constants.h"
+#include "ui/app_list/vector_icons/vector_icons.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "ui/gfx/image/image_skia_source.h"
+#include "ui/gfx/paint_vector_icon.h"
+
+using content::BrowserThread;
+
+namespace {
+// The id prefix to identify a Play Store search result.
+constexpr char kPlayAppPrefix[] = "play://";
+// Badge icon color, #000 at 54% opacity.
+constexpr SkColor kBadgeColor = SkColorSetARGBMacro(0x8A, 0x00, 0x00, 0x00);
+}  // namespace
+
+namespace app_list {
+
+////////////////////////////////////////////////////////////////////////////////
+// IconSource
+
+class IconSource : public gfx::ImageSkiaSource {
+ public:
+  IconSource(const SkBitmap& decoded_bitmap, int resource_size_in_dip);
+  explicit IconSource(int resource_size_in_dip);
+  ~IconSource() override = default;
+
+  void SetDecodedImage(const SkBitmap& decoded_bitmap);
+
+ private:
+  gfx::ImageSkiaRep GetImageForScale(float scale) override;
+
+  const int resource_size_in_dip_;
+  gfx::ImageSkia decoded_icon_;
+
+  DISALLOW_COPY_AND_ASSIGN(IconSource);
+};
+
+IconSource::IconSource(int resource_size_in_dip)
+    : resource_size_in_dip_(resource_size_in_dip) {}
+
+void IconSource::SetDecodedImage(const SkBitmap& decoded_bitmap) {
+  decoded_icon_.AddRepresentation(gfx::ImageSkiaRep(
+      decoded_bitmap, ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P)));
+}
+
+gfx::ImageSkiaRep IconSource::GetImageForScale(float scale) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  // We use the icon if it was decoded successfully, otherwise use the default
+  // ARC icon.
+  const gfx::ImageSkia* icon_to_scale;
+  if (decoded_icon_.isNull()) {
+    int resource_id =
+        scale >= 1.5f ? IDR_ARC_SUPPORT_ICON_96 : IDR_ARC_SUPPORT_ICON_48;
+    icon_to_scale =
+        ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
+  } else {
+    icon_to_scale = &decoded_icon_;
+  }
+  DCHECK(icon_to_scale);
+
+  gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage(
+      *icon_to_scale, skia::ImageOperations::RESIZE_BEST,
+      gfx::Size(resource_size_in_dip_, resource_size_in_dip_));
+  return resized_image.GetRepresentation(scale);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IconDecodeRequest
+
+class ArcPlayStoreSearchResult::IconDecodeRequest
+    : public ImageDecoder::ImageRequest {
+ public:
+  explicit IconDecodeRequest(ArcPlayStoreSearchResult* search_result)
+      : search_result_(search_result) {}
+  ~IconDecodeRequest() override = default;
+
+  // ImageDecoder::ImageRequest overrides.
+  void OnImageDecoded(const SkBitmap& bitmap) override {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+    const gfx::Size resource_size(app_list::kGridIconDimension,
+                                  app_list::kGridIconDimension);
+    IconSource* icon_source = new IconSource(app_list::kGridIconDimension);
+    icon_source->SetDecodedImage(bitmap);
+    const gfx::ImageSkia icon = gfx::ImageSkia(icon_source, resource_size);
+    icon.EnsureRepsForSupportedScales();
+
+    search_result_->SetIcon(icon);
+  }
+
+  void OnDecodeImageFailed() override {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+    DLOG(ERROR) << "Failed to decode an app icon image.";
+
+    const gfx::Size resource_size(app_list::kGridIconDimension,
+                                  app_list::kGridIconDimension);
+    IconSource* icon_source = new IconSource(app_list::kGridIconDimension);
+    const gfx::ImageSkia icon = gfx::ImageSkia(icon_source, resource_size);
+    icon.EnsureRepsForSupportedScales();
+
+    search_result_->SetIcon(icon);
+  }
+
+ private:
+  // ArcPlayStoreSearchResult owns IconDecodeRequest, so it will outlive this.
+  ArcPlayStoreSearchResult* const search_result_;
+
+  DISALLOW_COPY_AND_ASSIGN(IconDecodeRequest);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// ArcPlayStoreSearchResult
+
+ArcPlayStoreSearchResult::ArcPlayStoreSearchResult(
+    arc::mojom::AppDiscoveryResultPtr data,
+    Profile* profile,
+    AppListControllerDelegate* list_controller)
+    : data_(std::move(data)),
+      profile_(profile),
+      list_controller_(list_controller) {
+  set_title(base::UTF8ToUTF16(label().value()));
+  set_id(kPlayAppPrefix +
+         crx_file::id_util::GenerateId(install_intent_uri().value()));
+  set_display_type(DISPLAY_TILE);
+  SetBadgeIcon(gfx::CreateVectorIcon(
+      is_instant_app() ? kIcBadgeInstantIcon : kIcBadgePlayIcon,
+      kAppBadgeIconSize, kBadgeColor));
+  SetFormattedPrice(base::UTF8ToUTF16(formatted_price().value()));
+  SetRating(review_score());
+  set_result_type(is_instant_app() ? RESULT_INSTANT_APP : RESULT_PLAYSTORE_APP);
+
+  icon_decode_request_ = base::MakeUnique<IconDecodeRequest>(this);
+  ImageDecoder::StartWithOptions(icon_decode_request_.get(), icon_png_data(),
+                                 ImageDecoder::DEFAULT_CODEC, true,
+                                 gfx::Size());
+}
+
+ArcPlayStoreSearchResult::~ArcPlayStoreSearchResult() = default;
+
+std::unique_ptr<SearchResult> ArcPlayStoreSearchResult::Duplicate() const {
+  std::unique_ptr<ArcPlayStoreSearchResult> result =
+      base::MakeUnique<ArcPlayStoreSearchResult>(data_.Clone(), profile_,
+                                                 list_controller_);
+  result->SetIcon(icon());
+  return result;
+}
+
+void ArcPlayStoreSearchResult::Open(int event_flags) {
+  arc::mojom::AppInstance* app_instance =
+      arc::ArcServiceManager::Get()
+          ? ARC_GET_INSTANCE_FOR_METHOD(
+                arc::ArcServiceManager::Get()->arc_bridge_service()->app(),
+                LaunchIntent)
+          : nullptr;
+  if (app_instance == nullptr)
+    return;
+
+  app_instance->LaunchIntent(install_intent_uri().value(), base::nullopt);
+}
+
+ui::MenuModel* ArcPlayStoreSearchResult::GetContextMenuModel() {
+  context_menu_ = base::MakeUnique<ArcPlayStoreAppContextMenu>(
+      this, profile_, list_controller_);
+  return context_menu_->GetMenuModel();
+}
+
+void ArcPlayStoreSearchResult::ExecuteLaunchCommand(int event_flags) {
+  Open(event_flags);
+}
+
+}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.h b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.h
new file mode 100644
index 0000000..244efdc
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/arc/arc_playstore_search_result.h
@@ -0,0 +1,70 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_ARC_ARC_PLAYSTORE_SEARCH_RESULT_H_
+#define CHROME_BROWSER_UI_APP_LIST_SEARCH_ARC_ARC_PLAYSTORE_SEARCH_RESULT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/optional.h"
+#include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
+#include "components/arc/common/app.mojom.h"
+#include "ui/app_list/search_result.h"
+
+class AppListControllerDelegate;
+class ArcPlayStoreAppContextMenu;
+class Profile;
+
+namespace app_list {
+
+class ArcPlayStoreSearchResult : public SearchResult,
+                                 public AppContextMenuDelegate {
+ public:
+  ArcPlayStoreSearchResult(arc::mojom::AppDiscoveryResultPtr data,
+                           Profile* profile,
+                           AppListControllerDelegate* list_controller_);
+  ~ArcPlayStoreSearchResult() override;
+
+  // app_list::SearchResult overrides:
+  std::unique_ptr<SearchResult> Duplicate() const override;
+
+  // app_list::AppContextMenuDelegate overrides:
+  ui::MenuModel* GetContextMenuModel() override;
+  void Open(int event_flags) override;
+  void ExecuteLaunchCommand(int event_flags) override;
+
+ private:
+  class IconDecodeRequest;
+
+  const base::Optional<std::string>& install_intent_uri() const {
+    return data_->install_intent_uri;
+  }
+  const base::Optional<std::string>& label() const { return data_->label; }
+  bool is_instant_app() const { return data_->is_instant_app; }
+  const base::Optional<std::string>& formatted_price() const {
+    return data_->formatted_price;
+  }
+  float review_score() const { return data_->review_score; }
+  const std::vector<uint8_t>& icon_png_data() const {
+    return data_->icon_png_data;
+  }
+
+  arc::mojom::AppDiscoveryResultPtr data_;
+  std::unique_ptr<IconDecodeRequest> icon_decode_request_;
+
+  // |profile_| is owned by ProfileInfo.
+  Profile* const profile_;
+  // |list_controller_| is owned by AppListServiceAsh and lives
+  // until the service finishes.
+  AppListControllerDelegate* const list_controller_;
+  std::unique_ptr<ArcPlayStoreAppContextMenu> context_menu_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArcPlayStoreSearchResult);
+};
+
+}  // namespace app_list
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_ARC_ARC_PLAYSTORE_SEARCH_RESULT_H_
diff --git a/chrome/browser/ui/app_list/search/arc_app_result.cc b/chrome/browser/ui/app_list/search/arc_app_result.cc
index a0a6a3e5..e1ed3cc6 100644
--- a/chrome/browser/ui/app_list/search/arc_app_result.cc
+++ b/chrome/browser/ui/app_list/search/arc_app_result.cc
@@ -66,8 +66,10 @@
 }
 
 ui::MenuModel* ArcAppResult::GetContextMenuModel() {
-  context_menu_.reset(new ArcAppContextMenu(
-      this, profile(), app_id(), controller()));
+  if (!context_menu_) {
+    context_menu_.reset(
+        new ArcAppContextMenu(this, profile(), app_id(), controller()));
+  }
   return context_menu_->GetMenuModel();
 }
 
diff --git a/chrome/browser/ui/app_list/search/search_controller_factory.cc b/chrome/browser/ui/app_list/search/search_controller_factory.cc
index b0d21a3..dfa37ec6 100644
--- a/chrome/browser/ui/app_list/search/search_controller_factory.cc
+++ b/chrome/browser/ui/app_list/search/search_controller_factory.cc
@@ -28,6 +28,10 @@
 #include "ui/app_list/search/mixer.h"
 #include "ui/app_list/search_controller.h"
 
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.h"
+#endif
+
 namespace app_list {
 
 namespace {
@@ -38,6 +42,9 @@
 constexpr size_t kMaxWebstoreResults = 2;
 constexpr size_t kMaxSuggestionsResults = 6;
 constexpr size_t kMaxLauncherSearchResults = 2;
+#if defined(OS_CHROMEOS)
+constexpr size_t kMaxPlayStoreResults = 2;
+#endif
 
 // Constants related to the SuggestionsService in AppList field trial.
 constexpr char kSuggestionsProviderFieldTrialName[] =
@@ -113,6 +120,13 @@
                             base::MakeUnique<LauncherSearchProvider>(profile));
   }
 
+#if defined(OS_CHROMEOS)
+  size_t playstore_api_group_id =
+      controller->AddGroup(kMaxPlayStoreResults, 1.0);
+  controller->AddProvider(playstore_api_group_id,
+                          base::MakeUnique<ArcPlayStoreSearchProvider>(
+                              kMaxPlayStoreResults, profile, list_controller));
+#endif
   return controller;
 }
 
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc
index 7576baa5..50aeec6c 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/ui/views/harmony/chrome_layout_provider.h"
 #include "chrome/grit/generated_resources.h"
 #include "extensions/browser/api/device_permissions_manager.h"
+#include "extensions/browser/api/file_system/saved_file_entry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/api_permission.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -296,9 +297,9 @@
     apps::SavedFilesService* service = apps::SavedFilesService::Get(profile_);
     // The SavedFilesService can be null for incognito profiles.
     if (service) {
-      std::vector<apps::SavedFileEntry> retained_file_entries =
+      std::vector<extensions::SavedFileEntry> retained_file_entries =
           service->GetAllFileEntries(app_->id());
-      for (std::vector<apps::SavedFileEntry>::const_iterator it =
+      for (std::vector<extensions::SavedFileEntry>::const_iterator it =
                retained_file_entries.begin();
            it != retained_file_entries.end(); ++it) {
         retained_file_paths.push_back(it->path.LossyDisplayName());
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 29840ec..4197368 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -506,6 +506,7 @@
       "//third_party/accessibility-audit/axs_testing.js",
       "//third_party/chaijs/chai.js",
       "//third_party/mocha/mocha.js",
+      "//third_party/polymer/v1_0/components-chromium/iron-test-helpers/mock-interactions.js",
       "//third_party/pyftpdlib/",
       "//third_party/pywebsocket/",
       "//third_party/tlslite/",
diff --git a/chrome/test/DEPS b/chrome/test/DEPS
index 5123256..6072e6e1 100644
--- a/chrome/test/DEPS
+++ b/chrome/test/DEPS
@@ -28,5 +28,4 @@
   "+mojo/edk/embedder",
   "+sandbox/win/tests",
   "+third_party/ocmock",
-  "+win8/test",
 ]
diff --git a/chrome/test/data/webui/polymer_browser_test_base.js b/chrome/test/data/webui/polymer_browser_test_base.js
index 150a30b..bbbac975 100644
--- a/chrome/test/data/webui/polymer_browser_test_base.js
+++ b/chrome/test/data/webui/polymer_browser_test_base.js
@@ -40,6 +40,8 @@
     'ui/webui/resources/js/promise_resolver.js',
     'third_party/mocha/mocha.js',
     'chrome/test/data/webui/mocha_adapter.js',
+    'third_party/polymer/v1_0/components-chromium/iron-test-helpers/' +
+        'mock-interactions.js',
   ],
 
   /** @override */
@@ -76,22 +78,11 @@
       }
     };
 
-    // Import Polymer and iron-test-helpers before running tests.
+    // Import Polymer before running tests.
     suiteSetup(function() {
-      var promises = [];
       if (!window.Polymer) {
-        promises.push(
-            PolymerTest.importHtml('chrome://resources/html/polymer.html'));
+        return PolymerTest.importHtml('chrome://resources/html/polymer.html');
       }
-      if (typeof MockInteractions != 'object') {
-        // Avoid importing the HTML file because iron-test-helpers assumes it is
-        // not being imported separately alongside a vulcanized Polymer.
-        promises.push(
-            PolymerTest.loadScript(
-                'chrome://resources/polymer/v1_0/iron-test-helpers/' +
-                'mock-interactions.js'));
-      }
-      return Promise.all(promises);
     });
   },
 
diff --git a/components/arc/common/app.mojom b/components/arc/common/app.mojom
index 6348d525..b1e474b9 100644
--- a/components/arc/common/app.mojom
+++ b/components/arc/common/app.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Next MinVersion: 20
+// Next MinVersion: 21
 
 module arc.mojom;
 
@@ -75,6 +75,19 @@
   MANAGE_LINKS = 1,
 };
 
+// Describes a Play Store app discovery result.
+struct AppDiscoveryResult {
+  string? launch_intent_uri;
+  string? install_intent_uri;
+  string? label;
+  bool is_instant_app;
+  bool is_recent;
+  string? publisher_name;
+  string? formatted_price;
+  float review_score;
+  array<uint8> icon_png_data;
+};
+
 // Next method ID: 18
 interface AppHost {
   // Sends newly added ARC app to Chrome. This message is sent when ARC receives
@@ -161,7 +174,7 @@
 };
 
 // TODO(lhchavez): Migrate all request/response messages to Mojo.
-// Next method ID: 16
+// Next method ID: 17
 // Deprecated method ID: 9
 interface AppInstance {
   Init@0(AppHost host_ptr);
@@ -233,4 +246,9 @@
   // Sends a request to ARC to uninstall the given package.  Error (if ever
   // happens) is ignored, and uninstall option should appear in the UI.
   [MinVersion=2] UninstallPackage@5(string package_name);
+
+  // Starts a query for Play Store apps.
+  [MinVersion=20] GetRecentAndSuggestedAppsFromPlayStore@16(
+      string query, int32 max_results) =>
+          (array<AppDiscoveryResult> results);
 };
diff --git a/components/arc/test/fake_app_instance.cc b/components/arc/test/fake_app_instance.cc
index 0b3ecbf..83e4ee8 100644
--- a/components/arc/test/fake_app_instance.cc
+++ b/components/arc/test/fake_app_instance.cc
@@ -42,6 +42,12 @@
     : app_host_(app_host) {}
 FakeAppInstance::~FakeAppInstance() {}
 
+void FakeAppInstance::Init(mojom::AppHostPtr host_ptr) {
+  // ARC app instance calls RefreshAppList after Init() successfully. Call
+  // RefreshAppList() here to keep the same behavior.
+  RefreshAppList();
+}
+
 void FakeAppInstance::RefreshAppList() {
   ++refresh_app_list_count_;
 }
@@ -267,6 +273,13 @@
   app_host_->OnPackageAdded(std::move(arcPackageInfo));
 }
 
+void FakeAppInstance::GetRecentAndSuggestedAppsFromPlayStore(
+    const std::string& query,
+    int32_t max_results,
+    const GetRecentAndSuggestedAppsFromPlayStoreCallback& callback) {
+  callback.Run(std::vector<arc::mojom::AppDiscoveryResultPtr>());
+}
+
 void FakeAppInstance::LaunchIntent(
     const std::string& intent_uri,
     const base::Optional<gfx::Rect>& dimension_on_screen) {
diff --git a/components/arc/test/fake_app_instance.h b/components/arc/test/fake_app_instance.h
index 52e287a..ce65bce 100644
--- a/components/arc/test/fake_app_instance.h
+++ b/components/arc/test/fake_app_instance.h
@@ -79,7 +79,7 @@
   ~FakeAppInstance() override;
 
   // mojom::AppInstance overrides:
-  void Init(mojom::AppHostPtr host_ptr) override {}
+  void Init(mojom::AppHostPtr host_ptr) override;
   void RefreshAppList() override;
   void LaunchApp(const std::string& package_name,
                  const std::string& activity,
@@ -112,6 +112,10 @@
   void SetNotificationsEnabled(const std::string& package_name,
                                bool enabled) override;
   void InstallPackage(mojom::ArcPackageInfoPtr arcPackageInfo) override;
+  void GetRecentAndSuggestedAppsFromPlayStore(
+      const std::string& query,
+      int32_t max_results,
+      const GetRecentAndSuggestedAppsFromPlayStoreCallback& callback) override;
 
   // Methods to reply messages.
   void SendRefreshAppList(const std::vector<mojom::AppInfo>& apps);
diff --git a/components/navigation_interception/intercept_navigation_delegate.cc b/components/navigation_interception/intercept_navigation_delegate.cc
index b6a3b11..b8cd316 100644
--- a/components/navigation_interception/intercept_navigation_delegate.cc
+++ b/components/navigation_interception/intercept_navigation_delegate.cc
@@ -89,7 +89,7 @@
 InterceptNavigationDelegate::CreateThrottleFor(
     content::NavigationHandle* handle) {
   return base::MakeUnique<InterceptNavigationThrottle>(
-      handle, base::Bind(&CheckIfShouldIgnoreNavigationOnUIThread));
+      handle, base::Bind(&CheckIfShouldIgnoreNavigationOnUIThread), false);
 }
 
 // static
diff --git a/components/navigation_interception/intercept_navigation_throttle.cc b/components/navigation_interception/intercept_navigation_throttle.cc
index db7a1dd1..a6bbcd0 100644
--- a/components/navigation_interception/intercept_navigation_throttle.cc
+++ b/components/navigation_interception/intercept_navigation_throttle.cc
@@ -12,11 +12,38 @@
 
 namespace navigation_interception {
 
+namespace {
+
+using ChecksPerformedCallback = base::Callback<void(bool)>;
+
+// This is used to run |should_ignore_callback| if it can destroy the
+// WebContents (and the InterceptNavigationThrottle along). In that case,
+// |on_checks_performed_callback| will be a no-op.
+void RunCallback(
+    content::WebContents* web_contents,
+    const NavigationParams& navigation_params,
+    InterceptNavigationThrottle::CheckCallback should_ignore_callback,
+    ChecksPerformedCallback on_checks_performed_callback,
+    base::WeakPtr<InterceptNavigationThrottle> throttle) {
+  bool should_ignore_navigation =
+      should_ignore_callback.Run(web_contents, navigation_params);
+
+  // If the InterceptNavigationThrottle that called RunCallback is still alive
+  // after |should_ignore_callback| has run, this will run
+  // InterceptNavigationThrottle::OnAsynchronousChecksPerformed.
+  on_checks_performed_callback.Run(should_ignore_navigation);
+}
+
+}  // namespace
+
 InterceptNavigationThrottle::InterceptNavigationThrottle(
     content::NavigationHandle* navigation_handle,
-    CheckCallback should_ignore_callback)
+    CheckCallback should_ignore_callback,
+    bool run_callback_synchronously)
     : content::NavigationThrottle(navigation_handle),
-      should_ignore_callback_(should_ignore_callback) {}
+      should_ignore_callback_(should_ignore_callback),
+      run_callback_synchronously_(run_callback_synchronously),
+      weak_factory_(this) {}
 
 InterceptNavigationThrottle::~InterceptNavigationThrottle() {}
 
@@ -45,11 +72,58 @@
       navigation_handle()->GetPageTransition(), is_redirect,
       navigation_handle()->IsExternalProtocol(), true,
       navigation_handle()->GetBaseURLForDataURL());
-  bool should_ignore_navigation = should_ignore_callback_.Run(
-      navigation_handle()->GetWebContents(), navigation_params);
-  return should_ignore_navigation
-             ? content::NavigationThrottle::CANCEL_AND_IGNORE
-             : content::NavigationThrottle::PROCEED;
+
+  if (run_callback_synchronously_) {
+    bool should_ignore_navigation = should_ignore_callback_.Run(
+        navigation_handle()->GetWebContents(), navigation_params);
+    return should_ignore_navigation
+               ? content::NavigationThrottle::CANCEL_AND_IGNORE
+               : content::NavigationThrottle::PROCEED;
+  }
+
+  // When the callback can potentially destroy the WebContents, along with the
+  // NavigationHandle and this InterceptNavigationThrottle, it should be run
+  // asynchronously. This will ensure that no objects on the stack can be
+  // deleted, and that the stack does not unwind through them in a deleted
+  // state.
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::Bind(&InterceptNavigationThrottle::RunCallbackAsynchronously,
+                 weak_factory_.GetWeakPtr(), navigation_params));
+  return DEFER;
+}
+
+void InterceptNavigationThrottle::RunCallbackAsynchronously(
+    const NavigationParams& navigation_params) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  // Run the callback in a helper function as it may lead ot the destruction of
+  // this InterceptNavigationThrottle.
+  RunCallback(
+      navigation_handle()->GetWebContents(), navigation_params,
+      should_ignore_callback_,
+      base::Bind(&InterceptNavigationThrottle::OnAsynchronousChecksPerformed,
+                 weak_factory_.GetWeakPtr()),
+      weak_factory_.GetWeakPtr());
+
+  // DO NOT ADD CODE AFTER HERE: at this point the InterceptNavigationThrottle
+  // may have been destroyed by the |should_ignore_callback_|. Adding code here
+  // will cause use-after-free bugs.
+  //
+  // Code that needs to act on the result of the |should_ignore_callback_|
+  // should be put inside OnAsynchronousChecksPerformed. This function will be
+  // called after |should_ignore_callback_| has run, if this
+  // InterceptNavigationThrottle is still alive.
+}
+
+void InterceptNavigationThrottle::OnAsynchronousChecksPerformed(
+    bool should_ignore_navigation) {
+  if (should_ignore_navigation) {
+    navigation_handle()->CancelDeferredNavigation(
+        content::NavigationThrottle::CANCEL_AND_IGNORE);
+  } else {
+    navigation_handle()->Resume();
+  }
 }
 
 }  // namespace navigation_interception
diff --git a/components/navigation_interception/intercept_navigation_throttle.h b/components/navigation_interception/intercept_navigation_throttle.h
index 7607fd5f..09158b8 100644
--- a/components/navigation_interception/intercept_navigation_throttle.h
+++ b/components/navigation_interception/intercept_navigation_throttle.h
@@ -30,7 +30,8 @@
       CheckCallback;
 
   InterceptNavigationThrottle(content::NavigationHandle* navigation_handle,
-                              CheckCallback should_ignore_callback);
+                              CheckCallback should_ignore_callback,
+                              bool run_callback_synchronously);
   ~InterceptNavigationThrottle() override;
 
   // content::NavigationThrottle implementation:
@@ -41,8 +42,19 @@
  private:
   ThrottleCheckResult CheckIfShouldIgnoreNavigation(bool is_redirect);
 
+  // Called to perform the checks asynchronously
+  void RunCallbackAsynchronously(const NavigationParams& navigation_params);
+  void OnAsynchronousChecksPerformed(bool should_ignore_navigation);
+
   CheckCallback should_ignore_callback_;
 
+  // Whether the callback will be run synchronously or not. If the callback can
+  // lead to the destruction of the WebContents, this should be false.
+  // Otherwise this should be true.
+  const bool run_callback_synchronously_;
+
+  base::WeakPtrFactory<InterceptNavigationThrottle> weak_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(InterceptNavigationThrottle);
 };
 
diff --git a/components/navigation_interception/intercept_navigation_throttle_unittest.cc b/components/navigation_interception/intercept_navigation_throttle_unittest.cc
index 0b3ed14..a6d27c49 100644
--- a/components/navigation_interception/intercept_navigation_throttle_unittest.cc
+++ b/components/navigation_interception/intercept_navigation_throttle_unittest.cc
@@ -68,7 +68,8 @@
         base::MakeUnique<InterceptNavigationThrottle>(
             test_handle.get(),
             base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation,
-                       base::Unretained(mock_callback_receiver_.get()))));
+                       base::Unretained(mock_callback_receiver_.get())),
+            true));
     return test_handle->CallWillStartRequestForTesting(
         is_post, content::Referrer(), false, ui::PAGE_TRANSITION_LINK, false);
   }
@@ -81,7 +82,8 @@
         base::MakeUnique<InterceptNavigationThrottle>(
             test_handle.get(),
             base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation,
-                       base::Unretained(mock_callback_receiver_.get()))));
+                       base::Unretained(mock_callback_receiver_.get())),
+            true));
     test_handle->CallWillStartRequestForTesting(
         true, content::Referrer(), false, ui::PAGE_TRANSITION_LINK, false);
     return test_handle->CallWillRedirectRequestForTesting(GURL(kTestUrl), false,
diff --git a/components/offline_pages/core/DEPS b/components/offline_pages/core/DEPS
index 5ef8eb9f..5d8bc16 100644
--- a/components/offline_pages/core/DEPS
+++ b/components/offline_pages/core/DEPS
@@ -1,8 +1,4 @@
 include_rules = [
   "+components/keyed_service",
-  "+components/gcm_driver",
-  "+components/ntp_snippets",
-  "+components/version_info",
-  "+net",
   "+sql",
 ]
diff --git a/components/offline_pages/core/background/DEPS b/components/offline_pages/core/background/DEPS
index 6fff87d..5f8ec85 100644
--- a/components/offline_pages/core/background/DEPS
+++ b/components/offline_pages/core/background/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+sql",
+  "+net",
 ]
diff --git a/components/offline_pages/core/prefetch/DEPS b/components/offline_pages/core/prefetch/DEPS
index 9a26362..4a85e6c 100644
--- a/components/offline_pages/core/prefetch/DEPS
+++ b/components/offline_pages/core/prefetch/DEPS
@@ -1,3 +1,7 @@
 include_rules = [
   "+google_apis",
+  "+components/gcm_driver",
+  "+components/ntp_snippets",
+  "+components/version_info",
+  "+net",
 ]
diff --git a/components/safe_browsing/common/BUILD.gn b/components/safe_browsing/common/BUILD.gn
index 51b0570..eb5b5f9 100644
--- a/components/safe_browsing/common/BUILD.gn
+++ b/components/safe_browsing/common/BUILD.gn
@@ -14,10 +14,13 @@
     "safebrowsing_switches.cc",
     "safebrowsing_switches.h",
     "safebrowsing_types.h",
+    "utils.cc",
+    "utils.h",
   ]
 
   deps = [
     "//base",
+    "//crypto:crypto",
     "//ipc",
     "//url/ipc:url_ipc",
   ]
diff --git a/components/safe_browsing/common/DEPS b/components/safe_browsing/common/DEPS
index 61eff427..c908d82 100644
--- a/components/safe_browsing/common/DEPS
+++ b/components/safe_browsing/common/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+components/prefs",
+  "+crypto/sha2.h",
   "+ipc",
   "+url"
 ]
diff --git a/components/safe_browsing/common/utils.cc b/components/safe_browsing/common/utils.cc
new file mode 100644
index 0000000..67baa8c1
--- /dev/null
+++ b/components/safe_browsing/common/utils.cc
@@ -0,0 +1,25 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/safe_browsing/common/utils.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "crypto/sha2.h"
+
+namespace safe_browsing {
+
+std::string ShortURLForReporting(const GURL& url) {
+  std::string spec(url.spec());
+  if (url.SchemeIs(url::kDataScheme)) {
+    size_t comma_pos = spec.find(',');
+    if (comma_pos != std::string::npos && comma_pos != spec.size() - 1) {
+      std::string hash_value = crypto::SHA256HashString(spec);
+      spec.erase(comma_pos + 1);
+      spec += base::HexEncode(hash_value.data(), hash_value.size());
+    }
+  }
+  return spec;
+}
+
+}  // namespace safe_browsing
diff --git a/components/safe_browsing/common/utils.h b/components/safe_browsing/common/utils.h
new file mode 100644
index 0000000..fa3b8a7
--- /dev/null
+++ b/components/safe_browsing/common/utils.h
@@ -0,0 +1,20 @@
+// Copyrights 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.
+//
+// Safe Browsing utility functions.
+
+#ifndef COMPONENTS_SAFE_BROWSING_COMMON_UTILS_H_
+#define COMPONENTS_SAFE_BROWSING_COMMON_UTILS_H_
+
+#include "url/gurl.h"
+
+namespace safe_browsing {
+
+// Shorten URL by replacing its contents with its SHA256 hash if it has data
+// scheme.
+std::string ShortURLForReporting(const GURL& url);
+
+}  // namespace safe_browsing
+
+#endif  // COMPONENTS_SAFE_BROWSING_COMMON_UTILS_H_
diff --git a/components/safe_browsing/csd.proto b/components/safe_browsing/csd.proto
index 132372dc..0e3a5aa 100644
--- a/components/safe_browsing/csd.proto
+++ b/components/safe_browsing/csd.proto
@@ -509,6 +509,8 @@
       [deprecated = true];
 }
 
+// Please update SafeBrowsingNavigationObserverManager::SanitizeReferrerChain()
+// if you're adding more fields to this message.
 message ReferrerChainEntry {
   enum URLType {
     // URL of safe browsing events that are at the end of the referrer chain.
diff --git a/content/browser/DEPS b/content/browser/DEPS
index ed5f31a5..da28d41 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -41,7 +41,6 @@
   "+ui/aura_extra",
   "+ui/vector_icons",
   "+ui/webui",
-  "+win8/util",
 
   # In general, //content shouldn't depend on //device.
   # This is the an exception.
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 298b011..df73588 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -1405,10 +1405,13 @@
   RunHtmlTest(FILE_PATH_LITERAL("modal-dialog-in-iframe-closed.html"));
 }
 
+// Disabled because it is flaky in several platforms
+/*
 IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                        AccessibilityModalDialogInIframeOpened) {
   RunHtmlTest(FILE_PATH_LITERAL("modal-dialog-in-iframe-opened.html"));
 }
+*/
 
 IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
                        AccessibilityModalDialogStack) {
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index c130d36..95bc35f 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -262,7 +262,8 @@
     const CommonNavigationParams& common_params,
     const BeginNavigationParams& begin_params,
     int current_history_list_offset,
-    int current_history_list_length) {
+    int current_history_list_length,
+    bool override_user_agent) {
   // Only normal navigations to a different document or reloads are expected.
   // - Renderer-initiated fragment-navigations never take place in the browser,
   //   even with PlzNavigate.
@@ -274,12 +275,10 @@
          common_params.navigation_type ==
              FrameMsg_Navigate_Type::DIFFERENT_DOCUMENT);
 
-  // TODO(clamy): See how we should handle override of the user agent when the
-  // navigation may start in a renderer and commit in another one.
   // TODO(clamy): See if the navigation start time should be measured in the
   // renderer and sent to the browser instead of being measured here.
   RequestNavigationParams request_params(
-      false,                // is_overriding_user_agent
+      override_user_agent,
       std::vector<GURL>(),  // redirects
       common_params.url, common_params.method,
       false,                          // can_load_local_resources
@@ -358,13 +357,14 @@
                                 common_params_.method == "POST");
 
   // Add necessary headers that may not be present in the BeginNavigationParams.
-  std::string user_agent_override;
-  if (entry) {
+  if (entry)
     nav_entry_id_ = entry->GetUniqueID();
-    if (entry->GetIsOverridingUserAgent()) {
-      user_agent_override =
-          frame_tree_node_->navigator()->GetDelegate()->GetUserAgentOverride();
-    }
+
+  std::string user_agent_override;
+  if (request_params.is_overriding_user_agent ||
+      (entry && entry->GetIsOverridingUserAgent())) {
+    user_agent_override =
+        frame_tree_node_->navigator()->GetDelegate()->GetUserAgentOverride();
   }
 
   net::HttpRequestHeaders headers;
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index 73856c4..a4b2e01 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -98,7 +98,8 @@
       const CommonNavigationParams& common_params,
       const BeginNavigationParams& begin_params,
       int current_history_list_offset,
-      int current_history_list_length);
+      int current_history_list_length,
+      bool override_user_agent);
 
   ~NavigationRequest() override;
 
diff --git a/content/browser/frame_host/navigator_impl.cc b/content/browser/frame_host/navigator_impl.cc
index f21afa5..9f9285ee 100644
--- a/content/browser/frame_host/navigator_impl.cc
+++ b/content/browser/frame_host/navigator_impl.cc
@@ -1022,11 +1022,14 @@
     navigation_data_.reset();
   }
   NavigationEntryImpl* pending_entry = controller_->GetPendingEntry();
+  NavigationEntryImpl* current_entry = controller_->GetLastCommittedEntry();
+  bool override_user_agent =
+      current_entry ? current_entry->GetIsOverridingUserAgent() : false;
   frame_tree_node->CreatedNavigationRequest(
       NavigationRequest::CreateRendererInitiated(
           frame_tree_node, pending_entry, common_params, begin_params,
           controller_->GetLastCommittedEntryIndex(),
-          controller_->GetEntryCount()));
+          controller_->GetEntryCount(), override_user_agent));
   NavigationRequest* navigation_request = frame_tree_node->navigation_request();
 
   // For main frames, NavigationHandle will be created after the call to
diff --git a/content/browser/renderer_host/input/legacy_input_router_impl_unittest.cc b/content/browser/renderer_host/input/legacy_input_router_impl_unittest.cc
index 10fc3ff..178797d1 100644
--- a/content/browser/renderer_host/input/legacy_input_router_impl_unittest.cc
+++ b/content/browser/renderer_host/input/legacy_input_router_impl_unittest.cc
@@ -149,31 +149,55 @@
 }
 #endif  // defined(USE_AURA)
 
+enum WheelScrollingMode {
+  kWheelScrollingModeNone,
+  kWheelScrollLatching,
+  kAsyncWheelEvents,
+};
+
 }  // namespace
 
 class LegacyInputRouterImplTest : public testing::Test {
  public:
-  LegacyInputRouterImplTest(bool raf_aligned_touch = true,
-                            bool wheel_scroll_latching = true)
-      : scoped_task_environment_(
+  LegacyInputRouterImplTest(
+      bool raf_aligned_touch = true,
+      WheelScrollingMode wheel_scrolling_mode = kWheelScrollLatching)
+      : wheel_scroll_latching_enabled_(wheel_scrolling_mode !=
+                                       kWheelScrollingModeNone),
+        scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::UI) {
-    if (raf_aligned_touch && wheel_scroll_latching) {
+    if (raf_aligned_touch && wheel_scrolling_mode == kAsyncWheelEvents) {
+      feature_list_.InitWithFeatures({features::kRafAlignedTouchInputEvents,
+                                      features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents},
+                                     {});
+    } else if (raf_aligned_touch &&
+               wheel_scrolling_mode == kWheelScrollLatching) {
       feature_list_.InitWithFeatures(
           {features::kRafAlignedTouchInputEvents,
            features::kTouchpadAndWheelScrollLatching},
-          {});
-    } else if (raf_aligned_touch && !wheel_scroll_latching) {
-      feature_list_.InitWithFeatures(
-          {features::kRafAlignedTouchInputEvents},
-          {features::kTouchpadAndWheelScrollLatching});
-    } else if (!raf_aligned_touch && wheel_scroll_latching) {
+          {features::kAsyncWheelEvents});
+    } else if (raf_aligned_touch &&
+               wheel_scrolling_mode == kWheelScrollingModeNone) {
+      feature_list_.InitWithFeatures({features::kRafAlignedTouchInputEvents},
+                                     {features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents});
+    } else if (!raf_aligned_touch &&
+               wheel_scrolling_mode == kAsyncWheelEvents) {
+      feature_list_.InitWithFeatures({features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents},
+                                     {features::kRafAlignedTouchInputEvents});
+    } else if (!raf_aligned_touch &&
+               wheel_scrolling_mode == kWheelScrollLatching) {
       feature_list_.InitWithFeatures(
           {features::kTouchpadAndWheelScrollLatching},
-          {features::kRafAlignedTouchInputEvents});
-    } else {  // !raf_aligned_touch && !wheel_scroll_latching
-      feature_list_.InitWithFeatures(
-          {}, {features::kRafAlignedTouchInputEvents,
-               features::kTouchpadAndWheelScrollLatching});
+          {features::kRafAlignedTouchInputEvents, features::kAsyncWheelEvents});
+    } else {  // !raf_aligned_touch && wheel_scroll_latching ==
+              // kWheelScrollingModeNone.
+      feature_list_.InitWithFeatures({},
+                                     {features::kRafAlignedTouchInputEvents,
+                                      features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents});
     }
   }
 
@@ -248,6 +272,21 @@
     input_router_->SendWheelEvent(MouseWheelEventWithLatencyInfo(wheel_event));
   }
 
+  void SimulateWheelEventPossiblyIncludingPhase(
+      bool ignore_phase,
+      float x,
+      float y,
+      float dX,
+      float dY,
+      int modifiers,
+      bool precise,
+      WebMouseWheelEvent::Phase phase) {
+    if (ignore_phase)
+      SimulateWheelEvent(x, y, dX, dY, modifiers, precise);
+    else
+      SimulateWheelEventWithPhase(x, y, dX, dY, modifiers, precise, phase);
+  }
+
   void SimulateMouseEvent(WebInputEvent::Type type, int x, int y) {
     input_router_->SendMouseEvent(MouseEventWithLatencyInfo(
         SyntheticWebMouseEventBuilder::Build(type, x, y, 0)));
@@ -379,91 +418,16 @@
     base::RunLoop().Run();
   }
 
-  void UnhandledWheelEvent(bool wheel_scroll_latching_enabled) {
-    // Simulate wheel events.
-    if (wheel_scroll_latching_enabled) {
-      SimulateWheelEventWithPhase(
-          0, 0, 0, -5, 0, false,
-          WebMouseWheelEvent::kPhaseBegan);  // sent directly
-      SimulateWheelEventWithPhase(
-          0, 0, 0, -10, 0, false,
-          WebMouseWheelEvent::kPhaseChanged);  // enqueued
-    } else {
-      SimulateWheelEvent(0, 0, 0, -5, 0, false);   // sent directly
-      SimulateWheelEvent(0, 0, 0, -10, 0, false);  // enqueued
-    }
+  void UnhandledWheelEvent();
 
-    // Check that only the first event was sent.
-    EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
-        InputMsg_HandleInputEvent::ID));
-    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-
-    // Indicate that the wheel event was unhandled.
-    SendInputEventACK(WebInputEvent::kMouseWheel,
-                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-
-    // Check that the ack for the MouseWheel and ScrollBegin
-    // were processed.
-    EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
-
-    // There should be a ScrollBegin and ScrollUpdate, MouseWheel sent.
-    EXPECT_EQ(3U, GetSentMessageCountAndResetSink());
-
-    EXPECT_EQ(ack_handler_->acked_wheel_event().delta_y, -5);
-    SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
-                      INPUT_EVENT_ACK_STATE_CONSUMED);
-
-    if (wheel_scroll_latching_enabled) {
-      // Check that the ack for ScrollUpdate were processed.
-      EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
-    } else {
-      // The GestureScrollUpdate ACK releases the GestureScrollEnd.
-      EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-
-      // Check that the ack for the ScrollUpdate and ScrollEnd
-      // were processed.
-      EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
-    }
-
-    SendInputEventACK(WebInputEvent::kMouseWheel,
-                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-
-    if (wheel_scroll_latching_enabled) {
-      // There should be a ScrollUpdate sent.
-      EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-      EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
-    } else {
-      // There should be a ScrollBegin and ScrollUpdate sent.
-      EXPECT_EQ(2U, GetSentMessageCountAndResetSink());
-      EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
-    }
-
-    // Check that the correct unhandled wheel event was received.
-    EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
-              ack_handler_->acked_wheel_event_state());
-    EXPECT_EQ(ack_handler_->acked_wheel_event().delta_y, -10);
-
-    SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
-                      INPUT_EVENT_ACK_STATE_CONSUMED);
-
-    if (wheel_scroll_latching_enabled) {
-      // Check that the ack for ScrollUpdate were processed.
-      EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
-    } else {
-      // The GestureScrollUpdate ACK releases the GestureScrollEnd.
-      EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-
-      // Check that the ack for the ScrollUpdate and ScrollEnd
-      // were processed.
-      EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
-    }
-  }
+  void OverscrollDispatch();
 
   InputRouter::Config config_;
   std::unique_ptr<MockRenderProcessHost> process_;
   std::unique_ptr<MockInputRouterClient> client_;
   std::unique_ptr<MockInputAckHandler> ack_handler_;
   std::unique_ptr<LegacyInputRouterImpl> input_router_;
+  bool wheel_scroll_latching_enabled_;
 
  private:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
@@ -477,14 +441,21 @@
     : public LegacyInputRouterImplTest {
  public:
   LegacyInputRouterImplRafAlignedTouchDisabledTest()
-      : LegacyInputRouterImplTest(false, false) {}
+      : LegacyInputRouterImplTest(false, kWheelScrollingModeNone) {}
 };
 
 class LegacyInputRouterImplWheelScrollLatchingDisabledTest
     : public LegacyInputRouterImplTest {
  public:
   LegacyInputRouterImplWheelScrollLatchingDisabledTest()
-      : LegacyInputRouterImplTest(true, false) {}
+      : LegacyInputRouterImplTest(true, kWheelScrollingModeNone) {}
+};
+
+class LegacyInputRouterImplAsyncWheelEventEnabledTest
+    : public LegacyInputRouterImplTest {
+ public:
+  LegacyInputRouterImplAsyncWheelEventEnabledTest()
+      : LegacyInputRouterImplTest(true, kAsyncWheelEvents) {}
 };
 
 TEST_F(LegacyInputRouterImplTest, CoalescesRangeSelection) {
@@ -578,8 +549,8 @@
       new InputMsg_SelectRange(0, gfx::Point(7, 8), gfx::Point(9, 10))));
   EXPECT_EQ(0u, GetSentMessageCountAndResetSink());
 
-  // Ack the messages and verify that they're not coalesced and that they're in
-  // correct order.
+  // Ack the messages and verify that they're not coalesced and that they're
+  // in correct order.
 
   // Ack the first message.
   {
@@ -658,8 +629,8 @@
     input_router_->OnMessageReceived(*response);
   }
 
-  // Verify that the three MoveRangeSelectionExtent messages are coalesced into
-  // one message.
+  // Verify that the three MoveRangeSelectionExtent messages are coalesced
+  // into one message.
   ExpectIPCMessageWithArg1<InputMsg_MoveRangeSelectionExtent>(
       process_->sink().GetMessageAt(0), gfx::Point(9, 10));
   EXPECT_EQ(1u, GetSentMessageCountAndResetSink());
@@ -774,7 +745,7 @@
   EXPECT_TRUE(ack_handler_->unexpected_event_ack_called());
 }
 
-// Tests ported from RenderWidgetHostTest --------------------------------------
+// Tests ported from RenderWidgetHostTest -------------------------------------
 
 TEST_F(LegacyInputRouterImplTest, HandleKeyEventsWeSent) {
   // Simulate a keyboard event.
@@ -806,11 +777,21 @@
 
 TEST_F(LegacyInputRouterImplTest, CoalescesWheelEvents) {
   // Simulate wheel events.
-  SimulateWheelEvent(0, 0, 0, -5, 0, false);   // sent directly
-  SimulateWheelEvent(0, 0, 0, -10, 0, false);  // enqueued
-  SimulateWheelEvent(0, 0, 8, -6, 0, false);   // coalesced into previous event
-  SimulateWheelEvent(0, 0, 9, -7, 1, false);   // enqueued, different modifiers
-  SimulateWheelEvent(0, 0, 0, -10, 0, false);  // enqueued, different modifiers
+  SimulateWheelEventPossiblyIncludingPhase(
+      !wheel_scroll_latching_enabled_, 0, 0, 0, -5, 0, false,
+      WebMouseWheelEvent::kPhaseBegan);  // sent directly
+  SimulateWheelEventPossiblyIncludingPhase(
+      !wheel_scroll_latching_enabled_, 0, 0, 0, -10, 0, false,
+      WebMouseWheelEvent::kPhaseChanged);  // enqueued
+  SimulateWheelEventPossiblyIncludingPhase(
+      !wheel_scroll_latching_enabled_, 0, 0, 8, -6, 0, false,
+      WebMouseWheelEvent::kPhaseChanged);  // coalesced into previous event
+  SimulateWheelEventPossiblyIncludingPhase(
+      !wheel_scroll_latching_enabled_, 0, 0, 9, -7, 1, false,
+      WebMouseWheelEvent::kPhaseChanged);  // enqueued, different modifiers
+  SimulateWheelEventPossiblyIncludingPhase(
+      !wheel_scroll_latching_enabled_, 0, 0, 0, -10, 0, false,
+      WebMouseWheelEvent::kPhaseChanged);  // enqueued, different modifiers
   // Explicitly verify that PhaseEnd isn't coalesced to avoid bugs like
   // https://crbug.com/154740.
   SimulateWheelEventWithPhase(WebMouseWheelEvent::kPhaseEnded);  // enqueued
@@ -960,8 +941,8 @@
   EXPECT_EQ(0U, GetSentMessageCountAndResetSink());
 }
 
-// Tests that the touch-queue is emptied after a page stops listening for touch
-// events and the outstanding ack is received.
+// Tests that the touch-queue is emptied after a page stops listening for
+// touch events and the outstanding ack is received.
 TEST_F(LegacyInputRouterImplTest, TouchEventQueueFlush) {
   OnHasTouchEventHandlers(true);
   EXPECT_TRUE(client_->has_touch_handler());
@@ -1089,12 +1070,131 @@
 }
 #endif  // defined(USE_AURA)
 
+void LegacyInputRouterImplTest::UnhandledWheelEvent() {
+  // Simulate wheel events.
+  SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 0,
+                                           0, 0, -5, 0, false,
+                                           WebMouseWheelEvent::kPhaseBegan);
+  SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 0,
+                                           0, 0, -10, 0, false,
+                                           WebMouseWheelEvent::kPhaseChanged);
+
+  // Check that only the first event was sent.
+  EXPECT_TRUE(
+      process_->sink().GetUniqueMessageMatching(InputMsg_HandleInputEvent::ID));
+  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+
+  // Indicate that the wheel event was unhandled.
+  SendInputEventACK(WebInputEvent::kMouseWheel,
+                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // Check that the ack for the MouseWheel and ScrollBegin
+  // were processed.
+  EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
+
+  // There should be a ScrollBegin and ScrollUpdate, MouseWheel sent.
+  EXPECT_EQ(3U, GetSentMessageCountAndResetSink());
+
+  EXPECT_EQ(ack_handler_->acked_wheel_event().delta_y, -5);
+  SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
+                    INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  if (wheel_scroll_latching_enabled_) {
+    // Check that the ack for ScrollUpdate were processed.
+    EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
+  } else {
+    // The GestureScrollUpdate ACK releases the GestureScrollEnd.
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+
+    // Check that the ack for the ScrollUpdate and ScrollEnd
+    // were processed.
+    EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
+  }
+
+  SendInputEventACK(WebInputEvent::kMouseWheel,
+                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  if (wheel_scroll_latching_enabled_) {
+    // There should be a ScrollUpdate sent.
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
+  } else {
+    // There should be a ScrollBegin and ScrollUpdate sent.
+    EXPECT_EQ(2U, GetSentMessageCountAndResetSink());
+    EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
+  }
+
+  // Check that the correct unhandled wheel event was received.
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
+            ack_handler_->acked_wheel_event_state());
+  EXPECT_EQ(ack_handler_->acked_wheel_event().delta_y, -10);
+
+  SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
+                    INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  if (wheel_scroll_latching_enabled_) {
+    // Check that the ack for ScrollUpdate were processed.
+    EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
+  } else {
+    // The GestureScrollUpdate ACK releases the GestureScrollEnd.
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+
+    // Check that the ack for the ScrollUpdate and ScrollEnd
+    // were processed.
+    EXPECT_EQ(2U, ack_handler_->GetAndResetAckCount());
+  }
+}
+
 TEST_F(LegacyInputRouterImplTest, UnhandledWheelEvent) {
-  UnhandledWheelEvent(true);
+  UnhandledWheelEvent();
 }
 TEST_F(LegacyInputRouterImplWheelScrollLatchingDisabledTest,
        UnhandledWheelEvent) {
-  UnhandledWheelEvent(false);
+  UnhandledWheelEvent();
+}
+TEST_F(LegacyInputRouterImplAsyncWheelEventEnabledTest, UnhandledWheelEvent) {
+  // Simulate wheel events.
+  SimulateWheelEventWithPhase(0, 0, 0, -5, 0, false,
+                              WebMouseWheelEvent::kPhaseBegan);
+  SimulateWheelEventWithPhase(0, 0, 0, -10, 0, false,
+                              WebMouseWheelEvent::kPhaseChanged);
+
+  // Check that only the first event was sent.
+  EXPECT_TRUE(
+      process_->sink().GetUniqueMessageMatching(InputMsg_HandleInputEvent::ID));
+  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+
+  // Indicate that the wheel event was unhandled.
+  SendInputEventACK(WebInputEvent::kMouseWheel,
+                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // Check that the ack for the first MouseWheel, ScrollBegin, and the second
+  // MouseWheel were processed.
+  EXPECT_EQ(3U, ack_handler_->GetAndResetAckCount());
+
+  // There should be a ScrollBegin and ScrollUpdate, MouseWheel sent.
+  EXPECT_EQ(3U, GetSentMessageCountAndResetSink());
+
+  // The last acked wheel event should be the second one since the input router
+  // has alread sent the immediate ack for the second wheel event.
+  EXPECT_EQ(ack_handler_->acked_wheel_event().delta_y, -10);
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_IGNORED,
+            ack_handler_->acked_wheel_event_state());
+
+  SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
+                    INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // Check that the ack for the first ScrollUpdate were processed.
+  EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
+
+  // There should be a second ScrollUpdate sent.
+  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+
+  SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
+                    INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // Check that the ack for ScrollUpdate were processed.
+  EXPECT_EQ(1U, ack_handler_->GetAndResetAckCount());
 }
 
 TEST_F(LegacyInputRouterImplTest, TouchTypesIgnoringAck) {
@@ -1892,7 +1992,7 @@
 
 // Test proper routing of overscroll notifications received either from
 // event acks or from |DidOverscroll| IPC messages.
-TEST_F(LegacyInputRouterImplTest, OverscrollDispatch) {
+void LegacyInputRouterImplTest::OverscrollDispatch() {
   DidOverscrollParams overscroll;
   overscroll.accumulated_overscroll = gfx::Vector2dF(-14, 14);
   overscroll.latest_overscroll_delta = gfx::Vector2dF(-7, 0);
@@ -1912,7 +2012,9 @@
   wheel_overscroll.latest_overscroll_delta = gfx::Vector2dF(3, 0);
   wheel_overscroll.current_fling_velocity = gfx::Vector2dF(1, 0);
 
-  SimulateWheelEvent(0, 0, 3, 0, 0, false);
+  SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 0,
+                                           0, 3, 0, 0, false,
+                                           WebMouseWheelEvent::kPhaseBegan);
   InputEventAck ack(InputEventAckSource::COMPOSITOR_THREAD,
                     WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -1927,6 +2029,16 @@
   EXPECT_EQ(wheel_overscroll.current_fling_velocity,
             client_overscroll.current_fling_velocity);
 }
+TEST_F(LegacyInputRouterImplTest, OverscrollDispatch) {
+  OverscrollDispatch();
+}
+TEST_F(LegacyInputRouterImplWheelScrollLatchingDisabledTest,
+       OverscrollDispatch) {
+  OverscrollDispatch();
+}
+TEST_F(LegacyInputRouterImplAsyncWheelEventEnabledTest, OverscrollDispatch) {
+  OverscrollDispatch();
+}
 
 // Tests that touch event stream validation passes when events are filtered
 // out. See crbug.com/581231 for details.
@@ -2024,7 +2136,8 @@
 
 TEST_F(LegacyInputRouterImplScaleEventTest, ScaleMouseWheelEventTest) {
   ASSERT_EQ(0u, process_->sink().message_count());
-  SimulateWheelEvent(5, 5, 10, 10, 0, false);
+  SimulateWheelEventWithPhase(5, 5, 10, 10, 0, false,
+                              WebMouseWheelEvent::kPhaseBegan);
   ASSERT_EQ(1u, process_->sink().message_count());
 
   const WebMouseWheelEvent* sent_event =
diff --git a/content/browser/renderer_host/input/mouse_latency_browsertest.cc b/content/browser/renderer_host/input/mouse_latency_browsertest.cc
index 69fcb2d..b9c0e4bf 100644
--- a/content/browser/renderer_host/input/mouse_latency_browsertest.cc
+++ b/content/browser/renderer_host/input/mouse_latency_browsertest.cc
@@ -145,7 +145,8 @@
 // MouseDown events in the case where no swap is generated.
 // Disabled on Android because we don't support synthetic mouse input on
 // Android (crbug.com/723618).
-#if defined(OS_ANDROID)
+// Test is flaky on Linux (crbug.com/736836).
+#if defined (OS_ANDROID) || defined(OS_LINUX)
 #define MAYBE_MouseDownAndUpRecordedWithoutSwap \
   DISABLED_MouseDownAndUpRecordedWithoutSwap
 #else
diff --git a/content/browser/renderer_host/input/mouse_wheel_event_queue.cc b/content/browser/renderer_host/input/mouse_wheel_event_queue.cc
index 2652afa0..9b850fd 100644
--- a/content/browser/renderer_host/input/mouse_wheel_event_queue.cc
+++ b/content/browser/renderer_host/input/mouse_wheel_event_queue.cc
@@ -7,6 +7,8 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/trace_event.h"
+#include "content/common/input/input_event_dispatch_type.h"
+#include "content/public/common/content_features.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/blink/web_input_event_traits.h"
 
@@ -40,6 +42,9 @@
       needs_scroll_begin_(true),
       needs_scroll_end_(false),
       enable_scroll_latching_(enable_scroll_latching),
+      enable_async_wheel_events_(
+          base::FeatureList::IsEnabled(features::kAsyncWheelEvents)),
+      send_wheel_events_async_(false),
       scrolling_device_(blink::kWebGestureDeviceUninitialized) {
   DCHECK(client);
 }
@@ -176,6 +181,7 @@
     if (enable_scroll_latching_) {
       if (event_sent_for_gesture_ack_->event.phase ==
           blink::WebMouseWheelEvent::kPhaseBegan) {
+        send_wheel_events_async_ = true;
         SendScrollBegin(scroll_update, false);
       }
 
@@ -267,6 +273,20 @@
   event_sent_for_gesture_ack_ = std::move(wheel_queue_.front());
   wheel_queue_.pop_front();
 
+  if (enable_async_wheel_events_) {
+    DCHECK(event_sent_for_gesture_ack_->event.phase !=
+               blink::WebMouseWheelEvent::kPhaseNone ||
+           event_sent_for_gesture_ack_->event.momentum_phase !=
+               blink::WebMouseWheelEvent::kPhaseNone);
+    if (event_sent_for_gesture_ack_->event.phase ==
+        blink::WebMouseWheelEvent::kPhaseBegan) {
+      send_wheel_events_async_ = false;
+    } else if (send_wheel_events_async_) {
+      event_sent_for_gesture_ack_->event.dispatch_type =
+          WebInputEvent::kEventNonBlocking;
+    }
+  }
+
   client_->SendMouseWheelEventImmediately(*event_sent_for_gesture_ack_);
 }
 
diff --git a/content/browser/renderer_host/input/mouse_wheel_event_queue.h b/content/browser/renderer_host/input/mouse_wheel_event_queue.h
index d4e9c7e..b0389f95 100644
--- a/content/browser/renderer_host/input/mouse_wheel_event_queue.h
+++ b/content/browser/renderer_host/input/mouse_wheel_event_queue.h
@@ -90,9 +90,17 @@
   // GSB has been sent in the past.
   bool needs_scroll_end_;
 
-  // True if the touchpad and wheel scroll latching is enabled.
+  // True if the touchpad and wheel scroll latching flag is enabled.
   bool enable_scroll_latching_;
 
+  // True if the async wheel events flag is enabled.
+  bool enable_async_wheel_events_;
+
+  // True if the ack for the first wheel event in a scroll sequence is not
+  // consumed. This lets us to send the rest of the wheel events in the sequence
+  // as non-blocking.
+  bool send_wheel_events_async_;
+
   blink::WebGestureDevice scrolling_device_;
 
   DISALLOW_COPY_AND_ASSIGN(MouseWheelEventQueue);
diff --git a/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc b/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc
index 56a5d43..c65b4dc 100644
--- a/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc
+++ b/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc
@@ -13,10 +13,12 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/renderer_host/input/timeout_monitor.h"
 #include "content/common/input/synthetic_web_input_event_builders.h"
+#include "content/public/common/content_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/WebKit/public/platform/WebInputEvent.h"
 #include "ui/events/base_event_utils.h"
@@ -135,19 +137,43 @@
 #define EXPECT_MOUSE_WHEEL(event) \
   EXPECT_EQ(WebInputEvent::kMouseWheel, event->GetType());
 
+enum WheelScrollingMode {
+  kWheelScrollingModeNone,
+  kWheelScrollLatching,
+  kAsyncWheelEvents,
+};
+
 }  // namespace
 
-class MouseWheelEventQueueTest : public testing::TestWithParam<bool>,
-                                 public MouseWheelEventQueueClient {
+class MouseWheelEventQueueTest
+    : public testing::TestWithParam<WheelScrollingMode>,
+      public MouseWheelEventQueueClient {
  public:
   MouseWheelEventQueueTest()
       : scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::UI),
         acked_event_count_(0),
         last_acked_event_state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {
-    scroll_latching_enabled_ = GetParam();
+    scroll_latching_enabled_ = GetParam() != kWheelScrollingModeNone;
+    switch (GetParam()) {
+      case kWheelScrollingModeNone:
+        feature_list_.InitWithFeatures(
+            {}, {features::kTouchpadAndWheelScrollLatching,
+                 features::kAsyncWheelEvents});
+        break;
+      case kWheelScrollLatching:
+        feature_list_.InitWithFeatures(
+            {features::kTouchpadAndWheelScrollLatching},
+            {features::kAsyncWheelEvents});
+        break;
+      case kAsyncWheelEvents:
+        feature_list_.InitWithFeatures(
+            {features::kTouchpadAndWheelScrollLatching,
+             features::kAsyncWheelEvents},
+            {});
+    }
+
     queue_.reset(new MouseWheelEventQueue(this, scroll_latching_enabled_));
-    scroll_end_timeout_ms_ = scroll_latching_enabled_ ? 100 : 0;
   }
 
   ~MouseWheelEventQueueTest() override {}
@@ -177,10 +203,6 @@
     last_acked_event_state_ = ack_result;
   }
 
-  base::TimeDelta DefaultScrollEndTimeoutDelay() {
-    return base::TimeDelta::FromMilliseconds(scroll_end_timeout_ms_);
-  }
-
   bool scroll_latching_enabled() { return scroll_latching_enabled_; }
 
  protected:
@@ -507,21 +529,27 @@
   size_t acked_event_count_;
   InputEventAckState last_acked_event_state_;
   WebMouseWheelEvent last_acked_event_;
-  int64_t scroll_end_timeout_ms_;
   bool scroll_latching_enabled_;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 // Tests that mouse wheel events are queued properly.
 TEST_P(MouseWheelEventQueueTest, Basic) {
-  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
-                 kWheelScrollGlobalY, 1, 1, 0, false);
+  SendMouseWheelPossiblyIncludingPhase(
+      !scroll_latching_enabled_, kWheelScrollX, kWheelScrollY,
+      kWheelScrollGlobalX, kWheelScrollGlobalY, 1, 1, 0, false,
+      WebMouseWheelEvent::kPhaseBegan, WebMouseWheelEvent::kPhaseNone);
   EXPECT_EQ(0U, queued_event_count());
   EXPECT_TRUE(event_in_flight());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
 
   // The second mouse wheel should not be sent since one is already in queue.
-  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
-                 kWheelScrollGlobalY, 5, 5, 0, false);
+  SendMouseWheelPossiblyIncludingPhase(
+      !scroll_latching_enabled_, kWheelScrollX, kWheelScrollY,
+      kWheelScrollGlobalX, kWheelScrollGlobalY, 5, 5, 0, false,
+      WebMouseWheelEvent::kPhaseChanged, WebMouseWheelEvent::kPhaseNone);
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_TRUE(event_in_flight());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
@@ -784,6 +812,8 @@
 
 INSTANTIATE_TEST_CASE_P(MouseWheelEventQueueTests,
                         MouseWheelEventQueueTest,
-                        testing::Bool());
+                        testing::Values(kWheelScrollingModeNone,
+                                        kWheelScrollLatching,
+                                        kAsyncWheelEvents));
 
 }  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.cc b/content/browser/renderer_host/render_widget_host_input_event_router.cc
index c88f7d6..d537fb1 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router.cc
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.cc
@@ -790,26 +790,21 @@
     return;
   }
 
-  // We use GestureTapDown to detect the start of a gesture sequence since there
-  // is no WebGestureEvent equivalent for ET_GESTURE_BEGIN. Note that this
-  // means the GestureFlingCancel that always comes between ET_GESTURE_BEGIN and
-  // GestureTapDown is sent to the previous target, in case it is still in a
-  // fling.
-  if (event->GetType() == blink::WebInputEvent::kGestureTapDown) {
-    bool no_target = touchscreen_gesture_target_queue_.empty();
-    // This UMA metric is temporary, and will be removed once it has fulfilled
-    // it's purpose, namely telling us when the incidents of empty
-    // gesture-queues has dropped to zero. https://crbug.com/642008
-    UMA_HISTOGRAM_BOOLEAN("Event.FrameEventRouting.NoGestureTarget", no_target);
-    if (no_target) {
-      LOG(ERROR) << "Gesture sequence start detected with no target available.";
-      // Ignore this gesture sequence as no target is available.
-      // TODO(wjmaclean): this only happens on Windows, and should not happen.
-      // https://crbug.com/595422
-      touchscreen_gesture_target_.target = nullptr;
-      return;
-    }
-
+  // On Android it is possible for touchscreen gesture events to arrive that
+  // are not associated with touch events, because non-synthetic events can be
+  // created by ContentView. In that case the target queue will be empty.
+  if (touchscreen_gesture_target_queue_.empty()) {
+    gfx::Point transformed_point;
+    gfx::Point original_point(event->x, event->y);
+    touchscreen_gesture_target_.target =
+        FindEventTarget(root_view, original_point, &transformed_point);
+    touchscreen_gesture_target_.delta = transformed_point - original_point;
+  } else if (event->GetType() == blink::WebInputEvent::kGestureTapDown) {
+    // We use GestureTapDown to detect the start of a gesture sequence since
+    // there is no WebGestureEvent equivalent for ET_GESTURE_BEGIN. Note that
+    // this means the GestureFlingCancel that always comes between
+    // ET_GESTURE_BEGIN and GestureTapDown is sent to the previous target, in
+    // case it is still in a fling.
     touchscreen_gesture_target_ = touchscreen_gesture_target_queue_.front();
     touchscreen_gesture_target_queue_.pop_front();
 
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index d3a9b51d..9584fe7 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -512,15 +512,44 @@
       render_view_host_delegate_view_;
 };
 
+enum WheelScrollingMode {
+  kWheelScrollingModeNone,
+  kWheelScrollLatching,
+  kAsyncWheelEvents,
+};
+
 // RenderWidgetHostTest --------------------------------------------------------
 
 class RenderWidgetHostTest : public testing::Test {
  public:
-  RenderWidgetHostTest()
+  RenderWidgetHostTest(
+      WheelScrollingMode wheel_scrolling_mode = kWheelScrollLatching)
       : process_(NULL),
         handle_key_press_event_(false),
         handle_mouse_event_(false),
-        simulated_event_time_delta_seconds_(0) {
+        simulated_event_time_delta_seconds_(0),
+        wheel_scroll_latching_enabled_(wheel_scrolling_mode !=
+                                       kWheelScrollingModeNone) {
+    switch (wheel_scrolling_mode) {
+      case kWheelScrollingModeNone:
+        feature_list_.InitWithFeatures(
+            {features::kRafAlignedTouchInputEvents},
+            {features::kTouchpadAndWheelScrollLatching,
+             features::kAsyncWheelEvents});
+        break;
+      case kWheelScrollLatching:
+        feature_list_.InitWithFeatures(
+            {features::kRafAlignedTouchInputEvents,
+             features::kTouchpadAndWheelScrollLatching},
+            {features::kAsyncWheelEvents});
+        break;
+      case kAsyncWheelEvents:
+        feature_list_.InitWithFeatures(
+            {features::kRafAlignedTouchInputEvents,
+             features::kTouchpadAndWheelScrollLatching,
+             features::kAsyncWheelEvents},
+            {});
+    }
     last_simulated_event_time_seconds_ =
         ui::EventTimeStampToSeconds(ui::EventTimeForNow());
   }
@@ -538,9 +567,6 @@
   void SetUp() override {
     base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
     command_line->AppendSwitch(switches::kValidateInputEventStream);
-    feature_list_.InitFromCommandLine(
-        features::kRafAlignedTouchInputEvents.name, "");
-
     browser_context_.reset(new TestBrowserContext());
     delegate_.reset(new MockRenderWidgetHostDelegate());
     process_ = new RenderWidgetHostProcess(browser_context_.get());
@@ -659,6 +685,19 @@
         0, 0, dX, dY, modifiers, precise));
   }
 
+  void SimulateWheelEventPossiblyIncludingPhase(
+      float dX,
+      float dY,
+      int modifiers,
+      bool precise,
+      WebMouseWheelEvent::Phase phase) {
+    WebMouseWheelEvent wheel_event = SyntheticWebMouseWheelEventBuilder::Build(
+        0, 0, dX, dY, modifiers, precise);
+    if (wheel_scroll_latching_enabled_)
+      wheel_event.phase = phase;
+    host_->ForwardWheelEvent(wheel_event);
+  }
+
   void SimulateWheelEventWithLatencyInfo(float dX,
                                          float dY,
                                          int modifiers,
@@ -670,6 +709,20 @@
         ui_latency);
   }
 
+  void SimulateWheelEventWithLatencyInfoAndPossiblyPhase(
+      float dX,
+      float dY,
+      int modifiers,
+      bool precise,
+      const ui::LatencyInfo& ui_latency,
+      WebMouseWheelEvent::Phase phase) {
+    WebMouseWheelEvent wheel_event = SyntheticWebMouseWheelEventBuilder::Build(
+        0, 0, dX, dY, modifiers, precise);
+    if (wheel_scroll_latching_enabled_)
+      wheel_event.phase = phase;
+    host_->ForwardWheelEventWithLatencyInfo(wheel_event, ui_latency);
+  }
+
   void SimulateMouseMove(int x, int y, int modifiers) {
     SimulateMouseEvent(WebInputEvent::kMouseMove, x, y, modifiers, false);
   }
@@ -735,6 +788,10 @@
     return reinterpret_cast<const WebInputEvent*>(data);
   }
 
+  void UnhandledWheelEvent();
+  void HandleWheelEvent();
+  void InputEventRWHLatencyComponent();
+
   std::unique_ptr<TestBrowserContext> browser_context_;
   RenderWidgetHostProcess* process_;  // Deleted automatically by the widget.
   std::unique_ptr<MockRenderWidgetHostDelegate> delegate_;
@@ -748,6 +805,7 @@
   IPC::TestSink* sink_;
   std::unique_ptr<FakeRendererCompositorFrameSink>
       renderer_compositor_frame_sink_;
+  bool wheel_scroll_latching_enabled_;
 
  private:
   SyntheticWebTouchEvent touch_event_;
@@ -759,6 +817,20 @@
   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostTest);
 };
 
+class RenderWidgetHostWheelScrollLatchingDisabledTest
+    : public RenderWidgetHostTest {
+ public:
+  RenderWidgetHostWheelScrollLatchingDisabledTest()
+      : RenderWidgetHostTest(kWheelScrollingModeNone) {}
+};
+
+class RenderWidgetHostAsyncWheelEventsEnabledTest
+    : public RenderWidgetHostTest {
+ public:
+  RenderWidgetHostAsyncWheelEventsEnabledTest()
+      : RenderWidgetHostTest(kAsyncWheelEvents) {}
+};
+
 #if GTEST_HAS_PARAM_TEST
 // RenderWidgetHostWithSourceTest ----------------------------------------------
 
@@ -1153,12 +1225,13 @@
   EXPECT_EQ(WebInputEvent::kKeyUp, delegate_->unhandled_keyboard_event_type());
 }
 
-TEST_F(RenderWidgetHostTest, UnhandledWheelEvent) {
-  SimulateWheelEvent(-5, 0, 0, true);
+void RenderWidgetHostTest::UnhandledWheelEvent() {
+  SimulateWheelEventPossiblyIncludingPhase(-5, 0, 0, true,
+                                           WebMouseWheelEvent::kPhaseBegan);
 
   // Make sure we sent the input event to the renderer.
-  EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
-                  InputMsg_HandleInputEvent::ID));
+  EXPECT_TRUE(
+      process_->sink().GetUniqueMessageMatching(InputMsg_HandleInputEvent::ID));
   process_->sink().ClearMessages();
 
   // Send the simulated response from the renderer back.
@@ -1168,16 +1241,26 @@
   EXPECT_EQ(1, view_->unhandled_wheel_event_count());
   EXPECT_EQ(-5, view_->unhandled_wheel_event().delta_x);
 }
+TEST_F(RenderWidgetHostTest, UnhandledWheelEvent) {
+  UnhandledWheelEvent();
+}
+TEST_F(RenderWidgetHostWheelScrollLatchingDisabledTest, UnhandledWheelEvent) {
+  UnhandledWheelEvent();
+}
+TEST_F(RenderWidgetHostAsyncWheelEventsEnabledTest, UnhandledWheelEvent) {
+  UnhandledWheelEvent();
+}
 
-TEST_F(RenderWidgetHostTest, HandleWheelEvent) {
+void RenderWidgetHostTest::HandleWheelEvent() {
   // Indicate that we're going to handle this wheel event
   delegate_->set_handle_wheel_event(true);
 
-  SimulateWheelEvent(-5, 0, 0, true);
+  SimulateWheelEventPossiblyIncludingPhase(-5, 0, 0, true,
+                                           WebMouseWheelEvent::kPhaseBegan);
 
   // Make sure we sent the input event to the renderer.
-  EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
-                  InputMsg_HandleInputEvent::ID));
+  EXPECT_TRUE(
+      process_->sink().GetUniqueMessageMatching(InputMsg_HandleInputEvent::ID));
   process_->sink().ClearMessages();
 
   // Send the simulated response from the renderer back.
@@ -1190,6 +1273,15 @@
   // and that it suppressed the unhandled wheel event handler.
   EXPECT_EQ(0, view_->unhandled_wheel_event_count());
 }
+TEST_F(RenderWidgetHostTest, HandleWheelEvent) {
+  HandleWheelEvent();
+}
+TEST_F(RenderWidgetHostWheelScrollLatchingDisabledTest, HandleWheelEvent) {
+  HandleWheelEvent();
+}
+TEST_F(RenderWidgetHostAsyncWheelEventsEnabledTest, HandleWheelEvent) {
+  HandleWheelEvent();
+}
 
 TEST_F(RenderWidgetHostTest, UnhandledGestureEvent) {
   SimulateGestureEvent(WebInputEvent::kGestureTwoFingerTap,
@@ -1793,18 +1885,20 @@
 // or ForwardXXXEventWithLatencyInfo(), LatencyInfo component
 // ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT will always present in the
 // event's LatencyInfo.
-TEST_F(RenderWidgetHostTest, InputEventRWHLatencyComponent) {
+void RenderWidgetHostTest::InputEventRWHLatencyComponent() {
   host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true));
   process_->sink().ClearMessages();
 
   // Tests RWHI::ForwardWheelEvent().
-  SimulateWheelEvent(-5, 0, 0, true);
+  SimulateWheelEventPossiblyIncludingPhase(-5, 0, 0, true,
+                                           WebMouseWheelEvent::kPhaseBegan);
   CheckLatencyInfoComponentInMessage(process_, GetLatencyComponentId(),
                                      WebInputEvent::kMouseWheel);
   SendInputEventACK(WebInputEvent::kMouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED);
 
   // Tests RWHI::ForwardWheelEventWithLatencyInfo().
-  SimulateWheelEventWithLatencyInfo(-5, 0, 0, true, ui::LatencyInfo());
+  SimulateWheelEventWithLatencyInfoAndPossiblyPhase(
+      -5, 0, 0, true, ui::LatencyInfo(), WebMouseWheelEvent::kPhaseChanged);
   CheckLatencyInfoComponentInMessage(process_, GetLatencyComponentId(),
                                      WebInputEvent::kMouseWheel);
   SendInputEventACK(WebInputEvent::kMouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED);
@@ -1847,6 +1941,17 @@
   CheckLatencyInfoComponentInMessage(process_, GetLatencyComponentId(),
                                      WebInputEvent::kTouchStart);
 }
+TEST_F(RenderWidgetHostTest, InputEventRWHLatencyComponent) {
+  InputEventRWHLatencyComponent();
+}
+TEST_F(RenderWidgetHostWheelScrollLatchingDisabledTest,
+       InputEventRWHLatencyComponent) {
+  InputEventRWHLatencyComponent();
+}
+TEST_F(RenderWidgetHostAsyncWheelEventsEnabledTest,
+       InputEventRWHLatencyComponent) {
+  InputEventRWHLatencyComponent();
+}
 
 TEST_F(RenderWidgetHostTest, RendererExitedResetsInputRouter) {
   // RendererExited will delete the view.
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 82872c6..77970a32 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -1015,9 +1015,14 @@
     bool causes_scrolling = false;
     ui::LatencyInfo latency_info(ui::SourceEventType::TOUCH);
     latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
-    host_->ForwardTouchEventWithLatencyInfo(
-        ui::CreateWebTouchEventFromMotionEvent(*cancel_event, causes_scrolling),
-        latency_info);
+    blink::WebTouchEvent web_event =
+        ui::CreateWebTouchEventFromMotionEvent(*cancel_event, causes_scrolling);
+    if (host_->delegate()->GetInputEventRouter()) {
+      host_->delegate()->GetInputEventRouter()->RouteTouchEvent(
+          this, &web_event, latency_info);
+    } else {
+      host_->ForwardTouchEventWithLatencyInfo(web_event, latency_info);
+    }
   }
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index f915f1d6..ff342a07 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -578,6 +578,12 @@
   return reinterpret_cast<const WebInputEvent*>(data);
 }
 
+enum WheelScrollingMode {
+  kWheelScrollingModeNone,
+  kWheelScrollLatching,
+  kAsyncWheelEvents,
+};
+
 }  // namespace
 
 class RenderWidgetHostViewAuraTest : public testing::Test {
@@ -745,24 +751,41 @@
         "", features::kTouchpadAndWheelScrollLatching.name);
   }
 
-  void SetFeatureList(bool raf_aligned_touch, bool wheel_scroll_latching) {
-    if (raf_aligned_touch && wheel_scroll_latching) {
+  void SetFeatureList(
+      bool raf_aligned_touch,
+      WheelScrollingMode wheel_scrolling_mode = kWheelScrollLatching) {
+    if (raf_aligned_touch && wheel_scrolling_mode == kAsyncWheelEvents) {
+      feature_list_.InitWithFeatures({features::kRafAlignedTouchInputEvents,
+                                      features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents},
+                                     {});
+    } else if (raf_aligned_touch &&
+               wheel_scrolling_mode == kWheelScrollLatching) {
       feature_list_.InitWithFeatures(
           {features::kRafAlignedTouchInputEvents,
            features::kTouchpadAndWheelScrollLatching},
-          {});
-    } else if (raf_aligned_touch && !wheel_scroll_latching) {
-      feature_list_.InitWithFeatures(
-          {features::kRafAlignedTouchInputEvents},
-          {features::kTouchpadAndWheelScrollLatching});
-    } else if (!raf_aligned_touch && wheel_scroll_latching) {
+          {features::kAsyncWheelEvents});
+    } else if (raf_aligned_touch &&
+               wheel_scrolling_mode == kWheelScrollingModeNone) {
+      feature_list_.InitWithFeatures({features::kRafAlignedTouchInputEvents},
+                                     {features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents});
+    } else if (!raf_aligned_touch &&
+               wheel_scrolling_mode == kAsyncWheelEvents) {
+      feature_list_.InitWithFeatures({features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents},
+                                     {features::kRafAlignedTouchInputEvents});
+    } else if (!raf_aligned_touch &&
+               wheel_scrolling_mode == kWheelScrollLatching) {
       feature_list_.InitWithFeatures(
           {features::kTouchpadAndWheelScrollLatching},
-          {features::kRafAlignedTouchInputEvents});
-    } else {  // !raf_aligned_touch && !wheel_scroll_latching
-      feature_list_.InitWithFeatures(
-          {}, {features::kRafAlignedTouchInputEvents,
-               features::kTouchpadAndWheelScrollLatching});
+          {features::kRafAlignedTouchInputEvents, features::kAsyncWheelEvents});
+    } else {  // !raf_aligned_touch && wheel_scroll_latching ==
+              // kWheelScrollingModeNone.
+      feature_list_.InitWithFeatures({},
+                                     {features::kRafAlignedTouchInputEvents,
+                                      features::kTouchpadAndWheelScrollLatching,
+                                      features::kAsyncWheelEvents});
     }
   }
 
@@ -908,8 +931,10 @@
     : public RenderWidgetHostViewAuraTest {
  public:
   RenderWidgetHostViewAuraOverscrollTest(
-      bool wheel_scroll_latching_enabled = true)
-      : wheel_scroll_latching_enabled_(wheel_scroll_latching_enabled) {}
+      WheelScrollingMode wheel_scrolling_mode = kWheelScrollLatching)
+      : wheel_scroll_latching_enabled_(wheel_scrolling_mode !=
+                                       kWheelScrollingModeNone),
+        wheel_scrolling_mode_(wheel_scrolling_mode) {}
 
   // We explicitly invoke SetUp to allow gesture debounce customization.
   void SetUp() override {}
@@ -922,7 +947,7 @@
   void SetUpOverscrollEnvironment() { SetUpOverscrollEnvironmentImpl(0); }
 
   void SetUpOverscrollEnvironmentImpl(int debounce_interval_in_ms) {
-    SetFeatureList(true, wheel_scroll_latching_enabled_);
+    SetFeatureList(true, wheel_scrolling_mode_);
     ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
         debounce_interval_in_ms);
 
@@ -1128,13 +1153,29 @@
     EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
   }
 
-  void ExpectGestureScrollEventsAfterMouseWheelACK(bool is_first_ack,
-                                                   bool enqueued_mouse_wheel) {
+  void ExpectGestureScrollEventsAfterMouseWheelACK(
+      bool is_first_ack,
+      size_t enqueued_wheel_event_count) {
     InputMsg_HandleInputEvent::Param params;
     const blink::WebInputEvent* event;
     size_t expected_message_count;
     if (is_first_ack) {
-      expected_message_count = enqueued_mouse_wheel ? 3 : 2;
+      if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+        // If the ack for the first sent event is not consumed,
+        // MouseWheelEventQueue(MWEQ) sends the rest of the wheel events in the
+        // current scrolling sequence as non-blocking events. Since MWEQ
+        // receives the ack for non-blocking events asynchronously, it sends the
+        // next queued wheel event immediately and this continues till the queue
+        // is empty.
+        expected_message_count = enqueued_wheel_event_count + 2;
+      } else {
+        // Since the MWEQ must wait for ack of the sent event before sending the
+        // next queued event, when wheel events are blocking only one queued
+        // event will be sent regardless of the number of the queued wheel
+        // events.
+        expected_message_count = enqueued_wheel_event_count ? 3 : 2;
+      }
+
       EXPECT_EQ(expected_message_count, sink_->message_count());
 
       // The first message is GestureScrollBegin.
@@ -1149,15 +1190,15 @@
         EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, event->GetType());
       }
 
-      if (enqueued_mouse_wheel) {
-        // The last message is the queued MouseWheel.
-        if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(2), &params)) {
+      for (size_t i = 2; i < expected_message_count; i++) {
+        // The last messages are the queued MouseWheel event(s).
+        if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(i), &params)) {
           event = std::get<0>(params);
           EXPECT_EQ(WebInputEvent::kMouseWheel, event->GetType());
         }
       }
     } else {  // !is_first_ack
-      expected_message_count = enqueued_mouse_wheel ? 2 : 1;
+      expected_message_count = enqueued_wheel_event_count ? 2 : 1;
       size_t scroll_update_index = 0;
 
       if (!wheel_scroll_latching_enabled_) {
@@ -1178,7 +1219,7 @@
         EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, event->GetType());
       }
 
-      if (enqueued_mouse_wheel) {
+      if (enqueued_wheel_event_count) {
         // The last message is the queued MouseWheel.
         if (InputMsg_HandleInputEvent::Read(
                 sink_->GetMessageAt(expected_message_count - 1), &params)) {
@@ -1190,6 +1231,45 @@
     EXPECT_EQ(expected_message_count, GetSentMessageCountAndResetSink());
   }
 
+  void ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(
+      bool wheel_was_queued) {
+    size_t gesture_scroll_update_index;
+    InputMsg_HandleInputEvent::Param params;
+    if (wheel_was_queued) {
+      // The queued wheel event is already sent.
+      gesture_scroll_update_index = 0;
+    } else {
+      // The first sent must be the wheel event and the second one must be
+      // GestureScrollUpdate since the ack for the wheel event is non-blocking.
+      if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(0), &params)) {
+        EXPECT_EQ(WebInputEvent::kMouseWheel, (std::get<0>(params))->GetType());
+      }
+      gesture_scroll_update_index = 1;
+    }
+    if (InputMsg_HandleInputEvent::Read(
+            sink_->GetMessageAt(gesture_scroll_update_index), &params)) {
+      EXPECT_EQ(WebInputEvent::kGestureScrollUpdate,
+                (std::get<0>(params))->GetType());
+    }
+    EXPECT_EQ(gesture_scroll_update_index + 1,
+              GetSentMessageCountAndResetSink());
+  }
+
+  void ExpectGestureScrollEndAfterNonBlockingMouseWheelACK() {
+    InputMsg_HandleInputEvent::Param params;
+    // The first sent must be the wheel event and the second one must be
+    // GestureScrollEnd since the ack for the wheel event is non-blocking.
+    if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(0), &params)) {
+      EXPECT_EQ(WebInputEvent::kMouseWheel, (std::get<0>(params))->GetType());
+    }
+
+    if (InputMsg_HandleInputEvent::Read(sink_->GetMessageAt(1), &params)) {
+      EXPECT_EQ(WebInputEvent::kGestureScrollEnd,
+                (std::get<0>(params))->GetType());
+    }
+    EXPECT_EQ(2U, GetSentMessageCountAndResetSink());
+  }
+
   void WheelNotPreciseScrollEvent();
   void WheelScrollOverscrollToggle();
   void OverscrollMouseMoveCompletion();
@@ -1198,6 +1278,7 @@
   void ScrollEventsOverscrollWithFling();
   void OverscrollDirectionChangeMouseWheel();
   void OverscrollStateResetsAfterScroll();
+  void ScrollDeltasResetOnEnd();
   void ScrollEventsOverscrollWithZeroFling();
 
   SyntheticWebTouchEvent touch_event_;
@@ -1205,6 +1286,7 @@
   std::unique_ptr<TestOverscrollDelegate> overscroll_delegate_;
 
   bool wheel_scroll_latching_enabled_;
+  WheelScrollingMode wheel_scrolling_mode_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraOverscrollTest);
@@ -1214,7 +1296,14 @@
     : public RenderWidgetHostViewAuraOverscrollTest {
  public:
   RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest()
-      : RenderWidgetHostViewAuraOverscrollTest(false) {}
+      : RenderWidgetHostViewAuraOverscrollTest(kWheelScrollingModeNone) {}
+};
+
+class RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest
+    : public RenderWidgetHostViewAuraOverscrollTest {
+ public:
+  RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest()
+      : RenderWidgetHostViewAuraOverscrollTest(kAsyncWheelEvents) {}
 };
 
 class RenderWidgetHostViewAuraShutdownTest
@@ -3722,28 +3811,36 @@
   // Receive ACK the first wheel event as not processed.
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, true);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 1);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEndForWheelScrolling(false);
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode());
 
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(false);
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  if (wheel_scroll_latching_enabled_) {
-    SimulateWheelEventWithPhase(0, 0, 0, false,
-                                WebMouseWheelEvent::kPhaseEnded);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
+    ExpectGestureScrollEndAfterNonBlockingMouseWheelACK();
+  } else if (wheel_scroll_latching_enabled_) {
+    SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
     EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
     SendInputEventACK(WebInputEvent::kMouseWheel,
                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEndForWheelScrolling(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(true);
   }
-  ExpectGestureScrollEndForWheelScrolling(true);
 
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode());
@@ -3755,6 +3852,10 @@
        WheelNotPreciseScrollEvent) {
   WheelNotPreciseScrollEvent();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       WheelNotPreciseScrollEvent) {
+  WheelNotPreciseScrollEvent();
+}
 
 void RenderWidgetHostViewAuraOverscrollTest::WheelScrollEventOverscrolls() {
   SetUpOverscrollEnvironment();
@@ -3784,25 +3885,28 @@
   // Receive ACK the first wheel event as not processed.
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, true);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 2);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEndForWheelScrolling(false);
 
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode());
-
-  // Receive ACK for the second (coalesced) event as not processed. This will
-  // start a back navigation. However, this will also cause the queued next
-  // event to be sent to the renderer. But since overscroll navigation has
-  // started, that event will also be included in the overscroll computation
-  // instead of being sent to the renderer. So the result will be an overscroll
-  // back navigation, and no ScrollUpdate event will be sent to the renderer.
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, true);
-
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(false);
+    // Receive ACK for the second (coalesced) event as not processed. This will
+    // start a back navigation. However, this will also cause the queued next
+    // event to be sent to the renderer. But since overscroll navigation has
+    // started, that event will also be included in the overscroll computation
+    // instead of being sent to the renderer. So the result will be an
+    // overscroll back navigation, and no ScrollUpdate event will be sent to the
+    // renderer.
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 1);
+  }
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   ExpectGestureScrollEndForWheelScrolling(false);
@@ -3836,6 +3940,10 @@
        WheelScrollEventOverscrolls) {
   WheelScrollEventOverscrolls();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       WheelScrollEventOverscrolls) {
+  WheelScrollEventOverscrolls();
+}
 
 // Tests that if some scroll events are consumed towards the start, then
 // subsequent scrolls do not horizontal overscroll.
@@ -3869,42 +3977,54 @@
   // Receive ACK the first wheel event as processed.
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, true);
-
-  SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
-                    INPUT_EVENT_ACK_STATE_CONSUMED);
-  ExpectGestureScrollEndForWheelScrolling(false);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 2);
 
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode());
 
-  // Receive ACK for the second (coalesced) event as not processed. This should
-  // not initiate overscroll, since the beginning of the scroll has been
-  // consumed. The queued event with different modifiers should be sent to the
-  // renderer.
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  if (wheel_scrolling_mode_ != kAsyncWheelEvents) {
+    SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_CONSUMED);
+    ExpectGestureScrollEndForWheelScrolling(false);
+    // Receive ACK for the second (coalesced) event as not processed. This
+    // should not initiate overscroll, since the beginning of the scroll has
+    // been consumed. The queued event with different modifiers should be sent
+    // to the renderer.
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 1);
+  }
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, true);
+
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    // The first and second GSU events are coalesced. This is the ack for the
+    // coalesced event. Since it is the first GSU, the ack should be consumed.
+    SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_CONSUMED);
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(true);
+  } else {
+    SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEndForWheelScrolling(false);
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEndForWheelScrolling(false);
-
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
-
-  SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  if (wheel_scroll_latching_enabled_) {
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
+    ExpectGestureScrollEndAfterNonBlockingMouseWheelACK();
+  } else if (wheel_scroll_latching_enabled_) {
     SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
     EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
     SendInputEventACK(WebInputEvent::kMouseWheel,
                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEndForWheelScrolling(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(true);
   }
-
-  ExpectGestureScrollEndForWheelScrolling(true);
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
 }
 TEST_F(RenderWidgetHostViewAuraOverscrollTest,
@@ -3915,6 +4035,10 @@
        WheelScrollConsumedDoNotHorizOverscroll) {
   WheelScrollConsumedDoNotHorizOverscroll();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       WheelScrollConsumedDoNotHorizOverscroll) {
+  WheelScrollConsumedDoNotHorizOverscroll();
+}
 
 // Tests that wheel-scrolling correctly turns overscroll on and off.
 void RenderWidgetHostViewAuraOverscrollTest::WheelScrollOverscrollToggle() {
@@ -3928,7 +4052,7 @@
   EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, false);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 0);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -3941,10 +4065,14 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 10,
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -3957,10 +4085,14 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 40,
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -3993,10 +4125,14 @@
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
 
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -4010,20 +4146,29 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, -55,
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  if (wheel_scroll_latching_enabled_) {
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
+    ExpectGestureScrollEndAfterNonBlockingMouseWheelACK();
+  } else if (wheel_scroll_latching_enabled_) {
     SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
     EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
     SendInputEventACK(WebInputEvent::kMouseWheel,
                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEndForWheelScrolling(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(true);
   }
-  ExpectGestureScrollEndForWheelScrolling(true);
 
   EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode());
   EXPECT_EQ(OVERSCROLL_WEST, overscroll_delegate()->current_mode());
@@ -4038,6 +4183,10 @@
        WheelScrollOverscrollToggle) {
   WheelScrollOverscrollToggle();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       WheelScrollOverscrollToggle) {
+  WheelScrollOverscrollToggle();
+}
 
 void RenderWidgetHostViewAuraOverscrollTest::ScrollEventsOverscrollWithFling() {
   SetUpOverscrollEnvironment();
@@ -4050,7 +4199,7 @@
   EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, false);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 0);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -4063,9 +4212,14 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 20,
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, sink_->message_count());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
@@ -4076,11 +4230,14 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 30,
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
-
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   ExpectGestureScrollEndForWheelScrolling(false);
@@ -4112,6 +4269,10 @@
        ScrollEventsOverscrollWithFling) {
   ScrollEventsOverscrollWithFling();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       ScrollEventsOverscrollWithFling) {
+  ScrollEventsOverscrollWithFling();
+}
 
 // Same as ScrollEventsOverscrollWithFling, but with zero velocity. Checks that
 // the zero-velocity fling does not reach the renderer.
@@ -4127,7 +4288,7 @@
   EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, false);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 0);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -4140,10 +4301,14 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 20,
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -4156,10 +4321,14 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 30,
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -4192,6 +4361,10 @@
        ScrollEventsOverscrollWithZeroFling) {
   ScrollEventsOverscrollWithZeroFling();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       ScrollEventsOverscrollWithZeroFling) {
+  ScrollEventsOverscrollWithZeroFling();
+}
 
 // Tests that a fling in the opposite direction of the overscroll cancels the
 // overscroll nav instead of completing it.
@@ -4649,7 +4822,7 @@
   EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, false);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 0);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -4665,10 +4838,15 @@
   SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_,
                                            -260, 0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
-  EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(false);
+  } else {
+    EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
+
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
@@ -4683,8 +4861,10 @@
                                            0, 0, true,
                                            WebMouseWheelEvent::kPhaseChanged);
   EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  if (wheel_scrolling_mode_ != kAsyncWheelEvents) {
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  }
 
   // wheel event ack generates gesture scroll update; which gets consumed
   // solely by the overflow controller.
@@ -4706,6 +4886,10 @@
        OverscrollDirectionChangeMouseWheel) {
   OverscrollDirectionChangeMouseWheel();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       OverscrollDirectionChangeMouseWheel) {
+  OverscrollDirectionChangeMouseWheel();
+}
 
 void RenderWidgetHostViewAuraOverscrollTest::OverscrollMouseMoveCompletion() {
   SetUpOverscrollEnvironment();
@@ -4732,20 +4916,24 @@
   // Receive ACK the first wheel event as not processed.
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, true);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 1);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEndForWheelScrolling(false);
 
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_delegate()->current_mode());
 
-  // Receive ACK for the second (coalesced) event as not processed. This will
-  // start an overcroll gesture.
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(false);
+    // Receive ACK for the second (coalesced) event as not processed. This will
+    // start an overcroll gesture.
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -4812,6 +5000,10 @@
        OverscrollMouseMoveCompletion) {
   OverscrollMouseMoveCompletion();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       OverscrollMouseMoveCompletion) {
+  OverscrollMouseMoveCompletion();
+}
 
 // Tests that if a page scrolled, then the overscroll controller's states are
 // reset after the end of the scroll.
@@ -4837,32 +5029,41 @@
   // The first wheel event is consumed. Dispatches the queued wheel event.
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, true);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 1);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_CONSUMED);
-  ExpectGestureScrollEndForWheelScrolling(false);
   EXPECT_TRUE(ScrollStateIsContentScrolling());
 
-  // The second wheel event is consumed.
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(false);
+    // The second wheel event is consumed.
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_CONSUMED);
 
-  if (wheel_scroll_latching_enabled_) {
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
+    ExpectGestureScrollEndAfterNonBlockingMouseWheelACK();
+  } else if (wheel_scroll_latching_enabled_) {
     SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
     EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
     SendInputEventACK(WebInputEvent::kMouseWheel,
                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEndForWheelScrolling(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(true);
   }
-  ExpectGestureScrollEndForWheelScrolling(true);
   EXPECT_TRUE(ScrollStateIsContentScrolling());
 
-  // Touchpad scroll can end with a zero-velocity fling. But it is not
-  // dispatched, but it should still reset the overscroll controller state.
+  // Touchpad scroll can end with a zero-velocity fling which is not dispatched,
+  // but it should still reset the overscroll controller state.
   SimulateGestureEvent(WebInputEvent::kGestureScrollBegin,
                        blink::kWebGestureDeviceTouchscreen);
   SimulateGestureFlingStartEvent(0.f, 0.f, blink::kWebGestureDeviceTouchpad);
@@ -4896,28 +5097,37 @@
   // yet, since enough hasn't been scrolled.
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(true, true);
+  ExpectGestureScrollEventsAfterMouseWheelACK(true, 1);
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEndForWheelScrolling(false);
+
   EXPECT_TRUE(ScrollStateIsUnknown());
 
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  ExpectGestureScrollEventsAfterMouseWheelACK(false, false);
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    ExpectGestureScrollUpdateAfterNonBlockingMouseWheelACK(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(false);
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEventsAfterMouseWheelACK(false, 0);
+  }
 
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
 
-  if (wheel_scroll_latching_enabled_) {
+  if (wheel_scrolling_mode_ == kAsyncWheelEvents) {
+    SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
+    ExpectGestureScrollEndAfterNonBlockingMouseWheelACK();
+  } else if (wheel_scroll_latching_enabled_) {
     SimulateWheelEventWithPhase(0, 0, 0, true, WebMouseWheelEvent::kPhaseEnded);
     EXPECT_EQ(1U, GetSentMessageCountAndResetSink());
     SendInputEventACK(WebInputEvent::kMouseWheel,
                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    ExpectGestureScrollEndForWheelScrolling(true);
+  } else {
+    ExpectGestureScrollEndForWheelScrolling(true);
   }
-
-  ExpectGestureScrollEndForWheelScrolling(true);
   EXPECT_EQ(OVERSCROLL_WEST, overscroll_mode());
   EXPECT_TRUE(ScrollStateIsOverscrolling());
 
@@ -4940,6 +5150,10 @@
        OverscrollStateResetsAfterScroll) {
   OverscrollStateResetsAfterScroll();
 }
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       OverscrollStateResetsAfterScroll) {
+  OverscrollStateResetsAfterScroll();
+}
 
 TEST_F(RenderWidgetHostViewAuraOverscrollTest, OverscrollResetsOnBlur) {
   SetUpOverscrollEnvironment();
@@ -5185,10 +5399,12 @@
 // Tests that the scroll deltas stored within the overscroll controller get
 // reset at the end of the overscroll gesture even if the overscroll threshold
 // isn't surpassed and the overscroll mode stays OVERSCROLL_NONE.
-TEST_F(RenderWidgetHostViewAuraOverscrollTest, ScrollDeltasResetOnEnd) {
+void RenderWidgetHostViewAuraOverscrollTest::ScrollDeltasResetOnEnd() {
   SetUpOverscrollEnvironment();
   // Wheel event scroll ending with mouse move.
-  SimulateWheelEvent(-30, -10, 0, true);  // sent directly
+  SimulateWheelEventPossiblyIncludingPhase(
+      !wheel_scroll_latching_enabled_, -30, -10, 0, true,
+      WebMouseWheelEvent::kPhaseBegan);  // sent directly
   SendInputEventACK(WebInputEvent::kMouseWheel,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
@@ -5215,14 +5431,22 @@
   EXPECT_EQ(0.f, overscroll_delta_y());
 
   // Wheel event scroll ending with a fling.
-  SimulateWheelEvent(5, 0, 0, true);
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 5,
+                                           0, 0, true,
+                                           WebMouseWheelEvent::kPhaseChanged);
+  if (wheel_scrolling_mode_ != kAsyncWheelEvents) {
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  }
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-  SimulateWheelEvent(10, -5, 0, true);
-  SendInputEventACK(WebInputEvent::kMouseWheel,
-                    INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  SimulateWheelEventPossiblyIncludingPhase(!wheel_scroll_latching_enabled_, 10,
+                                           -5, 0, true,
+                                           WebMouseWheelEvent::kPhaseChanged);
+  if (wheel_scrolling_mode_ != kAsyncWheelEvents) {
+    SendInputEventACK(WebInputEvent::kMouseWheel,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  }
   SendInputEventACK(WebInputEvent::kGestureScrollUpdate,
                     INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
@@ -5234,6 +5458,17 @@
   EXPECT_EQ(0.f, overscroll_delta_x());
   EXPECT_EQ(0.f, overscroll_delta_y());
 }
+TEST_F(RenderWidgetHostViewAuraOverscrollTest, ScrollDeltasResetOnEnd) {
+  ScrollDeltasResetOnEnd();
+}
+TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest,
+       ScrollDeltasResetOnEnd) {
+  ScrollDeltasResetOnEnd();
+}
+TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
+       ScrollDeltasResetOnEnd) {
+  ScrollDeltasResetOnEnd();
+}
 
 TEST_F(RenderWidgetHostViewAuraTest, ForwardMouseEvent) {
   aura::Window* root = parent_view_->GetNativeView()->GetRootWindow();
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 617f484..52ddbab 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -988,6 +988,8 @@
   }
   if (browser_plugin_embedder_)
     browser_plugin_embedder_->CancelGuestDialogs();
+  if (delegate_)
+    delegate_->HideValidationMessage(this);
 }
 
 void WebContentsImpl::ClosePage() {
@@ -4694,9 +4696,6 @@
   // Cancel any visible dialogs so they are not left dangling over the sad tab.
   CancelActiveAndPendingDialogs();
 
-  if (delegate_)
-    delegate_->HideValidationMessage(this);
-
   audio_stream_monitor_.RenderProcessGone(rvh->GetProcess()->GetID());
 
   // Reset the loading progress. TODO(avi): What does it mean to have a
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 2f161b01..fea1eb6 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -1467,4 +1467,78 @@
   wc->SetJavaScriptDialogManagerForTesting(nullptr);
 }
 
+class FormBubbleDelegate : public WebContentsDelegate {
+ public:
+  FormBubbleDelegate() = default;
+
+  void WaitUntilShown() {
+    while (!is_visible_) {
+      message_loop_runner_ = new MessageLoopRunner;
+      message_loop_runner_->Run();
+    }
+  }
+
+  void WaitUntilHidden() {
+    while (is_visible_) {
+      message_loop_runner_ = new MessageLoopRunner;
+      message_loop_runner_->Run();
+    }
+  }
+
+ private:
+  void ShowValidationMessage(WebContents* web_contents,
+                             const gfx::Rect& anchor_in_root_view,
+                             const base::string16& main_text,
+                             const base::string16& sub_text) override {
+    is_visible_ = true;
+    if (message_loop_runner_)
+      message_loop_runner_->Quit();
+  }
+
+  void HideValidationMessage(WebContents* web_contents) override {
+    is_visible_ = false;
+    if (message_loop_runner_)
+      message_loop_runner_->Quit();
+  }
+
+  bool is_visible_ = false;
+  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+                       NavigationHidesFormValidationBubble) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
+
+  // Start listening for requests to show or hide the form validation bubble.
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+  FormBubbleDelegate bubble_delegate;
+  web_contents->SetDelegate(&bubble_delegate);
+
+  // Trigger a form validation bubble and verify that the bubble is shown.
+  std::string script = R"(
+      var input_field = document.createElement('input');
+      input_field.required = true;
+      var form = document.createElement('form');
+      form.appendChild(input_field);
+      document.body.appendChild(form);
+
+      setTimeout(function() {
+              input_field.setCustomValidity('Custom validity message');
+              input_field.reportValidity();
+          },
+          0);
+      )";
+  ASSERT_TRUE(ExecuteScript(web_contents, script));
+  bubble_delegate.WaitUntilShown();
+
+  // Navigate to another page and verify that the form validation bubble is
+  // hidden.
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
+  bubble_delegate.WaitUntilHidden();
+}
+
 }  // namespace content
diff --git a/content/browser/webrtc/webrtc_media_recorder_browsertest.cc b/content/browser/webrtc/webrtc_media_recorder_browsertest.cc
index 3d815a27..071bf9ca 100644
--- a/content/browser/webrtc/webrtc_media_recorder_browsertest.cc
+++ b/content/browser/webrtc/webrtc_media_recorder_browsertest.cc
@@ -154,8 +154,16 @@
                   kMediaRecorderHtmlFile);
 }
 
+// Flaky on Linux Tsan (crbug.com/736268)
+#if defined(THREAD_SANITIZER)
+#define MAYBE_IllegalStartWhilePausedThrowsDOMError \
+  DISABLED_IllegalStartWhilePausedThrowsDOMError
+#else
+#define MAYBE_IllegalStartWhilePausedThrowsDOMError \
+  IllegalStartWhilePausedThrowsDOMError
+#endif
 IN_PROC_BROWSER_TEST_F(WebRtcMediaRecorderTest,
-                       IllegalStartWhilePausedThrowsDOMError) {
+                       MAYBE_IllegalStartWhilePausedThrowsDOMError) {
   MakeTypicalCall("testIllegalStartInPausedStateThrowsDOMError();",
                   kMediaRecorderHtmlFile);
 }
diff --git a/content/common/site_isolation_policy.cc b/content/common/site_isolation_policy.cc
index 7fa66c2..3fc8ba5f 100644
--- a/content/common/site_isolation_policy.cc
+++ b/content/common/site_isolation_policy.cc
@@ -6,7 +6,6 @@
 
 #include "base/command_line.h"
 #include "base/feature_list.h"
-#include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 
@@ -14,17 +13,7 @@
 
 // static
 bool SiteIsolationPolicy::AreCrossProcessFramesPossible() {
-// Before turning this on for Android, input event routing needs to be
-// completed there, and perf regressions in https://crbug.com/690229 need to be
-// investigated.
-#if defined(OS_ANDROID)
-  return UseDedicatedProcessesForAllSites() ||
-         IsTopDocumentIsolationEnabled() || AreIsolatedOriginsEnabled() ||
-         GetContentClient()->IsSupplementarySiteIsolationModeEnabled() ||
-         base::FeatureList::IsEnabled(::features::kGuestViewCrossProcessFrames);
-#else
   return true;
-#endif
 }
 
 // static
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index ba68c9030..dee3dde 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -21,6 +21,10 @@
 const base::Feature kAsmJsToWebAssembly{"AsmJsToWebAssembly",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables async wheel events.
+const base::Feature kAsyncWheelEvents{"AsyncWheelEvents",
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Block subresource requests whose URLs contain embedded credentials (e.g.
 // `https://user:pass@example.com/resource`).
 const base::Feature kBlockCredentialedSubresources{
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index e06b6dc..b7ba58be 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -19,6 +19,7 @@
 CONTENT_EXPORT extern const base::Feature
     kAllowContentInitiatedDataUrlNavigations;
 CONTENT_EXPORT extern const base::Feature kAsmJsToWebAssembly;
+CONTENT_EXPORT extern const base::Feature kAsyncWheelEvents;
 CONTENT_EXPORT extern const base::Feature kBlockCredentialedSubresources;
 CONTENT_EXPORT extern const base::Feature kBrotliEncoding;
 CONTENT_EXPORT extern const base::Feature kBrowserSideNavigation;
diff --git a/content/renderer/media/rtc_peer_connection_handler.cc b/content/renderer/media/rtc_peer_connection_handler.cc
index 1afd649..21792a5 100644
--- a/content/renderer/media/rtc_peer_connection_handler.cc
+++ b/content/renderer/media/rtc_peer_connection_handler.cc
@@ -1105,6 +1105,8 @@
       dependency_factory_(dependency_factory),
       track_adapter_map_(
           new WebRtcMediaStreamTrackAdapterMap(dependency_factory_)),
+      stream_adapter_map_(new WebRtcMediaStreamAdapterMap(dependency_factory_,
+                                                          track_adapter_map_)),
       weak_factory_(this) {
   CHECK(client_);
   GetPeerConnectionHandlers()->insert(this);
@@ -1524,8 +1526,8 @@
     const blink::WebMediaConstraints& options) {
   DCHECK(thread_checker_.CalledOnValidThread());
   TRACE_EVENT0("webrtc", "RTCPeerConnectionHandler::addStream");
-  for (const auto& adapter : local_streams_) {
-    if (adapter->IsEqual(stream)) {
+  for (const auto& adapter_ref : local_streams_) {
+    if (adapter_ref->adapter().IsEqual(stream)) {
       DVLOG(1) << "RTCPeerConnectionHandler::addStream called with the same "
                << "stream twice. id=" << stream.Id().Utf8();
       return false;
@@ -1539,11 +1541,11 @@
 
   PerSessionWebRTCAPIMetrics::GetInstance()->IncrementStreamCounter();
 
-  local_streams_.push_back(base::MakeUnique<WebRtcMediaStreamAdapter>(
-      dependency_factory_, track_adapter_map_, stream));
+  local_streams_.push_back(
+      stream_adapter_map_->GetOrCreateLocalStreamAdapter(stream));
 
   webrtc::MediaStreamInterface* webrtc_stream =
-      local_streams_.back()->webrtc_media_stream();
+      local_streams_.back()->adapter().webrtc_media_stream();
   track_metrics_.AddStream(MediaStreamTrackMetrics::SENT_STREAM,
                            webrtc_stream);
 
@@ -1567,8 +1569,8 @@
   scoped_refptr<webrtc::MediaStreamInterface> webrtc_stream;
   for (auto adapter_it = local_streams_.begin();
        adapter_it != local_streams_.end(); ++adapter_it) {
-    if ((*adapter_it)->IsEqual(stream)) {
-      webrtc_stream = (*adapter_it)->webrtc_media_stream();
+    if ((*adapter_it)->adapter().IsEqual(stream)) {
+      webrtc_stream = (*adapter_it)->adapter().webrtc_media_stream();
       local_streams_.erase(adapter_it);
       break;
     }
@@ -1759,9 +1761,9 @@
 
   // Find the WebRtc track referenced by the blink track's ID.
   webrtc::AudioTrackInterface* webrtc_track = nullptr;
-  for (const auto& adapter : local_streams_) {
-    webrtc_track =
-        adapter->webrtc_media_stream()->FindAudioTrack(track.Id().Utf8());
+  for (const auto& adapter_ref : local_streams_) {
+    webrtc_track = adapter_ref->adapter().webrtc_media_stream()->FindAudioTrack(
+        track.Id().Utf8());
     if (webrtc_track)
       break;
   }
diff --git a/content/renderer/media/rtc_peer_connection_handler.h b/content/renderer/media/rtc_peer_connection_handler.h
index 77de858f..89bcb95 100644
--- a/content/renderer/media/rtc_peer_connection_handler.h
+++ b/content/renderer/media/rtc_peer_connection_handler.h
@@ -21,6 +21,7 @@
 #include "base/threading/thread_checker.h"
 #include "content/common/content_export.h"
 #include "content/renderer/media/webrtc/media_stream_track_metrics.h"
+#include "content/renderer/media/webrtc/webrtc_media_stream_adapter_map.h"
 #include "content/renderer/media/webrtc/webrtc_media_stream_track_adapter_map.h"
 #include "ipc/ipc_platform_file.h"
 #include "third_party/WebKit/public/platform/WebMediaStreamSource.h"
@@ -43,7 +44,6 @@
 class PeerConnectionTracker;
 class RemoteMediaStreamImpl;
 class RtcDataChannelHandler;
-class WebRtcMediaStreamAdapter;
 
 // Mockable wrapper for blink::WebRTCStatsResponse
 class CONTENT_EXPORT LocalRTCStatsResponse
@@ -262,12 +262,23 @@
   // needs to reference it, and automatically disposed when there are no longer
   // any components referencing it.
   scoped_refptr<WebRtcMediaStreamTrackAdapterMap> track_adapter_map_;
+  // Map and owners of stream adapters. Every stream that is in use by the peer
+  // connection has an associated blink and webrtc layer representation of it.
+  // The map keeps track of the relationship between |blink::WebMediaStream|s
+  // and |webrtc::MediaStreamInterface|s. Stream adapters are created on the fly
+  // when a component (such as |local_streams_| or a sender) needs to reference
+  // it, and automatically disposed when there are no longer any components
+  // referencing it.
+  // TODO(hbos): Update and use the map for the |remote_streams_| case too.
+  // crbug.com/705901
+  scoped_refptr<WebRtcMediaStreamAdapterMap> stream_adapter_map_;
   // Local stream adapters. Every stream that is in use by the peer connection
   // has an associated blink and webrtc layer representation of it. This vector
   // keeps track of the relationship between |blink::WebMediaStream|s and
   // |webrtc::MediaStreamInterface|s. Streams are added and removed from the
   // peer connection using |AddStream| and |RemoveStream|.
-  std::vector<std::unique_ptr<WebRtcMediaStreamAdapter>> local_streams_;
+  std::vector<std::unique_ptr<WebRtcMediaStreamAdapterMap::AdapterRef>>
+      local_streams_;
 
   base::WeakPtr<PeerConnectionTracker> peer_connection_tracker_;
 
diff --git a/content/test/gpu/generate_buildbot_json.py b/content/test/gpu/generate_buildbot_json.py
index 5c1c8ba..1f10d559 100755
--- a/content/test/gpu/generate_buildbot_json.py
+++ b/content/test/gpu/generate_buildbot_json.py
@@ -1584,7 +1584,6 @@
     'tester_configs': [
       {
         'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
       },
     ],
     'disabled_tester_configs': [
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 01cf24a..3d62527 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -68,6 +68,18 @@
     self.Fail('conformance2/textures/misc/tex-base-level-bug.html',
         ['win', 'd3d11'], bug=705865)
 
+    # Failing intermittently with out-of-memory crashes on some Windows bots.
+    self.Flaky('deqp/functional/gles3/texturefiltering/3d_formats_05.html',
+               ['win'], bug=735527)
+    self.Flaky('deqp/functional/gles3/texturefiltering/3d_formats_06.html',
+               ['win'], bug=735527)
+    self.Flaky('deqp/functional/gles3/texturefiltering/3d_formats_07.html',
+               ['win'], bug=735527)
+    self.Flaky('deqp/functional/gles3/texturefiltering/3d_sizes_02.html',
+               ['win'], bug=735527)
+    self.Flaky('deqp/functional/gles3/texturefiltering/3d_sizes_03.html',
+               ['win'], bug=735527)
+
     # Win / NVidia
     self.Flaky('deqp/functional/gles3/fbomultisample*',
         ['win', 'nvidia', 'd3d11'], bug=631317)
diff --git a/docs/fuchsia_build_instructions.md b/docs/fuchsia_build_instructions.md
new file mode 100644
index 0000000..5a5f317c
--- /dev/null
+++ b/docs/fuchsia_build_instructions.md
@@ -0,0 +1,116 @@
+# Checking out and building on Fuchsia
+
+***Note that the Fuchsia port is in the early stages, and things are likely to
+frequently be broken. Try #cr-fuchsia on Freenode if something seems awry.***
+
+There are instructions for other platforms linked from the 
+[get the code](get_the_code.md) page.
+
+## System requirements
+
+*   A 64-bit Intel machine with at least 8GB of RAM. More than 16GB is highly
+    recommended.
+*   At least 100GB of free disk space.
+*   You must have Git and Python installed already.
+
+Most development is done on Ubuntu.
+
+## Install `depot_tools`
+
+Clone the `depot_tools` repository:
+
+```shell
+$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
+```
+
+Add `depot_tools` to the end of your PATH (you will probably want to put this
+in your `~/.bashrc` or `~/.zshrc`). Assuming you cloned `depot_tools` to
+`/path/to/depot_tools`:
+
+```shell
+$ export PATH="$PATH:/path/to/depot_tools"
+```
+
+## Get the code
+
+Create a `chromium` directory for the checkout and change to it (you can call
+this whatever you like and put it wherever you like, as long as the full path
+has no spaces):
+
+```shell
+$ mkdir ~/chromium && cd ~/chromium
+```
+
+Run the `fetch` tool from depot_tools to check out the code and its
+dependencies.
+
+```shell
+$ fetch --nohooks chromium
+```
+
+If you don't want the full repo history, you can save a lot of time by
+adding the `--no-history` flag to `fetch`.
+
+Expect the command to take 30 minutes on even a fast connection, and many
+hours on slower ones.
+
+If you've already installed the build dependencies on the machine (from another
+checkout, for example), you can omit the `--nohooks` flag and `fetch`
+will automatically execute `gclient runhooks` at the end.
+
+When `fetch` completes, it will have created a hidden `.gclient` file and a
+directory called `src` in the working directory. The remaining instructions
+assume you have switched to the `src` directory:
+
+```shell
+$ cd src
+```
+
+### Configure for building on Fuchsia
+
+Edit `.gclient` to include (this is a list, so it could also include `android`,
+etc. if necessary.)
+
+```
+target_os = ['fuchsia']
+```
+
+You will then need to re-run `gclient runhooks`. This makes sure the Fuchsia SDK
+is available in third\_party and keeps it up to date.
+
+
+## Setting up the build
+
+Chromium uses [Ninja](https://ninja-build.org) as its main build tool along
+with a tool called [GN](../tools/gn/docs/quick_start.md) to generate `.ninja`
+files. You can create any number of *build directories* with different
+configurations. To create a build directory, run:
+
+```shell
+$ gn gen out/fuch --args="is_debug=false dcheck_always_on=true is_component_build=false target_os=\"fuchsia\""
+```
+
+## Build
+
+Currently, not all targets build on Fuchsia. You can build base\_unittests, for
+example:
+
+```shell
+$ ninja -C out/fuch base_unittests
+```
+
+## Run
+
+Once it is built, you can run by:
+
+```shell
+$ out/fuch/bin/run_base_unittests
+```
+
+This packages the built binary and test data into a disk image, and runs a QEMU
+instance from the Fuchsia SDK, outputting to the console.
+
+Common gtest arguments such as `--gtest_filter=...` are supported by the run
+script.
+
+The run script also symbolizes backtraces.
diff --git a/docs/memory/README.md b/docs/memory/README.md
index 2cf5c2dc..ad99948 100644
--- a/docs/memory/README.md
+++ b/docs/memory/README.md
@@ -60,15 +60,15 @@
 
 | Topic | Description |
 |-------|-------------|
-| [Key Concepts in Chrome Memory](/memory/key_concepts.md) | Primer for memory terminology in Chrome. |
-| [memory-infra](/memory-infra/README.md) | The primary tool used for inspecting allocations. |
+| [Key Concepts in Chrome Memory](/docs/memory/key_concepts.md) | Primer for memory terminology in Chrome. |
+| [memory-infra](/docs/memory-infra/README.md) | The primary tool used for inspecting allocations. |
 
 
 ## What are people actively working on?
 | Project | Description |
 |---------|-------------|
 | [Memory Coordinator](https://docs.google.com/document/d/1dkUXXmpJk7xBUeQM-olBpTHJ2MXamDgY_kjNrl9JXMs/edit#heading=h.swke19b7apg5) (including [Purge+Throttle/Suspend](https://docs.google.com/document/d/1EgLimgxWK5DGhptnNVbEGSvVn6Q609ZJaBkLjEPRJvI/edit)) | Centralized policy and coordination of all memory components in Chrome |
-| [Memory-Infra](/memory-infra/README.md) | Tooling and infrastructure for Memory |
+| [Memory-Infra](/docs/memory-infra/README.md) | Tooling and infrastructure for Memory |
 | [System health benchmarks](https://docs.google.com/document/d/1pEeCnkbtrbsK3uuPA-ftbg4kzM4Bk7a2A9rhRYklmF8/edit?usp=sharing) | Automated tests based on telemetry |
 
 
diff --git a/extensions/browser/api/BUILD.gn b/extensions/browser/api/BUILD.gn
index 22db2bf..700a17c0 100644
--- a/extensions/browser/api/BUILD.gn
+++ b/extensions/browser/api/BUILD.gn
@@ -57,6 +57,7 @@
     "//extensions/browser/api/dns",
     "//extensions/browser/api/document_scan",
     "//extensions/browser/api/file_handlers",
+    "//extensions/browser/api/file_system",
     "//extensions/browser/api/hid",
     "//extensions/browser/api/idle",
     "//extensions/browser/api/management",
diff --git a/extensions/browser/api/file_system/BUILD.gn b/extensions/browser/api/file_system/BUILD.gn
new file mode 100644
index 0000000..0e5fa0b
--- /dev/null
+++ b/extensions/browser/api/file_system/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//extensions/features/features.gni")
+
+assert(enable_extensions,
+       "Cannot depend on extensions because enable_extensions=false.")
+
+source_set("file_system") {
+  sources = [
+    "saved_file_entry.cc",
+    "saved_file_entry.h",
+    "saved_files_service_interface.h",
+  ]
+
+  deps = [
+    "//base:base",
+  ]
+}
diff --git a/extensions/browser/api/file_system/saved_file_entry.cc b/extensions/browser/api/file_system/saved_file_entry.cc
new file mode 100644
index 0000000..77a6530
--- /dev/null
+++ b/extensions/browser/api/file_system/saved_file_entry.cc
@@ -0,0 +1,20 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/file_system/saved_file_entry.h"
+
+namespace extensions {
+
+SavedFileEntry::SavedFileEntry() : is_directory(false), sequence_number(0) {}
+
+SavedFileEntry::SavedFileEntry(const std::string& id,
+                               const base::FilePath& path,
+                               bool is_directory,
+                               int sequence_number)
+    : id(id),
+      path(path),
+      is_directory(is_directory),
+      sequence_number(sequence_number) {}
+
+}  // namespace extensions
diff --git a/extensions/browser/api/file_system/saved_file_entry.h b/extensions/browser/api/file_system/saved_file_entry.h
new file mode 100644
index 0000000..ad8bc92
--- /dev/null
+++ b/extensions/browser/api/file_system/saved_file_entry.h
@@ -0,0 +1,40 @@
+// 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 EXTENSIONS_BROWSER_API_FILE_SYSTEM_SAVED_FILE_ENTRY_H_
+#define EXTENSIONS_BROWSER_API_FILE_SYSTEM_SAVED_FILE_ENTRY_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+
+namespace extensions {
+
+// Represents a file entry that a user has given an app permission to
+// access. Must be serializable for persisting to disk.
+struct SavedFileEntry {
+  SavedFileEntry();
+
+  SavedFileEntry(const std::string& id,
+                 const base::FilePath& path,
+                 bool is_directory,
+                 int sequence_number);
+
+  // The opaque id of this file entry.
+  std::string id;
+
+  // The path to a file entry that the app had permission to access.
+  base::FilePath path;
+
+  // Whether or not the entry refers to a directory.
+  bool is_directory;
+
+  // The sequence number in the LRU of the file entry. The value 0 indicates
+  // that the entry is not in the LRU.
+  int sequence_number;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_API_FILE_SYSTEM_SAVED_FILE_ENTRY_H_
diff --git a/extensions/browser/api/file_system/saved_files_service_interface.h b/extensions/browser/api/file_system/saved_files_service_interface.h
new file mode 100644
index 0000000..9b075c4
--- /dev/null
+++ b/extensions/browser/api/file_system/saved_files_service_interface.h
@@ -0,0 +1,49 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_FILE_SYSTEM_SAVED_FILES_SERVICE_INTERFACE_H_
+#define EXTENSIONS_BROWSER_API_FILE_SYSTEM_SAVED_FILES_SERVICE_INTERFACE_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+
+namespace extensions {
+
+struct SavedFileEntry;
+
+// Provides an LRU of saved file entries that persist across app reloads.
+class SavedFilesServiceInterface {
+ public:
+  virtual ~SavedFilesServiceInterface() {}
+
+  // Registers a file entry with the saved files service, making it eligible to
+  // be put into the queue. File entries that are in the retained files queue at
+  // object construction are automatically registered.
+  virtual void RegisterFileEntry(const std::string& extension_id,
+                                 const std::string& id,
+                                 const base::FilePath& file_path,
+                                 bool is_directory) = 0;
+
+  // If the file with |id| is not in the queue of files to be retained
+  // permanently, adds the file to the back of the queue, evicting the least
+  // recently used entry at the front of the queue if it is full. If it is
+  // already present, moves it to the back of the queue. The |id| must have been
+  // registered.
+  virtual void EnqueueFileEntry(const std::string& extension_id,
+                                const std::string& id) = 0;
+
+  // Returns whether the file entry with the given |id| has been registered.
+  virtual bool IsRegistered(const std::string& extension_id,
+                            const std::string& id) = 0;
+
+  // Gets a borrowed pointer to the file entry with the specified |id|. Returns
+  // nullptr if the file entry has not been registered.
+  virtual const SavedFileEntry* GetFileEntry(const std::string& extension_id,
+                                             const std::string& id) = 0;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_API_FILE_SYSTEM_SAVED_FILES_SERVICE_INTERFACE_H_
diff --git a/gin/public/v8_platform.h b/gin/public/v8_platform.h
index 896a21e..9bcc57894 100644
--- a/gin/public/v8_platform.h
+++ b/gin/public/v8_platform.h
@@ -54,6 +54,7 @@
   void AddTraceStateObserver(v8::Platform::TraceStateObserver*) override;
   void RemoveTraceStateObserver(v8::Platform::TraceStateObserver*) override;
   StackTracePrinter GetStackTracePrinter() override;
+  v8::TracingController* GetTracingController() override;
 
  private:
   friend struct base::LazyInstanceTraitsBase<V8Platform>;
@@ -61,6 +62,9 @@
   V8Platform();
   ~V8Platform() override;
 
+  class TracingControllerImpl;
+  std::unique_ptr<TracingControllerImpl> tracing_controller_;
+
   DISALLOW_COPY_AND_ASSIGN(V8Platform);
 };
 
diff --git a/gin/v8_platform.cc b/gin/v8_platform.cc
index 1bfa2f6..fd0ef94 100644
--- a/gin/v8_platform.cc
+++ b/gin/v8_platform.cc
@@ -54,12 +54,144 @@
   trace.Print();
 }
 
+class ConvertableToTraceFormatWrapper final
+    : public base::trace_event::ConvertableToTraceFormat {
+ public:
+  explicit ConvertableToTraceFormatWrapper(
+      std::unique_ptr<v8::ConvertableToTraceFormat>& inner)
+      : inner_(std::move(inner)) {}
+  ~ConvertableToTraceFormatWrapper() override = default;
+  void AppendAsTraceFormat(std::string* out) const final {
+    inner_->AppendAsTraceFormat(out);
+  }
+
+ private:
+  std::unique_ptr<v8::ConvertableToTraceFormat> inner_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConvertableToTraceFormatWrapper);
+};
+
+class EnabledStateObserverImpl final
+    : public base::trace_event::TraceLog::EnabledStateObserver {
+ public:
+  EnabledStateObserverImpl() = default;
+
+  void OnTraceLogEnabled() final {
+    base::AutoLock lock(mutex_);
+    for (auto* o : observers_) {
+      o->OnTraceEnabled();
+    }
+  }
+
+  void OnTraceLogDisabled() final {
+    base::AutoLock lock(mutex_);
+    for (auto* o : observers_) {
+      o->OnTraceDisabled();
+    }
+  }
+
+  void AddObserver(v8::TracingController::TraceStateObserver* observer) {
+    {
+      base::AutoLock lock(mutex_);
+      DCHECK(!observers_.count(observer));
+      if (observers_.empty()) {
+        base::trace_event::TraceLog::GetInstance()->AddEnabledStateObserver(
+            this);
+      }
+      observers_.insert(observer);
+    }
+    // Fire the observer if recording is already in progress.
+    if (base::trace_event::TraceLog::GetInstance()->IsEnabled())
+      observer->OnTraceEnabled();
+  }
+
+  void RemoveObserver(v8::TracingController::TraceStateObserver* observer) {
+    base::AutoLock lock(mutex_);
+    DCHECK(observers_.count(observer) == 1);
+    observers_.erase(observer);
+    if (observers_.empty()) {
+      base::trace_event::TraceLog::GetInstance()->RemoveEnabledStateObserver(
+          this);
+    }
+  }
+
+ private:
+  base::Lock mutex_;
+  std::unordered_set<v8::TracingController::TraceStateObserver*> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(EnabledStateObserverImpl);
+};
+
+base::LazyInstance<EnabledStateObserverImpl>::Leaky g_trace_state_dispatcher =
+    LAZY_INSTANCE_INITIALIZER;
+
 }  // namespace
 
+class V8Platform::TracingControllerImpl : public v8::TracingController {
+ public:
+  TracingControllerImpl() = default;
+  ~TracingControllerImpl() override = default;
+
+  // TracingController implementation.
+  const uint8_t* GetCategoryGroupEnabled(const char* name) override {
+    return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(name);
+  }
+  uint64_t AddTraceEvent(
+      char phase,
+      const uint8_t* category_enabled_flag,
+      const char* name,
+      const char* scope,
+      uint64_t id,
+      uint64_t bind_id,
+      int32_t num_args,
+      const char** arg_names,
+      const uint8_t* arg_types,
+      const uint64_t* arg_values,
+      std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables,
+      unsigned int flags) override {
+    std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
+        convertables[2];
+    if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) {
+      convertables[0].reset(
+          new ConvertableToTraceFormatWrapper(arg_convertables[0]));
+    }
+    if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) {
+      convertables[1].reset(
+          new ConvertableToTraceFormatWrapper(arg_convertables[1]));
+    }
+    DCHECK_LE(num_args, 2);
+    base::trace_event::TraceEventHandle handle =
+        TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_BIND_ID(
+            phase, category_enabled_flag, name, scope, id, bind_id, num_args,
+            arg_names, arg_types, (const long long unsigned int*)arg_values,
+            convertables, flags);
+    uint64_t result;
+    memcpy(&result, &handle, sizeof(result));
+    return result;
+  }
+  void UpdateTraceEventDuration(const uint8_t* category_enabled_flag,
+                                const char* name,
+                                uint64_t handle) override {
+    base::trace_event::TraceEventHandle traceEventHandle;
+    memcpy(&traceEventHandle, &handle, sizeof(handle));
+    TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION(category_enabled_flag, name,
+                                                traceEventHandle);
+  }
+  void AddTraceStateObserver(TraceStateObserver* observer) override {
+    g_trace_state_dispatcher.Get().AddObserver(observer);
+  }
+  void RemoveTraceStateObserver(TraceStateObserver* observer) override {
+    g_trace_state_dispatcher.Get().RemoveObserver(observer);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TracingControllerImpl);
+};
+
 // static
 V8Platform* V8Platform::Get() { return g_v8_platform.Pointer(); }
 
-V8Platform::V8Platform() {}
+V8Platform::V8Platform() : tracing_controller_(new TracingControllerImpl) {}
 
 V8Platform::~V8Platform() {}
 
@@ -125,8 +257,12 @@
       static_cast<double>(base::Time::kMicrosecondsPerSecond);
 }
 
+v8::TracingController* V8Platform::GetTracingController() {
+  return tracing_controller_.get();
+}
+
 const uint8_t* V8Platform::GetCategoryGroupEnabled(const char* name) {
-  return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(name);
+  return tracing_controller_->GetCategoryGroupEnabled(name);
 }
 
 const char* V8Platform::GetCategoryGroupName(
@@ -135,27 +271,6 @@
       category_enabled_flag);
 }
 
-namespace {
-
-class ConvertableToTraceFormatWrapper
-    : public base::trace_event::ConvertableToTraceFormat {
- public:
-  explicit ConvertableToTraceFormatWrapper(
-      std::unique_ptr<v8::ConvertableToTraceFormat>& inner)
-      : inner_(std::move(inner)) {}
-  ~ConvertableToTraceFormatWrapper() override = default;
-  void AppendAsTraceFormat(std::string* out) const final {
-    inner_->AppendAsTraceFormat(out);
-  }
-
- private:
-  std::unique_ptr<v8::ConvertableToTraceFormat> inner_;
-
-  DISALLOW_COPY_AND_ASSIGN(ConvertableToTraceFormatWrapper);
-};
-
-}  // namespace
-
 uint64_t V8Platform::AddTraceEvent(
     char phase,
     const uint8_t* category_enabled_flag,
@@ -169,101 +284,26 @@
     const uint64_t* arg_values,
     std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables,
     unsigned int flags) {
-  std::unique_ptr<base::trace_event::ConvertableToTraceFormat> convertables[2];
-  if (num_args > 0 && arg_types[0] == TRACE_VALUE_TYPE_CONVERTABLE) {
-    convertables[0].reset(
-        new ConvertableToTraceFormatWrapper(arg_convertables[0]));
-  }
-  if (num_args > 1 && arg_types[1] == TRACE_VALUE_TYPE_CONVERTABLE) {
-    convertables[1].reset(
-        new ConvertableToTraceFormatWrapper(arg_convertables[1]));
-  }
-  DCHECK_LE(num_args, 2);
-  base::trace_event::TraceEventHandle handle =
-      TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_BIND_ID(
-          phase, category_enabled_flag, name, scope, id, bind_id, num_args,
-          arg_names, arg_types, (const long long unsigned int*)arg_values,
-          convertables, flags);
-  uint64_t result;
-  memcpy(&result, &handle, sizeof(result));
-  return result;
+  return tracing_controller_->AddTraceEvent(
+      phase, category_enabled_flag, name, scope, id, bind_id, num_args,
+      arg_names, arg_types, arg_values, arg_convertables, flags);
 }
 
 void V8Platform::UpdateTraceEventDuration(const uint8_t* category_enabled_flag,
                                           const char* name,
                                           uint64_t handle) {
-  base::trace_event::TraceEventHandle traceEventHandle;
-  memcpy(&traceEventHandle, &handle, sizeof(handle));
-  TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION(category_enabled_flag, name,
-                                              traceEventHandle);
+  tracing_controller_->UpdateTraceEventDuration(category_enabled_flag, name,
+                                                handle);
 }
 
-namespace {
-
-class EnabledStateObserverImpl final
-    : public base::trace_event::TraceLog::EnabledStateObserver {
- public:
-  EnabledStateObserverImpl() = default;
-
-  void OnTraceLogEnabled() final {
-    base::AutoLock lock(mutex_);
-    for (auto* o : observers_) {
-      o->OnTraceEnabled();
-    }
-  }
-
-  void OnTraceLogDisabled() final {
-    base::AutoLock lock(mutex_);
-    for (auto* o : observers_) {
-      o->OnTraceDisabled();
-    }
-  }
-
-  void AddObserver(v8::Platform::TraceStateObserver* observer) {
-    {
-      base::AutoLock lock(mutex_);
-      DCHECK(!observers_.count(observer));
-      if (observers_.empty()) {
-        base::trace_event::TraceLog::GetInstance()->AddEnabledStateObserver(
-            this);
-      }
-      observers_.insert(observer);
-    }
-    // Fire the observer if recording is already in progress.
-    if (base::trace_event::TraceLog::GetInstance()->IsEnabled())
-      observer->OnTraceEnabled();
-  }
-
-  void RemoveObserver(v8::Platform::TraceStateObserver* observer) {
-    base::AutoLock lock(mutex_);
-    DCHECK(observers_.count(observer) == 1);
-    observers_.erase(observer);
-    if (observers_.empty()) {
-      base::trace_event::TraceLog::GetInstance()->RemoveEnabledStateObserver(
-          this);
-    }
-  }
-
- private:
-  base::Lock mutex_;
-  std::unordered_set<v8::Platform::TraceStateObserver*> observers_;
-
-  DISALLOW_COPY_AND_ASSIGN(EnabledStateObserverImpl);
-};
-
-base::LazyInstance<EnabledStateObserverImpl>::Leaky g_trace_state_dispatcher =
-    LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
 void V8Platform::AddTraceStateObserver(
     v8::Platform::TraceStateObserver* observer) {
-  g_trace_state_dispatcher.Get().AddObserver(observer);
+  tracing_controller_->AddTraceStateObserver(observer);
 }
 
 void V8Platform::RemoveTraceStateObserver(
     v8::Platform::TraceStateObserver* observer) {
-  g_trace_state_dispatcher.Get().RemoveObserver(observer);
+  tracing_controller_->RemoveTraceStateObserver(observer);
 }
 
 v8::Platform::StackTracePrinter V8Platform::GetStackTracePrinter() {
diff --git a/net/spdy/core/spdy_framer.cc b/net/spdy/core/spdy_framer.cc
index 47482b2..fc131bb 100644
--- a/net/spdy/core/spdy_framer.cc
+++ b/net/spdy/core/spdy_framer.cc
@@ -72,10 +72,6 @@
 // Used to indicate no flags in a HTTP2 flags field.
 const uint8_t kNoFlags = 0;
 
-// Wire sizes of priority payloads.
-const size_t kPriorityDependencyPayloadSize = 4;
-const size_t kPriorityWeightPayloadSize = 1;
-
 // Wire size of pad length field.
 const size_t kPadLengthFieldSize = 1;
 
@@ -215,82 +211,55 @@
 }
 
 size_t SpdyFramer::GetDataFrameMinimumSize() const {
-  return kDataFrameMinimumSize;
+  return size_utils::GetDataFrameMinimumSize();
 }
 
-// Size, in bytes, of the control frame header.
 size_t SpdyFramer::GetFrameHeaderSize() const {
-  return kFrameHeaderSize;
+  return size_utils::GetFrameHeaderSize();
 }
 
 size_t SpdyFramer::GetRstStreamSize() const {
-  // Size, in bytes, of a RST_STREAM frame.
-  // Calculated as:
-  // frame prefix + 4 (status code)
-  return GetFrameHeaderSize() + 4;
+  return size_utils::GetRstStreamSize();
 }
 
 size_t SpdyFramer::GetSettingsMinimumSize() const {
-  // Size, in bytes, of a SETTINGS frame not including the IDs and values
-  // from the variable-length value block.
-  return GetFrameHeaderSize();
+  return size_utils::GetSettingsMinimumSize();
 }
 
 size_t SpdyFramer::GetPingSize() const {
-  // Size, in bytes, of this PING frame.
-  // Calculated as:
-  // control frame header + 8 (id)
-  return GetFrameHeaderSize() + 8;
+  return size_utils::GetPingSize();
 }
 
 size_t SpdyFramer::GetGoAwayMinimumSize() const {
-  // Size, in bytes, of this GOAWAY frame. Calculated as:
-  // Control frame header + last stream id (4 bytes) + error code (4 bytes).
-  return GetFrameHeaderSize() + 8;
+  return size_utils::GetGoAwayMinimumSize();
 }
 
 size_t SpdyFramer::GetHeadersMinimumSize() const {
-  // Size, in bytes, of a HEADERS frame not including the variable-length
-  // header block.
-  return GetFrameHeaderSize();
+  return size_utils::GetFrameHeaderSize();
 }
 
 size_t SpdyFramer::GetWindowUpdateSize() const {
-  // Size, in bytes, of a WINDOW_UPDATE frame.
-  // Calculated as:
-  // frame prefix + 4 (delta)
-  return GetFrameHeaderSize() + 4;
+  return size_utils::GetWindowUpdateSize();
 }
 
 size_t SpdyFramer::GetPushPromiseMinimumSize() const {
-  // Size, in bytes, of a PUSH_PROMISE frame, sans the embedded header block.
-  // Calculated as frame prefix + 4 (promised stream id)
-  return GetFrameHeaderSize() + 4;
+  return size_utils::GetPushPromiseMinimumSize();
 }
 
 size_t SpdyFramer::GetContinuationMinimumSize() const {
-  // Size, in bytes, of a CONTINUATION frame not including the variable-length
-  // headers fragments.
-  return GetFrameHeaderSize();
+  return size_utils::GetContinuationMinimumSize();
 }
 
 size_t SpdyFramer::GetAltSvcMinimumSize() const {
-  // Size, in bytes, of an ALTSVC frame not including the Field-Value and
-  // (optional) Origin fields, both of which can vary in length.  Note that
-  // this gives a lower bound on the frame size rather than a true minimum;
-  // the actual frame should always be larger than this.
-  // Calculated as frame prefix + 2 (origin_len).
-  return GetFrameHeaderSize() + 2;
+  return size_utils::GetAltSvcMinimumSize();
 }
 
 size_t SpdyFramer::GetPrioritySize() const {
-  // Size, in bytes, of a PRIORITY frame.
-  return GetFrameHeaderSize() + kPriorityDependencyPayloadSize +
-         kPriorityWeightPayloadSize;
+  return size_utils::GetPrioritySize();
 }
 
 size_t SpdyFramer::GetFrameMinimumSize() const {
-  return GetFrameHeaderSize();
+  return size_utils::GetFrameMinimumSize();
 }
 
 size_t SpdyFramer::GetFrameMaximumSize() const {
diff --git a/net/spdy/core/spdy_protocol.cc b/net/spdy/core/spdy_protocol.cc
index 89b9c9541..c95fc0f 100644
--- a/net/spdy/core/spdy_protocol.cc
+++ b/net/spdy/core/spdy_protocol.cc
@@ -13,6 +13,89 @@
 const char* const kHttp2ConnectionHeaderPrefix =
     "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
 
+namespace size_utils {
+
+size_t GetDataFrameMinimumSize() {
+  return kDataFrameMinimumSize;
+}
+
+// Size, in bytes, of the control frame header.
+size_t GetFrameHeaderSize() {
+  return kFrameHeaderSize;
+}
+
+size_t GetRstStreamSize() {
+  // Size, in bytes, of a RST_STREAM frame.
+  // Calculated as:
+  // frame prefix + 4 (status code)
+  return GetFrameHeaderSize() + 4;
+}
+
+size_t GetSettingsMinimumSize() {
+  // Size, in bytes, of a SETTINGS frame not including the IDs and values
+  // from the variable-length value block.
+  return GetFrameHeaderSize();
+}
+
+size_t GetPingSize() {
+  // Size, in bytes, of this PING frame.
+  // Calculated as:
+  // control frame header + 8 (id)
+  return GetFrameHeaderSize() + 8;
+}
+
+size_t GetGoAwayMinimumSize() {
+  // Size, in bytes, of this GOAWAY frame. Calculated as:
+  // Control frame header + last stream id (4 bytes) + error code (4 bytes).
+  return GetFrameHeaderSize() + 8;
+}
+
+size_t GetHeadersMinimumSize() {
+  // Size, in bytes, of a HEADERS frame not including the variable-length
+  // header block.
+  return GetFrameHeaderSize();
+}
+
+size_t GetWindowUpdateSize() {
+  // Size, in bytes, of a WINDOW_UPDATE frame.
+  // Calculated as:
+  // frame prefix + 4 (delta)
+  return GetFrameHeaderSize() + 4;
+}
+
+size_t GetPushPromiseMinimumSize() {
+  // Size, in bytes, of a PUSH_PROMISE frame, sans the embedded header block.
+  // Calculated as frame prefix + 4 (promised stream id)
+  return GetFrameHeaderSize() + 4;
+}
+
+size_t GetContinuationMinimumSize() {
+  // Size, in bytes, of a CONTINUATION frame not including the variable-length
+  // headers fragments.
+  return GetFrameHeaderSize();
+}
+
+size_t GetAltSvcMinimumSize() {
+  // Size, in bytes, of an ALTSVC frame not including the Field-Value and
+  // (optional) Origin fields, both of which can vary in length.  Note that
+  // this gives a lower bound on the frame size rather than a true minimum;
+  // the actual frame should always be larger than this.
+  // Calculated as frame prefix + 2 (origin_len).
+  return GetFrameHeaderSize() + 2;
+}
+
+size_t GetPrioritySize() {
+  // Size, in bytes, of a PRIORITY frame.
+  return GetFrameHeaderSize() + kPriorityDependencyPayloadSize +
+         kPriorityWeightPayloadSize;
+}
+
+size_t GetFrameMinimumSize() {
+  return GetFrameHeaderSize();
+}
+
+}  // namespace size_utils
+
 std::ostream& operator<<(std::ostream& out, SpdySettingsIds id) {
   return out << static_cast<uint16_t>(id);
 }
@@ -208,6 +291,10 @@
 
 const char* const kHttp2Npn = "h2";
 
+int SpdyFrameIR::flow_control_window_consumed() const {
+  return 0;
+}
+
 SpdyFrameWithHeaderBlockIR::SpdyFrameWithHeaderBlockIR(
     SpdyStreamId stream_id,
     SpdyHeaderBlock header_block)
@@ -252,6 +339,10 @@
   return SpdyFrameType::DATA;
 }
 
+int SpdyDataIR::flow_control_window_consumed() const {
+  return padded() ? 1 + padding_payload_len() + data_len() : data_len();
+}
+
 SpdyRstStreamIR::SpdyRstStreamIR(SpdyStreamId stream_id,
                                  SpdyErrorCode error_code)
     : SpdyFrameIR(stream_id) {
@@ -389,4 +480,12 @@
   return static_cast<SpdyFrameType>(type());
 }
 
+int SpdyUnknownIR::flow_control_window_consumed() const {
+  if (frame_type() == SpdyFrameType::DATA) {
+    return payload_.size();
+  } else {
+    return 0;
+  }
+}
+
 }  // namespace net
diff --git a/net/spdy/core/spdy_protocol.h b/net/spdy/core/spdy_protocol.h
index d40f6fb..12fe4151 100644
--- a/net/spdy/core/spdy_protocol.h
+++ b/net/spdy/core/spdy_protocol.h
@@ -277,6 +277,31 @@
 // The NPN string for HTTP2, "h2".
 extern const char* const kHttp2Npn;
 
+// Wire sizes of priority payloads.
+const size_t kPriorityDependencyPayloadSize = 4;
+const size_t kPriorityWeightPayloadSize = 1;
+
+namespace size_utils {
+
+// Returns the (minimum) size of frames (sans variable-length portions).
+size_t GetDataFrameMinimumSize();
+size_t GetFrameHeaderSize();
+size_t GetRstStreamSize();
+size_t GetSettingsMinimumSize();
+size_t GetPingSize();
+size_t GetGoAwayMinimumSize();
+size_t GetHeadersMinimumSize();
+size_t GetWindowUpdateSize();
+size_t GetPushPromiseMinimumSize();
+size_t GetContinuationMinimumSize();
+size_t GetAltSvcMinimumSize();
+size_t GetPrioritySize();
+
+// Returns the minimum size a frame can be (data or control).
+size_t GetFrameMinimumSize();
+
+}  // namespace size_utils
+
 // Variant type (i.e. tagged union) that is either a SPDY 3.x priority value,
 // or else an HTTP/2 stream dependency tuple {parent stream ID, weight,
 // exclusive bit}. Templated to allow for use by QUIC code; SPDY and HTTP/2
@@ -384,6 +409,10 @@
   virtual SpdyFrameType frame_type() const = 0;
   SpdyStreamId stream_id() const { return stream_id_; }
 
+  // Returns the number of bytes of flow control window that would be consumed
+  // by this frame if written to the wire.
+  virtual int flow_control_window_consumed() const;
+
  protected:
   SpdyFrameIR() : stream_id_(0) {}
   explicit SpdyFrameIR(SpdyStreamId stream_id) : stream_id_(stream_id) {}
@@ -499,6 +528,8 @@
 
   SpdyFrameType frame_type() const override;
 
+  int flow_control_window_consumed() const override;
+
  private:
   // Used to store data that this SpdyDataIR should own.
   std::unique_ptr<SpdyString> data_store_;
@@ -812,6 +843,8 @@
 
   SpdyFrameType frame_type() const override;
 
+  int flow_control_window_consumed() const override;
+
  private:
   uint8_t type_;
   uint8_t flags_;
diff --git a/net/spdy/core/spdy_protocol_test.cc b/net/spdy/core/spdy_protocol_test.cc
index cc222605..e5a71fe 100644
--- a/net/spdy/core/spdy_protocol_test.cc
+++ b/net/spdy/core/spdy_protocol_test.cc
@@ -230,11 +230,13 @@
   SpdyDataIR d2(/* stream_id = */ 2, s2);
   EXPECT_EQ(SpdyStringPiece(d2.data(), d2.data_len()), s2);
   EXPECT_NE(SpdyStringPiece(d1.data(), d1.data_len()), s2);
+  EXPECT_EQ((int)d1.data_len(), d1.flow_control_window_consumed());
 
   // Confirm copies a const string.
   const SpdyString foo = "foo";
   SpdyDataIR d3(/* stream_id = */ 3, foo);
   EXPECT_EQ(foo, d3.data());
+  EXPECT_EQ((int)d3.data_len(), d3.flow_control_window_consumed());
 
   // Confirm copies a non-const string.
   SpdyString bar = "bar";
@@ -252,6 +254,10 @@
   // Confirms makes a copy of string literal.
   SpdyDataIR d7(/* stream_id = */ 7, "something else");
   EXPECT_EQ(SpdyStringPiece(d7.data(), d7.data_len()), "something else");
+
+  SpdyDataIR d8(/* stream_id = */ 8, "shawarma");
+  d8.set_padding_len(20);
+  EXPECT_EQ(28, d8.flow_control_window_consumed());
 }
 
 }  // namespace test
diff --git a/printing/DEPS b/printing/DEPS
index 038006b3..8cfa7a2 100644
--- a/printing/DEPS
+++ b/printing/DEPS
@@ -9,5 +9,4 @@
   "+ui/base/resource",
   "+ui/base/text",
   "+ui/gfx",
-  "+win8/util",
 ]
diff --git a/remoting/client/input/keyboard_input_strategy.h b/remoting/client/input/keyboard_input_strategy.h
index 9f18dc6..389d87f 100644
--- a/remoting/client/input/keyboard_input_strategy.h
+++ b/remoting/client/input/keyboard_input_strategy.h
@@ -23,8 +23,8 @@
 
   // Handle a text event.
   virtual void HandleTextEvent(const std::string& text, uint8_t modifiers) = 0;
-  // Handle delete event.
-  virtual void HandleDeleteEvent(uint8_t modifiers) = 0;
+  // Handle keys event as keycodes.
+  virtual void HandleKeysEvent(std::queue<KeyEvent> keys) = 0;
 };
 
 }  // namespace remoting
diff --git a/remoting/client/input/keyboard_interpreter.cc b/remoting/client/input/keyboard_interpreter.cc
index 725956a..55b28e4 100644
--- a/remoting/client/input/keyboard_interpreter.cc
+++ b/remoting/client/input/keyboard_interpreter.cc
@@ -6,6 +6,7 @@
 
 #include "base/logging.h"
 #include "remoting/client/input/text_keyboard_input_strategy.h"
+#include "ui/events/keycodes/dom/dom_code.h"
 
 namespace remoting {
 
@@ -22,7 +23,43 @@
 }
 
 void KeyboardInterpreter::HandleDeleteEvent(uint8_t modifiers) {
-  input_strategy_->HandleDeleteEvent(modifiers);
+  std::queue<KeyEvent> keys;
+  // TODO(nicholss): Handle modifers.
+  // Key press.
+  keys.push({static_cast<uint32_t>(ui::DomCode::BACKSPACE), true});
+
+  // Key release.
+  keys.push({static_cast<uint32_t>(ui::DomCode::BACKSPACE), false});
+
+  input_strategy_->HandleKeysEvent(keys);
+}
+
+void KeyboardInterpreter::HandleCtrlAltDeleteEvent() {
+  std::queue<KeyEvent> keys;
+
+  // Key press.
+  keys.push({static_cast<uint32_t>(ui::DomCode::CONTROL_LEFT), true});
+  keys.push({static_cast<uint32_t>(ui::DomCode::ALT_LEFT), true});
+  keys.push({static_cast<uint32_t>(ui::DomCode::DEL), true});
+
+  // Key release.
+  keys.push({static_cast<uint32_t>(ui::DomCode::DEL), false});
+  keys.push({static_cast<uint32_t>(ui::DomCode::ALT_LEFT), false});
+  keys.push({static_cast<uint32_t>(ui::DomCode::CONTROL_LEFT), false});
+
+  input_strategy_->HandleKeysEvent(keys);
+}
+
+void KeyboardInterpreter::HandlePrintScreenEvent() {
+  std::queue<KeyEvent> keys;
+
+  // Key press.
+  keys.push({static_cast<uint32_t>(ui::DomCode::PRINT_SCREEN), true});
+
+  // Key release.
+  keys.push({static_cast<uint32_t>(ui::DomCode::PRINT_SCREEN), false});
+
+  input_strategy_->HandleKeysEvent(keys);
 }
 
 }  // namespace remoting
diff --git a/remoting/client/input/keyboard_interpreter.h b/remoting/client/input/keyboard_interpreter.h
index 3780397..300b43ad 100644
--- a/remoting/client/input/keyboard_interpreter.h
+++ b/remoting/client/input/keyboard_interpreter.h
@@ -26,6 +26,12 @@
   void HandleTextEvent(const std::string& text, uint8_t modifiers);
   // Delegates to |KeyboardInputStrategy| to covert and send the delete.
   void HandleDeleteEvent(uint8_t modifiers);
+  // Assembles CTRL+ALT+DEL key event and then delegates to
+  // |KeyboardInputStrategy| send the keys.
+  void HandleCtrlAltDeleteEvent();
+  // Assembles PRINT_SCREEN key event and then delegates to
+  // |KeyboardInputStrategy| send the keys.
+  void HandlePrintScreenEvent();
 
  private:
   std::unique_ptr<KeyboardInputStrategy> input_strategy_;
diff --git a/remoting/client/input/text_keyboard_input_strategy.cc b/remoting/client/input/text_keyboard_input_strategy.cc
index c1b11c59..e4d2061 100644
--- a/remoting/client/input/text_keyboard_input_strategy.cc
+++ b/remoting/client/input/text_keyboard_input_strategy.cc
@@ -24,8 +24,7 @@
   input_injector_->SendTextEvent(text);
 }
 
-void TextKeyboardInputStrategy::HandleDeleteEvent(uint8_t modifiers) {
-  std::queue<KeyEvent> keys = ConvertDeleteEvent(modifiers);
+void TextKeyboardInputStrategy::HandleKeysEvent(std::queue<KeyEvent> keys) {
   while (!keys.empty()) {
     KeyEvent key = keys.front();
     input_injector_->SendKeyEvent(0, key.keycode, key.keydown);
@@ -33,17 +32,4 @@
   }
 }
 
-std::queue<KeyEvent> TextKeyboardInputStrategy::ConvertDeleteEvent(
-    uint8_t modifiers) {
-  std::queue<KeyEvent> keys;
-  // TODO(nicholss): Handle modifers.
-  // Key press.
-  keys.push({static_cast<uint32_t>(ui::DomCode::BACKSPACE), true});
-
-  // Key release.
-  keys.push({static_cast<uint32_t>(ui::DomCode::BACKSPACE), false});
-
-  return keys;
-}
-
 }  // namespace remoting
diff --git a/remoting/client/input/text_keyboard_input_strategy.h b/remoting/client/input/text_keyboard_input_strategy.h
index 4772449..0c6ca1fa 100644
--- a/remoting/client/input/text_keyboard_input_strategy.h
+++ b/remoting/client/input/text_keyboard_input_strategy.h
@@ -21,7 +21,7 @@
 
   // KeyboardInputStrategy overrides.
   void HandleTextEvent(const std::string& text, uint8_t modifiers) override;
-  void HandleDeleteEvent(uint8_t modifiers) override;
+  void HandleKeysEvent(std::queue<KeyEvent> keys) override;
 
  private:
   std::queue<KeyEvent> ConvertDeleteEvent(uint8_t modifiers);
diff --git a/remoting/ios/app/app_delegate.h b/remoting/ios/app/app_delegate.h
index 9235202..68320f30 100644
--- a/remoting/ios/app/app_delegate.h
+++ b/remoting/ios/app/app_delegate.h
@@ -17,6 +17,17 @@
 @property(strong, nonatomic) UIWindow* window;
 @property(class, strong, nonatomic, readonly) AppDelegate* instance;
 
+// This will push the FAQ view controller onto the provided nav controller.
+- (void)navigateToFAQs:(UINavigationController*)navigationController;
+
+// This will push the Help Center view controller onto the provided nav
+// controller.
+- (void)navigateToHelpCenter:(UINavigationController*)navigationController;
+
+// This will push the Send Feedback view controller onto the provided nav
+// controller.
+- (void)navigateToSendFeedback:(UINavigationController*)navigationController;
+
 @end
 
 #endif  // REMOTING_IOS_APP_APP_DELEGATE_H_
diff --git a/remoting/ios/app/app_delegate.mm b/remoting/ios/app/app_delegate.mm
index 9a9abec..4b137f1 100644
--- a/remoting/ios/app/app_delegate.mm
+++ b/remoting/ios/app/app_delegate.mm
@@ -8,6 +8,8 @@
 
 #import "remoting/ios/app/app_delegate.h"
 
+#import <WebKit/WebKit.h>
+
 #include "base/logging.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -21,6 +23,13 @@
 }
 @end
 
+// TODO(nicholss): These urls should come from a global config.
+static NSString* const kHelpCenterUrl =
+    @"https://support.google.com/chrome/answer/1649523?co=GENIE.Platform%3DiOS";
+// TODO(nicholss): There is no FAQ page at the moment.
+static NSString* const kFAQsUrl =
+    @"https://support.google.com/chrome/answer/1649523?co=GENIE.Platform%3DiOS";
+
 @implementation AppDelegate
 
 @synthesize window = _window;
@@ -82,7 +91,9 @@
 #pragma mark - Properties
 
 + (AppDelegate*)instance {
-  return (AppDelegate*)UIApplication.sharedApplication.delegate;
+  id<UIApplicationDelegate> delegate = UIApplication.sharedApplication.delegate;
+  DCHECK([delegate isKindOfClass:AppDelegate.class]);
+  return (AppDelegate*)delegate;
 }
 
 #pragma mark - Private
@@ -98,4 +109,42 @@
   [self.window makeKeyAndVisible];
 }
 
+#pragma mark - AppDelegate
+
+- (void)navigateToFAQs:(UINavigationController*)navigationController {
+  [self navigateToUrl:kFAQsUrl
+                     title:@"FAQs"
+      navigationController:navigationController];
+}
+
+- (void)navigateToHelpCenter:(UINavigationController*)navigationController {
+  [self navigateToUrl:kHelpCenterUrl
+                     title:@"Help Center"
+      navigationController:navigationController];
+}
+
+- (void)navigateToSendFeedback:(UINavigationController*)navigationController {
+  UIViewController* feedbackController = [[UIViewController alloc] init];
+  feedbackController.title = @"Feedback";
+  [navigationController pushViewController:feedbackController animated:YES];
+}
+
+#pragma mark - Private
+
+- (void)navigateToUrl:(NSString*)url
+                   title:(NSString*)title
+    navigationController:(UINavigationController*)navigationController {
+  UIViewController* viewController = [[UIViewController alloc] init];
+  viewController.title = title;
+
+  NSURLRequest* request =
+      [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
+  WKWebView* webView =
+      [[WKWebView alloc] initWithFrame:viewController.view.frame
+                         configuration:[[WKWebViewConfiguration alloc] init]];
+  [viewController.view addSubview:webView];
+  [navigationController pushViewController:viewController animated:YES];
+  [webView loadRequest:request];
+}
+
 @end
diff --git a/remoting/ios/app/host_view_controller.mm b/remoting/ios/app/host_view_controller.mm
index b761e2d..6bf3993 100644
--- a/remoting/ios/app/host_view_controller.mm
+++ b/remoting/ios/app/host_view_controller.mm
@@ -28,7 +28,8 @@
 static const CGFloat kKeyboardAnimationTime = 0.3;
 
 @interface HostViewController ()<ClientKeyboardDelegate,
-                                 ClientGesturesDelegate> {
+                                 ClientGesturesDelegate,
+                                 RemotingSettingsViewControllerDelegate> {
   RemotingClient* _client;
   MDCActionImageView* _actionImageView;
   MDCFloatingButton* _floatingButton;
@@ -226,6 +227,40 @@
   [self hideKeyboard];
 }
 
+#pragma mark - RemotingSettingsViewControllerDelegate
+
+- (void)setShrinkToFit:(BOOL)shrinkToFit {
+  // TODO(nicholss): I don't think this option makes sense for mobile.
+  NSLog(@"TODO: shrinkToFit %d", shrinkToFit);
+}
+
+- (void)setResizeToFit:(BOOL)resizeToFit {
+  // TODO(nicholss): I don't think this option makes sense for phones. Maybe
+  // for an iPad. Maybe we add a native screen size mimimum before enabling
+  // this option? Ask Jon.
+  NSLog(@"TODO: resizeToFit %d", resizeToFit);
+}
+
+- (void)useDirectInputMode {
+  // TODO(nicholss): Store this as a preference.
+  _client.gestureInterpreter->SetInputMode(
+      remoting::GestureInterpreter::DIRECT_INPUT_MODE);
+}
+
+- (void)useTrackpadInputMode {
+  // TODO(nicholss): Store this as a preference.
+  _client.gestureInterpreter->SetInputMode(
+      remoting::GestureInterpreter::TRACKPAD_INPUT_MODE);
+}
+
+- (void)sendCtrAltDel {
+  _client.keyboardInterpreter->HandleCtrlAltDeleteEvent();
+}
+
+- (void)sendPrintScreen {
+  _client.keyboardInterpreter->HandlePrintScreenEvent();
+}
+
 #pragma mark - Private
 
 - (void)didTap:(id)sender {
@@ -241,6 +276,7 @@
   if ([self isKeyboardActive]) {
     void (^hideKeyboardHandler)(UIAlertAction*) = ^(UIAlertAction*) {
       [self hideKeyboard];
+      [_actionImageView setActive:NO animated:YES];
     };
     [alert addAction:[UIAlertAction actionWithTitle:@"Hide Keyboard"
                                               style:UIAlertActionStyleDefault
@@ -248,6 +284,7 @@
   } else {
     void (^showKeyboardHandler)(UIAlertAction*) = ^(UIAlertAction*) {
       [self showKeyboard];
+      [_actionImageView setActive:NO animated:YES];
     };
     [alert addAction:[UIAlertAction actionWithTitle:@"Show Keyboard"
                                               style:UIAlertActionStyleDefault
@@ -265,6 +302,7 @@
         currentInputMode == remoting::GestureInterpreter::DIRECT_INPUT_MODE
             ? remoting::GestureInterpreter::TRACKPAD_INPUT_MODE
             : remoting::GestureInterpreter::DIRECT_INPUT_MODE);
+    [_actionImageView setActive:NO animated:YES];
   };
   [alert addAction:[UIAlertAction actionWithTitle:switchInputModeTitle
                                             style:UIAlertActionStyleDefault
@@ -273,6 +311,7 @@
   void (^disconnectHandler)(UIAlertAction*) = ^(UIAlertAction*) {
     [_client disconnectFromHost];
     [self.navigationController popViewControllerAnimated:YES];
+    [_actionImageView setActive:NO animated:YES];
   };
   [alert addAction:[UIAlertAction actionWithTitle:@"Disconnect"
                                             style:UIAlertActionStyleDefault
@@ -282,9 +321,12 @@
   void (^settingsHandler)(UIAlertAction*) = ^(UIAlertAction*) {
     RemotingSettingsViewController* settingsViewController =
         [[RemotingSettingsViewController alloc] init];
-    [weakSelf presentViewController:settingsViewController
-                           animated:YES
-                         completion:nil];
+    settingsViewController.delegate = weakSelf;
+    settingsViewController.inputMode = currentInputMode;
+    UINavigationController* navController = [[UINavigationController alloc]
+        initWithRootViewController:settingsViewController];
+    [weakSelf presentViewController:navController animated:YES completion:nil];
+    [_actionImageView setActive:NO animated:YES];
   };
   [alert addAction:[UIAlertAction actionWithTitle:@"Settings"
                                             style:UIAlertActionStyleDefault
diff --git a/remoting/ios/app/settings/remoting_settings_view_controller.h b/remoting/ios/app/settings/remoting_settings_view_controller.h
index 8a8957e8..c675336 100644
--- a/remoting/ios/app/settings/remoting_settings_view_controller.h
+++ b/remoting/ios/app/settings/remoting_settings_view_controller.h
@@ -7,7 +7,36 @@
 
 #import "ios/third_party/material_components_ios/src/components/Collections/src/MaterialCollections.h"
 
+#include "remoting/client/gesture_interpreter.h"
+
+@protocol RemotingSettingsViewControllerDelegate<NSObject>
+
+@optional  // Applies to all methods.
+
+// Sets the setting to shrink the display of the host to the client window.
+- (void)setShrinkToFit:(BOOL)shrinkToFit;
+// Sets the setting to resize the host to fix the client window.
+- (void)setResizeToFit:(BOOL)resizeToFit;
+// Use the direct input mode for the current connection.
+- (void)useDirectInputMode;
+// Use the trackpad input mode for the current connection.
+- (void)useTrackpadInputMode;
+// Sends the key combo ctrl + alt + del to the host.
+- (void)sendCtrAltDel;
+// Sends the key Print Screen to the host.
+- (void)sendPrintScreen;
+
+@end
+
+// |RemotingSettingsViewController| is intended to be reinitialized before it
+// is displayed. It only synchronizes to the delegate.
+// TODO(nicholss): Perhaps we should remove the internal settings storage and
+// get the state from the delegate directly. It will remove the sync issue.
 @interface RemotingSettingsViewController : MDCCollectionViewController
+
+@property(weak, nonatomic) id<RemotingSettingsViewControllerDelegate> delegate;
+@property(nonatomic) remoting::GestureInterpreter::InputMode inputMode;
+
 @end
 
 #endif  // REMOTING_IOS_APP_REMOTING_SETTINGS_SETTINGS_VIEW_CONTROLLER_H_
diff --git a/remoting/ios/app/settings/remoting_settings_view_controller.mm b/remoting/ios/app/settings/remoting_settings_view_controller.mm
index 664b1e4..923847a 100644
--- a/remoting/ios/app/settings/remoting_settings_view_controller.mm
+++ b/remoting/ios/app/settings/remoting_settings_view_controller.mm
@@ -10,6 +10,7 @@
 
 #import "ios/third_party/material_components_ios/src/components/AppBar/src/MaterialAppBar.h"
 #import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
+#import "remoting/ios/app/app_delegate.h"
 #import "remoting/ios/app/remoting_theme.h"
 #import "remoting/ios/app/settings/setting_option.h"
 
@@ -26,6 +27,9 @@
 
 @implementation RemotingSettingsViewController
 
+@synthesize delegate = _delegate;
+@synthesize inputMode = _inputMode;
+
 - (id)init {
   self = [super init];
   if (self) {
@@ -73,70 +77,15 @@
   _sections = @[
     @"Display options", @"Mouse options", @"Keyboard controls", @"Support"
   ];
-
-  _content = [NSMutableArray array];
-
-  SettingOption* shrinkOption = [[SettingOption alloc] init];
-  shrinkOption.title = @"Shrink to fit";
-  // TODO(nicholss): I think this text changes based on value. Confirm.
-  shrinkOption.subtext = @"Don't change resolution to match window";
-  shrinkOption.style = OptionCheckbox;
-  shrinkOption.checked = NO;
-
-  SettingOption* resizeOption = [[SettingOption alloc] init];
-  resizeOption.title = @"Resize to fit";
-  // TODO(nicholss): I think this text changes based on value. Confirm.
-  resizeOption.subtext = @"Update remote resolution to match window";
-  resizeOption.style = OptionCheckbox;
-  resizeOption.checked = YES;
-
-  [_content addObject:@[ shrinkOption, resizeOption ]];
-
-  SettingOption* directMode = [[SettingOption alloc] init];
-  directMode.title = @"Touch mode";
-  // TODO(nicholss): I think this text changes based on value. Confirm.
-  directMode.subtext = @"Screen acts like a touch screen";
-  directMode.style = OptionSelector;
-  directMode.checked = YES;
-
-  SettingOption* trackpadMode = [[SettingOption alloc] init];
-  trackpadMode.title = @"Trackpad mode";
-  // TODO(nicholss): I think this text changes based on value. Confirm.
-  trackpadMode.subtext = @"Screen acts like a trackpad";
-  trackpadMode.style = OptionSelector;
-  trackpadMode.checked = NO;
-
-  [_content addObject:@[ directMode, trackpadMode ]];
-
-  SettingOption* ctrlAltDelOption = [[SettingOption alloc] init];
-  ctrlAltDelOption.title = @"Press \"Ctrl+Alt+Del\"";
-  ctrlAltDelOption.style = FlatButton;
-
-  SettingOption* printScreenOption = [[SettingOption alloc] init];
-  printScreenOption.title = @"Press \"Print Screen\"";
-  printScreenOption.style = FlatButton;
-
-  [_content addObject:@[ ctrlAltDelOption, printScreenOption ]];
-
-  SettingOption* helpCenterOption = [[SettingOption alloc] init];
-  helpCenterOption.title = @"Help center";
-  helpCenterOption.style = FlatButton;
-
-  SettingOption* faqsOption = [[SettingOption alloc] init];
-  faqsOption.title = @"FAQs";
-  faqsOption.style = FlatButton;
-
-  SettingOption* sendFeedbackOption = [[SettingOption alloc] init];
-  sendFeedbackOption.title = @"Send feedback";
-  sendFeedbackOption.style = FlatButton;
-
-  [_content addObject:@[ helpCenterOption, faqsOption, sendFeedbackOption ]];
-
-  DCHECK_EQ(_content.count, _sections.count);
-
   self.styler.cellStyle = MDCCollectionViewCellStyleCard;
 }
 
+- (void)viewWillAppear:(BOOL)animated {
+  [super viewWillAppear:animated];
+  [self.navigationController setNavigationBarHidden:YES animated:animated];
+  [self loadContent];
+}
+
 #pragma mark - UICollectionViewDataSource
 
 - (NSInteger)numberOfSectionsInCollectionView:
@@ -208,10 +157,34 @@
     didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
   [super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
 
-  MDCCollectionViewTextCell* cell = (MDCCollectionViewTextCell*)[collectionView
-      cellForItemAtIndexPath:indexPath];
+  SettingOption* setting = _content[indexPath.section][indexPath.item];
 
-  NSLog(@"Tapped: %@", cell);
+  NSMutableArray* updatedIndexPaths = [NSMutableArray arrayWithCapacity:1];
+  int i = 0;
+  switch (setting.style) {
+    case OptionCheckbox:
+      setting.checked = !setting.checked;
+      [updatedIndexPaths
+          addObject:[NSIndexPath indexPathForItem:indexPath.item
+                                        inSection:indexPath.section]];
+      break;
+    case OptionSelector:
+      for (SettingOption* s in _content[indexPath.section]) {
+        s.checked = NO;
+        [updatedIndexPaths
+            addObject:[NSIndexPath indexPathForItem:i
+                                          inSection:indexPath.section]];
+        i++;
+      }
+      setting.checked = YES;
+      break;
+    case FlatButton:
+      break;
+  }
+  [self.collectionView reloadItemsAtIndexPaths:updatedIndexPaths];
+  if (setting.action) {
+    setting.action();
+  }
 }
 
 - (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView
@@ -245,4 +218,115 @@
   [self dismissViewControllerAnimated:YES completion:nil];
 }
 
+- (void)loadContent {
+  _content = [NSMutableArray array];
+
+  __weak RemotingSettingsViewController* weakSelf = self;
+
+  SettingOption* shrinkOption = [[SettingOption alloc] init];
+  shrinkOption.title = @"Shrink to fit";
+  // TODO(nicholss): I think this text changes based on value. Confirm.
+  shrinkOption.subtext = @"Don't change resolution to match window";
+  shrinkOption.style = OptionCheckbox;
+  shrinkOption.checked = NO;
+  __weak SettingOption* weakShrinkOption = shrinkOption;
+  shrinkOption.action = ^{
+    if ([weakSelf.delegate respondsToSelector:@selector(setShrinkToFit:)]) {
+      [weakSelf.delegate setShrinkToFit:weakShrinkOption.checked];
+    }
+  };
+
+  SettingOption* resizeOption = [[SettingOption alloc] init];
+  resizeOption.title = @"Resize to fit";
+  // TODO(nicholss): I think this text changes based on value. Confirm.
+  resizeOption.subtext = @"Update remote resolution to match window";
+  resizeOption.style = OptionCheckbox;
+  resizeOption.checked = YES;
+  __weak SettingOption* weakResizeOption = resizeOption;
+  resizeOption.action = ^{
+    if ([weakSelf.delegate respondsToSelector:@selector(setResizeToFit:)]) {
+      [weakSelf.delegate setResizeToFit:weakResizeOption.checked];
+    }
+  };
+
+  [_content addObject:@[ shrinkOption, resizeOption ]];
+
+  SettingOption* directMode = [[SettingOption alloc] init];
+  directMode.title = @"Touch mode";
+  // TODO(nicholss): I think this text changes based on value. Confirm.
+  directMode.subtext = @"Screen acts like a touch screen";
+  directMode.style = OptionSelector;
+  directMode.checked =
+      self.inputMode == remoting::GestureInterpreter::DIRECT_INPUT_MODE;
+  directMode.action = ^{
+    if ([weakSelf.delegate respondsToSelector:@selector(useDirectInputMode)]) {
+      [weakSelf.delegate useDirectInputMode];
+    }
+  };
+
+  SettingOption* trackpadMode = [[SettingOption alloc] init];
+  trackpadMode.title = @"Trackpad mode";
+  // TODO(nicholss): I think this text changes based on value. Confirm.
+  trackpadMode.subtext = @"Screen acts like a trackpad";
+  trackpadMode.style = OptionSelector;
+  trackpadMode.checked =
+      self.inputMode == remoting::GestureInterpreter::TRACKPAD_INPUT_MODE;
+  trackpadMode.action = ^{
+    if ([weakSelf.delegate
+            respondsToSelector:@selector(useTrackpadInputMode)]) {
+      [weakSelf.delegate useTrackpadInputMode];
+    }
+  };
+
+  [_content addObject:@[ directMode, trackpadMode ]];
+
+  SettingOption* ctrlAltDelOption = [[SettingOption alloc] init];
+  ctrlAltDelOption.title = @"Press \"Ctrl+Alt+Del\"";
+  ctrlAltDelOption.style = FlatButton;
+  ctrlAltDelOption.action = ^{
+    if ([weakSelf.delegate respondsToSelector:@selector(sendCtrAltDel)]) {
+      [weakSelf.delegate sendCtrAltDel];
+    }
+  };
+
+  SettingOption* printScreenOption = [[SettingOption alloc] init];
+  printScreenOption.title = @"Press \"Print Screen\"";
+  printScreenOption.style = FlatButton;
+  printScreenOption.action = ^{
+    if ([weakSelf.delegate respondsToSelector:@selector(sendPrintScreen)]) {
+      [weakSelf.delegate sendPrintScreen];
+    }
+  };
+
+  [_content addObject:@[ ctrlAltDelOption, printScreenOption ]];
+
+  SettingOption* helpCenterOption = [[SettingOption alloc] init];
+  helpCenterOption.title = @"Help center";
+  helpCenterOption.style = FlatButton;
+  helpCenterOption.action = ^{
+    [AppDelegate.instance navigateToHelpCenter:weakSelf.navigationController];
+    [weakSelf.navigationController setNavigationBarHidden:NO animated:YES];
+  };
+
+  SettingOption* faqsOption = [[SettingOption alloc] init];
+  faqsOption.title = @"FAQs";
+  faqsOption.style = FlatButton;
+  faqsOption.action = ^{
+    [AppDelegate.instance navigateToFAQs:weakSelf.navigationController];
+    [weakSelf.navigationController setNavigationBarHidden:NO animated:YES];
+  };
+
+  SettingOption* sendFeedbackOption = [[SettingOption alloc] init];
+  sendFeedbackOption.title = @"Send feedback";
+  sendFeedbackOption.style = FlatButton;
+  sendFeedbackOption.action = ^{
+    [AppDelegate.instance navigateToSendFeedback:self.navigationController];
+    [weakSelf.navigationController setNavigationBarHidden:NO animated:YES];
+  };
+
+  [_content addObject:@[ helpCenterOption, faqsOption, sendFeedbackOption ]];
+
+  DCHECK_EQ(_content.count, _sections.count);
+}
+
 @end
diff --git a/testing/android/empty_apk/AndroidManifest.xml b/testing/android/empty_apk/AndroidManifest.xml
new file mode 100644
index 0000000..610dcdd
--- /dev/null
+++ b/testing/android/empty_apk/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="{{package}}">
+</manifest>
diff --git a/testing/android/empty_apk/empty_apk.gni b/testing/android/empty_apk/empty_apk.gni
new file mode 100644
index 0000000..7d6ca46f
--- /dev/null
+++ b/testing/android/empty_apk/empty_apk.gni
@@ -0,0 +1,22 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+template("empty_apk") {
+  manifest_target_name = "${target_name}__manifest"
+  manifest_path = "${target_gen_dir}/${target_name}/AndroidManifest.xml"
+
+  jinja_template(manifest_target_name) {
+    input = "//testing/android/empty_apk/AndroidManifest.xml"
+    output = manifest_path
+    variables = [ "package=${invoker.package_name}" ]
+  }
+
+  android_apk(target_name) {
+    forward_variables_from(invoker, "*")
+    android_manifest = manifest_path
+    android_manifest_dep = ":$manifest_target_name"
+  }
+}
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 80ed296..389aee0a 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -3434,6 +3434,30 @@
             }
           ]
         }
+      },
+      {
+        "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "context_lost_tests",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:104a",
+              "os": "Ubuntu"
+            }
+          ]
+        }
       }
     ]
   },
diff --git a/testing/buildbot/filters/ash_unittests_mash.filter b/testing/buildbot/filters/ash_unittests_mash.filter
index e698354c..34a7b6eb 100644
--- a/testing/buildbot/filters/ash_unittests_mash.filter
+++ b/testing/buildbot/filters/ash_unittests_mash.filter
@@ -1,4 +1,14 @@
 # Failures and crashes
+-AppListPresenterDelegateTest.AppListViewDragHandler
+-AppListPresenterDelegateTest.AppListViewDragHandlerMaximizeModeFromAllApps
+-AppListPresenterDelegateTest.AppListViewDragHandlerMaximizeModeFromSearch
+-AppListPresenterDelegateTest.BottomShelfAlignmentTextStateTransitions
+-AppListPresenterDelegateTest.HalfToFullscreenWhenMaximizeModeIsActive
+-AppListPresenterDelegateTest.MaximizeModeTextStateTransitions
+-AppListPresenterDelegateTest.PeekingToFullscreenWhenMaximizeModeIsActive
+-AppListPresenterDelegateTest.SideShelfAlignmentTextStateTransitions
+-AppListPresenterDelegateTest.TapAndClickOutsideClosesPeekingAppList
+-AppListPresenterDelegateTest.TapAndClickOutsideClosesHalfAppList
 -NativeCursorManagerAshTest.FractionalScale
 -NativeCursorManagerAshTest.LockCursor
 -NativeCursorManagerAshTest.SetCursor
diff --git a/testing/buildbot/filters/ash_unittests_mus.filter b/testing/buildbot/filters/ash_unittests_mus.filter
index 1ca5b15..df4e4ca 100644
--- a/testing/buildbot/filters/ash_unittests_mus.filter
+++ b/testing/buildbot/filters/ash_unittests_mus.filter
@@ -1,3 +1,14 @@
+# TODO: fix these. http://crbug.com/726838
+-AppListPresenterDelegateTest.BottomShelfAlignmentTextStateTransitions
+-AppListPresenterDelegateTest.MaximizeModeTextStateTransitions
+-AppListPresenterDelegateTest.PeekingToFullscreenWhenMaximizeModeIsActive
+-AppListPresenterDelegateTest.HalfToFullscreenWhenMaximizeModeIsActive
+-AppListPresenterDelegateTest.AppListViewDragHandler
+-AppListPresenterDelegateTest.AppListViewDragHandlerMaximizeModeFromAllApps
+-AppListPresenterDelegateTest.AppListViewDragHandlerMaximizeModeFromSearch
+-AppListPresenterDelegateTest.TapAndClickOutsideClosesPeekingAppList
+-AppListPresenterDelegateTest.TapAndClickOutsideClosesHalfAppList
+
 # TODO: fix these. They fail because wm::CursorManager isn't created.
 # http://crbug.com/734806.
 # The following fail as they use wm::CursorManager:
diff --git a/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
index 60b106ce..53c56d99 100644
--- a/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
@@ -85,6 +85,8 @@
 -PaymentAppBrowserTest.PaymentAppInvocation
 -PaymentAppBrowserTest.PaymentAppOpenWindowFailed
 -PlzNavigateNavigationHandleImplBrowserTest.ErrorPageNetworkError
+-PowerMonitorTest.TestRendererProcess
+-PowerMonitorTest.TestUtilityProcess
 -PreviewsStateResourceDispatcherHostBrowserTest.ShouldEnableLoFiModeNavigateBackThenForward
 -PreviewsStateResourceDispatcherHostBrowserTest.ShouldEnableLoFiModeOff
 -PreviewsStateResourceDispatcherHostBrowserTest.ShouldEnableLoFiModeOn
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index feab373e..9d9370e 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1551,6 +1551,24 @@
             ]
         }
     ],
+    "NTPCaptureThumbnailOnLoadFinished": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "win"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "CaptureThumbnailOnLoadFinished"
+                    ]
+                }
+            ]
+        }
+    ],
     "NTPFaviconsFromNewServer": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-network-service b/third_party/WebKit/LayoutTests/FlagExpectations/enable-network-service
index 3544d15..d8f3e0d1 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-network-service
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-network-service
@@ -21,6 +21,8 @@
 Bug(none) bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-error.html [ Timeout ]
 Bug(none) bluetooth/characteristic/readValue/read-succeeds.html [ Timeout ]
 Bug(none) bluetooth/characteristic/readValue/read-updates-value.html [ Timeout ]
+Bug(none) bluetooth/characteristic/writeValue/write-succeeds.html [ Timeout ]
+Bug(none) bluetooth/descriptor/readValue/read-succeeds.html [ Timeout ]
 Bug(none) bluetooth/idl/idl-BluetoothDevice.html [ Timeout ]
 Bug(none) bluetooth/requestDevice [ Timeout ]
 Bug(none) bluetooth/server/connect/connection-succeeds.html [ Timeout ]
diff --git a/third_party/WebKit/LayoutTests/MSANExpectations b/third_party/WebKit/LayoutTests/MSANExpectations
index 3a330c7..1688d3a0 100644
--- a/third_party/WebKit/LayoutTests/MSANExpectations
+++ b/third_party/WebKit/LayoutTests/MSANExpectations
@@ -39,6 +39,7 @@
 
 crbug.com/671556 [ Linux ] virtual/mojo-loading/http/tests/security/xssAuditor/report-script-tag-replace-state.html [ Timeout Pass ]
 crbug.com/671556 [ Linux ] virtual/mojo-loading/http/tests/security/xssAuditor/report-script-tag.html [ Timeout Pass ]
+crbug.com/736802 [ Linux ] virtual/mojo-loading/http/tests/fetch/serviceworker-proxied/thorough/cors-preflight2-other-https.html [ Crash ]
 
 crbug.com/736370 [ Linux ] external/wpt/editing/run/removeformat.html [ Timeout ]
 crbug.com/736554 [ Linux ] external/wpt/IndexedDB/nested-cloning-large-multiple.html [ Timeout ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 008cc444..45d72c89 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1251,6 +1251,8 @@
 crbug.com/658305 css3/filters/effect-reference-zoom-hw.html [ Failure Pass ]
 crbug.com/658305 css3/filters/filter-effect-removed.html [ Failure Pass ]
 
+crbug.com/736429 webaudio/internals/audiosource-premature-gc.html [ Timeout Pass ]
+
 crbug.com/267206 [ Mac ] fast/scrolling/scrollbar-tickmarks-hittest.html [ Timeout ]
 crbug.com/267206 [ Mac ] virtual/scroll_customization/fast/scrolling/scrollbar-tickmarks-hittest.html [ Timeout ]
 
@@ -2003,6 +2005,10 @@
 crbug.com/736056 external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-misc.html [ Pass Timeout ]
 crbug.com/736056 external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href.html [ Pass Timeout ]
 
+# rebaseline-cl does not handle ref-tests yet...
+crbug.com/736811 fast/css/text-overflow-ellipsis-multiple-shadows.html [ Pass Failure ]
+crbug.com/736811 external/wpt/css/selectors4/focus-within-004.html [ Pass Failure ]
+
 # Failures in non-wpt tests from:
 # https://chromium-review.googlesource.com/c/543695/
 # Possibly became flaky after a change in testharness.js.
diff --git a/third_party/WebKit/PerformanceTests/DOM/page-up-with-many-lines.html b/third_party/WebKit/PerformanceTests/Editing/page-up-with-many-lines.html
similarity index 100%
rename from third_party/WebKit/PerformanceTests/DOM/page-up-with-many-lines.html
rename to third_party/WebKit/PerformanceTests/Editing/page-up-with-many-lines.html
diff --git a/third_party/WebKit/Source/bindings/core/v8/ToV8ForCore.cpp b/third_party/WebKit/Source/bindings/core/v8/ToV8ForCore.cpp
index 442f1d9..60cb109 100644
--- a/third_party/WebKit/Source/bindings/core/v8/ToV8ForCore.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/ToV8ForCore.cpp
@@ -8,12 +8,16 @@
 #include "core/events/EventTarget.h"
 #include "core/frame/DOMWindow.h"
 #include "core/frame/Frame.h"
+#include "platform/bindings/RuntimeCallStats.h"
 
 namespace blink {
 
 v8::Local<v8::Value> ToV8(DOMWindow* window,
                           v8::Local<v8::Object> creation_context,
                           v8::Isolate* isolate) {
+  RUNTIME_CALL_TIMER_SCOPE(isolate,
+                           RuntimeCallStats::CounterId::kToV8DOMWindow);
+
   // Notice that we explicitly ignore creationContext because the DOMWindow
   // has its own creationContext.
 
diff --git a/third_party/WebKit/Source/bindings/core/v8/V8BindingForCore.cpp b/third_party/WebKit/Source/bindings/core/v8/V8BindingForCore.cpp
index 649da0e..04a9fd7 100644
--- a/third_party/WebKit/Source/bindings/core/v8/V8BindingForCore.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/V8BindingForCore.cpp
@@ -56,6 +56,7 @@
 #include "core/workers/WorkerGlobalScope.h"
 #include "core/workers/WorkletGlobalScope.h"
 #include "core/xml/XPathNSResolver.h"
+#include "platform/bindings/RuntimeCallStats.h"
 #include "platform/bindings/V8BindingMacros.h"
 #include "platform/bindings/V8ObjectConstructor.h"
 #include "platform/instrumentation/tracing/TracedValue.h"
@@ -716,6 +717,8 @@
 ExecutionContext* ToExecutionContext(v8::Local<v8::Context> context) {
   if (context.IsEmpty())
     return 0;
+  RUNTIME_CALL_TIMER_SCOPE(context->GetIsolate(),
+                           RuntimeCallStats::CounterId::kToExecutionContext);
   v8::Local<v8::Object> global = context->Global();
   v8::Local<v8::Object> window_wrapper =
       V8Window::findInstanceInPrototypeChain(global, context->GetIsolate());
diff --git a/third_party/WebKit/Source/bindings/templates/methods.cpp.tmpl b/third_party/WebKit/Source/bindings/templates/methods.cpp.tmpl
index 391c465..7edf163 100644
--- a/third_party/WebKit/Source/bindings/templates/methods.cpp.tmpl
+++ b/third_party/WebKit/Source/bindings/templates/methods.cpp.tmpl
@@ -3,9 +3,8 @@
 {##############################################################################}
 {% macro runtime_timer_scope(counter) %}
 {% if counter -%}
-RuntimeCallStats* runtime_call_stats = RuntimeCallStats::From(info.GetIsolate());
-RuntimeCallTimerScope timer_scope(runtime_call_stats,
-                                  RuntimeCallStats::CounterId::{{counter}});
+RUNTIME_CALL_TIMER_SCOPE(info.GetIsolate(),
+                         RuntimeCallStats::CounterId::{{counter}});
 {%- endif %}
 {% endmacro %}
 
diff --git a/third_party/WebKit/Source/bindings/tests/results/core/V8TestObject.cpp b/third_party/WebKit/Source/bindings/tests/results/core/V8TestObject.cpp
index 35c98a3c..0c81ec1 100644
--- a/third_party/WebKit/Source/bindings/tests/results/core/V8TestObject.cpp
+++ b/third_party/WebKit/Source/bindings/tests/results/core/V8TestObject.cpp
@@ -3776,9 +3776,8 @@
 }
 
 static void RuntimeCallStatsCounterAttributeAttributeGetter(const v8::FunctionCallbackInfo<v8::Value>& info) {
-  RuntimeCallStats* runtime_call_stats = RuntimeCallStats::From(info.GetIsolate());
-  RuntimeCallTimerScope timer_scope(runtime_call_stats,
-                                    RuntimeCallStats::CounterId::kRuntimeCallStatsCounterAttribute_Getter);
+  RUNTIME_CALL_TIMER_SCOPE(info.GetIsolate(),
+                           RuntimeCallStats::CounterId::kRuntimeCallStatsCounterAttribute_Getter);
 
   v8::Local<v8::Object> holder = info.Holder();
 
@@ -3788,9 +3787,8 @@
 }
 
 static void RuntimeCallStatsCounterAttributeAttributeSetter(v8::Local<v8::Value> v8Value, const v8::FunctionCallbackInfo<v8::Value>& info) {
-  RuntimeCallStats* runtime_call_stats = RuntimeCallStats::From(info.GetIsolate());
-  RuntimeCallTimerScope timer_scope(runtime_call_stats,
-                                    RuntimeCallStats::CounterId::kRuntimeCallStatsCounterAttribute_Setter);
+  RUNTIME_CALL_TIMER_SCOPE(info.GetIsolate(),
+                           RuntimeCallStats::CounterId::kRuntimeCallStatsCounterAttribute_Setter);
   v8::Isolate* isolate = info.GetIsolate();
   ALLOW_UNUSED_LOCAL(isolate);
 
@@ -3810,9 +3808,8 @@
 }
 
 static void RuntimeCallStatsCounterReadOnlyAttributeAttributeGetter(const v8::FunctionCallbackInfo<v8::Value>& info) {
-  RuntimeCallStats* runtime_call_stats = RuntimeCallStats::From(info.GetIsolate());
-  RuntimeCallTimerScope timer_scope(runtime_call_stats,
-                                    RuntimeCallStats::CounterId::kRuntimeCallStatsCounterReadOnlyAttribute_Getter);
+  RUNTIME_CALL_TIMER_SCOPE(info.GetIsolate(),
+                           RuntimeCallStats::CounterId::kRuntimeCallStatsCounterReadOnlyAttribute_Getter);
 
   v8::Local<v8::Object> holder = info.Holder();
 
@@ -9251,9 +9248,8 @@
 }
 
 static void RuntimeCallStatsCounterMethodMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
-  RuntimeCallStats* runtime_call_stats = RuntimeCallStats::From(info.GetIsolate());
-  RuntimeCallTimerScope timer_scope(runtime_call_stats,
-                                    RuntimeCallStats::CounterId::kRuntimeCallStatsCounterMethod);
+  RUNTIME_CALL_TIMER_SCOPE(info.GetIsolate(),
+                           RuntimeCallStats::CounterId::kRuntimeCallStatsCounterMethod);
 
   TestObject* impl = V8TestObject::toImpl(info.Holder());
 
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index 5f661d84..eae9de11 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -2119,9 +2119,8 @@
 void Document::UpdateStyle() {
   DCHECK(!View()->ShouldThrottleRendering());
   TRACE_EVENT_BEGIN0("blink,blink_style", "Document::updateStyle");
-  RuntimeCallTimerScope scope(
-      RuntimeCallStats::From(V8PerIsolateData::MainThreadIsolate()),
-      RuntimeCallStats::CounterId::kUpdateStyle);
+  RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(),
+                           RuntimeCallStats::CounterId::kUpdateStyle);
   double start_time = MonotonicallyIncreasingTime();
 
   unsigned initial_element_count = GetStyleEngine().StyleForElementCount();
@@ -3251,8 +3250,6 @@
     return;
 
   if (load_event_progress_ <= kUnloadEventInProgress) {
-    if (GetPage())
-      GetPage()->WillUnloadDocument(*this);
     Element* current_focused_element = FocusedElement();
     if (isHTMLInputElement(current_focused_element))
       toHTMLInputElement(*current_focused_element).EndEditing();
diff --git a/third_party/WebKit/Source/core/dom/DocumentTest.cpp b/third_party/WebKit/Source/core/dom/DocumentTest.cpp
index 6ebc468..e36b1953 100644
--- a/third_party/WebKit/Source/core/dom/DocumentTest.cpp
+++ b/third_party/WebKit/Source/core/dom/DocumentTest.cpp
@@ -294,11 +294,9 @@
   MockValidationMessageClient() { Reset(); }
   void Reset() {
     show_validation_message_was_called = false;
-    will_unload_document_was_called = false;
     document_detached_was_called = false;
   }
   bool show_validation_message_was_called;
-  bool will_unload_document_was_called;
   bool document_detached_was_called;
 
   // ValidationMessageClient functions.
@@ -313,9 +311,6 @@
   bool IsValidationMessageVisible(const Element& anchor) override {
     return true;
   }
-  void WillUnloadDocument(const Document&) override {
-    will_unload_document_was_called = true;
-  }
   void DocumentDetached(const Document&) override {
     document_detached_was_called = true;
   }
@@ -826,7 +821,6 @@
 
   // prepareForCommit() unloads the document, and shutdown.
   GetDocument().GetFrame()->PrepareForCommit();
-  EXPECT_TRUE(mock_client->will_unload_document_was_called);
   EXPECT_TRUE(mock_client->document_detached_was_called);
   // Unload handler tried to show a validation message, but it should fail.
   EXPECT_FALSE(mock_client->show_validation_message_was_called);
diff --git a/third_party/WebKit/Source/core/dom/Element.idl b/third_party/WebKit/Source/core/dom/Element.idl
index 227b074..fa0e969 100644
--- a/third_party/WebKit/Source/core/dom/Element.idl
+++ b/third_party/WebKit/Source/core/dom/Element.idl
@@ -80,7 +80,7 @@
 
     // DOM Parsing and Serialization
     // https://w3c.github.io/DOM-Parsing/#extensions-to-the-element-interface
-    [TreatNullAs=NullString, CEReactions, CustomElementCallbacks, RaisesException=Setter] attribute DOMString innerHTML;
+    [TreatNullAs=NullString, CEReactions, CustomElementCallbacks, RaisesException=Setter, RuntimeCallStatsCounter=ElementInnerHTML] attribute DOMString innerHTML;
     [TreatNullAs=NullString, CEReactions, CustomElementCallbacks, RaisesException=Setter] attribute DOMString outerHTML;
     [CEReactions, CustomElementCallbacks, RaisesException] void insertAdjacentHTML(DOMString position, DOMString text);
 
diff --git a/third_party/WebKit/Source/core/events/EventTarget.idl b/third_party/WebKit/Source/core/events/EventTarget.idl
index 1619a0e..9faf117 100644
--- a/third_party/WebKit/Source/core/events/EventTarget.idl
+++ b/third_party/WebKit/Source/core/events/EventTarget.idl
@@ -27,5 +27,5 @@
 ] interface EventTarget {
     [Custom=(CallPrologue,CallEpilogue)] void addEventListener(DOMString type, EventListener? listener, optional (AddEventListenerOptions or boolean) options);
     [Custom=(CallPrologue,CallEpilogue)] void removeEventListener(DOMString type, EventListener? listener, optional (EventListenerOptions or boolean) options);
-    [ImplementedAs=dispatchEventForBindings, RaisesException] boolean dispatchEvent(Event event);
+    [ImplementedAs=dispatchEventForBindings, RaisesException, RuntimeCallStatsCounter=EventTargetDispatchEvent] boolean dispatchEvent(Event event);
 };
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
index 7f2a73a6..c67e00c 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
@@ -1140,9 +1140,8 @@
 
   TRACE_EVENT0("blink,benchmark", "LocalFrameView::layout");
 
-  RuntimeCallTimerScope scope(
-      RuntimeCallStats::From(V8PerIsolateData::MainThreadIsolate()),
-      RuntimeCallStats::CounterId::kUpdateLayout);
+  RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(),
+                           RuntimeCallStats::CounterId::kUpdateLayout);
 
   if (auto_size_info_)
     auto_size_info_->AutoSizeIfNeeded();
@@ -3389,9 +3388,9 @@
 
   SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Blink.Compositing.UpdateTime");
 
-  paint_artifact_compositor_->Update(
-      paint_controller_->GetPaintArtifact(),
-      is_storing_composited_layer_debug_info_, composited_element_ids);
+  paint_artifact_compositor_->Update(paint_controller_->GetPaintArtifact(),
+                                     is_storing_composited_layer_debug_info_,
+                                     composited_element_ids);
 }
 
 std::unique_ptr<JSONObject> LocalFrameView::CompositedLayersAsJSON(
diff --git a/third_party/WebKit/Source/core/frame/WindowTimers.idl b/third_party/WebKit/Source/core/frame/WindowTimers.idl
index d433fbb..9f8fc07 100644
--- a/third_party/WebKit/Source/core/frame/WindowTimers.idl
+++ b/third_party/WebKit/Source/core/frame/WindowTimers.idl
@@ -36,7 +36,7 @@
     // FIXME: would be clearer as a union type, like:
     // typedef (Function or DOMString) Handler
     // Needs spec update and better union support: http://crbug.com/240176
-    [CallWith=ScriptState] long setTimeout(Function handler, optional long timeout = 0, any... arguments);
+    [CallWith=ScriptState, RuntimeCallStatsCounter=WindowSetTimeout] long setTimeout(Function handler, optional long timeout = 0, any... arguments);
     [CallWith=ScriptState] long setTimeout(DOMString handler, optional long timeout = 0, any... arguments);
     void clearTimeout(optional long handle = 0);
     [CallWith=ScriptState] long setInterval(Function handler, optional long timeout = 0, any... arguments);
diff --git a/third_party/WebKit/Source/core/html/HTMLElement.idl b/third_party/WebKit/Source/core/html/HTMLElement.idl
index ad8390f7..7338183 100644
--- a/third_party/WebKit/Source/core/html/HTMLElement.idl
+++ b/third_party/WebKit/Source/core/html/HTMLElement.idl
@@ -30,7 +30,7 @@
 
     // user interaction
     [CEReactions, Reflect] attribute boolean hidden;
-    void click();
+    [RuntimeCallStatsCounter=HTMLElementClick] void click();
     [CEReactions, CustomElementCallbacks] attribute long tabIndex;
     [CEReactions, RuntimeEnabled=InertAttribute, Reflect] attribute boolean inert;
     void focus();
diff --git a/third_party/WebKit/Source/core/html/HTMLFormControlElementTest.cpp b/third_party/WebKit/Source/core/html/HTMLFormControlElementTest.cpp
index 470da5e..3a3ffcf6 100644
--- a/third_party/WebKit/Source/core/html/HTMLFormControlElementTest.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFormControlElementTest.cpp
@@ -41,7 +41,6 @@
     return anchor_ == &anchor;
   }
 
-  void WillUnloadDocument(const Document&) override {}
   void DocumentDetached(const Document&) override {}
   void WillBeDestroyed() override {}
   DEFINE_INLINE_VIRTUAL_TRACE() {
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
index ca12153..db3aa784 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
@@ -741,19 +741,28 @@
 }
 
 float FrameFetchContext::ClientHintsDeviceRAM(int64_t physical_memory_mb) {
-  // TODO(fmeawad): The calculations in this method are still evolving as the
-  // spec gets updated: https://github.com/WICG/device-ram. The reported
-  // device-ram is rounded down to next power of 2 in GB. Ex. 3072MB will return
-  // 2, and 768MB will return 0.5.
+  // The calculations in this method are described in the specifcations:
+  // https://github.com/WICG/device-ram.
   DCHECK_GT(physical_memory_mb, 0);
+  int lower_bound = physical_memory_mb;
   int power = 0;
-  // Extract the MSB location.
-  while (physical_memory_mb > 1) {
-    physical_memory_mb >>= 1;
+
+  // Extract the most significant 2-bits and their location.
+  while (lower_bound >= 4) {
+    lower_bound >>= 1;
     power++;
   }
-  // Restore to the power of 2, and convert to GB.
-  return static_cast<float>(1 << power) / 1024.0;
+  // The lower_bound value is either 0b10 or 0b11.
+  DCHECK(lower_bound & 2);
+
+  int64_t upper_bound = lower_bound + 1;
+  lower_bound = lower_bound << power;
+  upper_bound = upper_bound << power;
+
+  // Find the closest bound, and convert it to GB.
+  if (physical_memory_mb - lower_bound <= upper_bound - physical_memory_mb)
+    return static_cast<float>(lower_bound) / 1024.0;
+  return static_cast<float>(upper_bound) / 1024.0;
 }
 
 void FrameFetchContext::AddClientHintsIfNecessary(
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
index 7cb7363..eaec6655 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
@@ -548,9 +548,9 @@
   MemoryCoordinator::SetPhysicalMemoryMBForTesting(2048);
   ExpectHeader("http://www.example.com/1.gif", "device-ram", true, "2");
   MemoryCoordinator::SetPhysicalMemoryMBForTesting(64385);
-  ExpectHeader("http://www.example.com/1.gif", "device-ram", true, "32");
+  ExpectHeader("http://www.example.com/1.gif", "device-ram", true, "64");
   MemoryCoordinator::SetPhysicalMemoryMBForTesting(768);
-  ExpectHeader("http://www.example.com/1.gif", "device-ram", true, "0.5");
+  ExpectHeader("http://www.example.com/1.gif", "device-ram", true, "0.75");
   ExpectHeader("http://www.example.com/1.gif", "DPR", false, "");
   ExpectHeader("http://www.example.com/1.gif", "Width", false, "");
   ExpectHeader("http://www.example.com/1.gif", "Viewport-Width", false, "");
@@ -617,20 +617,21 @@
 TEST_F(FrameFetchContextHintsTest, ClientHintsDeviceRAM) {
   EXPECT_EQ(0.125, FrameFetchContext::ClientHintsDeviceRAM(128));  // 128MB
   EXPECT_EQ(0.25, FrameFetchContext::ClientHintsDeviceRAM(256));   // 256MB
-  EXPECT_EQ(0.25, FrameFetchContext::ClientHintsDeviceRAM(510));   // <512MB
+  EXPECT_EQ(0.5, FrameFetchContext::ClientHintsDeviceRAM(510));    // <512MB
   EXPECT_EQ(0.5, FrameFetchContext::ClientHintsDeviceRAM(512));    // 512MB
   EXPECT_EQ(0.5, FrameFetchContext::ClientHintsDeviceRAM(640));    // 512+128MB
-  EXPECT_EQ(0.5, FrameFetchContext::ClientHintsDeviceRAM(768));    // 512+256MB
-  EXPECT_EQ(0.5, FrameFetchContext::ClientHintsDeviceRAM(1000));   // <1GB
+  EXPECT_EQ(0.75, FrameFetchContext::ClientHintsDeviceRAM(768));   // 512+256MB
+  EXPECT_EQ(1, FrameFetchContext::ClientHintsDeviceRAM(1000));     // <1GB
   EXPECT_EQ(1, FrameFetchContext::ClientHintsDeviceRAM(1024));     // 1GB
-  EXPECT_EQ(1, FrameFetchContext::ClientHintsDeviceRAM(1536));     // 1.5GB
-  EXPECT_EQ(1, FrameFetchContext::ClientHintsDeviceRAM(2000));     // <2GB
+  EXPECT_EQ(1.5, FrameFetchContext::ClientHintsDeviceRAM(1536));   // 1.5GB
+  EXPECT_EQ(2, FrameFetchContext::ClientHintsDeviceRAM(2000));     // <2GB
   EXPECT_EQ(2, FrameFetchContext::ClientHintsDeviceRAM(2048));     // 2GB
+  EXPECT_EQ(3, FrameFetchContext::ClientHintsDeviceRAM(3000));     // <3GB
   EXPECT_EQ(4, FrameFetchContext::ClientHintsDeviceRAM(5120));     // 5GB
   EXPECT_EQ(8, FrameFetchContext::ClientHintsDeviceRAM(8192));     // 8GB
   EXPECT_EQ(16, FrameFetchContext::ClientHintsDeviceRAM(16384));   // 16GB
   EXPECT_EQ(32, FrameFetchContext::ClientHintsDeviceRAM(32768));   // 32GB
-  EXPECT_EQ(32, FrameFetchContext::ClientHintsDeviceRAM(64385));   // <64GB
+  EXPECT_EQ(64, FrameFetchContext::ClientHintsDeviceRAM(64385));   // <64GB
 }
 
 TEST_F(FrameFetchContextTest, MainResourceCachePolicy) {
diff --git a/third_party/WebKit/Source/core/page/Page.cpp b/third_party/WebKit/Source/core/page/Page.cpp
index 4a08302..825901d2 100644
--- a/third_party/WebKit/Source/core/page/Page.cpp
+++ b/third_party/WebKit/Source/core/page/Page.cpp
@@ -253,11 +253,6 @@
   main_frame_ = main_frame;
 }
 
-void Page::WillUnloadDocument(const Document& document) {
-  if (validation_message_client_)
-    validation_message_client_->WillUnloadDocument(document);
-}
-
 void Page::DocumentDetached(Document* document) {
   pointer_lock_controller_->DocumentDetached(document);
   context_menu_controller_->DocumentDetached(document);
diff --git a/third_party/WebKit/Source/core/page/Page.h b/third_party/WebKit/Source/core/page/Page.h
index ccaea49b..6af63ad9 100644
--- a/third_party/WebKit/Source/core/page/Page.h
+++ b/third_party/WebKit/Source/core/page/Page.h
@@ -152,7 +152,6 @@
     return ToLocalFrame(main_frame_);
   }
 
-  void WillUnloadDocument(const Document&);
   void DocumentDetached(Document*);
 
   bool OpenedByDOM() const;
diff --git a/third_party/WebKit/Source/core/page/ValidationMessageClient.h b/third_party/WebKit/Source/core/page/ValidationMessageClient.h
index 396f4b5..fe2285f 100644
--- a/third_party/WebKit/Source/core/page/ValidationMessageClient.h
+++ b/third_party/WebKit/Source/core/page/ValidationMessageClient.h
@@ -56,7 +56,6 @@
   // is visible.
   virtual bool IsValidationMessageVisible(const Element& anchor) = 0;
 
-  virtual void WillUnloadDocument(const Document&) = 0;
   virtual void DocumentDetached(const Document&) = 0;
 
   virtual void WillBeDestroyed() = 0;
diff --git a/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.cpp b/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.cpp
index f2e3c3b..7eb5f345 100644
--- a/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.cpp
+++ b/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.cpp
@@ -115,16 +115,11 @@
   return current_anchor_ == &anchor;
 }
 
-void ValidationMessageClientImpl::WillUnloadDocument(const Document& document) {
+void ValidationMessageClientImpl::DocumentDetached(const Document& document) {
   if (current_anchor_ && current_anchor_->GetDocument() == document)
     HideValidationMessage(*current_anchor_);
 }
 
-void ValidationMessageClientImpl::DocumentDetached(const Document& document) {
-  DCHECK(!current_anchor_ || current_anchor_->GetDocument() != document)
-      << "willUnloadDocument() should be called beforehand.";
-}
-
 void ValidationMessageClientImpl::CheckAnchorStatus(TimerBase*) {
   DCHECK(current_anchor_);
   if (MonotonicallyIncreasingTime() >= finish_time_ || !CurrentView()) {
diff --git a/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.h b/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.h
index 0816472..e261d96 100644
--- a/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.h
+++ b/third_party/WebKit/Source/core/page/ValidationMessageClientImpl.h
@@ -63,7 +63,6 @@
                              TextDirection sub_message_dir) override;
   void HideValidationMessage(const Element& anchor) override;
   bool IsValidationMessageVisible(const Element& anchor) override;
-  void WillUnloadDocument(const Document&) override;
   void DocumentDetached(const Document&) override;
   void WillBeDestroyed() override;
 
diff --git a/third_party/WebKit/Source/core/paint/PaintLayer.cpp b/third_party/WebKit/Source/core/paint/PaintLayer.cpp
index 4f7f591..f75ee2e 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayer.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayer.cpp
@@ -288,8 +288,8 @@
 void PaintLayer::UpdateLayerPositionsAfterLayout() {
   TRACE_EVENT0("blink,benchmark",
                "PaintLayer::updateLayerPositionsAfterLayout");
-  RuntimeCallTimerScope runtime_timer_scope(
-      RuntimeCallStats::From(V8PerIsolateData::MainThreadIsolate()),
+  RUNTIME_CALL_TIMER_SCOPE(
+      V8PerIsolateData::MainThreadIsolate(),
       RuntimeCallStats::CounterId::kUpdateLayerPositionsAfterLayout);
 
   ClearClipRects();
diff --git a/third_party/WebKit/Source/core/xml/XSLTProcessorLibxslt.cpp b/third_party/WebKit/Source/core/xml/XSLTProcessorLibxslt.cpp
index 64c5e0b6a..7a57419 100644
--- a/third_party/WebKit/Source/core/xml/XSLTProcessorLibxslt.cpp
+++ b/third_party/WebKit/Source/core/xml/XSLTProcessorLibxslt.cpp
@@ -113,6 +113,9 @@
           RawResource::FetchSynchronously(params, g_global_resource_fetcher);
       if (!resource || !g_global_processor)
         return nullptr;
+      RefPtr<const SharedBuffer> data = resource->ResourceBuffer();
+      if (!data)
+        return nullptr;
 
       FrameConsole* console = nullptr;
       LocalFrame* frame =
@@ -122,13 +125,25 @@
       xmlSetStructuredErrorFunc(console, XSLTProcessor::ParseErrorFunc);
       xmlSetGenericErrorFunc(console, XSLTProcessor::GenericErrorFunc);
 
+      xmlDocPtr doc = nullptr;
+
       // We don't specify an encoding here. Neither Gecko nor WinIE respects
       // the encoding specified in the HTTP headers.
-      RefPtr<const SharedBuffer> data = resource->ResourceBuffer();
-      xmlDocPtr doc = data ? xmlReadMemory(data->Data(), data->size(),
-                                           (const char*)uri, 0, options)
-                           : nullptr;
+      xmlParserCtxtPtr ctx = xmlCreatePushParserCtxt(
+          nullptr, nullptr, nullptr, 0, reinterpret_cast<const char*>(uri));
+      if (ctx && !xmlCtxtUseOptions(ctx, options)) {
+        data->ForEachSegment([&data, &ctx](const char* segment,
+                                           size_t segment_size,
+                                           size_t segment_offset) -> bool {
+          bool final_chunk = segment_offset + segment_size == data->size();
+          return !xmlParseChunk(ctx, segment, segment_size, final_chunk);
+        });
 
+        if (ctx->wellFormed)
+          doc = ctx->myDoc;
+      }
+
+      xmlFreeParserCtxt(ctx);
       xmlSetStructuredErrorFunc(0, 0);
       xmlSetGenericErrorFunc(0, 0);
 
diff --git a/third_party/WebKit/Source/modules/webaudio/OfflineAudioDestinationNode.cpp b/third_party/WebKit/Source/modules/webaudio/OfflineAudioDestinationNode.cpp
index e15549c..eafe405 100644
--- a/third_party/WebKit/Source/modules/webaudio/OfflineAudioDestinationNode.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/OfflineAudioDestinationNode.cpp
@@ -182,8 +182,14 @@
     // already held, simply delay rendering until the next quantum.
     CrossThreadPersistentRegion::LockScope gc_lock(
         ProcessHeap::GetCrossThreadPersistentRegion(), true);
-    if (!gc_lock.HasLock())
+    if (!gc_lock.HasLock()) {
+      // To ensure that the rendering step eventually happens, repost.
+      render_thread_->GetWebTaskRunner()->PostTask(
+          BLINK_FROM_HERE,
+          Bind(&OfflineAudioDestinationHandler::DoOfflineRendering,
+               WrapPassRefPtr(this)));
       return;
+    }
 
     number_of_channels = render_target_->numberOfChannels();
     destinations.ReserveInitialCapacity(number_of_channels);
diff --git a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5 b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
index 10c8769..fecc6263 100644
--- a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
+++ b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
@@ -112,6 +112,9 @@
       status: "stable",
     },
     {
+      name: "BlinkRuntimeCallStats",
+    },
+    {
       name: "BlockCredentialedSubresources",
       status: "stable",
     },
diff --git a/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.cpp b/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.cpp
index 513797f..5f35638 100644
--- a/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.cpp
+++ b/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.cpp
@@ -55,16 +55,6 @@
   return V8PerIsolateData::From(isolate)->GetRuntimeCallStats();
 }
 
-void RuntimeCallStats::Enter(RuntimeCallTimer* timer, CounterId id) {
-  timer->Start(GetCounter(id), current_timer_);
-  current_timer_ = timer;
-}
-
-void RuntimeCallStats::Leave(RuntimeCallTimer* timer) {
-  DCHECK_EQ(timer, current_timer_);
-  current_timer_ = timer->Stop();
-}
-
 void RuntimeCallStats::Reset() {
   for (int i = 0; i < number_of_counters_; i++) {
     counters_[i].Reset();
@@ -88,7 +78,7 @@
 
 // static
 void RuntimeCallStats::SetRuntimeCallStatsForTesting() {
-  DEFINE_STATIC_LOCAL(RuntimeCallStatsForTesting, s_rcs_for_testing, ());
+  DEFINE_STATIC_LOCAL(RuntimeCallStats, s_rcs_for_testing, ());
   g_runtime_call_stats_for_testing =
       static_cast<RuntimeCallStats*>(&s_rcs_for_testing);
 }
diff --git a/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.h b/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.h
index e596b9a..07eaeff 100644
--- a/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.h
+++ b/third_party/WebKit/Source/platform/bindings/RuntimeCallStats.h
@@ -9,7 +9,9 @@
 #define RuntimeCallStats_h
 
 #include "platform/PlatformExport.h"
+#include "platform/RuntimeEnabledFeatures.h"
 #include "platform/wtf/Allocator.h"
+#include "platform/wtf/Optional.h"
 #include "platform/wtf/Time.h"
 #include "platform/wtf/text/WTFString.h"
 #include "v8/include/v8.h"
@@ -48,7 +50,7 @@
 
 // Used to track elapsed time for a counter.
 // NOTE: Do not use this class directly to track execution times, instead use it
-// with RuntimeCallStats::Enter/Leave.
+// with the macros below.
 class PLATFORM_EXPORT RuntimeCallTimer {
  public:
   RuntimeCallTimer() = default;
@@ -88,6 +90,51 @@
   TimeDelta elapsed_time_;
 };
 
+// Macros that take RuntimeCallStats as a parameter; used only in
+// RuntimeCallStatsTest.
+#define RUNTIME_CALL_STATS_ENTER_WITH_RCS(runtime_call_stats, timer,      \
+                                          counterId)                      \
+  if (UNLIKELY(RuntimeEnabledFeatures::BlinkRuntimeCallStatsEnabled())) { \
+    (runtime_call_stats)->Enter(timer, counterId);                        \
+  }
+
+#define RUNTIME_CALL_STATS_LEAVE_WITH_RCS(runtime_call_stats, timer)      \
+  if (UNLIKELY(RuntimeEnabledFeatures::BlinkRuntimeCallStatsEnabled())) { \
+    (runtime_call_stats)->Leave(timer);                                   \
+  }
+
+#define RUNTIME_CALL_TIMER_SCOPE_WITH_RCS(runtime_call_stats, counterId)  \
+  Optional<RuntimeCallTimerScope> rcs_scope;                              \
+  if (UNLIKELY(RuntimeEnabledFeatures::BlinkRuntimeCallStatsEnabled())) { \
+    rcs_scope.emplace(runtime_call_stats, counterId);                     \
+  }
+
+#define RUNTIME_CALL_TIMER_SCOPE_WITH_OPTIONAL_RCS(                       \
+    optional_scope_name, runtime_call_stats, counterId)                   \
+  if (UNLIKELY(RuntimeEnabledFeatures::BlinkRuntimeCallStatsEnabled())) { \
+    optional_scope_name.emplace(runtime_call_stats, counterId);           \
+  }
+
+// Use the macros below instead of directly using RuntimeCallStats::Enter,
+// RuntimeCallStats::Leave and RuntimeCallTimerScope. They force an early
+// exit if Runtime Call Stats is disabled.
+#define RUNTIME_CALL_STATS_ENTER(isolate, timer, counterId)                 \
+  RUNTIME_CALL_STATS_ENTER_WITH_RCS(RuntimeCallStats::From(isolate), timer, \
+                                    counterId)
+
+#define RUNTIME_CALL_STATS_LEAVE(isolate, timer) \
+  RUNTIME_CALL_STATS_LEAVE_WITH_RCS(RuntimeCallStats::From(isolate), timer)
+
+#define RUNTIME_CALL_TIMER_SCOPE(isolate, counterId) \
+  RUNTIME_CALL_TIMER_SCOPE_WITH_RCS(RuntimeCallStats::From(isolate), counterId)
+
+#define RUNTIME_CALL_TIMER_SCOPE_IF_ISOLATE_EXISTS(isolate, counterId) \
+  Optional<RuntimeCallTimerScope> rcs_scope;                           \
+  if (isolate) {                                                       \
+    RUNTIME_CALL_TIMER_SCOPE_WITH_OPTIONAL_RCS(                        \
+        rcs_scope, RuntimeCallStats::From(isolate), counterId)         \
+  }
+
 // Maintains a stack of timers and provides functions to manage recording scopes
 // by pausing and resuming timers in the chain when entering and leaving a
 // scope.
@@ -122,13 +169,23 @@
 
 // Counters
 #define FOR_EACH_COUNTER(V)                                             \
-  V(UpdateStyle)                                                        \
-  V(UpdateLayout)                                                       \
+  V(AssociateObjectWithWrapper)                                         \
   V(CollectGarbage)                                                     \
-  V(PerformIdleLazySweep)                                               \
-  V(UpdateLayerPositionsAfterLayout)                                    \
+  V(CreateWrapper)                                                      \
   V(PaintContents)                                                      \
+  V(PerformIdleLazySweep)                                               \
   V(ProcessStyleSheet)                                                  \
+  V(ToExecutionContext)                                                 \
+  V(ToV8DOMWindow)                                                      \
+  V(UpdateLayerPositionsAfterLayout)                                    \
+  V(UpdateLayout)                                                       \
+  V(UpdateStyle)                                                        \
+  V(SetReturnValueFromStringSlow)                                       \
+  V(V8ExternalStringSlow)                                               \
+  BINDINGS_METHOD(V, EventTargetDispatchEvent)                          \
+  BINDINGS_METHOD(V, HTMLElementClick)                                  \
+  BINDINGS_METHOD(V, WindowSetTimeout)                                  \
+  BINDINGS_ATTRIBUTE(V, ElementInnerHTML)                               \
   V(TestCounter1)                                                       \
   V(TestCounter2)                                                       \
   BINDINGS_METHOD(V, BindingsMethodTestCounter)                         \
@@ -144,12 +201,20 @@
 
   // Enters a new recording scope by pausing the currently running timer that
   // was started by the current instance, and starting <timer>.
-  virtual void Enter(RuntimeCallTimer*, CounterId);
+  // NOTE: Do not use this function directly, use RUNTIME_CALL_STATS_ENTER.
+  void Enter(RuntimeCallTimer* timer, CounterId id) {
+    timer->Start(GetCounter(id), current_timer_);
+    current_timer_ = timer;
+  }
 
   // Exits the current recording scope, by stopping <timer> (and updating the
   // counter associated with <timer>) and resuming the timer that was paused
   // before entering the current scope.
-  virtual void Leave(RuntimeCallTimer*);
+  // NOTE: Do not use this function directly, use RUNTIME_CALL_STATS_LEAVE.
+  void Leave(RuntimeCallTimer* timer) {
+    DCHECK_EQ(timer, current_timer_);
+    current_timer_ = timer->Stop();
+  }
 
   // Reset all the counters.
   void Reset();
@@ -160,7 +225,6 @@
 
   String ToString() const;
 
-  // Use these two functions to stub out RuntimeCallStats in tests.
   static void SetRuntimeCallStatsForTesting();
   static void ClearRuntimeCallStatsForTesting();
 
@@ -173,6 +237,7 @@
 
 // A utility class that creates a RuntimeCallTimer and uses it with
 // RuntimeCallStats to measure execution time of a C++ scope.
+// Do not use this class directly, use RUNTIME_CALL_TIMER_SCOPE instead.
 class PLATFORM_EXPORT RuntimeCallTimerScope {
  public:
   RuntimeCallTimerScope(RuntimeCallStats* stats,
@@ -187,12 +252,6 @@
   RuntimeCallTimer timer_;
 };
 
-// Used to stub out RuntimeCallStats for testing.
-class RuntimeCallStatsForTesting : public RuntimeCallStats {
-  void Enter(RuntimeCallTimer*, RuntimeCallStats::CounterId) override {}
-  void Leave(RuntimeCallTimer*) override {}
-};
-
 }  // namespace blink
 
 #endif  // RuntimeCallStats_h
diff --git a/third_party/WebKit/Source/platform/bindings/RuntimeCallStatsTest.cpp b/third_party/WebKit/Source/platform/bindings/RuntimeCallStatsTest.cpp
index 9ccece9..b5833229 100644
--- a/third_party/WebKit/Source/platform/bindings/RuntimeCallStatsTest.cpp
+++ b/third_party/WebKit/Source/platform/bindings/RuntimeCallStatsTest.cpp
@@ -4,6 +4,7 @@
 
 #include "platform/bindings/RuntimeCallStats.h"
 
+#include "platform/RuntimeEnabledFeatures.h"
 #include "platform/wtf/CurrentTime.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -36,10 +37,12 @@
 
   void TearDown() override {
     SetTimeFunctionsForTesting(original_time_function_);
+    features_backup_.Restore();
   }
 
  private:
   TimeFunction original_time_function_;
+  RuntimeEnabledFeatures::Backup features_backup_;
 };
 
 TEST_F(RuntimeCallStatsTest, InitialCountShouldBeZero) {
@@ -233,4 +236,92 @@
   EXPECT_EQ(0ul, counter2->GetCount());
 }
 
+TEST_F(RuntimeCallStatsTest, TestEnterAndLeaveMacrosWithCallStatsDisabled) {
+  RuntimeEnabledFeatures::SetBlinkRuntimeCallStatsEnabled(false);
+  RuntimeCallStats stats;
+  RuntimeCallCounter* counter = stats.GetCounter(test_counter_1_id);
+  RuntimeCallTimer timer;
+
+  RUNTIME_CALL_STATS_ENTER_WITH_RCS(&stats, &timer, test_counter_1_id);
+  AdvanceClock(25);
+  RUNTIME_CALL_STATS_LEAVE_WITH_RCS(&stats, &timer);
+
+  EXPECT_EQ(0ul, counter->GetCount());
+  EXPECT_EQ(0, counter->GetTime().InMilliseconds());
+}
+
+TEST_F(RuntimeCallStatsTest, TestEnterAndLeaveMacrosWithCallStatsEnabled) {
+  RuntimeEnabledFeatures::SetBlinkRuntimeCallStatsEnabled(true);
+  RuntimeCallStats stats;
+  RuntimeCallCounter* counter = stats.GetCounter(test_counter_1_id);
+  RuntimeCallTimer timer;
+
+  RUNTIME_CALL_STATS_ENTER_WITH_RCS(&stats, &timer, test_counter_1_id);
+  AdvanceClock(25);
+  RUNTIME_CALL_STATS_LEAVE_WITH_RCS(&stats, &timer);
+
+  EXPECT_EQ(1ul, counter->GetCount());
+  EXPECT_EQ(25, counter->GetTime().InMilliseconds());
+}
+
+TEST_F(RuntimeCallStatsTest, TestScopeMacroWithCallStatsDisabled) {
+  RuntimeEnabledFeatures::SetBlinkRuntimeCallStatsEnabled(false);
+  RuntimeCallStats stats;
+  RuntimeCallCounter* counter = stats.GetCounter(test_counter_1_id);
+
+  {
+    RUNTIME_CALL_TIMER_SCOPE_WITH_RCS(&stats, test_counter_1_id);
+    AdvanceClock(25);
+  }
+
+  EXPECT_EQ(0ul, counter->GetCount());
+  EXPECT_EQ(0, counter->GetTime().InMilliseconds());
+}
+
+TEST_F(RuntimeCallStatsTest, TestScopeMacroWithCallStatsEnabled) {
+  RuntimeEnabledFeatures::SetBlinkRuntimeCallStatsEnabled(true);
+  RuntimeCallStats stats;
+  RuntimeCallCounter* counter = stats.GetCounter(test_counter_1_id);
+
+  {
+    RUNTIME_CALL_TIMER_SCOPE_WITH_RCS(&stats, test_counter_1_id);
+    AdvanceClock(25);
+  }
+
+  EXPECT_EQ(1ul, counter->GetCount());
+  EXPECT_EQ(25, counter->GetTime().InMilliseconds());
+}
+
+TEST_F(RuntimeCallStatsTest, TestScopeWithOptionalMacroWithCallStatsDisabled) {
+  RuntimeEnabledFeatures::SetBlinkRuntimeCallStatsEnabled(false);
+  RuntimeCallStats stats;
+  RuntimeCallCounter* counter = stats.GetCounter(test_counter_1_id);
+
+  {
+    Optional<RuntimeCallTimerScope> scope;
+    RUNTIME_CALL_TIMER_SCOPE_WITH_OPTIONAL_RCS(scope, &stats,
+                                               test_counter_1_id);
+    AdvanceClock(25);
+  }
+
+  EXPECT_EQ(0ul, counter->GetCount());
+  EXPECT_EQ(0, counter->GetTime().InMilliseconds());
+}
+
+TEST_F(RuntimeCallStatsTest, TestScopeWithOptionalMacroWithCallStatsEnabled) {
+  RuntimeEnabledFeatures::SetBlinkRuntimeCallStatsEnabled(true);
+  RuntimeCallStats stats;
+  RuntimeCallCounter* counter = stats.GetCounter(test_counter_1_id);
+
+  {
+    Optional<RuntimeCallTimerScope> scope;
+    RUNTIME_CALL_TIMER_SCOPE_WITH_OPTIONAL_RCS(scope, &stats,
+                                               test_counter_1_id);
+    AdvanceClock(25);
+  }
+
+  EXPECT_EQ(1ul, counter->GetCount());
+  EXPECT_EQ(25, counter->GetTime().InMilliseconds());
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.cpp b/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.cpp
index ac6da70..4febb73 100644
--- a/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.cpp
+++ b/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.cpp
@@ -41,6 +41,9 @@
     v8::Isolate* isolate,
     v8::Local<v8::Object> creation_context,
     const WrapperTypeInfo* type) {
+  RUNTIME_CALL_TIMER_SCOPE(isolate,
+                           RuntimeCallStats::CounterId::kCreateWrapper);
+
   // TODO(adithyas): We should abort wrapper creation if the context access
   // check fails and throws an exception.
   V8WrapperInstantiationScope scope(creation_context, isolate, type);
diff --git a/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.h b/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.h
index b91592b..89a2d266 100644
--- a/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.h
+++ b/third_party/WebKit/Source/platform/bindings/V8DOMWrapper.h
@@ -33,6 +33,7 @@
 
 #include "platform/PlatformExport.h"
 #include "platform/bindings/DOMDataStore.h"
+#include "platform/bindings/RuntimeCallStats.h"
 #include "platform/bindings/ScriptWrappable.h"
 #include "platform/bindings/V8Binding.h"
 #include "platform/bindings/WrapperCreationSecurityCheck.h"
@@ -114,6 +115,8 @@
     ScriptWrappable* impl,
     const WrapperTypeInfo* wrapper_type_info,
     v8::Local<v8::Object> wrapper) {
+  RUNTIME_CALL_TIMER_SCOPE(
+      isolate, RuntimeCallStats::CounterId::kAssociateObjectWithWrapper);
   if (DOMDataStore::SetWrapper(isolate, impl, wrapper_type_info, wrapper)) {
     WrapperTypeInfo::WrapperCreated();
     SetNativeInfo(isolate, wrapper, wrapper_type_info, impl);
diff --git a/third_party/WebKit/Source/platform/bindings/V8ValueCache.cpp b/third_party/WebKit/Source/platform/bindings/V8ValueCache.cpp
index ba755bb2..8878875 100644
--- a/third_party/WebKit/Source/platform/bindings/V8ValueCache.cpp
+++ b/third_party/WebKit/Source/platform/bindings/V8ValueCache.cpp
@@ -26,6 +26,7 @@
 #include "platform/bindings/V8ValueCache.h"
 
 #include <utility>
+#include "platform/bindings/RuntimeCallStats.h"
 #include "platform/bindings/V8Binding.h"
 #include "platform/wtf/text/StringHash.h"
 
@@ -93,6 +94,8 @@
 v8::Local<v8::String> StringCache::V8ExternalStringSlow(
     v8::Isolate* isolate,
     StringImpl* string_impl) {
+  RUNTIME_CALL_TIMER_SCOPE(isolate,
+                           RuntimeCallStats::CounterId::kV8ExternalStringSlow);
   if (!string_impl->length())
     return v8::String::Empty(isolate);
 
@@ -110,6 +113,9 @@
 void StringCache::SetReturnValueFromStringSlow(
     v8::ReturnValue<v8::Value> return_value,
     StringImpl* string_impl) {
+  RUNTIME_CALL_TIMER_SCOPE(
+      return_value.GetIsolate(),
+      RuntimeCallStats::CounterId::kSetReturnValueFromStringSlow);
   if (!string_impl->length()) {
     return_value.SetEmptyString();
     return;
diff --git a/third_party/WebKit/Source/platform/graphics/ContentLayerDelegate.cpp b/third_party/WebKit/Source/platform/graphics/ContentLayerDelegate.cpp
index 684ec562..3f9aa70 100644
--- a/third_party/WebKit/Source/platform/graphics/ContentLayerDelegate.cpp
+++ b/third_party/WebKit/Source/platform/graphics/ContentLayerDelegate.cpp
@@ -57,9 +57,8 @@
     WebDisplayItemList* web_display_item_list,
     WebContentLayerClient::PaintingControlSetting painting_control) {
   TRACE_EVENT0("blink,benchmark", "ContentLayerDelegate::paintContents");
-  RuntimeCallTimerScope runtime_timer_scope(
-      RuntimeCallStats::From(V8PerIsolateData::MainThreadIsolate()),
-      RuntimeCallStats::CounterId::kPaintContents);
+  RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(),
+                           RuntimeCallStats::CounterId::kPaintContents);
 
   PaintController& paint_controller = graphics_layer_->GetPaintController();
   paint_controller.SetDisplayItemConstructionIsDisabled(
diff --git a/third_party/WebKit/Source/platform/heap/ThreadState.cpp b/third_party/WebKit/Source/platform/heap/ThreadState.cpp
index a8716456..9d2fd134 100644
--- a/third_party/WebKit/Source/platform/heap/ThreadState.cpp
+++ b/third_party/WebKit/Source/platform/heap/ThreadState.cpp
@@ -618,11 +618,8 @@
   if (SweepForbidden())
     return;
 
-  Optional<RuntimeCallTimerScope> timer_scope;
-  if (v8::Isolate* isolate = GetIsolate()) {
-    timer_scope.emplace(RuntimeCallStats::From(isolate),
-                        RuntimeCallStats::CounterId::kPerformIdleLazySweep);
-  }
+  RUNTIME_CALL_TIMER_SCOPE_IF_ISOLATE_EXISTS(
+      GetIsolate(), RuntimeCallStats::CounterId::kPerformIdleLazySweep);
 
   TRACE_EVENT1("blink_gc,devtools.timeline",
                "ThreadState::performIdleLazySweep", "idleDeltaInSeconds",
@@ -1444,11 +1441,8 @@
   CHECK(!IsGCForbidden());
   CompleteSweep();
 
-  Optional<RuntimeCallTimerScope> timer_scope;
-  if (v8::Isolate* isolate = GetIsolate()) {
-    timer_scope.emplace(RuntimeCallStats::From(isolate),
-                        RuntimeCallStats::CounterId::kCollectGarbage);
-  }
+  RUNTIME_CALL_TIMER_SCOPE_IF_ISOLATE_EXISTS(
+      GetIsolate(), RuntimeCallStats::CounterId::kCollectGarbage);
 
   GCForbiddenScope gc_forbidden_scope(this);
 
diff --git a/tools/perf/benchmarks/smoothness.py b/tools/perf/benchmarks/smoothness.py
index 55bb137..6c28e04 100644
--- a/tools/perf/benchmarks/smoothness.py
+++ b/tools/perf/benchmarks/smoothness.py
@@ -127,7 +127,6 @@
     return 'smoothness.tough_webgl_cases'
 
 
-@benchmark.Disabled('win') # http://crbug.com/692663
 @benchmark.Disabled('android-webview')  # http://crbug.com/653933
 @benchmark.Owner(emails=['kbr@chromium.org', 'zmo@chromium.org'])
 class SmoothnessMaps(perf_benchmark.PerfBenchmark):
diff --git a/tools/perf/measurements/smoothness_unittest.py b/tools/perf/measurements/smoothness_unittest.py
index 3d88127..2e22a03 100644
--- a/tools/perf/measurements/smoothness_unittest.py
+++ b/tools/perf/measurements/smoothness_unittest.py
@@ -55,7 +55,7 @@
 class CustomResultsWrapperUnitTest(unittest.TestCase):
 
   def testOnlyOneInteractionRecordPerPage(self):
-    test_page = page.Page('http://dummy', None)
+    test_page = page.Page('http://dummy', None, name='http://dummy')
 
     # pylint: disable=protected-access
     results_wrapper = smoothness._CustomResultsWrapper()
@@ -82,7 +82,7 @@
     self._options.browser_options.wpr_mode = wpr_modes.WPR_OFF
 
   def testSyntheticDelayConfiguration(self):
-    test_page = page.Page('http://dummy', None)
+    test_page = page.Page('http://dummy', None, name='http://dummy')
     test_page.synthetic_delays = {
         'cc.BeginMainFrame': {'target_duration': 0.012},
         'cc.DrawAndSwap': {'target_duration': 0.012, 'mode': 'alternating'},
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
index c82004f..508eebc8 100644
--- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
@@ -282,6 +282,7 @@
      */
     @VisibleForTesting
     public static List<String> convertToImageMimeTypes(List<String> fileTypes) {
+        if (fileTypes.size() == 0) return null;
         List<String> mimeTypes = new ArrayList<>();
         for (String type : fileTypes) {
             String mimeType = ensureMimeType(type);
diff --git a/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java b/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
index fac9e83..10c6623 100644
--- a/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
+++ b/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
@@ -106,6 +106,7 @@
         // Unknown extension, expect default response:
         assertEquals("application/octet-stream", SelectFileDialog.ensureMimeType(".flv"));
 
+        assertEquals(null, SelectFileDialog.convertToImageMimeTypes(new ArrayList<>()));
         assertEquals(null, SelectFileDialog.convertToImageMimeTypes(Arrays.asList("")));
         assertEquals(null, SelectFileDialog.convertToImageMimeTypes(Arrays.asList("foo/bar")));
         assertEquals(Arrays.asList("image/jpeg"),
diff --git a/ui/app_list/BUILD.gn b/ui/app_list/BUILD.gn
index 8dd18cd..27b8fb68 100644
--- a/ui/app_list/BUILD.gn
+++ b/ui/app_list/BUILD.gn
@@ -4,18 +4,6 @@
 
 import("//build/config/ui.gni")
 import("//testing/test.gni")
-import("//ui/vector_icons/vector_icons.gni")
-
-aggregate_vector_icons("app_list_vector_icons") {
-  icon_directory = "vector_icons"
-
-  icons = [
-    "ic_google_black.1x.icon",
-    "ic_google_black.icon",
-    "ic_mic_black.1x.icon",
-    "ic_mic_black.icon",
-  ]
-}
 
 component("app_list") {
   sources = [
@@ -80,12 +68,9 @@
     "speech_ui_model_observer.h",
   ]
 
-  sources += get_target_outputs(":app_list_vector_icons")
-
   defines = [ "APP_LIST_IMPLEMENTATION" ]
 
   deps = [
-    ":app_list_vector_icons",
     "//base",
     "//base:i18n",
     "//base/third_party/dynamic_annotations",
@@ -95,6 +80,7 @@
     "//third_party/icu",
     "//ui/accessibility",
     "//ui/app_list/resources",
+    "//ui/app_list/vector_icons",
     "//ui/base",
     "//ui/base/ime",
     "//ui/compositor",
diff --git a/ui/app_list/presenter/app_list_presenter_impl.cc b/ui/app_list/presenter/app_list_presenter_impl.cc
index c4ef3092..dd1c55a 100644
--- a/ui/app_list/presenter/app_list_presenter_impl.cc
+++ b/ui/app_list/presenter/app_list_presenter_impl.cc
@@ -6,6 +6,7 @@
 
 #include "base/metrics/user_metrics.h"
 #include "ui/app_list/app_list_constants.h"
+#include "ui/app_list/app_list_features.h"
 #include "ui/app_list/app_list_switches.h"
 #include "ui/app_list/pagination_model.h"
 #include "ui/app_list/presenter/app_list_presenter_delegate_factory.h"
@@ -34,7 +35,8 @@
 
 AppListPresenterImpl::AppListPresenterImpl(
     std::unique_ptr<AppListPresenterDelegateFactory> factory)
-    : factory_(std::move(factory)) {
+    : factory_(std::move(factory)),
+      is_fullscreen_app_list_enabled_(features::IsFullscreenAppListEnabled()) {
   DCHECK(factory_);
 }
 
@@ -268,10 +270,16 @@
   if (!view_)
     return;
 
+  // Disable overscroll animation when the fullscreen app list feature is
+  // enabled.
+  if (is_fullscreen_app_list_enabled_)
+    return;
+
   PaginationModel* pagination_model = view_->GetAppsPaginationModel();
 
   const PaginationModel::Transition& transition =
       pagination_model->transition();
+
   if (pagination_model->is_valid_page(transition.target_page))
     return;
 
diff --git a/ui/app_list/presenter/app_list_presenter_impl.h b/ui/app_list/presenter/app_list_presenter_impl.h
index 4f1ee1b..eb0d7e7 100644
--- a/ui/app_list/presenter/app_list_presenter_impl.h
+++ b/ui/app_list/presenter/app_list_presenter_impl.h
@@ -134,6 +134,9 @@
   // Whether should schedule snap back animation.
   bool should_snap_back_ = false;
 
+  // Whether the fullscreen app list feature is enabled;
+  const bool is_fullscreen_app_list_enabled_;
+
   // The app list interface pointer; used for reporting visibility changes.
   mojom::AppListPtr app_list_;
 
diff --git a/ui/app_list/vector_icons/BUILD.gn b/ui/app_list/vector_icons/BUILD.gn
new file mode 100644
index 0000000..3c49259
--- /dev/null
+++ b/ui/app_list/vector_icons/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//ui/vector_icons/vector_icons.gni")
+
+aggregate_vector_icons("app_list_vector_icons") {
+  icon_directory = "."
+
+  icons = [
+    "ic_badge_instant.1x.icon",
+    "ic_badge_instant.icon",
+    "ic_badge_play.1x.icon",
+    "ic_badge_play.icon",
+    "ic_google_black.1x.icon",
+    "ic_google_black.icon",
+    "ic_mic_black.1x.icon",
+    "ic_mic_black.icon",
+  ]
+}
+
+source_set("vector_icons") {
+  sources = get_target_outputs(":app_list_vector_icons")
+
+  deps = [
+    ":app_list_vector_icons",
+    "//skia",
+  ]
+}
diff --git a/ui/app_list/vector_icons/ic_badge_instant.1x.icon b/ui/app_list/vector_icons/ic_badge_instant.1x.icon
new file mode 100644
index 0000000..77d71b1
--- /dev/null
+++ b/ui/app_list/vector_icons/ic_badge_instant.1x.icon
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 12,
+MOVE_TO, 5.36f, 11,
+LINE_TO, 4.73f, 11,
+LINE_TO, 5.27f, 7,
+LINE_TO, 3.34f, 7,
+LINE_TO, 3.31f, 7,
+CUBIC_TO, 3.14f, 7, 3, 6.86f, 3, 6.69f,
+CUBIC_TO, 3, 6.62f, 3.07f, 6.49f, 3.07f, 6.49f,
+LINE_TO, 6.62f, 1,
+LINE_TO, 7.26f, 1,
+LINE_TO, 6.72f, 5,
+LINE_TO, 8.65f, 5,
+LINE_TO, 8.69f, 5,
+CUBIC_TO, 8.86f, 5, 9, 5.14f, 9, 5.31f,
+CUBIC_TO, 9, 5.38f, 8.98f, 5.44f, 8.94f, 5.49f,
+LINE_TO, 5.36f, 11,
+CLOSE,
+END
diff --git a/ui/app_list/vector_icons/ic_badge_instant.icon b/ui/app_list/vector_icons/ic_badge_instant.icon
new file mode 100644
index 0000000..f88d166
--- /dev/null
+++ b/ui/app_list/vector_icons/ic_badge_instant.icon
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 10.73f, 22,
+LINE_TO, 9.47f, 22,
+LINE_TO, 10.53f, 14,
+LINE_TO, 6.68f, 14.01f,
+LINE_TO, 6.63f, 14.01f,
+CUBIC_TO, 6.28f, 14.01f, 6, 13.73f, 6, 13.39f,
+CUBIC_TO, 6, 13.24f, 6.14f, 12.98f, 6.14f, 12.98f,
+LINE_TO, 13.24f, 2,
+LINE_TO, 14.51f, 2.01f,
+LINE_TO, 13.44f, 10,
+LINE_TO, 17.31f, 9.99f,
+LINE_TO, 17.37f, 9.99f,
+CUBIC_TO, 17.72f, 9.99f, 18, 10.27f, 18, 10.61f,
+CUBIC_TO, 18.01f, 10.76f, 17.95f, 10.88f, 17.88f, 10.99f,
+LINE_TO, 10.73f, 22,
+CLOSE,
+END
diff --git a/ui/app_list/vector_icons/ic_badge_play.1x.icon b/ui/app_list/vector_icons/ic_badge_play.1x.icon
new file mode 100644
index 0000000..9fd281b
--- /dev/null
+++ b/ui/app_list/vector_icons/ic_badge_play.1x.icon
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 12,
+MOVE_TO, 8.06f, 4.5f,
+LINE_TO, 6.63f, 5.93f,
+LINE_TO, 8.06f, 7.36f,
+LINE_TO, 9.09f, 6.49f,
+CUBIC_TO, 9.37f, 6.34f, 9.5f, 6.14f, 9.5f, 5.93f,
+CUBIC_TO, 9.5f, 5.73f, 9.37f, 5.53f, 9.09f, 5.37f,
+LINE_TO, 8.06f, 4.5f,
+CLOSE,
+MOVE_TO, 6.07f, 5.37f,
+LINE_TO, 7.34f, 4.09f,
+LINE_TO, 1.99f, 1.06f,
+CUBIC_TO, 1.96f, 1.04f, 1.92f, 1.03f, 1.89f, 1.01f,
+CUBIC_TO, 1.8f, 0.98f, 1.74f, 1.04f, 1.81f, 1.12f,
+CUBIC_TO, 1.83f, 1.13f, 1.84f, 1.14f, 1.86f, 1.16f,
+LINE_TO, 6.07f, 5.37f,
+CLOSE,
+MOVE_TO, 1.82f, 10.75f,
+CUBIC_TO, 1.74f, 10.82f, 1.81f, 10.89f, 1.89f, 10.85f,
+CUBIC_TO, 1.93f, 10.84f, 1.96f, 10.82f, 2, 10.8f,
+LINE_TO, 7.34f, 7.77f,
+LINE_TO, 6.07f, 6.5f,
+CUBIC_TO, 6.07f, 6.5f, 2.47f, 10.1f, 1.86f, 10.71f,
+LINE_TO, 1.82f, 10.75f,
+CLOSE,
+MOVE_TO, 5.5f, 5.93f,
+LINE_TO, 1.19f, 1.62f,
+CUBIC_TO, 1.1f, 1.53f, 1, 1.59f, 1, 1.72f,
+LINE_TO, 1, 10.15f,
+CUBIC_TO, 1, 10.28f, 1.1f, 10.34f, 1.19f, 10.24f,
+LINE_TO, 5.5f, 5.93f,
+CLOSE,
+END
diff --git a/ui/app_list/vector_icons/ic_badge_play.icon b/ui/app_list/vector_icons/ic_badge_play.icon
new file mode 100644
index 0000000..77e62cd41
--- /dev/null
+++ b/ui/app_list/vector_icons/ic_badge_play.icon
@@ -0,0 +1,40 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 20.18f, 10.88f,
+LINE_TO, 17.12f, 9.14f,
+LINE_TO, 14.26f, 12,
+LINE_TO, 17.12f, 14.86f,
+LINE_TO, 20.18f, 13.12f,
+CUBIC_TO, 20.73f, 12.81f, 21, 12.41f, 21, 12,
+CUBIC_TO, 21, 11.59f, 20.73f, 11.19f, 20.18f, 10.88f,
+LINE_TO, 20.18f, 10.88f,
+CLOSE,
+MOVE_TO, 4.71f, 2.45f,
+LINE_TO, 13.13f, 10.87f,
+LINE_TO, 15.68f, 8.32f,
+LINE_TO, 4.98f, 2.26f,
+CUBIC_TO, 4.91f, 2.22f, 4.84f, 2.19f, 4.77f, 2.16f,
+CUBIC_TO, 4.6f, 2.09f, 4.47f, 2.21f, 4.62f, 2.37f,
+CUBIC_TO, 4.65f, 2.39f, 4.68f, 2.42f, 4.71f, 2.45f,
+LINE_TO, 4.71f, 2.45f,
+CLOSE,
+MOVE_TO, 4.71f, 21.55f,
+LINE_TO, 4.63f, 21.63f,
+CUBIC_TO, 4.48f, 21.78f, 4.61f, 21.91f, 4.78f, 21.84f,
+CUBIC_TO, 4.85f, 21.81f, 4.92f, 21.78f, 4.99f, 21.74f,
+LINE_TO, 15.68f, 15.68f,
+LINE_TO, 13.13f, 13.13f,
+CUBIC_TO, 13.13f, 13.13f, 5.93f, 20.34f, 4.71f, 21.55f,
+LINE_TO, 4.71f, 21.55f,
+CLOSE,
+MOVE_TO, 12, 12,
+LINE_TO, 3.38f, 3.38f,
+CUBIC_TO, 3.19f, 3.19f, 3, 3.31f, 3, 3.57f,
+LINE_TO, 3, 20.43f,
+CUBIC_TO, 3, 20.69f, 3.19f, 20.81f, 3.38f, 20.62f,
+LINE_TO, 12, 12,
+CLOSE,
+END
diff --git a/ui/app_list/vector_icons/vector_icons.cc.template b/ui/app_list/vector_icons/vector_icons.cc.template
index 7af5d5b4..9a44601 100644
--- a/ui/app_list/vector_icons/vector_icons.cc.template
+++ b/ui/app_list/vector_icons/vector_icons.cc.template
@@ -5,7 +5,7 @@
 // vector_icons.cc.template is used to generate vector_icons.cc. Edit the former
 // rather than the latter.
 
-#include "ui/app_list/vector_icons.h"
+#include "ui/app_list/vector_icons/vector_icons.h"
 
 #include "base/logging.h"
 #include "ui/gfx/vector_icon_types.h"
diff --git a/ui/app_list/views/app_list_view.cc b/ui/app_list/views/app_list_view.cc
index 128b018..79fa7426 100644
--- a/ui/app_list/views/app_list_view.cc
+++ b/ui/app_list/views/app_list_view.cc
@@ -521,7 +521,7 @@
   // fling.
   int const new_y_position = location.y() - initial_drag_point_.y() +
                              fullscreen_widget_->GetWindowBoundsInScreen().y();
-  if (std::abs(last_fling_velocity_) > kAppListDragVelocityThreshold) {
+  if (std::abs(last_fling_velocity_) >= kAppListDragVelocityThreshold) {
     // If the user releases drag with velocity over the threshold, snap to
     // the next state, ignoring the drag release position.
 
@@ -724,12 +724,13 @@
     return;
 
   switch (event->type()) {
+    case ui::ET_SCROLL_FLING_START:
     case ui::ET_GESTURE_SCROLL_BEGIN:
       StartDrag(event->location());
       event->SetHandled();
       break;
     case ui::ET_GESTURE_SCROLL_UPDATE:
-      last_fling_velocity_ = event->details().velocity_y();
+      last_fling_velocity_ = event->details().scroll_y();
       UpdateDrag(event->location());
       event->SetHandled();
       break;
diff --git a/ui/app_list/views/search_box_view.cc b/ui/app_list/views/search_box_view.cc
index 7841388b..bd0141c 100644
--- a/ui/app_list/views/search_box_view.cc
+++ b/ui/app_list/views/search_box_view.cc
@@ -17,7 +17,7 @@
 #include "ui/app_list/resources/grit/app_list_resources.h"
 #include "ui/app_list/search_box_model.h"
 #include "ui/app_list/speech_ui_model.h"
-#include "ui/app_list/vector_icons.h"
+#include "ui/app_list/vector_icons/vector_icons.h"
 #include "ui/app_list/views/app_list_view.h"
 #include "ui/app_list/views/contents_view.h"
 #include "ui/app_list/views/search_box_view_delegate.h"
diff --git a/ui/app_list/views/search_result_tile_item_view.cc b/ui/app_list/views/search_result_tile_item_view.cc
index 70c8e26..61f27dc 100644
--- a/ui/app_list/views/search_result_tile_item_view.cc
+++ b/ui/app_list/views/search_result_tile_item_view.cc
@@ -111,6 +111,10 @@
   if (!old_item || !item->icon().BackedBySameObjectAs(old_item->icon())) {
     OnIconChanged();
   }
+  if (!old_item ||
+      !item->badge_icon().BackedBySameObjectAs(old_item->badge_icon())) {
+    OnBadgeIconChanged();
+  }
 }
 
 void SearchResultTileItemView::SetRating(float rating) {
@@ -155,10 +159,12 @@
 
 void SearchResultTileItemView::OnIconChanged() {
   SetIcon(item_->icon());
+  Layout();
 }
 
 void SearchResultTileItemView::OnBadgeIconChanged() {
   SetBadgeIcon(item_->badge_icon());
+  Layout();
 }
 
 void SearchResultTileItemView::OnRatingChanged() {
diff --git a/ui/aura/test/mus/test_window_manager_client.cc b/ui/aura/test/mus/test_window_manager_client.cc
index 9e500d73..5ca10a2a 100644
--- a/ui/aura/test/mus/test_window_manager_client.cc
+++ b/ui/aura/test/mus/test_window_manager_client.cc
@@ -48,7 +48,9 @@
     const std::vector<display::Display>& displays,
     std::vector<::ui::mojom::WmViewportMetricsPtr> viewport_metrics,
     int64_t primary_display_id,
-    const SetDisplayConfigurationCallback& callback) {}
+    const SetDisplayConfigurationCallback& callback) {
+  changes_.push_back(WindowManagerClientChangeType::SET_DISPLAY_CONFIGURATION);
+}
 
 void TestWindowManagerClient::WmResponse(uint32_t change_id, bool response) {}
 
diff --git a/ui/aura/test/mus/test_window_manager_client.h b/ui/aura/test/mus/test_window_manager_client.h
index 68d94332..31ac57fc 100644
--- a/ui/aura/test/mus/test_window_manager_client.h
+++ b/ui/aura/test/mus/test_window_manager_client.h
@@ -17,6 +17,7 @@
 
 enum class WindowManagerClientChangeType {
   ADD_ACTIVATION_PARENT,
+  SET_DISPLAY_CONFIGURATION,
 };
 
 // WindowManagerClient implementation for tests.
diff --git a/ui/display/manager/display_manager.cc b/ui/display/manager/display_manager.cc
index 7390e32..f8be65d0 100644
--- a/ui/display/manager/display_manager.cc
+++ b/ui/display/manager/display_manager.cc
@@ -126,6 +126,15 @@
   return *iter;
 }
 
+bool ContainsDisplayWithId(const std::vector<Display>& displays,
+                           int64_t display_id) {
+  for (auto& display : displays) {
+    if (display.id() == display_id)
+      return true;
+  }
+  return false;
+}
+
 }  // namespace
 
 using std::string;
@@ -906,10 +915,7 @@
 }
 
 bool DisplayManager::IsActiveDisplayId(int64_t display_id) const {
-  return std::find_if(active_display_list_.begin(), active_display_list_.end(),
-                      [display_id](const Display& display) {
-                        return display.id() == display_id;
-                      }) != active_display_list_.end();
+  return ContainsDisplayWithId(active_display_list_, display_id);
 }
 
 bool DisplayManager::IsInMirrorMode() const {
@@ -1110,7 +1116,19 @@
     // Don't notify observers if the mirrored window has changed.
     if (software_mirroring_enabled() && mirroring_display_id_ == display_id)
       return false;
+
+    // In unified mode then |active_display_list_| won't have a display for
+    // |display_id| but |software_mirroring_display_list_| should. Reconfigure
+    // the displays so the unified display size is recomputed.
+    if (IsInUnifiedMode() &&
+        ContainsDisplayWithId(software_mirroring_display_list_, display_id)) {
+      DCHECK(!IsActiveDisplayId(display_id));
+      ReconfigureDisplays();
+      return true;
+    }
+
     Display* display = FindDisplayForId(display_id);
+    DCHECK(display);
     display->SetSize(display_info_[display_id].size_in_pixel());
     NotifyMetricsChanged(*display, DisplayObserver::DISPLAY_METRIC_BOUNDS);
     return true;
@@ -1329,7 +1347,7 @@
   // TODO(oshima): This happens when windows in unified desktop have
   // been moved to a normal window. Fix this.
   if (id != kUnifiedDisplayId)
-    DLOG(WARNING) << "Could not find display:" << id;
+    DLOG(ERROR) << "Could not find display:" << id;
   return nullptr;
 }
 
diff --git a/ui/display/mojo/display.mojom b/ui/display/mojo/display.mojom
index 6b2cce08..708f2be 100644
--- a/ui/display/mojo/display.mojom
+++ b/ui/display/mojo/display.mojom
@@ -21,6 +21,13 @@
   UNAVAILABLE,
 };
 
+// Corresponds to display::Display::AccelerometerSupport.
+enum AccelerometerSupport {
+  UNKNOWN,
+  AVAILABLE,
+  UNAVAILABLE,
+};
+
 // Corresponds to display::Display.
 struct Display {
   int64 id;
@@ -30,6 +37,7 @@
   float device_scale_factor;
   Rotation rotation;
   TouchSupport touch_support;
+  AccelerometerSupport accelerometer_support;
   gfx.mojom.Size maximum_cursor_size;
   int32 color_depth;
   int32 depth_per_component;
diff --git a/ui/display/mojo/display.typemap b/ui/display/mojo/display.typemap
index 69a12e7..35d8b1d 100644
--- a/ui/display/mojo/display.typemap
+++ b/ui/display/mojo/display.typemap
@@ -19,4 +19,5 @@
   "display.mojom.Display=display::Display",
   "display.mojom.DisplayRotation=display::Display::Rotation",
   "display.mojom.TouchSupport=display::Display::TouchSupport",
+  "display.mojom.AccelerometerSupport=display::Display::AccelerometerSupport",
 ]
diff --git a/ui/display/mojo/display_struct_traits.cc b/ui/display/mojo/display_struct_traits.cc
index d7d11b8..4b61b0a 100644
--- a/ui/display/mojo/display_struct_traits.cc
+++ b/ui/display/mojo/display_struct_traits.cc
@@ -77,6 +77,41 @@
   return false;
 }
 
+display::mojom::AccelerometerSupport
+EnumTraits<display::mojom::AccelerometerSupport,
+           display::Display::AccelerometerSupport>::
+    ToMojom(display::Display::AccelerometerSupport accelerometer_support) {
+  switch (accelerometer_support) {
+    case display::Display::ACCELEROMETER_SUPPORT_UNKNOWN:
+      return display::mojom::AccelerometerSupport::UNKNOWN;
+    case display::Display::ACCELEROMETER_SUPPORT_AVAILABLE:
+      return display::mojom::AccelerometerSupport::AVAILABLE;
+    case display::Display::ACCELEROMETER_SUPPORT_UNAVAILABLE:
+      return display::mojom::AccelerometerSupport::UNAVAILABLE;
+  }
+  NOTREACHED();
+  return display::mojom::AccelerometerSupport::UNKNOWN;
+}
+
+bool EnumTraits<display::mojom::AccelerometerSupport,
+                display::Display::AccelerometerSupport>::
+    FromMojom(display::mojom::AccelerometerSupport accelerometer_support,
+              display::Display::AccelerometerSupport* out) {
+  switch (accelerometer_support) {
+    case display::mojom::AccelerometerSupport::UNKNOWN:
+      *out = display::Display::ACCELEROMETER_SUPPORT_UNKNOWN;
+      return true;
+    case display::mojom::AccelerometerSupport::AVAILABLE:
+      *out = display::Display::ACCELEROMETER_SUPPORT_AVAILABLE;
+      return true;
+    case display::mojom::AccelerometerSupport::UNAVAILABLE:
+      *out = display::Display::ACCELEROMETER_SUPPORT_UNAVAILABLE;
+      return true;
+  }
+  NOTREACHED();
+  return false;
+}
+
 bool StructTraits<display::mojom::DisplayDataView, display::Display>::Read(
     display::mojom::DisplayDataView data,
     display::Display* out) {
@@ -99,6 +134,9 @@
   if (!data.ReadTouchSupport(&out->touch_support_))
     return false;
 
+  if (!data.ReadAccelerometerSupport(&out->accelerometer_support_))
+    return false;
+
   if (!data.ReadMaximumCursorSize(&out->maximum_cursor_size_))
     return false;
 
diff --git a/ui/display/mojo/display_struct_traits.h b/ui/display/mojo/display_struct_traits.h
index 00009ed..3e951e9c 100644
--- a/ui/display/mojo/display_struct_traits.h
+++ b/ui/display/mojo/display_struct_traits.h
@@ -28,6 +28,15 @@
 };
 
 template <>
+struct EnumTraits<display::mojom::AccelerometerSupport,
+                  display::Display::AccelerometerSupport> {
+  static display::mojom::AccelerometerSupport ToMojom(
+      display::Display::AccelerometerSupport type);
+  static bool FromMojom(display::mojom::AccelerometerSupport type,
+                        display::Display::AccelerometerSupport* output);
+};
+
+template <>
 struct StructTraits<display::mojom::DisplayDataView, display::Display> {
   static int64_t id(const display::Display& display) { return display.id(); }
 
@@ -56,6 +65,11 @@
     return display.touch_support();
   }
 
+  static display::Display::AccelerometerSupport accelerometer_support(
+      const display::Display& display) {
+    return display.accelerometer_support();
+  }
+
   static const gfx::Size& maximum_cursor_size(const display::Display& display) {
     return display.maximum_cursor_size();
   }
diff --git a/ui/display/mojo/display_struct_traits_unittest.cc b/ui/display/mojo/display_struct_traits_unittest.cc
index 9e2ed40..42ae8278 100644
--- a/ui/display/mojo/display_struct_traits_unittest.cc
+++ b/ui/display/mojo/display_struct_traits_unittest.cc
@@ -3,6 +3,9 @@
 // found in the LICENSE file.
 
 #include <memory>
+#include <string>
+#include <utility>
+#include <vector>
 
 #include "base/macros.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -36,6 +39,7 @@
   EXPECT_EQ(input.device_scale_factor(), output.device_scale_factor());
   EXPECT_EQ(input.rotation(), output.rotation());
   EXPECT_EQ(input.touch_support(), output.touch_support());
+  EXPECT_EQ(input.accelerometer_support(), output.accelerometer_support());
   EXPECT_EQ(input.maximum_cursor_size(), output.maximum_cursor_size());
   EXPECT_EQ(input.color_depth(), output.color_depth());
   EXPECT_EQ(input.depth_per_component(), output.depth_per_component());
@@ -51,10 +55,17 @@
   EXPECT_EQ(input.primary_id, output.primary_id);
 }
 
-bool CompareModes(const DisplayMode& lhs, const DisplayMode& rhs) {
-  return lhs.size() == rhs.size() &&
-         lhs.is_interlaced() == rhs.is_interlaced() &&
-         lhs.refresh_rate() == rhs.refresh_rate();
+void CheckDisplayModesEqual(const DisplayMode* input,
+                            const DisplayMode* output) {
+  // DisplaySnapshot can have null DisplayModes, so if |input| is null then
+  // |output| should be null too.
+  if (input == nullptr && output == nullptr)
+    return;
+
+  EXPECT_NE(input, output);  // Make sure they aren't the same object.
+  EXPECT_EQ(input->size(), output->size());
+  EXPECT_EQ(input->is_interlaced(), output->is_interlaced());
+  EXPECT_EQ(input->refresh_rate(), output->refresh_rate());
 }
 
 void CheckDisplaySnapShotMojoEqual(const DisplaySnapshotMojo& input,
@@ -77,19 +88,12 @@
   EXPECT_EQ(input.modes().size(), output.modes().size());
 
   for (size_t i = 0; i < input.modes().size(); i++)
-    EXPECT_TRUE(CompareModes(*input.modes()[i], *output.modes()[i]));
+    CheckDisplayModesEqual(input.modes()[i].get(), output.modes()[i].get());
 
   EXPECT_EQ(input.edid(), output.edid());
 
-  if (!input.current_mode())
-    EXPECT_EQ(nullptr, output.current_mode());
-  else
-    EXPECT_TRUE(CompareModes(*input.current_mode(), *output.current_mode()));
-
-  if (!input.native_mode())
-    EXPECT_EQ(nullptr, output.native_mode());
-  else
-    EXPECT_TRUE(CompareModes(*input.native_mode(), *output.native_mode()));
+  CheckDisplayModesEqual(input.current_mode(), output.current_mode());
+  CheckDisplayModesEqual(input.native_mode(), output.native_mode());
 
   EXPECT_EQ(input.maximum_cursor_size(), output.maximum_cursor_size());
 }
@@ -128,6 +132,7 @@
   input.set_device_scale_factor(2.0f);
   input.set_rotation(Display::ROTATE_270);
   input.set_touch_support(Display::TOUCH_SUPPORT_AVAILABLE);
+  input.set_accelerometer_support(Display::ACCELEROMETER_SUPPORT_UNAVAILABLE);
   input.set_maximum_cursor_size(maximum_cursor_size);
   input.set_color_depth(input.color_depth() + 1);
   input.set_depth_per_component(input.depth_per_component() + 1);
@@ -146,11 +151,7 @@
   std::unique_ptr<DisplayMode> output;
   SerializeAndDeserialize<mojom::DisplayMode>(input->Clone(), &output);
 
-  // We want to test each component individually to make sure each data member
-  // was correctly serialized and deserialized.
-  EXPECT_EQ(input->size(), output->size());
-  EXPECT_EQ(input->is_interlaced(), output->is_interlaced());
-  EXPECT_EQ(input->refresh_rate(), output->refresh_rate());
+  CheckDisplayModesEqual(input.get(), output.get());
 }
 
 TEST(DisplayStructTraitsTest, DisplayPlacementFlushAtTop) {
@@ -267,8 +268,7 @@
   const gfx::Point origin(1, 2);
   const gfx::Size physical_size(5, 9);
   const gfx::Size maximum_cursor_size(3, 5);
-  const DisplayConnectionType type =
-      display::DISPLAY_CONNECTION_TYPE_DISPLAYPORT;
+  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_DISPLAYPORT;
   const bool is_aspect_preserving_scaling = true;
   const bool has_overscan = true;
   const bool has_color_correction_matrix = true;
@@ -278,7 +278,7 @@
 
   const DisplayMode display_mode(gfx::Size(13, 11), true, 40.0f);
 
-  display::DisplaySnapshot::DisplayModeList modes;
+  DisplaySnapshot::DisplayModeList modes;
   modes.push_back(display_mode.Clone());
 
   const DisplayMode* current_mode = nullptr;
@@ -306,7 +306,7 @@
   const gfx::Point origin(11, 32);
   const gfx::Size physical_size(55, 49);
   const gfx::Size maximum_cursor_size(13, 95);
-  const DisplayConnectionType type = display::DISPLAY_CONNECTION_TYPE_VGA;
+  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_VGA;
   const bool is_aspect_preserving_scaling = true;
   const bool has_overscan = true;
   const bool has_color_correction_matrix = true;
@@ -316,7 +316,7 @@
 
   const DisplayMode display_mode(gfx::Size(13, 11), true, 50.0f);
 
-  display::DisplaySnapshot::DisplayModeList modes;
+  DisplaySnapshot::DisplayModeList modes;
   modes.push_back(display_mode.Clone());
 
   const DisplayMode* current_mode = nullptr;
@@ -344,7 +344,7 @@
   const gfx::Point origin(0, 1760);
   const gfx::Size physical_size(520, 320);
   const gfx::Size maximum_cursor_size(4, 5);
-  const DisplayConnectionType type = display::DISPLAY_CONNECTION_TYPE_HDMI;
+  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_HDMI;
   const bool is_aspect_preserving_scaling = false;
   const bool has_overscan = false;
   const bool has_color_correction_matrix = false;
@@ -356,7 +356,7 @@
   const DisplayMode display_current_mode(gfx::Size(1440, 900), false, 59.89f);
   const DisplayMode display_native_mode(gfx::Size(1920, 1200), false, 59.89f);
 
-  display::DisplaySnapshot::DisplayModeList modes;
+  DisplaySnapshot::DisplayModeList modes;
   modes.push_back(display_mode.Clone());
   modes.push_back(display_current_mode.Clone());
   modes.push_back(display_native_mode.Clone());
@@ -385,7 +385,7 @@
   const gfx::Point origin(0, 0);
   const gfx::Size physical_size(270, 180);
   const gfx::Size maximum_cursor_size(64, 64);
-  const DisplayConnectionType type = display::DISPLAY_CONNECTION_TYPE_INTERNAL;
+  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_INTERNAL;
   const bool is_aspect_preserving_scaling = true;
   const bool has_overscan = false;
   const bool has_color_correction_matrix = false;
@@ -395,7 +395,7 @@
 
   const DisplayMode display_mode(gfx::Size(2560, 1700), false, 95.96f);
 
-  display::DisplaySnapshot::DisplayModeList modes;
+  DisplaySnapshot::DisplayModeList modes;
   modes.push_back(display_mode.Clone());
 
   const DisplayMode* current_mode = modes[0].get();
diff --git a/ui/events/blink/blink_event_util.cc b/ui/events/blink/blink_event_util.cc
index ad6e31ae7..2225d933 100644
--- a/ui/events/blink/blink_event_util.cc
+++ b/ui/events/blink/blink_event_util.cc
@@ -283,6 +283,7 @@
   float old_wheelTicksY = event->wheel_ticks_y;
   float old_movementX = event->movement_x;
   float old_movementY = event->movement_y;
+  WebInputEvent::DispatchType old_dispatch_type = event->dispatch_type;
   *event = event_to_coalesce;
   event->delta_x += old_deltaX;
   event->delta_y += old_deltaY;
@@ -294,6 +295,8 @@
       GetAccelerationRatio(event->delta_x, unaccelerated_x);
   event->acceleration_ratio_y =
       GetAccelerationRatio(event->delta_y, unaccelerated_y);
+  event->dispatch_type =
+      MergeDispatchTypes(old_dispatch_type, event_to_coalesce.dispatch_type);
 }
 
 bool CanCoalesce(const WebTouchEvent& event_to_coalesce,
diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc
index 0f26dd9..f1ce986 100644
--- a/ui/events/blink/input_handler_proxy.cc
+++ b/ui/events/blink/input_handler_proxy.cc
@@ -678,6 +678,24 @@
     CancelCurrentFling();
 
   InputHandlerProxy::EventDisposition result = DROP_EVENT;
+
+  if (wheel_event.dispatch_type == WebInputEvent::kEventNonBlocking) {
+    // The first wheel event in the sequence should be cancellable.
+    DCHECK(wheel_event.phase != WebMouseWheelEvent::kPhaseBegan);
+
+    DCHECK(mouse_wheel_result_ != kEventDispositionUndefined);
+    result = static_cast<EventDisposition>(mouse_wheel_result_);
+
+    if (wheel_event.phase == WebMouseWheelEvent::kPhaseEnded ||
+        wheel_event.phase == WebMouseWheelEvent::kPhaseCancelled ||
+        wheel_event.momentum_phase == WebMouseWheelEvent::kPhaseEnded ||
+        wheel_event.momentum_phase == WebMouseWheelEvent::kPhaseCancelled) {
+      mouse_wheel_result_ = kEventDispositionUndefined;
+    }
+    if (mouse_wheel_result_ != kEventDispositionUndefined)
+      return result;
+  }
+
   cc::EventListenerProperties properties =
       input_handler_->GetEventListenerProperties(
           cc::EventListenerClass::kMouseWheel);
diff --git a/ui/shell_dialogs/DEPS b/ui/shell_dialogs/DEPS
index 629645a..41751b8 100644
--- a/ui/shell_dialogs/DEPS
+++ b/ui/shell_dialogs/DEPS
@@ -5,5 +5,4 @@
   "+ui/base",
   "+ui/gfx",
   "+ui/strings/grit/ui_strings.h",
-  "+win8/viewer",
 ]
diff --git a/ui/webui/resources/polymer_resources.grdp b/ui/webui/resources/polymer_resources.grdp
index 3579eca..1f97fedd 100644
--- a/ui/webui/resources/polymer_resources.grdp
+++ b/ui/webui/resources/polymer_resources.grdp
@@ -278,21 +278,6 @@
   <structure name="IDR_POLYMER_1_0_IRON_SELECTOR_IRON_SELECTOR_HTML"
              file="../../../third_party/polymer/v1_0/components-chromium/iron-selector/iron-selector.html"
              type="chrome_html" />
-  <structure name="IDR_POLYMER_1_0_IRON_TEST_HELPERS_IRON_TEST_HELPERS_HTML"
-             file="../../../third_party/polymer/v1_0/components-chromium/iron-test-helpers/iron-test-helpers.html"
-             type="chrome_html" />
-  <structure name="IDR_POLYMER_1_0_IRON_TEST_HELPERS_MOCK_INTERACTIONS_HTML"
-             file="../../../third_party/polymer/v1_0/components-chromium/iron-test-helpers/mock-interactions.html"
-             type="chrome_html" />
-  <structure name="IDR_POLYMER_1_0_IRON_TEST_HELPERS_MOCK_INTERACTIONS_JS"
-             file="../../../third_party/polymer/v1_0/components-chromium/iron-test-helpers/mock-interactions.js"
-             type="chrome_html" />
-  <structure name="IDR_POLYMER_1_0_IRON_TEST_HELPERS_TEST_HELPERS_HTML"
-             file="../../../third_party/polymer/v1_0/components-chromium/iron-test-helpers/test-helpers.html"
-             type="chrome_html" />
-  <structure name="IDR_POLYMER_1_0_IRON_TEST_HELPERS_TEST_HELPERS_JS"
-             file="../../../third_party/polymer/v1_0/components-chromium/iron-test-helpers/test-helpers.js"
-             type="chrome_html" />
   <structure name="IDR_POLYMER_1_0_IRON_VALIDATABLE_BEHAVIOR_IRON_VALIDATABLE_BEHAVIOR_EXTRACTED_JS"
              file="../../../third_party/polymer/v1_0/components-chromium/iron-validatable-behavior/iron-validatable-behavior-extracted.js"
              type="chrome_html" />