diff --git a/DEPS b/DEPS
index cf46198..538af75 100644
--- a/DEPS
+++ b/DEPS
@@ -86,7 +86,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '3d70ca9cc194d2e946ca7baf0ed5dec30b2149c7',
+  'angle_revision': '3f286cd1b6e29605a159ee0bd20c76929d4d5a9f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -308,7 +308,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0844d0e4444ced82d770668c12906a14636ce024',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '276e0454b797cc2124a3aa20bda093f97fad3d90',
 
   # DevTools node modules. Used on Linux buildbots only.
   'src/third_party/devtools-node-modules': {
@@ -465,7 +465,7 @@
     Var('chromium_git') + '/webm/libwebm.git' + '@' + '4956b2dec65352af32dc71bab553acb631c64177',
 
   'src/third_party/libyuv':
-    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '8fa02df3c0591754958a50cc2896aafae319f3bc',  # from r1675
+    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '12c904a97c81c3ef4cab0fc8fb1f0485b4ec4e8c',  # from r1678
 
   'src/third_party/lighttpd': {
       'url': Var('chromium_git') + '/chromium/deps/lighttpd.git' + '@' + Var('lighttpd_revision'),
@@ -621,7 +621,7 @@
     Var('chromium_git') + '/external/selenium/py.git' + '@' + '5fd78261a75fe08d27ca4835fb6c5ce4b42275bd',
 
   'src/third_party/webgl/src':
-    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '12192b948daa2d83269ab46cbf03d65e9f2a48a3',
+    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'e4919fa03c74bd561dcabf3e61668fa3c7e54353',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + '2707fb2782e7a47d0f53cf8e561bef0bc00fad66', # commit position 20628
diff --git a/ash/public/interfaces/wallpaper.mojom b/ash/public/interfaces/wallpaper.mojom
index d368fe9a..3db509c 100644
--- a/ash/public/interfaces/wallpaper.mojom
+++ b/ash/public/interfaces/wallpaper.mojom
@@ -4,8 +4,12 @@
 
 module ash.mojom;
 
+import "ash/public/interfaces/user_info.mojom";
+import "components/signin/public/interfaces/account_id.mojom";
+import "mojo/common/file_path.mojom";
 import "mojo/common/time.mojom";
 import "skia/public/interfaces/bitmap.mojom";
+import "url/mojo/url.mojom";
 
 // These values match wallpaper::WallpaperLayout.
 enum WallpaperLayout {
@@ -26,6 +30,9 @@
   DEVICE,
 };
 
+// TODO(crbug.com/776464): Remove this after WallpaperManager is removed.
+// WallpaperInfo will be an internal concept within WallpaperController.
+//
 // This corresponds to wallpaper::WallpaperInfo.
 struct WallpaperInfo {
   string location;
@@ -34,25 +41,118 @@
   mojo.common.mojom.Time date;
 };
 
+// User info needed to set wallpapers. Clients must specify the user because
+// it's not always the same with the active user, e.g., when showing wallpapers
+// for different user pods at login screen, or setting wallpapers selectively
+// for primary user and active user during a multi-profile session.
+struct WallpaperUserInfo {
+  // The user's account id.
+  signin.mojom.AccountId account_id;
+
+  // The user type. Matches user_manager::UserType.
+  UserType type;
+
+  // True if the user's non-cryptohome data (wallpaper, avatar etc.) is
+  // ephemeral. See |UserManager::IsCurrentUserNonCryptohomeDataEphemeral| for
+  // more details.
+  bool is_ephemeral;
+
+  // True if the user has gaia account.
+  bool has_gaia_account;
+};
+
 // Used by Chrome to set the wallpaper displayed by ash.
 interface WallpaperController {
-  // Calling this method triggers an initial notification of the wallpaper
-  // state. Observers are automatically removed as their connections are closed.
-  AddObserver(associated WallpaperObserver observer);
+  // Sets the client interface.
+  SetClient(WallpaperControllerClient client);
 
-  // Set the wallpaper picker interface, to let ash trigger Chrome's picker.
-  SetWallpaperPicker(WallpaperPicker picker);
+  // Sets wallpaper from policy or from a local file. Saves the custom wallpaper
+  // to file, posts task to generate thumbnail and updates local state.
+  // |user_info|: The user's information related to wallpaper.
+  // |wallpaper_files_id|: The unique id of each wallpaper file.
+  // |file_name|: The name of the wallpaper file.
+  // |layout|: The layout of the wallpaper, used for wallpaper resizing.
+  // |type|: The type of the wallpaper, e.g., default, policy etc.
+  // |image|: The wallpaper image.
+  // |show_wallpaper|: If false, don't show the new wallpaper now but only
+  //                   update cache.
+  SetCustomWallpaper(WallpaperUserInfo user_info,
+                     string wallpaper_files_id,
+                     string file_name,
+                     WallpaperLayout layout,
+                     WallpaperType type,
+                     skia.mojom.Bitmap image,
+                     bool show_wallpaper);
 
+  // Sets wallpaper from the wallpaper picker selection, i.e., the wallpaper
+  // type is ONLINE.
+  // |user_info|: The user's information related to wallpaper.
+  // |image|: The wallpaper image.
+  // |url|: The url corresponding to this wallpaper. Used as a placeholder for
+  //        the location in WallpaperInfo.
+  // |layout|: The layout of the wallpaper, used for wallpaper resizing.
+  // |show_wallpaper|: If false, don't show the new wallpaper now but only
+  //                   update cache.
+  SetOnlineWallpaper(WallpaperUserInfo user_info,
+                     skia.mojom.Bitmap image,
+                     string url,
+                     WallpaperLayout layout,
+                     bool show_wallpaper);
+
+  // Sets the user's wallpaper to be the default wallpaper. Note: different user
+  // types may have different default wallpapers. If |show_wallpaper| is false,
+  // don't show the default wallpaper now.
+  SetDefaultWallpaper(WallpaperUserInfo user_info, bool show_wallpaper);
+
+  // Sets a customized default wallpaper to be used wherever a default wallpaper
+  // is needed. Note: it doesn't change the default wallpaper for guest and
+  // child accounts.
+  // |wallpaper_url|: The url corresponding to this wallpaper.
+  // |file_path|: The path of the wallpaper file.
+  // |resized_directory|: The directory where resized versions are stored. Must
+  //                      be writable.
+  SetCustomizedDefaultWallpaper(url.mojom.Url wallpaper_url,
+                                mojo.common.mojom.FilePath file_path,
+                                mojo.common.mojom.FilePath resized_directory);
+
+  // Shows the user's wallpaper, which is determined in the following order:
+  // 1) Use device policy wallpaper if it exists AND we are at the login screen.
+  // 2) Use user policy wallpaper if it exists.
+  // 3) Use the wallpaper set by the user (either by |SetOnlineWallpaper| or
+  //    |SetCustomWallpaper|), if any.
+  // 4) Use the default wallpaper of this user.
+  ShowUserWallpaper(WallpaperUserInfo user_info);
+
+  // Used by the gaia-signin UI. Signin wallpaper is considered either as the
+  // device policy wallpaper or the default wallpaper.
+  ShowSigninWallpaper();
+
+  // Removes all of the user's saved wallpapers and related info.
+  RemoveUserWallpaper(WallpaperUserInfo user_info);
+
+  // TODO(crbug.com/776464): This is only used by WallpaperManager. Remove this
+  // after WallpaperManager is removed.
+  //
   // Set the wallpaper bitmap and info used for the ash desktop background.
   // A null or empty |wallpaper| bitmap is treated as a no-op.
   // TODO(crbug.com/655875): Optimize ash wallpaper transport; avoid sending
   // large bitmaps over Mojo; use shared memory like BitmapUploader, etc.
   SetWallpaper(skia.mojom.Bitmap? wallpaper, WallpaperInfo info);
 
+  // Calling this method triggers an initial notification of the wallpaper
+  // state. Observers are automatically removed as their connections are closed.
+  AddObserver(associated WallpaperObserver observer);
+
   // Runs to get wallpaper prominent colors.
   GetWallpaperColors() => (array<uint32> prominent_colors);
 };
 
+// Used by ash to control a Chrome client.
+interface WallpaperControllerClient {
+  // Opens the wallpaper picker window.
+  OpenWallpaperPicker();
+};
+
 // Used to listen for wallpaper state changed.
 interface WallpaperObserver {
   // Called when the colors extracted from the current wallpaper change. May
@@ -61,9 +161,3 @@
   // Colors are ordered and are referenced in wallpaper::ColorProfileType.
   OnWallpaperColorsChanged(array<uint32> prominent_colors);
 };
-
-// Used by ash to trigger Chrome's wallpaper picker functionality.
-interface WallpaperPicker {
-  // Open the wallpaper picker window.
-  Open();
-};
diff --git a/ash/shelf/shelf_context_menu_model_unittest.cc b/ash/shelf/shelf_context_menu_model_unittest.cc
index 82dce6e9..f32eb0e4 100644
--- a/ash/shelf/shelf_context_menu_model_unittest.cc
+++ b/ash/shelf/shelf_context_menu_model_unittest.cc
@@ -41,28 +41,28 @@
   DISALLOW_COPY_AND_ASSIGN(ShelfContextMenuModelTest);
 };
 
-// A test wallpaper picker class that counts the number of times it is opened.
-class TestWallpaperPicker : public mojom::WallpaperPicker {
+// A test wallpaper controller client class.
+class TestWallpaperControllerClient : public mojom::WallpaperControllerClient {
  public:
-  TestWallpaperPicker() : binding_(this) {}
-  ~TestWallpaperPicker() override = default;
+  TestWallpaperControllerClient() : binding_(this) {}
+  ~TestWallpaperControllerClient() override = default;
 
   size_t open_count() const { return open_count_; }
 
-  mojom::WallpaperPickerPtr CreateInterfacePtr() {
-    mojom::WallpaperPickerPtr ptr;
+  mojom::WallpaperControllerClientPtr CreateInterfacePtr() {
+    mojom::WallpaperControllerClientPtr ptr;
     binding_.Bind(mojo::MakeRequest(&ptr));
     return ptr;
   }
 
-  // mojom::WallpaperPicker:
-  void Open() override { open_count_++; }
+  // mojom::WallpaperControllerClient:
+  void OpenWallpaperPicker() override { open_count_++; }
 
  private:
   size_t open_count_ = 0;
-  mojo::Binding<mojom::WallpaperPicker> binding_;
+  mojo::Binding<mojom::WallpaperControllerClient> binding_;
 
-  DISALLOW_COPY_AND_ASSIGN(TestWallpaperPicker);
+  DISALLOW_COPY_AND_ASSIGN(TestWallpaperControllerClient);
 };
 
 // A test shelf item delegate that records the commands sent for execution.
@@ -141,13 +141,12 @@
   ShelfContextMenuModel menu3(MenuItemList(), nullptr, primary_id);
   submenu = menu3.GetSubmenuModelAt(1);
   EXPECT_TRUE(submenu->IsItemCheckedAt(0));
-  TestWallpaperPicker picker;
-  Shell::Get()->wallpaper_controller()->SetWallpaperPicker(
-      picker.CreateInterfacePtr());
-  EXPECT_EQ(0u, picker.open_count());
+  TestWallpaperControllerClient client;
+  Shell::Get()->wallpaper_controller()->SetClient(client.CreateInterfacePtr());
+  EXPECT_EQ(0u, client.open_count());
   menu3.ActivatedAt(2);
-  RunAllPendingInMessageLoop();
-  EXPECT_EQ(1u, picker.open_count());
+  Shell::Get()->wallpaper_controller()->FlushForTesting();
+  EXPECT_EQ(1u, client.open_count());
 }
 
 // Tests the prepending of custom items in a shelf context menu.
diff --git a/ash/wallpaper/wallpaper_controller.cc b/ash/wallpaper/wallpaper_controller.cc
index 65ce741..4d5e2160 100644
--- a/ash/wallpaper/wallpaper_controller.cc
+++ b/ash/wallpaper/wallpaper_controller.cc
@@ -368,9 +368,9 @@
 }
 
 void WallpaperController::OpenSetWallpaperPage() {
-  if (wallpaper_picker_ &&
+  if (wallpaper_controller_client_ &&
       Shell::Get()->wallpaper_delegate()->CanOpenSetWallpaperPage()) {
-    wallpaper_picker_->Open();
+    wallpaper_controller_client_->OpenWallpaperPicker();
   }
 }
 
@@ -387,26 +387,73 @@
              switches::kAshDisableLoginDimAndBlur);
 }
 
-void WallpaperController::AddObserver(
-    mojom::WallpaperObserverAssociatedPtrInfo observer) {
-  mojom::WallpaperObserverAssociatedPtr observer_ptr;
-  observer_ptr.Bind(std::move(observer));
-  observer_ptr->OnWallpaperColorsChanged(prominent_colors_);
-  mojo_observers_.AddPtr(std::move(observer_ptr));
+void WallpaperController::SetClient(
+    mojom::WallpaperControllerClientPtr client) {
+  wallpaper_controller_client_ = std::move(client);
 }
 
-void WallpaperController::SetWallpaperPicker(mojom::WallpaperPickerPtr picker) {
-  wallpaper_picker_ = std::move(picker);
+void WallpaperController::SetCustomWallpaper(
+    mojom::WallpaperUserInfoPtr user_info,
+    const std::string& wallpaper_files_id,
+    const std::string& file_name,
+    wallpaper::WallpaperLayout layout,
+    wallpaper::WallpaperType type,
+    const SkBitmap& image,
+    bool show_wallpaper) {
+  NOTIMPLEMENTED();
+}
+
+void WallpaperController::SetOnlineWallpaper(
+    mojom::WallpaperUserInfoPtr user_info,
+    const SkBitmap& image,
+    const std::string& url,
+    wallpaper::WallpaperLayout layout,
+    bool show_wallpaper) {
+  NOTIMPLEMENTED();
+}
+
+void WallpaperController::SetDefaultWallpaper(
+    mojom::WallpaperUserInfoPtr user_info,
+    bool show_wallpaper) {
+  NOTIMPLEMENTED();
+}
+
+void WallpaperController::SetCustomizedDefaultWallpaper(
+    const GURL& wallpaper_url,
+    const base::FilePath& file_path,
+    const base::FilePath& resized_directory) {
+  NOTIMPLEMENTED();
+}
+
+void WallpaperController::ShowUserWallpaper(
+    mojom::WallpaperUserInfoPtr user_info) {
+  NOTIMPLEMENTED();
+}
+
+void WallpaperController::ShowSigninWallpaper() {
+  NOTIMPLEMENTED();
+}
+
+void WallpaperController::RemoveUserWallpaper(
+    mojom::WallpaperUserInfoPtr user_info) {
+  NOTIMPLEMENTED();
 }
 
 void WallpaperController::SetWallpaper(const SkBitmap& wallpaper,
                                        const wallpaper::WallpaperInfo& info) {
   if (wallpaper.isNull())
     return;
-
   SetWallpaperImage(gfx::ImageSkia::CreateFrom1xBitmap(wallpaper), info);
 }
 
+void WallpaperController::AddObserver(
+    mojom::WallpaperObserverAssociatedPtrInfo observer) {
+  mojom::WallpaperObserverAssociatedPtr observer_ptr;
+  observer_ptr.Bind(std::move(observer));
+  observer_ptr->OnWallpaperColorsChanged(prominent_colors_);
+  mojo_observers_.AddPtr(std::move(observer_ptr));
+}
+
 void WallpaperController::GetWallpaperColors(
     GetWallpaperColorsCallback callback) {
   std::move(callback).Run(prominent_colors_);
@@ -425,6 +472,10 @@
   SetProminentColors(colors);
 }
 
+void WallpaperController::FlushForTesting() {
+  wallpaper_controller_client_.FlushForTesting();
+}
+
 void WallpaperController::InstallDesktopController(aura::Window* root_window) {
   WallpaperWidgetController* component = nullptr;
   int container_id = GetWallpaperContainerId(locked_);
diff --git a/ash/wallpaper/wallpaper_controller.h b/ash/wallpaper/wallpaper_controller.h
index e5c24c1..80ecc31 100644
--- a/ash/wallpaper/wallpaper_controller.h
+++ b/ash/wallpaper/wallpaper_controller.h
@@ -138,10 +138,31 @@
   bool IsWallpaperBlurred() const { return is_wallpaper_blurred_; }
 
   // mojom::WallpaperController overrides:
-  void AddObserver(mojom::WallpaperObserverAssociatedPtrInfo observer) override;
-  void SetWallpaperPicker(mojom::WallpaperPickerPtr picker) override;
+  void SetClient(mojom::WallpaperControllerClientPtr client) override;
+  void SetCustomWallpaper(mojom::WallpaperUserInfoPtr user_info,
+                          const std::string& wallpaper_files_id,
+                          const std::string& file_name,
+                          wallpaper::WallpaperLayout layout,
+                          wallpaper::WallpaperType type,
+                          const SkBitmap& image,
+                          bool show_wallpaper) override;
+  void SetOnlineWallpaper(mojom::WallpaperUserInfoPtr user_info,
+                          const SkBitmap& image,
+                          const std::string& url,
+                          wallpaper::WallpaperLayout layout,
+                          bool show_wallpaper) override;
+  void SetDefaultWallpaper(mojom::WallpaperUserInfoPtr user_info,
+                           bool show_wallpaper) override;
+  void SetCustomizedDefaultWallpaper(
+      const GURL& wallpaper_url,
+      const base::FilePath& file_path,
+      const base::FilePath& resized_directory) override;
+  void ShowUserWallpaper(mojom::WallpaperUserInfoPtr user_info) override;
+  void ShowSigninWallpaper() override;
+  void RemoveUserWallpaper(mojom::WallpaperUserInfoPtr user_info) override;
   void SetWallpaper(const SkBitmap& wallpaper,
                     const wallpaper::WallpaperInfo& wallpaper_info) override;
+  void AddObserver(mojom::WallpaperObserverAssociatedPtrInfo observer) override;
   void GetWallpaperColors(GetWallpaperColorsCallback callback) override;
 
   // WallpaperResizerObserver:
@@ -150,6 +171,9 @@
   // WallpaperColorCalculatorObserver:
   void OnColorCalculationComplete() override;
 
+  // Flushes the mojo message pipe to chrome.
+  void FlushForTesting();
+
  private:
   FRIEND_TEST_ALL_PREFIXES(WallpaperControllerTest, BasicReparenting);
   FRIEND_TEST_ALL_PREFIXES(WallpaperControllerTest,
@@ -208,8 +232,8 @@
 
   WallpaperMode wallpaper_mode_;
 
-  // Wallpaper picker interface in chrome browser, used to open the picker.
-  mojom::WallpaperPickerPtr wallpaper_picker_;
+  // Client interface in chrome browser.
+  mojom::WallpaperControllerClientPtr wallpaper_controller_client_;
 
   // Bindings for the WallpaperController interface.
   mojo::BindingSet<mojom::WallpaperController> bindings_;
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 7896441..402ec30 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -624,6 +624,7 @@
     "paint/paint_shader_unittest.cc",
     "paint/scoped_image_flags_unittest.cc",
     "paint/solid_color_analyzer_unittest.cc",
+    "paint/transfer_cache_unittest.cc",
     "raster/playback_image_provider_unittest.cc",
     "raster/raster_buffer_provider_unittest.cc",
     "raster/raster_source_unittest.cc",
diff --git a/cc/DEPS b/cc/DEPS
index 3a09aa6..c919fc61f 100644
--- a/cc/DEPS
+++ b/cc/DEPS
@@ -7,6 +7,7 @@
   "+gpu/command_buffer/client/gles2_interface_stub.h", # for tests
   "+gpu/command_buffer/client/gpu_memory_buffer_manager.h",
   "+gpu/command_buffer/common/capabilities.h",
+  "+gpu/command_buffer/common/discardable_handle.h",
   "+gpu/command_buffer/common/gpu_memory_allocation.h",
   "+gpu/command_buffer/common/mailbox.h",
   "+gpu/command_buffer/common/mailbox_holder.h",
@@ -50,4 +51,10 @@
     "+gpu/ipc",
     "+gpu/skia_bindings",
   ],
+  "transfer_cache_unittest\.cc" : [
+    "+gpu/command_buffer/client",
+    "+gpu/command_buffer/common",
+    "+gpu/command_buffer/service",
+    "+gpu/ipc",
+  ],
 }
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index fde9a89..c604daed 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -53,6 +53,8 @@
     "paint_text_blob_builder.h",
     "paint_typeface.cc",
     "paint_typeface.h",
+    "raw_memory_transfer_cache_entry.cc",
+    "raw_memory_transfer_cache_entry.h",
     "record_paint_canvas.cc",
     "record_paint_canvas.h",
     "scoped_image_flags.cc",
@@ -63,6 +65,8 @@
     "skia_paint_image_generator.h",
     "solid_color_analyzer.cc",
     "solid_color_analyzer.h",
+    "transfer_cache_entry.cc",
+    "transfer_cache_entry.h",
   ]
 
   defines = [ "CC_PAINT_IMPLEMENTATION=1" ]
diff --git a/cc/paint/paint_flags.cc b/cc/paint/paint_flags.cc
index 660b17b..607174e 100644
--- a/cc/paint/paint_flags.cc
+++ b/cc/paint/paint_flags.cc
@@ -5,6 +5,7 @@
 #include "cc/paint/paint_flags.h"
 
 #include "cc/paint/paint_op_buffer.h"
+#include "third_party/skia/include/core/SkFlattenableSerialization.h"
 
 namespace {
 
@@ -128,6 +129,64 @@
   return PaintOp::IsValidPaintFlagsSkBlendMode(getBlendMode());
 }
 
+static bool AreFlattenablesEqual(SkFlattenable* left, SkFlattenable* right) {
+  sk_sp<SkData> left_data(SkValidatingSerializeFlattenable(left));
+  sk_sp<SkData> right_data(SkValidatingSerializeFlattenable(right));
+  if (left_data->size() != right_data->size())
+    return false;
+  if (!left_data->equals(right_data.get()))
+    return false;
+  return true;
+}
+
+bool PaintFlags::operator==(const PaintFlags& other) const {
+  // Can't just ToSkPaint and operator== here as SkPaint does pointer
+  // comparisons on all the ref'd skia objects on the SkPaint, which
+  // is not true after serialization.
+  if (getTextSize() != other.getTextSize())
+    return false;
+  if (getColor() != other.getColor())
+    return false;
+  if (getStrokeWidth() != other.getStrokeWidth())
+    return false;
+  if (getStrokeMiter() != other.getStrokeMiter())
+    return false;
+  if (getBlendMode() != other.getBlendMode())
+    return false;
+  if (getStrokeCap() != other.getStrokeCap())
+    return false;
+  if (getStrokeJoin() != other.getStrokeJoin())
+    return false;
+  if (getStyle() != other.getStyle())
+    return false;
+  if (getTextEncoding() != other.getTextEncoding())
+    return false;
+  if (getHinting() != other.getHinting())
+    return false;
+  if (getFilterQuality() != other.getFilterQuality())
+    return false;
+
+  // TODO(enne): compare typeface too
+  if (!AreFlattenablesEqual(getPathEffect().get(), other.getPathEffect().get()))
+    return false;
+  if (!AreFlattenablesEqual(getMaskFilter().get(), other.getMaskFilter().get()))
+    return false;
+  if (!AreFlattenablesEqual(getColorFilter().get(),
+                            other.getColorFilter().get()))
+    return false;
+  if (!AreFlattenablesEqual(getLooper().get(), other.getLooper().get()))
+    return false;
+  if (!AreFlattenablesEqual(getImageFilter().get(),
+                            other.getImageFilter().get()))
+    return false;
+
+  if (!getShader() != !other.getShader())
+    return false;
+  if (getShader() && *getShader() != *other.getShader())
+    return false;
+  return true;
+}
+
 bool PaintFlags::HasDiscardableImages() const {
   if (!shader_)
     return false;
diff --git a/cc/paint/paint_flags.h b/cc/paint/paint_flags.h
index 5b03d52..5e2da8f 100644
--- a/cc/paint/paint_flags.h
+++ b/cc/paint/paint_flags.h
@@ -218,6 +218,8 @@
   SkPaint ToSkPaint() const;
 
   bool IsValid() const;
+  bool operator==(const PaintFlags& other) const;
+  bool operator!=(const PaintFlags& other) const { return !(*this == other); }
 
   bool HasDiscardableImages() const;
 
diff --git a/cc/paint/paint_image.cc b/cc/paint/paint_image.cc
index db04151..25ca3a3 100644
--- a/cc/paint/paint_image.cc
+++ b/cc/paint/paint_image.cc
@@ -31,15 +31,29 @@
 PaintImage& PaintImage::operator=(PaintImage&& other) = default;
 
 bool PaintImage::operator==(const PaintImage& other) const {
-  return sk_image_ == other.sk_image_ && paint_record_ == other.paint_record_ &&
-         paint_record_rect_ == other.paint_record_rect_ &&
-         paint_record_content_id_ == other.paint_record_content_id_ &&
-         paint_image_generator_ == other.paint_image_generator_ &&
-         id_ == other.id_ && animation_type_ == other.animation_type_ &&
-         completion_state_ == other.completion_state_ &&
-         subset_rect_ == other.subset_rect_ &&
-         frame_index_ == other.frame_index_ &&
-         is_multipart_ == other.is_multipart_;
+  if (sk_image_ != other.sk_image_)
+    return false;
+  if (paint_record_ != other.paint_record_)
+    return false;
+  if (paint_record_rect_ != other.paint_record_rect_)
+    return false;
+  if (paint_record_content_id_ != other.paint_record_content_id_)
+    return false;
+  if (paint_image_generator_ != other.paint_image_generator_)
+    return false;
+  if (id_ != other.id_)
+    return false;
+  if (animation_type_ != other.animation_type_)
+    return false;
+  if (completion_state_ != other.completion_state_)
+    return false;
+  if (subset_rect_ != other.subset_rect_)
+    return false;
+  if (frame_index_ != other.frame_index_)
+    return false;
+  if (is_multipart_ != other.is_multipart_)
+    return false;
+  return true;
 }
 
 // static
diff --git a/cc/paint/paint_image.h b/cc/paint/paint_image.h
index 21df1d9..5bc445b 100644
--- a/cc/paint/paint_image.h
+++ b/cc/paint/paint_image.h
@@ -106,6 +106,7 @@
   PaintImage& operator=(PaintImage&& other);
 
   bool operator==(const PaintImage& other) const;
+  bool operator!=(const PaintImage& other) const { return !(*this == other); }
 
   // Returns the smallest size that is at least as big as the requested_size
   // such that we can decode to exactly that scale. If the requested size is
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index c0004b2..6f75cc7 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -16,6 +16,7 @@
 #include "third_party/skia/include/core/SkAnnotation.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkRegion.h"
+#include "third_party/skia/include/core/SkWriteBuffer.h"
 
 namespace cc {
 namespace {
@@ -157,6 +158,11 @@
     TYPES(M)};
 #undef M
 
+using EqualsFunction = bool (*)(const PaintOp* left, const PaintOp* right);
+#define M(T) &T::AreEqual,
+static const EqualsFunction g_equals_operator[kNumOpTypes] = {TYPES(M)};
+#undef M
+
 // Most state ops (matrix, clip, save, restore) have a trivial destructor.
 // TODO(enne): evaluate if we need the nullptr optimization or if
 // we even need to differentiate trivial destructors here.
@@ -1205,6 +1211,366 @@
   canvas->translate(op->dx, op->dy);
 }
 
+static bool AreSkMatricesEqual(const SkMatrix& left, const SkMatrix& right) {
+  // Compare the 3x3 matrix values.
+  if (left != right)
+    return false;
+
+  // If a serialized matrix says it is identity, then the original must have
+  // those values, as the serialization process clobbers the matrix values.
+  if (left.isIdentity()) {
+    if (SkMatrix::I() != left)
+      return false;
+    if (SkMatrix::I() != right)
+      return false;
+  }
+
+  if (left.getType() != right.getType())
+    return false;
+
+  return true;
+}
+
+bool AnnotateOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const AnnotateOp*>(base_left);
+  auto* right = static_cast<const AnnotateOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->annotation_type != right->annotation_type)
+    return false;
+  if (left->rect != right->rect)
+    return false;
+  if (!left->data != !right->data)
+    return false;
+  if (left->data) {
+    if (left->data->size() != right->data->size())
+      return false;
+    if (0 !=
+        memcmp(left->data->data(), right->data->data(), right->data->size()))
+      return false;
+  }
+  return true;
+}
+
+bool ClipPathOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ClipPathOp*>(base_left);
+  auto* right = static_cast<const ClipPathOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->path != right->path)
+    return false;
+  if (left->op != right->op)
+    return false;
+  if (left->antialias != right->antialias)
+    return false;
+  return true;
+}
+
+bool ClipRectOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ClipRectOp*>(base_left);
+  auto* right = static_cast<const ClipRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->rect != right->rect)
+    return false;
+  if (left->op != right->op)
+    return false;
+  if (left->antialias != right->antialias)
+    return false;
+  return true;
+}
+
+bool ClipRRectOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const ClipRRectOp*>(base_left);
+  auto* right = static_cast<const ClipRRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->rrect != right->rrect)
+    return false;
+  if (left->op != right->op)
+    return false;
+  if (left->antialias != right->antialias)
+    return false;
+  return true;
+}
+
+bool ConcatOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ConcatOp*>(base_left);
+  auto* right = static_cast<const ConcatOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  return AreSkMatricesEqual(left->matrix, right->matrix);
+}
+
+bool DrawColorOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawColorOp*>(base_left);
+  auto* right = static_cast<const DrawColorOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  return left->color == right->color;
+}
+
+bool DrawDRRectOp::AreEqual(const PaintOp* base_left,
+                            const PaintOp* base_right) {
+  auto* left = static_cast<const DrawDRRectOp*>(base_left);
+  auto* right = static_cast<const DrawDRRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->outer != right->outer)
+    return false;
+  if (left->inner != right->inner)
+    return false;
+  return true;
+}
+
+bool DrawImageOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawImageOp*>(base_left);
+  auto* right = static_cast<const DrawImageOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  // TODO(enne): Test PaintImage equality once implemented
+  if (left->left != right->left)
+    return false;
+  if (left->top != right->top)
+    return false;
+  return true;
+}
+
+bool DrawImageRectOp::AreEqual(const PaintOp* base_left,
+                               const PaintOp* base_right) {
+  auto* left = static_cast<const DrawImageRectOp*>(base_left);
+  auto* right = static_cast<const DrawImageRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  // TODO(enne): Test PaintImage equality once implemented
+  if (left->src != right->src)
+    return false;
+  if (left->dst != right->dst)
+    return false;
+  return true;
+}
+
+bool DrawIRectOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawIRectOp*>(base_left);
+  auto* right = static_cast<const DrawIRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->rect != right->rect)
+    return false;
+  return true;
+}
+
+bool DrawLineOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawLineOp*>(base_left);
+  auto* right = static_cast<const DrawLineOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->x0 != right->x0)
+    return false;
+  if (left->y0 != right->y0)
+    return false;
+  if (left->x1 != right->x1)
+    return false;
+  if (left->y1 != right->y1)
+    return false;
+  return true;
+}
+
+bool DrawOvalOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawOvalOp*>(base_left);
+  auto* right = static_cast<const DrawOvalOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->oval != right->oval)
+    return false;
+  return true;
+}
+
+bool DrawPathOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawPathOp*>(base_left);
+  auto* right = static_cast<const DrawPathOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->path != right->path)
+    return false;
+  return true;
+}
+
+bool DrawRecordOp::AreEqual(const PaintOp* base_left,
+                            const PaintOp* base_right) {
+  auto* left = static_cast<const DrawRecordOp*>(base_left);
+  auto* right = static_cast<const DrawRecordOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!left->record != !right->record)
+    return false;
+  if (*left->record != *right->record)
+    return false;
+  return true;
+}
+
+bool DrawRectOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawRectOp*>(base_left);
+  auto* right = static_cast<const DrawRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->rect != right->rect)
+    return false;
+  return true;
+}
+
+bool DrawRRectOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawRRectOp*>(base_left);
+  auto* right = static_cast<const DrawRRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->rrect != right->rrect)
+    return false;
+  return true;
+}
+
+bool DrawTextBlobOp::AreEqual(const PaintOp* base_left,
+                              const PaintOp* base_right) {
+  auto* left = static_cast<const DrawTextBlobOp*>(base_left);
+  auto* right = static_cast<const DrawTextBlobOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->x != right->x)
+    return false;
+  if (left->y != right->y)
+    return false;
+
+  DCHECK(*left->blob);
+  DCHECK(*right->blob);
+
+  SkBinaryWriteBuffer left_flattened;
+  left->blob->ToSkTextBlob()->flatten(left_flattened);
+  std::vector<char> left_mem(left_flattened.bytesWritten());
+  left_flattened.writeToMemory(left_mem.data());
+
+  SkBinaryWriteBuffer right_flattened;
+  right->blob->ToSkTextBlob()->flatten(right_flattened);
+  std::vector<char> right_mem(right_flattened.bytesWritten());
+  right_flattened.writeToMemory(right_mem.data());
+
+  if (left_mem.size() != right_mem.size())
+    return false;
+  if (left_mem != right_mem)
+    return false;
+  return true;
+}
+
+bool NoopOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  return true;
+}
+
+bool RestoreOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  return true;
+}
+
+bool RotateOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const RotateOp*>(base_left);
+  auto* right = static_cast<const RotateOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->degrees != right->degrees)
+    return false;
+  return true;
+}
+
+bool SaveOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  return true;
+}
+
+bool SaveLayerOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const SaveLayerOp*>(base_left);
+  auto* right = static_cast<const SaveLayerOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->bounds != right->bounds)
+    return false;
+  return true;
+}
+
+bool SaveLayerAlphaOp::AreEqual(const PaintOp* base_left,
+                                const PaintOp* base_right) {
+  auto* left = static_cast<const SaveLayerAlphaOp*>(base_left);
+  auto* right = static_cast<const SaveLayerAlphaOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->bounds != right->bounds)
+    return false;
+  if (left->alpha != right->alpha)
+    return false;
+  if (left->preserve_lcd_text_requests != right->preserve_lcd_text_requests)
+    return false;
+  return true;
+}
+
+bool ScaleOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ScaleOp*>(base_left);
+  auto* right = static_cast<const ScaleOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->sx != right->sx)
+    return false;
+  if (left->sy != right->sy)
+    return false;
+  return true;
+}
+
+bool SetMatrixOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const SetMatrixOp*>(base_left);
+  auto* right = static_cast<const SetMatrixOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreSkMatricesEqual(left->matrix, right->matrix))
+    return false;
+  return true;
+}
+
+bool TranslateOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const TranslateOp*>(base_left);
+  auto* right = static_cast<const TranslateOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->dx != right->dx)
+    return false;
+  if (left->dy != right->dy)
+    return false;
+  return true;
+}
+
 bool PaintOp::IsDrawOp() const {
   return g_is_draw_op[type];
 }
@@ -1213,6 +1579,12 @@
   return g_has_paint_flags[type];
 }
 
+bool PaintOp::operator==(const PaintOp& other) const {
+  if (GetType() != other.GetType())
+    return false;
+  return g_equals_operator[type](this, &other);
+}
+
 void PaintOp::Raster(SkCanvas* canvas, const PlaybackParams& params) const {
   g_raster_functions[type](this, canvas, params);
 }
@@ -1827,4 +2199,29 @@
   }
 }
 
+bool PaintOpBuffer::operator==(const PaintOpBuffer& other) const {
+  if (op_count_ != other.op_count_)
+    return false;
+  if (num_slow_paths_ != other.num_slow_paths_)
+    return false;
+  if (subrecord_bytes_used_ != other.subrecord_bytes_used_)
+    return false;
+  if (has_non_aa_paint_ != other.has_non_aa_paint_)
+    return false;
+  if (has_discardable_images_ != other.has_discardable_images_)
+    return false;
+
+  auto left_iter = Iterator(this);
+  auto right_iter = Iterator(&other);
+
+  for (; left_iter != left_iter.end(); ++left_iter, ++right_iter) {
+    if (**left_iter != **right_iter)
+      return false;
+  }
+
+  DCHECK(left_iter == left_iter.end());
+  DCHECK(right_iter == right_iter.end());
+  return true;
+}
+
 }  // namespace cc
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index 93861eb..d0aa90d 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -110,7 +110,9 @@
   void Raster(SkCanvas* canvas, const PlaybackParams& params) const;
   bool IsDrawOp() const;
   bool IsPaintOpWithFlags() const;
-  bool IsValid() const;
+
+  bool operator==(const PaintOp& other) const;
+  bool operator!=(const PaintOp& other) const { return !(*this == other); }
 
   struct CC_PAINT_EXPORT SerializeOptions {
     SerializeOptions();
@@ -246,6 +248,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return rect.isFinite(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   PaintCanvas::AnnotationType annotation_type;
@@ -265,6 +268,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return IsValidSkClipOp(op) && IsValidPath(path); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   int CountSlowPaths() const;
   bool HasNonAAPaint() const { return !antialias; }
   HAS_SERIALIZATION_FUNCTIONS();
@@ -286,6 +290,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return IsValidSkClipOp(op) && rect.isFinite(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkRect rect;
@@ -302,6 +307,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return IsValidSkClipOp(op) && rrect.isValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   bool HasNonAAPaint() const { return !antialias; }
   HAS_SERIALIZATION_FUNCTIONS();
 
@@ -318,6 +324,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   ThreadsafeMatrix matrix;
@@ -333,6 +340,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return IsValidDrawColorSkBlendMode(mode); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkColor color;
@@ -354,6 +362,7 @@
   bool IsValid() const {
     return flags.IsValid() && outer.isValid() && inner.isValid();
   }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkRRect outer;
@@ -377,6 +386,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   bool HasDiscardableImages() const;
   bool HasNonAAPaint() const { return false; }
   HAS_SERIALIZATION_FUNCTIONS();
@@ -406,6 +416,7 @@
   bool IsValid() const {
     return flags.IsValid() && src.isFinite() && dst.isFinite();
   }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   bool HasDiscardableImages() const;
   HAS_SERIALIZATION_FUNCTIONS();
 
@@ -429,6 +440,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   bool HasNonAAPaint() const { return false; }
   HAS_SERIALIZATION_FUNCTIONS();
 
@@ -453,6 +465,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   int CountSlowPaths() const;
@@ -477,6 +490,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid() && oval.isFinite(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkRect oval;
@@ -496,6 +510,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid() && IsValidPath(path); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   int CountSlowPaths() const;
   HAS_SERIALIZATION_FUNCTIONS();
 
@@ -515,6 +530,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   size_t AdditionalBytesUsed() const;
   bool HasDiscardableImages() const;
   int CountSlowPaths() const;
@@ -535,6 +551,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid() && rect.isFinite(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkRect rect;
@@ -554,6 +571,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid() && rrect.isValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkRRect rrect;
@@ -576,6 +594,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   scoped_refptr<PaintTextBlob> blob;
@@ -594,6 +613,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params) {}
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 };
 
@@ -605,6 +625,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 };
 
@@ -616,6 +637,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkScalar degrees;
@@ -629,6 +651,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 };
 
@@ -643,6 +666,7 @@
                               SkCanvas* canvas,
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid() && IsValidOrUnsetRect(bounds); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   bool HasNonAAPaint() const { return false; }
   HAS_SERIALIZATION_FUNCTIONS();
 
@@ -666,6 +690,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return IsValidOrUnsetRect(bounds); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkRect bounds;
@@ -681,6 +706,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkScalar sx;
@@ -705,6 +731,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   ThreadsafeMatrix matrix;
@@ -718,6 +745,7 @@
                      SkCanvas* canvas,
                      const PlaybackParams& params);
   bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
   HAS_SERIALIZATION_FUNCTIONS();
 
   SkScalar dx;
@@ -772,6 +800,11 @@
   bool HasNonAAPaint() const { return has_non_aa_paint_; }
   bool HasDiscardableImages() const { return has_discardable_images_; }
 
+  bool operator==(const PaintOpBuffer& other) const;
+  bool operator!=(const PaintOpBuffer& other) const {
+    return !(*this == other);
+  }
+
   // Resize the PaintOpBuffer to exactly fit the current amount of used space.
   void ShrinkToFit();
 
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index fa5bc1f..27691439 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -13,6 +13,7 @@
 #include "cc/paint/paint_op_reader.h"
 #include "cc/paint/paint_op_writer.h"
 #include "cc/test/geometry_test_utils.h"
+#include "cc/test/paint_op_helper.h"
 #include "cc/test/skia_common.h"
 #include "cc/test/test_skcanvas.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -46,80 +47,6 @@
 
 class PaintOpSerializationTestUtils {
  public:
-  static void ExpectFlattenableEqual(SkFlattenable* expected,
-                                     SkFlattenable* actual) {
-    sk_sp<SkData> expected_data(SkValidatingSerializeFlattenable(expected));
-    sk_sp<SkData> actual_data(SkValidatingSerializeFlattenable(actual));
-    ASSERT_EQ(expected_data->size(), actual_data->size());
-    EXPECT_TRUE(expected_data->equals(actual_data.get()));
-  }
-
-  static void ExpectPaintShadersEqual(const PaintShader* one,
-                                      const PaintShader* two) {
-    if (!one) {
-      EXPECT_FALSE(two);
-      return;
-    }
-
-    ASSERT_TRUE(one);
-    ASSERT_TRUE(two);
-
-    EXPECT_EQ(one->shader_type_, two->shader_type_);
-    EXPECT_EQ(one->flags_, two->flags_);
-    EXPECT_EQ(one->end_radius_, two->end_radius_);
-    EXPECT_EQ(one->start_radius_, two->start_radius_);
-    EXPECT_EQ(one->tx_, two->tx_);
-    EXPECT_EQ(one->ty_, two->ty_);
-    EXPECT_EQ(one->fallback_color_, two->fallback_color_);
-    EXPECT_EQ(one->scaling_behavior_, two->scaling_behavior_);
-    if (one->local_matrix_) {
-      EXPECT_TRUE(two->local_matrix_.has_value());
-      EXPECT_TRUE(*one->local_matrix_ == *two->local_matrix_);
-    } else {
-      EXPECT_FALSE(two->local_matrix_.has_value());
-    }
-    EXPECT_EQ(one->center_, two->center_);
-    EXPECT_EQ(one->tile_, two->tile_);
-    EXPECT_EQ(one->start_point_, two->start_point_);
-    EXPECT_EQ(one->end_point_, two->end_point_);
-    EXPECT_EQ(one->start_degrees_, two->start_degrees_);
-    EXPECT_EQ(one->end_degrees_, two->end_degrees_);
-    EXPECT_THAT(one->colors_, testing::ElementsAreArray(two->colors_));
-    EXPECT_THAT(one->positions_, testing::ElementsAreArray(two->positions_));
-  }
-
-  static void ExpectPaintFlagsEqual(const PaintFlags& expected,
-                                    const PaintFlags& actual) {
-    // Can't just ToSkPaint and operator== here as SkPaint does pointer
-    // comparisons on all the ref'd skia objects on the SkPaint, which
-    // is not true after serialization.
-    EXPECT_EQ(expected.getTextSize(), actual.getTextSize());
-    EXPECT_EQ(expected.getColor(), actual.getColor());
-    EXPECT_EQ(expected.getStrokeWidth(), actual.getStrokeWidth());
-    EXPECT_EQ(expected.getStrokeMiter(), actual.getStrokeMiter());
-    EXPECT_EQ(expected.getBlendMode(), actual.getBlendMode());
-    EXPECT_EQ(expected.getStrokeCap(), actual.getStrokeCap());
-    EXPECT_EQ(expected.getStrokeJoin(), actual.getStrokeJoin());
-    EXPECT_EQ(expected.getStyle(), actual.getStyle());
-    EXPECT_EQ(expected.getTextEncoding(), actual.getTextEncoding());
-    EXPECT_EQ(expected.getHinting(), actual.getHinting());
-    EXPECT_EQ(expected.getFilterQuality(), actual.getFilterQuality());
-
-    // TODO(enne): compare typeface too
-    ExpectFlattenableEqual(expected.getPathEffect().get(),
-                           actual.getPathEffect().get());
-    ExpectFlattenableEqual(expected.getMaskFilter().get(),
-                           actual.getMaskFilter().get());
-    ExpectFlattenableEqual(expected.getColorFilter().get(),
-                           actual.getColorFilter().get());
-    ExpectFlattenableEqual(expected.getLooper().get(),
-                           actual.getLooper().get());
-    ExpectFlattenableEqual(expected.getImageFilter().get(),
-                           actual.getImageFilter().get());
-
-    ExpectPaintShadersEqual(expected.getShader(), actual.getShader());
-  }
-
   static void FillArbitraryShaderValues(PaintShader* shader, bool use_matrix) {
     shader->shader_type_ = PaintShader::Type::kTwoPointConicalGradient;
     shader->flags_ = 12345;
@@ -190,8 +117,7 @@
     ASSERT_EQ(iter->GetType(), PaintOpType::SaveLayer);
     SaveLayerOp* save_op = static_cast<SaveLayerOp*>(*iter);
     EXPECT_EQ(save_op->bounds, rect_);
-    PaintOpSerializationTestUtils::ExpectPaintFlagsEqual(save_op->flags,
-                                                         flags_);
+    EXPECT_EQ(save_op->flags, flags_);
     ++iter;
 
     ASSERT_EQ(iter->GetType(), PaintOpType::Save);
@@ -237,7 +163,6 @@
   // Original should be empty, and safe to destruct.
   EXPECT_EQ(original.size(), 0u);
   EXPECT_EQ(original.bytes_used(), sizeof(PaintOpBuffer));
-  EXPECT_EQ(PaintOpBuffer::Iterator(&original), false);
 }
 
 TEST_F(PaintOpAppendTest, MoveThenDestructOperatorEq) {
@@ -264,6 +189,7 @@
   // Should be possible to reappend to the original and get the same result.
   PushOps(&original);
   VerifyOps(&original);
+  EXPECT_EQ(original, destination);
 }
 
 TEST_F(PaintOpAppendTest, MoveThenReappendOperatorEq) {
@@ -276,6 +202,7 @@
   // Should be possible to reappend to the original and get the same result.
   PushOps(&original);
   VerifyOps(&original);
+  EXPECT_EQ(original, destination);
 }
 
 // Verify that a SaveLayerAlpha / Draw / Restore can be optimized to just
@@ -1628,242 +1555,6 @@
   ValidateOps<TranslateOp>(buffer);
 }
 
-void CompareFlags(const PaintFlags& original, const PaintFlags& written) {
-  PaintOpSerializationTestUtils::ExpectPaintFlagsEqual(original, written);
-}
-
-void CompareImages(const PaintImage& original, const PaintImage& written) {}
-
-void CompareMatrices(const SkMatrix& original, const SkMatrix& written) {
-  // Compare the 3x3 matrix values.
-  EXPECT_EQ(original, written);
-
-  // If a serialized matrix says it is identity, then the original must have
-  // those values, as the serialization process clobbers the matrix values.
-  if (original.isIdentity()) {
-    EXPECT_EQ(SkMatrix::I(), original);
-    EXPECT_EQ(SkMatrix::I(), written);
-  }
-
-  EXPECT_EQ(original.getType(), written.getType());
-}
-
-void CompareAnnotateOp(const AnnotateOp* original, const AnnotateOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->annotation_type, written->annotation_type);
-  EXPECT_EQ(original->rect, written->rect);
-  EXPECT_EQ(!!original->data, !!written->data);
-  if (original->data) {
-    EXPECT_EQ(original->data->size(), written->data->size());
-    EXPECT_EQ(0, memcmp(original->data->data(), written->data->data(),
-                        written->data->size()));
-  }
-}
-
-void CompareClipPathOp(const ClipPathOp* original, const ClipPathOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_TRUE(original->path == written->path);
-  EXPECT_EQ(original->op, written->op);
-  EXPECT_EQ(original->antialias, written->antialias);
-}
-
-void CompareClipRectOp(const ClipRectOp* original, const ClipRectOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->rect, written->rect);
-  EXPECT_EQ(original->op, written->op);
-  EXPECT_EQ(original->antialias, written->antialias);
-}
-
-void CompareClipRRectOp(const ClipRRectOp* original,
-                        const ClipRRectOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->rrect, written->rrect);
-  EXPECT_EQ(original->op, written->op);
-  EXPECT_EQ(original->antialias, written->antialias);
-}
-
-void CompareConcatOp(const ConcatOp* original, const ConcatOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareMatrices(original->matrix, written->matrix);
-}
-
-void CompareDrawColorOp(const DrawColorOp* original,
-                        const DrawColorOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->color, written->color);
-}
-
-void CompareDrawDRRectOp(const DrawDRRectOp* original,
-                         const DrawDRRectOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->outer, written->outer);
-  EXPECT_EQ(original->inner, written->inner);
-}
-
-void CompareDrawImageOp(const DrawImageOp* original,
-                        const DrawImageOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  CompareImages(original->image, written->image);
-  EXPECT_EQ(original->left, written->left);
-  EXPECT_EQ(original->top, written->top);
-}
-
-void CompareDrawImageRectOp(const DrawImageRectOp* original,
-                            const DrawImageRectOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  CompareImages(original->image, written->image);
-  EXPECT_EQ(original->src, written->src);
-  EXPECT_EQ(original->dst, written->dst);
-}
-
-void CompareDrawIRectOp(const DrawIRectOp* original,
-                        const DrawIRectOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->rect, written->rect);
-}
-
-void CompareDrawLineOp(const DrawLineOp* original, const DrawLineOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->x0, written->x0);
-  EXPECT_EQ(original->y0, written->y0);
-  EXPECT_EQ(original->x1, written->x1);
-  EXPECT_EQ(original->y1, written->y1);
-}
-
-void CompareDrawOvalOp(const DrawOvalOp* original, const DrawOvalOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->oval, written->oval);
-}
-
-void CompareDrawPathOp(const DrawPathOp* original, const DrawPathOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_TRUE(original->path == written->path);
-}
-
-void CompareDrawRectOp(const DrawRectOp* original, const DrawRectOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->rect, written->rect);
-}
-
-void CompareDrawRRectOp(const DrawRRectOp* original,
-                        const DrawRRectOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->rrect, written->rrect);
-}
-
-void CompareDrawTextBlobOp(const DrawTextBlobOp* original,
-                           const DrawTextBlobOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->x, written->x);
-  EXPECT_EQ(original->y, written->y);
-
-  ASSERT_TRUE(*original->blob);
-  ASSERT_TRUE(*written->blob);
-
-  SkBinaryWriteBuffer original_flattened;
-  original->blob->ToSkTextBlob()->flatten(original_flattened);
-  std::vector<char> original_mem(original_flattened.bytesWritten());
-  original_flattened.writeToMemory(original_mem.data());
-
-  SkBinaryWriteBuffer written_flattened;
-  written->blob->ToSkTextBlob()->flatten(written_flattened);
-  std::vector<char> written_mem(written_flattened.bytesWritten());
-  written_flattened.writeToMemory(written_mem.data());
-
-  ASSERT_EQ(original_mem.size(), written_mem.size());
-  EXPECT_EQ(original_mem, written_mem);
-}
-
-void CompareNoopOp(const NoopOp* original, const NoopOp* written) {
-  // Nothing to compare.
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-}
-
-void CompareRestoreOp(const RestoreOp* original, const RestoreOp* written) {
-  // Nothing to compare.
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-}
-
-void CompareRotateOp(const RotateOp* original, const RotateOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->degrees, written->degrees);
-}
-
-void CompareSaveOp(const SaveOp* original, const SaveOp* written) {
-  // Nothing to compare.
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-}
-
-void CompareSaveLayerOp(const SaveLayerOp* original,
-                        const SaveLayerOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareFlags(original->flags, written->flags);
-  EXPECT_EQ(original->bounds, written->bounds);
-}
-
-void CompareSaveLayerAlphaOp(const SaveLayerAlphaOp* original,
-                             const SaveLayerAlphaOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->bounds, written->bounds);
-  EXPECT_EQ(original->alpha, written->alpha);
-  EXPECT_EQ(original->preserve_lcd_text_requests,
-            written->preserve_lcd_text_requests);
-}
-
-void CompareScaleOp(const ScaleOp* original, const ScaleOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->sx, written->sx);
-  EXPECT_EQ(original->sy, written->sy);
-}
-
-void CompareSetMatrixOp(const SetMatrixOp* original,
-                        const SetMatrixOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  CompareMatrices(original->matrix, written->matrix);
-}
-
-void CompareTranslateOp(const TranslateOp* original,
-                        const TranslateOp* written) {
-  EXPECT_TRUE(original->IsValid());
-  EXPECT_TRUE(written->IsValid());
-  EXPECT_EQ(original->dx, written->dx);
-  EXPECT_EQ(original->dy, written->dy);
-}
-
 class PaintOpSerializationTest : public ::testing::TestWithParam<uint8_t> {
  public:
   PaintOpType GetParamType() const {
@@ -1953,119 +1644,6 @@
     }
   }
 
-  static void ExpectOpsEqual(const PaintOp* original, const PaintOp* written) {
-    ASSERT_TRUE(original);
-    ASSERT_TRUE(written);
-    ASSERT_EQ(original->GetType(), written->GetType());
-    EXPECT_EQ(original->skip, written->skip);
-
-    switch (original->GetType()) {
-      case PaintOpType::Annotate:
-        CompareAnnotateOp(static_cast<const AnnotateOp*>(original),
-                          static_cast<const AnnotateOp*>(written));
-        break;
-      case PaintOpType::ClipPath:
-        CompareClipPathOp(static_cast<const ClipPathOp*>(original),
-                          static_cast<const ClipPathOp*>(written));
-        break;
-      case PaintOpType::ClipRect:
-        CompareClipRectOp(static_cast<const ClipRectOp*>(original),
-                          static_cast<const ClipRectOp*>(written));
-        break;
-      case PaintOpType::ClipRRect:
-        CompareClipRRectOp(static_cast<const ClipRRectOp*>(original),
-                           static_cast<const ClipRRectOp*>(written));
-        break;
-      case PaintOpType::Concat:
-        CompareConcatOp(static_cast<const ConcatOp*>(original),
-                        static_cast<const ConcatOp*>(written));
-        break;
-      case PaintOpType::DrawColor:
-        CompareDrawColorOp(static_cast<const DrawColorOp*>(original),
-                           static_cast<const DrawColorOp*>(written));
-        break;
-      case PaintOpType::DrawDRRect:
-        CompareDrawDRRectOp(static_cast<const DrawDRRectOp*>(original),
-                            static_cast<const DrawDRRectOp*>(written));
-        break;
-      case PaintOpType::DrawImage:
-        CompareDrawImageOp(static_cast<const DrawImageOp*>(original),
-                           static_cast<const DrawImageOp*>(written));
-        break;
-      case PaintOpType::DrawImageRect:
-        CompareDrawImageRectOp(static_cast<const DrawImageRectOp*>(original),
-                               static_cast<const DrawImageRectOp*>(written));
-        break;
-      case PaintOpType::DrawIRect:
-        CompareDrawIRectOp(static_cast<const DrawIRectOp*>(original),
-                           static_cast<const DrawIRectOp*>(written));
-        break;
-      case PaintOpType::DrawLine:
-        CompareDrawLineOp(static_cast<const DrawLineOp*>(original),
-                          static_cast<const DrawLineOp*>(written));
-        break;
-      case PaintOpType::DrawOval:
-        CompareDrawOvalOp(static_cast<const DrawOvalOp*>(original),
-                          static_cast<const DrawOvalOp*>(written));
-        break;
-      case PaintOpType::DrawPath:
-        CompareDrawPathOp(static_cast<const DrawPathOp*>(original),
-                          static_cast<const DrawPathOp*>(written));
-        break;
-      case PaintOpType::DrawRecord:
-        // Not supported.
-        break;
-      case PaintOpType::DrawRect:
-        CompareDrawRectOp(static_cast<const DrawRectOp*>(original),
-                          static_cast<const DrawRectOp*>(written));
-        break;
-      case PaintOpType::DrawRRect:
-        CompareDrawRRectOp(static_cast<const DrawRRectOp*>(original),
-                           static_cast<const DrawRRectOp*>(written));
-        break;
-      case PaintOpType::DrawTextBlob:
-        CompareDrawTextBlobOp(static_cast<const DrawTextBlobOp*>(original),
-                              static_cast<const DrawTextBlobOp*>(written));
-        break;
-      case PaintOpType::Noop:
-        CompareNoopOp(static_cast<const NoopOp*>(original),
-                      static_cast<const NoopOp*>(written));
-        break;
-      case PaintOpType::Restore:
-        CompareRestoreOp(static_cast<const RestoreOp*>(original),
-                         static_cast<const RestoreOp*>(written));
-        break;
-      case PaintOpType::Rotate:
-        CompareRotateOp(static_cast<const RotateOp*>(original),
-                        static_cast<const RotateOp*>(written));
-        break;
-      case PaintOpType::Save:
-        CompareSaveOp(static_cast<const SaveOp*>(original),
-                      static_cast<const SaveOp*>(written));
-        break;
-      case PaintOpType::SaveLayer:
-        CompareSaveLayerOp(static_cast<const SaveLayerOp*>(original),
-                           static_cast<const SaveLayerOp*>(written));
-        break;
-      case PaintOpType::SaveLayerAlpha:
-        CompareSaveLayerAlphaOp(static_cast<const SaveLayerAlphaOp*>(original),
-                                static_cast<const SaveLayerAlphaOp*>(written));
-        break;
-      case PaintOpType::Scale:
-        CompareScaleOp(static_cast<const ScaleOp*>(original),
-                       static_cast<const ScaleOp*>(written));
-        break;
-      case PaintOpType::SetMatrix:
-        CompareSetMatrixOp(static_cast<const SetMatrixOp*>(original),
-                           static_cast<const SetMatrixOp*>(written));
-        break;
-      case PaintOpType::Translate:
-        CompareTranslateOp(static_cast<const TranslateOp*>(original),
-                           static_cast<const TranslateOp*>(written));
-        break;
-    }
-  }
-
   void ResizeOutputBuffer() {
     // An arbitrary deserialization buffer size that should fit all the ops
     // in the buffer_.
@@ -2118,7 +1696,7 @@
        DeserializerIterator(output_.get(), serializer.TotalBytesWritten())) {
     SCOPED_TRACE(base::StringPrintf(
         "%s #%zu", PaintOpTypeToString(GetParamType()).c_str(), i));
-    ExpectOpsEqual(*iter, base_written);
+    EXPECT_EQ(**iter, *base_written);
     ++iter;
     ++i;
   }
@@ -2296,7 +1874,7 @@
       // Root buffer.
       ASSERT_EQ(op->GetType(), (*serialized_iter)->GetType())
           << PaintOpTypeToString(op->GetType());
-      PaintOpSerializationTest::ExpectOpsEqual(op, *serialized_iter);
+      EXPECT_EQ(*op, **serialized_iter);
       ++serialized_iter;
       continue;
     }
@@ -2384,7 +1962,7 @@
 
     if (i == 6) {
       // Buffer.
-      PaintOpSerializationTest::ExpectOpsEqual(op, buffer.GetFirstOp());
+      EXPECT_EQ(*op, *buffer.GetFirstOp());
       continue;
     }
 
@@ -2431,7 +2009,7 @@
       // Nested buffer.
       ASSERT_EQ(op->GetType(), (*serialized_iter)->GetType())
           << PaintOpTypeToString(op->GetType());
-      PaintOpSerializationTest::ExpectOpsEqual(op, *serialized_iter);
+      EXPECT_EQ(*op, **serialized_iter);
       ++serialized_iter;
       continue;
     }
@@ -2481,7 +2059,7 @@
       // Root buffer.
       ASSERT_EQ(op->GetType(), (*serialized_iter)->GetType())
           << PaintOpTypeToString(op->GetType());
-      PaintOpSerializationTest::ExpectOpsEqual(op, *serialized_iter);
+      EXPECT_EQ(*op, **serialized_iter);
       ++serialized_iter;
       continue;
     }
diff --git a/cc/paint/paint_shader.cc b/cc/paint/paint_shader.cc
index 0904ad0..e5610e620 100644
--- a/cc/paint/paint_shader.cc
+++ b/cc/paint/paint_shader.cc
@@ -394,4 +394,53 @@
   return false;
 }
 
+bool PaintShader::operator==(const PaintShader& other) const {
+  if (shader_type_ != other.shader_type_)
+    return false;
+  if (flags_ != other.flags_)
+    return false;
+  if (end_radius_ != other.end_radius_)
+    return false;
+  if (start_radius_ != other.start_radius_)
+    return false;
+  if (tx_ != other.tx_)
+    return false;
+  if (ty_ != other.ty_)
+    return false;
+  if (fallback_color_ != other.fallback_color_)
+    return false;
+  if (scaling_behavior_ != other.scaling_behavior_)
+    return false;
+  if (local_matrix_) {
+    if (!other.local_matrix_.has_value())
+      return false;
+    if (*local_matrix_ != *other.local_matrix_)
+      return false;
+  } else {
+    if (other.local_matrix_.has_value())
+      return false;
+  }
+  if (center_ != other.center_)
+    return false;
+  if (tile_ != other.tile_)
+    return false;
+  if (start_point_ != other.start_point_)
+    return false;
+  if (end_point_ != other.end_point_)
+    return false;
+  if (start_degrees_ != other.start_degrees_)
+    return false;
+  if (end_degrees_ != other.end_degrees_)
+    return false;
+
+  // TODO(enne): add comparison of records once those are serialized.
+  // TODO(enne): add comparison of images once those are serialized.
+
+  if (colors_ != other.colors_)
+    return false;
+  if (positions_ != other.positions_)
+    return false;
+  return true;
+}
+
 }  // namespace cc
diff --git a/cc/paint/paint_shader.h b/cc/paint/paint_shader.h
index 0c76dde..6c72074 100644
--- a/cc/paint/paint_shader.h
+++ b/cc/paint/paint_shader.h
@@ -126,6 +126,9 @@
   // shader is correct is hard.
   bool IsValid() const;
 
+  bool operator==(const PaintShader& other) const;
+  bool operator!=(const PaintShader& other) const { return !(*this == other); }
+
  private:
   friend class PaintFlags;
   friend class PaintOpReader;
diff --git a/cc/paint/raw_memory_transfer_cache_entry.cc b/cc/paint/raw_memory_transfer_cache_entry.cc
new file mode 100644
index 0000000..302cf0d
--- /dev/null
+++ b/cc/paint/raw_memory_transfer_cache_entry.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 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/raw_memory_transfer_cache_entry.h"
+
+namespace cc {
+
+ClientRawMemoryTransferCacheEntry::ClientRawMemoryTransferCacheEntry(
+    std::vector<uint8_t> data)
+    : data_(std::move(data)) {}
+ClientRawMemoryTransferCacheEntry::~ClientRawMemoryTransferCacheEntry() =
+    default;
+
+TransferCacheEntryType ClientRawMemoryTransferCacheEntry::Type() const {
+  return TransferCacheEntryType::kRawMemory;
+}
+
+size_t ClientRawMemoryTransferCacheEntry::SerializedSize() const {
+  return data_.size();
+}
+
+bool ClientRawMemoryTransferCacheEntry::Serialize(size_t size,
+                                                  uint8_t* data) const {
+  if (size != data_.size())
+    return false;
+
+  memcpy(data, data_.data(), size);
+  return true;
+}
+
+ServiceRawMemoryTransferCacheEntry::ServiceRawMemoryTransferCacheEntry() =
+    default;
+ServiceRawMemoryTransferCacheEntry::~ServiceRawMemoryTransferCacheEntry() =
+    default;
+
+TransferCacheEntryType ServiceRawMemoryTransferCacheEntry::Type() const {
+  return TransferCacheEntryType::kRawMemory;
+}
+
+size_t ServiceRawMemoryTransferCacheEntry::Size() const {
+  return data_.size();
+}
+
+bool ServiceRawMemoryTransferCacheEntry::Deserialize(size_t size,
+                                                     uint8_t* data) {
+  data_.resize(size);
+  memcpy(data_.data(), data, size);
+  return true;
+}
+
+}  // namespace cc
diff --git a/cc/paint/raw_memory_transfer_cache_entry.h b/cc/paint/raw_memory_transfer_cache_entry.h
new file mode 100644
index 0000000..6180332
--- /dev/null
+++ b/cc/paint/raw_memory_transfer_cache_entry.h
@@ -0,0 +1,46 @@
+// Copyright (c) 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_RAW_MEMORY_TRANSFER_CACHE_ENTRY_H_
+#define CC_PAINT_RAW_MEMORY_TRANSFER_CACHE_ENTRY_H_
+
+#include <vector>
+
+#include "cc/paint/transfer_cache_entry.h"
+
+namespace cc {
+
+// Client/ServiceRawMemoryTransferCacheEntry implement a transfer cache entry
+// backed by raw memory, with no conversion during serialization or
+// deserialization.
+class CC_PAINT_EXPORT ClientRawMemoryTransferCacheEntry
+    : public ClientTransferCacheEntry {
+ public:
+  explicit ClientRawMemoryTransferCacheEntry(std::vector<uint8_t> data);
+  ~ClientRawMemoryTransferCacheEntry() override;
+  TransferCacheEntryType Type() const override;
+  size_t SerializedSize() const override;
+  bool Serialize(size_t size, uint8_t* data) const override;
+
+ private:
+  std::vector<uint8_t> data_;
+};
+
+class CC_PAINT_EXPORT ServiceRawMemoryTransferCacheEntry
+    : public ServiceTransferCacheEntry {
+ public:
+  ServiceRawMemoryTransferCacheEntry();
+  ~ServiceRawMemoryTransferCacheEntry() override;
+  TransferCacheEntryType Type() const override;
+  size_t Size() const override;
+  bool Deserialize(size_t size, uint8_t* data) override;
+  const std::vector<uint8_t>& data() { return data_; }
+
+ private:
+  std::vector<uint8_t> data_;
+};
+
+}  // namespace cc
+
+#endif  // CC_PAINT_RAW_MEMORY_TRANSFER_CACHE_ENTRY_H_
diff --git a/cc/paint/transfer_cache_entry.cc b/cc/paint/transfer_cache_entry.cc
new file mode 100644
index 0000000..c68a905
--- /dev/null
+++ b/cc/paint/transfer_cache_entry.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 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/transfer_cache_entry.h"
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "cc/paint/raw_memory_transfer_cache_entry.h"
+
+namespace cc {
+
+std::unique_ptr<ServiceTransferCacheEntry> ServiceTransferCacheEntry::Create(
+    TransferCacheEntryType type) {
+  switch (type) {
+    case TransferCacheEntryType::kRawMemory:
+      return base::MakeUnique<ServiceRawMemoryTransferCacheEntry>();
+  }
+
+  NOTREACHED();
+  return nullptr;
+}
+
+bool ServiceTransferCacheEntry::SafeConvertToType(
+    uint32_t raw_type,
+    TransferCacheEntryType* type) {
+  if (raw_type > static_cast<uint32_t>(TransferCacheEntryType::kLast))
+    return false;
+
+  *type = static_cast<TransferCacheEntryType>(raw_type);
+  return true;
+}
+
+}  // namespace cc
diff --git a/cc/paint/transfer_cache_entry.h b/cc/paint/transfer_cache_entry.h
new file mode 100644
index 0000000..ca281a2
--- /dev/null
+++ b/cc/paint/transfer_cache_entry.h
@@ -0,0 +1,57 @@
+// Copyright (c) 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_TRANSFER_CACHE_ENTRY_H_
+#define CC_PAINT_TRANSFER_CACHE_ENTRY_H_
+
+#include <memory>
+
+#include "cc/paint/paint_export.h"
+
+namespace cc {
+
+// To add a new transfer cache entry type:
+//  - Add a type name to the TransferCacheEntryType enum.
+//  - Implement a ClientTransferCacheEntry and ServiceTransferCacheEntry for
+//    your new type.
+//  - Update ServiceTransferCacheEntry::Create and ServiceTransferCacheEntry::
+//    DeduceType in transfer_cache_entry.cc.
+enum class TransferCacheEntryType : uint32_t {
+  kRawMemory,
+  // Add new entries above this line, make sure to update kLast.
+  kLast = kRawMemory,
+};
+
+// An interface used on the client to serialize a transfer cache entry
+// into raw bytes that can be sent to the service.
+class CC_PAINT_EXPORT ClientTransferCacheEntry {
+ public:
+  virtual ~ClientTransferCacheEntry() {}
+  virtual TransferCacheEntryType Type() const = 0;
+  virtual size_t SerializedSize() const = 0;
+  virtual bool Serialize(size_t size, uint8_t* data) const = 0;
+};
+
+// An interface which receives the raw data sent by the client and
+// deserializes it into the appropriate service-side object.
+class CC_PAINT_EXPORT ServiceTransferCacheEntry {
+ public:
+  static std::unique_ptr<ServiceTransferCacheEntry> Create(
+      TransferCacheEntryType type);
+
+  // Checks that |raw_type| represents a valid TransferCacheEntryType and
+  // populates |type|. If |raw_type| is not valid, the function returns false
+  // and |type| is not modified.
+  static bool SafeConvertToType(uint32_t raw_type,
+                                TransferCacheEntryType* type);
+
+  virtual ~ServiceTransferCacheEntry() {}
+  virtual TransferCacheEntryType Type() const = 0;
+  virtual size_t Size() const = 0;
+  virtual bool Deserialize(size_t size, uint8_t* data) = 0;
+};
+
+};  // namespace cc
+
+#endif  // CC_PAINT_TRANSFER_CACHE_ENTRY_H_
diff --git a/cc/paint/transfer_cache_unittest.cc b/cc/paint/transfer_cache_unittest.cc
new file mode 100644
index 0000000..20833e8
--- /dev/null
+++ b/cc/paint/transfer_cache_unittest.cc
@@ -0,0 +1,212 @@
+// 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 <vector>
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "cc/paint/raw_memory_transfer_cache_entry.h"
+#include "cc/paint/transfer_cache_entry.h"
+#include "cc/test/test_in_process_context_provider.h"
+#include "gpu/command_buffer/client/client_transfer_cache.h"
+#include "gpu/command_buffer/client/gles2_cmd_helper.h"
+#include "gpu/command_buffer/client/gles2_implementation.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/client/shared_memory_limits.h"
+#include "gpu/command_buffer/common/gles2_cmd_utils.h"
+#include "gpu/command_buffer/service/service_transfer_cache.h"
+#include "gpu/ipc/gl_in_process_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gl/gl_implementation.h"
+
+namespace cc {
+namespace {
+
+class TransferCacheTest : public testing::Test {
+ public:
+  TransferCacheTest()
+      : testing::Test(), test_client_entry_(std::vector<uint8_t>(100)) {}
+  void SetUp() override {
+    bool is_offscreen = true;
+    gpu::gles2::ContextCreationAttribHelper attribs;
+    attribs.alpha_size = -1;
+    attribs.depth_size = 24;
+    attribs.stencil_size = 8;
+    attribs.samples = 0;
+    attribs.sample_buffers = 0;
+    attribs.fail_if_major_perf_caveat = false;
+    attribs.bind_generates_resource = false;
+
+    context_ = gpu::GLInProcessContext::CreateWithoutInit();
+    auto result = context_->Initialize(
+        nullptr, nullptr, is_offscreen, gpu::kNullSurfaceHandle, nullptr,
+        attribs, gpu::SharedMemoryLimits(), &gpu_memory_buffer_manager_,
+        &image_factory_, base::ThreadTaskRunnerHandle::Get());
+
+    ASSERT_EQ(result, gpu::ContextResult::kSuccess);
+  }
+
+  void TearDown() override { context_.reset(); }
+
+  gpu::ClientTransferCache* ClientTransferCache() {
+    return &client_transfer_cache_;
+  }
+
+  gpu::ServiceTransferCache* ServiceTransferCache() {
+    return context_->ContextGroupForTesting()->transfer_cache();
+  }
+
+  gpu::gles2::GLES2Implementation* Gl() {
+    return context_->GetImplementation();
+  }
+
+  gpu::CommandBuffer* CommandBuffer() {
+    return context_->GetImplementation()->helper()->command_buffer();
+  }
+
+  const ClientRawMemoryTransferCacheEntry& test_client_entry() const {
+    return test_client_entry_;
+  }
+
+ private:
+  gpu::ClientTransferCache client_transfer_cache_;
+  viz::TestGpuMemoryBufferManager gpu_memory_buffer_manager_;
+  TestImageFactory image_factory_;
+  std::unique_ptr<gpu::GLInProcessContext> context_;
+  gl::DisableNullDrawGLBindings enable_pixel_output_;
+  ClientRawMemoryTransferCacheEntry test_client_entry_;
+};
+
+TEST_F(TransferCacheTest, Basic) {
+  auto* client_cache = ClientTransferCache();
+  auto* service_cache = ServiceTransferCache();
+  auto* gl = Gl();
+  auto* command_buffer = CommandBuffer();
+
+  // Create an entry and validate client-side state.
+  gpu::TransferCacheEntryId id =
+      client_cache->CreateCacheEntry(gl, command_buffer, test_client_entry());
+  EXPECT_FALSE(id.is_null());
+  gpu::ClientDiscardableHandle handle =
+      client_cache->DiscardableManagerForTesting()->GetHandle(id);
+  EXPECT_TRUE(handle.IsLockedForTesting());
+  gl->Finish();
+
+  // Validate service-side state.
+  EXPECT_NE(nullptr, service_cache->GetEntry(id));
+
+  // Unlock on client side and flush to service. Validate handle state.
+  client_cache->UnlockTransferCacheEntry(gl, id);
+  gl->Finish();
+  EXPECT_FALSE(handle.IsLockedForTesting());
+
+  // Re-lock on client side and validate state. Nop need to flush as lock is
+  // local.
+  EXPECT_TRUE(client_cache->LockTransferCacheEntry(id));
+  EXPECT_TRUE(handle.IsLockedForTesting());
+
+  // Delete on client side, flush, and validate that deletion reaches service.
+  client_cache->DeleteTransferCacheEntry(gl, id);
+  gl->Finish();
+  EXPECT_TRUE(handle.IsDeletedForTesting());
+  EXPECT_EQ(nullptr, service_cache->GetEntry(id));
+}
+
+TEST_F(TransferCacheTest, Eviction) {
+  auto* client_cache = ClientTransferCache();
+  auto* service_cache = ServiceTransferCache();
+  auto* gl = Gl();
+  auto* command_buffer = CommandBuffer();
+
+  // Create an entry and validate client-side state.
+  gpu::TransferCacheEntryId id =
+      client_cache->CreateCacheEntry(gl, command_buffer, test_client_entry());
+  EXPECT_FALSE(id.is_null());
+  gpu::ClientDiscardableHandle handle =
+      client_cache->DiscardableManagerForTesting()->GetHandle(id);
+  EXPECT_TRUE(handle.IsLockedForTesting());
+  gl->Finish();
+
+  // Validate service-side state.
+  EXPECT_NE(nullptr, service_cache->GetEntry(id));
+
+  // Unlock on client side and flush to service. Validate handle state.
+  client_cache->UnlockTransferCacheEntry(gl, id);
+  gl->Finish();
+  EXPECT_FALSE(handle.IsLockedForTesting());
+
+  // Evict on the service side.
+  service_cache->SetCacheSizeLimitForTesting(0);
+  EXPECT_TRUE(handle.IsDeletedForTesting());
+  EXPECT_EQ(nullptr, service_cache->GetEntry(id));
+
+  // Try to re-lock on the client side. This should fail.
+  EXPECT_FALSE(client_cache->LockTransferCacheEntry(id));
+  EXPECT_FALSE(handle.IsLockedForTesting());
+}
+
+TEST_F(TransferCacheTest, RawMemoryTransfer) {
+  auto* client_cache = ClientTransferCache();
+  auto* service_cache = ServiceTransferCache();
+  auto* gl = Gl();
+  auto* command_buffer = CommandBuffer();
+
+  // Create an entry with some initialized data.
+  std::vector<uint8_t> data;
+  data.resize(100);
+  for (size_t i = 0; i < data.size(); ++i) {
+    data[i] = i;
+  }
+
+  // Add the entry to the transfer cache
+  ClientRawMemoryTransferCacheEntry client_entry(data);
+  gpu::TransferCacheEntryId id =
+      client_cache->CreateCacheEntry(gl, command_buffer, client_entry);
+  EXPECT_FALSE(id.is_null());
+  gpu::ClientDiscardableHandle handle =
+      client_cache->DiscardableManagerForTesting()->GetHandle(id);
+  EXPECT_TRUE(handle.IsLockedForTesting());
+  gl->Finish();
+
+  // Validate service-side data matches.
+  ServiceTransferCacheEntry* service_entry = service_cache->GetEntry(id);
+  EXPECT_EQ(service_entry->Type(), client_entry.Type());
+  const std::vector<uint8_t> service_data =
+      static_cast<ServiceRawMemoryTransferCacheEntry*>(service_entry)->data();
+  EXPECT_EQ(data, service_data);
+}
+
+// A TransferCacheEntry that intentionally constructs on invalid
+// TransferCacheEntryType.
+class InvalidIdTransferCacheEntry : public ClientTransferCacheEntry {
+ public:
+  ~InvalidIdTransferCacheEntry() override = default;
+  TransferCacheEntryType Type() const override {
+    return static_cast<TransferCacheEntryType>(
+        static_cast<uint32_t>(TransferCacheEntryType::kLast) + 1);
+  }
+  size_t SerializedSize() const override { return sizeof(uint32_t); }
+  bool Serialize(size_t size, uint8_t* data) const override { return true; }
+};
+
+TEST_F(TransferCacheTest, InvalidTypeFails) {
+  auto* client_cache = ClientTransferCache();
+  auto* service_cache = ServiceTransferCache();
+  auto* gl = Gl();
+  auto* command_buffer = CommandBuffer();
+
+  // Add the entry to the transfer cache
+  gpu::TransferCacheEntryId id = client_cache->CreateCacheEntry(
+      gl, command_buffer, InvalidIdTransferCacheEntry());
+  EXPECT_FALSE(id.is_null());
+  gpu::ClientDiscardableHandle handle =
+      client_cache->DiscardableManagerForTesting()->GetHandle(id);
+  EXPECT_TRUE(handle.IsLockedForTesting());
+  gl->Finish();
+
+  // Nothing should be created service side, as the type was invalid.
+  EXPECT_EQ(nullptr, service_cache->GetEntry(id));
+}
+
+}  // namespace
+}  // namespace cc
diff --git a/cc/resources/layer_tree_resource_provider.cc b/cc/resources/layer_tree_resource_provider.cc
index 2bbb56b..6303503 100644
--- a/cc/resources/layer_tree_resource_provider.cc
+++ b/cc/resources/layer_tree_resource_provider.cc
@@ -60,7 +60,10 @@
 LayerTreeResourceProvider::~LayerTreeResourceProvider() {
   for (auto& pair : imported_resources_) {
     ImportedResource& imported = pair.second;
-    imported.release_callback->Run(gpu::SyncToken(), true /* is_lost */);
+    // If the resource is exported we can't report when it can be used again
+    // once this class is destroyed, so consider the resource lost.
+    bool is_lost = imported.exported_count || imported.returned_lost;
+    imported.release_callback->Run(imported.returned_sync_token, is_lost);
   }
 }
 
diff --git a/cc/resources/layer_tree_resource_provider_unittest.cc b/cc/resources/layer_tree_resource_provider_unittest.cc
index 6e976423..2075837 100644
--- a/cc/resources/layer_tree_resource_provider_unittest.cc
+++ b/cc/resources/layer_tree_resource_provider_unittest.cc
@@ -103,15 +103,15 @@
   provider().RemoveImportedResource(id);
 }
 
-TEST_P(LayerTreeResourceProviderTest, TransferableResourceLostOnShutdown) {
+TEST_P(LayerTreeResourceProviderTest, TransferableResourceNotLostOnShutdown) {
   MockReleaseCallback release;
   viz::TransferableResource tran = MakeTransferableResource(use_gpu(), 'a', 15);
   provider().ImportResource(
       tran, viz::SingleReleaseCallback::Create(base::Bind(
                 &MockReleaseCallback::Released, base::Unretained(&release))));
 
-  // On shutdown the TransferableResource is lost.
-  EXPECT_CALL(release, Released(_, true));
+  // On shutdown the TransferableResource is not lost if not exported.
+  EXPECT_CALL(release, Released(_, false));
   Shutdown();
 }
 
diff --git a/cc/test/paint_op_helper.h b/cc/test/paint_op_helper.h
index 5a0810d4..fcd95176b 100644
--- a/cc/test/paint_op_helper.h
+++ b/cc/test/paint_op_helper.h
@@ -19,7 +19,7 @@
 // implementation should be limited ot the header.
 class PaintOpHelper {
  public:
-  static std::string ToString(PaintOp* base_op) {
+  static std::string ToString(const PaintOp* base_op) {
     std::ostringstream str;
     str << std::boolalpha;
     switch (base_op->GetType()) {
@@ -378,4 +378,8 @@
 
 }  // namespace cc
 
+inline ::std::ostream& operator<<(::std::ostream& os, const cc::PaintOp& op) {
+  return os << cc::PaintOpHelper::ToString(&op);
+}
+
 #endif  // CC_TEST_PAINT_OP_HELPER_H_
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index ebf6130..41df90c7 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -312,6 +312,7 @@
     java_files += chrome_vr_java_sources
     deps += [
       "//device/vr:java",
+      "//third_party/gvr-android-keyboard:kb_java",
       "//third_party/gvr-android-sdk:gvr_common_java",
     ]
   }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/BuildConstants.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/BuildConstants.java
new file mode 100644
index 0000000..c91b06f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/BuildConstants.java
@@ -0,0 +1,18 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.vr_shell.keyboard;
+
+/**
+ * Miscellaneous build-related constants.
+ */
+public class BuildConstants {
+    /**
+     * The version number of the keyboard API. A local copy of this class should
+     * be built into both the client and the SDK. Then at SDK load time, the
+     * version numbers can be compared to make sure the client and SDK have
+     * compatible APIs.
+     */
+    public static final long API_VERSION = 1;
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/GvrKeyboardLoaderClient.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/GvrKeyboardLoaderClient.java
new file mode 100644
index 0000000..a7358ff
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/GvrKeyboardLoaderClient.java
@@ -0,0 +1,160 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.vr_shell.keyboard;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.google.vr.keyboard.IGvrKeyboardLoader;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/** Loads the GVR keyboard SDK dynamically using the Keyboard Service. */
+@JNINamespace("vr_shell")
+public class GvrKeyboardLoaderClient {
+    private static final String TAG = "ChromeGvrKbClient";
+    private static final boolean DEBUG_LOGS = false;
+
+    private static final String KEYBOARD_PACKAGE = "com.google.android.vr.inputmethod";
+    private static final String LOADER_NAME = "com.google.vr.keyboard.GvrKeyboardLoader";
+
+    private static IGvrKeyboardLoader sLoader = null;
+    private static ClassLoader sRemoteClassLoader = null;
+
+    @CalledByNative
+    public static long loadKeyboardSDK() {
+        if (DEBUG_LOGS) Log.i(TAG, "loadKeyboardSDK");
+        IGvrKeyboardLoader loader = getLoader();
+        if (loader == null) {
+            if (DEBUG_LOGS) Log.i(TAG, "Couldn't find GVR keyboard SDK.");
+            return 0;
+        }
+        try {
+            long handle = loader.loadGvrKeyboard(BuildConstants.API_VERSION);
+            return handle;
+        } catch (RemoteException e) {
+            if (DEBUG_LOGS) Log.i(TAG, "Couldn't load GVR keyboard SDK.");
+            return 0;
+        }
+    }
+
+    @CalledByNative
+    public static void closeKeyboardSDK(long handle) {
+        if (DEBUG_LOGS) Log.i(TAG, "loadKeyboardSDK");
+        IGvrKeyboardLoader loader = getLoader();
+        if (loader != null) {
+            try {
+                loader.closeGvrKeyboard(handle);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Couldn't close GVR keyboard library", e);
+            }
+        }
+    }
+
+    private static IGvrKeyboardLoader getLoader() {
+        if (sLoader == null) {
+            ClassLoader remoteClassLoader = (ClassLoader) getRemoteClassLoader();
+            if (remoteClassLoader != null) {
+                IBinder binder = newBinder(remoteClassLoader, LOADER_NAME);
+                sLoader = IGvrKeyboardLoader.Stub.asInterface(binder);
+            }
+        }
+        return sLoader;
+    }
+
+    private static Context getRemoteContext(Context context) {
+        try {
+            // The flags Context.CONTEXT_INCLUDE_CODE and Context.CONTEXT_IGNORE_SECURITY are
+            // needed to be able to load classes via the classloader of the returned context.
+            return context.createPackageContext(KEYBOARD_PACKAGE,
+                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Couldn't find remote context", e);
+        }
+        return null;
+    }
+
+    @CalledByNative
+    public static Context getContextWrapper() {
+        Context context = ContextUtils.getApplicationContext();
+        return new KeyboardContextWrapper(getRemoteContext(context), context);
+    }
+
+    @CalledByNative
+    public static Object getRemoteClassLoader() {
+        Context context = ContextUtils.getApplicationContext();
+        if (sRemoteClassLoader == null) {
+            Context remoteContext = getRemoteContext(context);
+            if (remoteContext != null) {
+                sRemoteClassLoader = remoteContext.getClassLoader();
+            }
+        }
+        return sRemoteClassLoader;
+    }
+
+    private static IBinder newBinder(ClassLoader classLoader, String className) {
+        try {
+            Class<?> clazz = classLoader.loadClass(className);
+            return (IBinder) clazz.getConstructor().newInstance();
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException("Unable to find dynamic class " + className);
+        } catch (InstantiationException e) {
+            throw new IllegalStateException("Unable to instantiate the remote class " + className);
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException(
+                    "Unable to call the default constructor of " + className);
+        } catch (Exception e) {
+            throw new IllegalStateException("Reflection error in " + className);
+        }
+    }
+
+    private static class KeyboardContextWrapper extends ContextWrapper {
+        private final Context mKeyboardContext;
+
+        private KeyboardContextWrapper(Context keyboardContext, Context baseContext) {
+            super(baseContext);
+            this.mKeyboardContext = keyboardContext;
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            // As the LAYOUT_INFLATER_SERVICE uses assets from the Context, it should point to the
+            // keyboard Context.
+            if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
+                return mKeyboardContext.getSystemService(name);
+            } else {
+                return super.getSystemService(name);
+            }
+        }
+
+        @Override
+        public Resources getResources() {
+            return mKeyboardContext.getResources();
+        }
+
+        @Override
+        public AssetManager getAssets() {
+            return mKeyboardContext.getAssets();
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            return this;
+        }
+
+        @Override
+        public ClassLoader getClassLoader() {
+            return mKeyboardContext.getClassLoader();
+        }
+    }
+}
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 5184586..bb96d92 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1402,6 +1402,8 @@
   "java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java",
   "java/src/org/chromium/chrome/browser/vr_shell/VrWindowAndroid.java",
   "java/src/org/chromium/chrome/browser/vr_shell/OnDispatchTouchEventCallback.java",
+  "java/src/org/chromium/chrome/browser/vr_shell/keyboard/BuildConstants.java",
+  "java/src/org/chromium/chrome/browser/vr_shell/keyboard/GvrKeyboardLoaderClient.java",
 ]
 
 chrome_test_java_sources = [
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index eb74828..e47e1f6 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -599,6 +599,9 @@
   <message name="IDS_SETTINGS_CREDIT_CARD_NONE" desc="Placeholder that is shown when there are no credit cards in the list of saved credit cards.">
     Saved cards will appear here
   </message>
+  <message name="IDS_SETTINGS_CREDIT_CARD_DISABLED" desc="Placeholder that is shown when credit card autofill has been disabled by policy.">
+    Saved cards are disabled by your administrator
+  </message>
   <message name="IDS_SETTINGS_PASSWORDS_NONE" desc="Placeholder that is shown when there are no passwords in the list of saved passwords.">
     Saved passwords will appear here
   </message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 1aeed0b1..12e5aeac 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2176,6 +2176,9 @@
     {"vr-browsing", flag_descriptions::kVrBrowsingName,
      flag_descriptions::kVrBrowsingDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(features::kVrBrowsing)},
+    {"vr-browser-keyboard", flag_descriptions::kVrBrowserKeyboardName,
+     flag_descriptions::kVrBrowserKeyboardDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(features::kVrBrowserKeyboard)},
     {"vr-browsing-experimental-features",
      flag_descriptions::kVrBrowsingExperimentalFeaturesName,
      flag_descriptions::kVrBrowsingExperimentalFeaturesDescription, kOsAndroid,
@@ -2196,6 +2199,11 @@
      flag_descriptions::kWebVrAutopresentFromIntentDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kWebVrAutopresentFromIntent)},
 #endif  // OS_ANDROID
+#if BUILDFLAG(ENABLE_OPENVR)
+    {"openvr", flag_descriptions::kOpenVRName,
+     flag_descriptions::kOpenVRDescription, kOsWin,
+     FEATURE_VALUE_TYPE(features::kOpenVR)},
+#endif  // ENABLE_OPENVR
 #endif  // ENABLE_VR
 #if defined(OS_CHROMEOS)
     {"disable-accelerated-mjpeg-decode",
@@ -3572,9 +3580,9 @@
      FEATURE_VALUE_TYPE(features::kClipboardContentSetting)},
 
 #if defined(OS_CHROMEOS)
-    {"native-samba", flag_descriptions::kNativeSambaName,
-     flag_descriptions::kNativeSambaDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(features::kNativeSamba)},
+    {"native-smb", flag_descriptions::kNativeSmbName,
+     flag_descriptions::kNativeSmbDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(features::kNativeSmb)},
 #endif  // defined(OS_CHROMEOS)
 
     {"enable-modern-media-controls",
diff --git a/chrome/browser/android/vr_shell/BUILD.gn b/chrome/browser/android/vr_shell/BUILD.gn
index 816920a..4e51490 100644
--- a/chrome/browser/android/vr_shell/BUILD.gn
+++ b/chrome/browser/android/vr_shell/BUILD.gn
@@ -20,6 +20,7 @@
     "autocomplete_controller.cc",
     "autocomplete_controller.h",
     "gl_browser_interface.h",
+    "gvr_keyboard_shim.cc",
     "gvr_util.cc",
     "gvr_util.h",
     "mailbox_to_surface_bridge.cc",
@@ -75,7 +76,10 @@
     "android",
   ]
 
-  configs += [ "//third_party/gvr-android-sdk:libgvr_config" ]
+  configs += [
+    "//third_party/gvr-android-keyboard:kb_config",
+    "//third_party/gvr-android-sdk:libgvr_config",
+  ]
 }
 
 generate_jni("vr_shell_jni_headers") {
@@ -85,6 +89,7 @@
     "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrCoreInfo.java",
     "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java",
     "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java",
+    "//chrome/android/java/src/org/chromium/chrome/browser/vr_shell/keyboard/GvrKeyboardLoaderClient.java",
   ]
   jni_package = "vr_shell"
 }
diff --git a/chrome/browser/android/vr_shell/DEPS b/chrome/browser/android/vr_shell/DEPS
index 1a4cc31..59f29a44 100644
--- a/chrome/browser/android/vr_shell/DEPS
+++ b/chrome/browser/android/vr_shell/DEPS
@@ -2,5 +2,6 @@
   "+cc/base",
   "+cc/layers",
   "+device/vr",
+  "+third_party/gvr-android-keyboard/src",
   "+third_party/gvr-android-sdk/src",
 ]
diff --git a/chrome/browser/android/vr_shell/gvr_keyboard_shim.cc b/chrome/browser/android/vr_shell/gvr_keyboard_shim.cc
new file mode 100644
index 0000000..cdc68151
--- /dev/null
+++ b/chrome/browser/android/vr_shell/gvr_keyboard_shim.cc
@@ -0,0 +1,246 @@
+// 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 "third_party/gvr-android-keyboard/src/libraries/headers/vr/gvr/capi/include/gvr_keyboard.h"
+
+#include <android/native_window_jni.h>
+#include <dlfcn.h>
+#include <jni.h>
+#include <cmath>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/logging.h"
+#include "jni/GvrKeyboardLoaderClient_jni.h"
+
+// Run CALL macro for every function defined in the API.
+#define FOR_EACH_API_FN                                         \
+  CALL(gvr_keyboard_initialize)                                 \
+  CALL(gvr_keyboard_create)                                     \
+  CALL(gvr_keyboard_get_input_mode)                             \
+  CALL(gvr_keyboard_set_input_mode)                             \
+  CALL(gvr_keyboard_get_recommended_world_from_keyboard_matrix) \
+  CALL(gvr_keyboard_set_world_from_keyboard_matrix)             \
+  CALL(gvr_keyboard_show)                                       \
+  CALL(gvr_keyboard_update_button_state)                        \
+  CALL(gvr_keyboard_update_controller_ray)                      \
+  CALL(gvr_keyboard_get_text)                                   \
+  CALL(gvr_keyboard_set_text)                                   \
+  CALL(gvr_keyboard_get_selection_indices)                      \
+  CALL(gvr_keyboard_set_selection_indices)                      \
+  CALL(gvr_keyboard_get_composing_indices)                      \
+  CALL(gvr_keyboard_set_composing_indices)                      \
+  CALL(gvr_keyboard_set_frame_time)                             \
+  CALL(gvr_keyboard_set_eye_from_world_matrix)                  \
+  CALL(gvr_keyboard_set_projection_matrix)                      \
+  CALL(gvr_keyboard_set_viewport)                               \
+  CALL(gvr_keyboard_advance_frame)                              \
+  CALL(gvr_keyboard_render)                                     \
+  CALL(gvr_keyboard_hide)                                       \
+  CALL(gvr_keyboard_destroy)
+
+namespace {
+
+// Declare implementation function pointers.
+#define CALL(fn) decltype(&fn) impl_##fn = nullptr;
+FOR_EACH_API_FN
+#undef CALL
+
+template <typename Fn>
+bool LoadFunction(void* handle, const char* function_name, Fn* fn_out) {
+  void* fn = dlsym(handle, function_name);
+  if (!fn) {
+    LOG(ERROR) << "Failed to load " << function_name
+               << " from GVR keyboard library: " << dlerror();
+    return false;
+  }
+  *fn_out = reinterpret_cast<Fn>(fn);
+  return true;
+}
+
+static void* sdk_handle = nullptr;
+
+void CloseSdk() {
+  if (!sdk_handle)
+    return;
+
+  JNIEnv* env = base::android::AttachCurrentThread();
+  CHECK(env);
+
+  vr_shell::Java_GvrKeyboardLoaderClient_closeKeyboardSDK(
+      env, reinterpret_cast<jlong>(sdk_handle));
+
+// Null all the function pointers.
+#define CALL(fn) impl_##fn = nullptr;
+  FOR_EACH_API_FN
+#undef CALL
+
+  sdk_handle = nullptr;
+}
+
+bool LoadSdk(void* closure, gvr_keyboard_callback callback) {
+  if (sdk_handle)
+    return true;
+
+  JNIEnv* env = base::android::AttachCurrentThread();
+  CHECK(env);
+
+  base::android::ScopedJavaLocalRef<jobject> context_wrapper =
+      vr_shell::Java_GvrKeyboardLoaderClient_getContextWrapper(env);
+
+  base::android::ScopedJavaLocalRef<jobject> remote_class_loader =
+      vr_shell::Java_GvrKeyboardLoaderClient_getRemoteClassLoader(env);
+
+  sdk_handle = reinterpret_cast<void*>(
+      vr_shell::Java_GvrKeyboardLoaderClient_loadKeyboardSDK(env));
+
+  if (!sdk_handle) {
+    LOG(ERROR) << "Failed to load GVR keyboard SDK.";
+    return false;
+  }
+
+  // Load all function pointers from the SDK.
+  bool success = true;
+#define CALL(fn) success &= LoadFunction(sdk_handle, #fn, &impl_##fn);
+  FOR_EACH_API_FN
+#undef CALL
+
+  if (!success) {
+    CloseSdk();
+    return false;
+  }
+
+  gvr_keyboard_initialize(env, context_wrapper.obj(),
+                          remote_class_loader.obj());
+
+  return success;
+}
+
+}  // namespace
+
+void gvr_keyboard_initialize(JNIEnv* env,
+                             jobject app_context,
+                             jobject class_loader) {
+  impl_gvr_keyboard_initialize(env, app_context, class_loader);
+}
+
+gvr_keyboard_context* gvr_keyboard_create(void* closure,
+                                          gvr_keyboard_callback callback) {
+  if (!LoadSdk(closure, callback)) {
+    return nullptr;
+  }
+  return impl_gvr_keyboard_create(closure, callback);
+}
+
+void gvr_keyboard_destroy(gvr_keyboard_context** context) {
+  impl_gvr_keyboard_destroy(context);
+  CloseSdk();
+}
+
+int32_t gvr_keyboard_get_input_mode(gvr_keyboard_context* context) {
+  return impl_gvr_keyboard_get_input_mode(context);
+}
+
+void gvr_keyboard_set_input_mode(gvr_keyboard_context* context,
+                                 int32_t input_mode) {
+  impl_gvr_keyboard_set_input_mode(context, input_mode);
+}
+
+void gvr_keyboard_get_recommended_world_from_keyboard_matrix(
+    float distance_from_eye,
+    gvr_mat4f* matrix) {
+  impl_gvr_keyboard_get_recommended_world_from_keyboard_matrix(
+      distance_from_eye, matrix);
+}
+
+void gvr_keyboard_set_world_from_keyboard_matrix(gvr_keyboard_context* context,
+                                                 const gvr_mat4f* matrix) {
+  impl_gvr_keyboard_set_world_from_keyboard_matrix(context, matrix);
+}
+
+void gvr_keyboard_show(gvr_keyboard_context* context) {
+  impl_gvr_keyboard_show(context);
+}
+
+void gvr_keyboard_update_button_state(gvr_keyboard_context* context,
+                                      int32_t button_index,
+                                      bool pressed) {
+  impl_gvr_keyboard_update_button_state(context, button_index, pressed);
+}
+
+bool gvr_keyboard_update_controller_ray(gvr_keyboard_context* context,
+                                        const gvr_vec3f* start,
+                                        const gvr_vec3f* end,
+                                        gvr_vec3f* hit) {
+  return impl_gvr_keyboard_update_controller_ray(context, start, end, hit);
+}
+
+char* gvr_keyboard_get_text(gvr_keyboard_context* context) {
+  return impl_gvr_keyboard_get_text(context);
+}
+
+void gvr_keyboard_set_text(gvr_keyboard_context* context, const char* text) {
+  return impl_gvr_keyboard_set_text(context, text);
+}
+
+void gvr_keyboard_get_selection_indices(gvr_keyboard_context* context,
+                                        size_t* start,
+                                        size_t* end) {
+  impl_gvr_keyboard_get_selection_indices(context, start, end);
+}
+
+void gvr_keyboard_set_selection_indices(gvr_keyboard_context* context,
+                                        size_t start,
+                                        size_t end) {
+  impl_gvr_keyboard_set_selection_indices(context, start, end);
+}
+
+void gvr_keyboard_get_composing_indices(gvr_keyboard_context* context,
+                                        size_t* start,
+                                        size_t* end) {
+  impl_gvr_keyboard_get_composing_indices(context, start, end);
+}
+
+void gvr_keyboard_set_composing_indices(gvr_keyboard_context* context,
+                                        size_t start,
+                                        size_t end) {
+  impl_gvr_keyboard_set_composing_indices(context, start, end);
+}
+
+void gvr_keyboard_set_frame_time(gvr_keyboard_context* context,
+                                 const gvr_clock_time_point* time) {
+  impl_gvr_keyboard_set_frame_time(context, time);
+}
+
+void gvr_keyboard_set_eye_from_world_matrix(gvr_keyboard_context* context,
+                                            int32_t eye_type,
+                                            const gvr_mat4f* matrix) {
+  impl_gvr_keyboard_set_eye_from_world_matrix(context, eye_type, matrix);
+}
+
+void gvr_keyboard_set_projection_matrix(gvr_keyboard_context* context,
+                                        int32_t eye_type,
+                                        const gvr_mat4f* projection) {
+  impl_gvr_keyboard_set_projection_matrix(context, eye_type, projection);
+}
+
+void gvr_keyboard_set_viewport(gvr_keyboard_context* context,
+                               int32_t eye_type,
+                               const gvr_recti* viewport) {
+  impl_gvr_keyboard_set_viewport(context, eye_type, viewport);
+}
+
+void gvr_keyboard_advance_frame(gvr_keyboard_context* context) {
+  impl_gvr_keyboard_advance_frame(context);
+}
+
+void gvr_keyboard_render(gvr_keyboard_context* context, int32_t eye_type) {
+  impl_gvr_keyboard_render(context, eye_type);
+}
+
+void gvr_keyboard_hide(gvr_keyboard_context* context) {
+  impl_gvr_keyboard_hide(context);
+}
+
+#undef FOR_EACH_API_FN
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.cc b/chrome/browser/android/vr_shell/vr_shell_gl.cc
index 1572c5e..4d10ce9 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.cc
@@ -101,6 +101,46 @@
 
 static constexpr float kRedrawSceneAngleDeltaDegrees = 1.0;
 
+static gvr_keyboard_context* keyboard_context;
+
+// TODO(ymalik,crbug.com/780318): This callback is temporary until we have an
+// editable input field.
+void OnKeyboardEvent(void*, int32_t event) {
+  switch (event) {
+    case GVR_KEYBOARD_ERROR_UNKNOWN:
+      LOG(ERROR) << "Unknown GVR keyboard error.";
+      break;
+    case GVR_KEYBOARD_ERROR_SERVICE_NOT_CONNECTED:
+      LOG(ERROR) << "GVR keyboard service not connected.";
+      break;
+    case GVR_KEYBOARD_ERROR_NO_LOCALES_FOUND:
+      LOG(ERROR) << "No GVR keyboard locales found.";
+      break;
+    case GVR_KEYBOARD_ERROR_SDK_LOAD_FAILED:
+      LOG(ERROR) << "GVR keyboard sdk load failed.";
+      break;
+    case GVR_KEYBOARD_SHOWN:
+      DVLOG(1) << "GVR keyboard shown.";
+      break;
+    case GVR_KEYBOARD_HIDDEN:
+      DVLOG(1) << "GVR keyboard hidden.";
+      break;
+    case GVR_KEYBOARD_TEXT_UPDATED: {
+      char* text = gvr_keyboard_get_text(keyboard_context);
+      DVLOG(1) << "GVR keyboard text updated: " << text;
+      free(reinterpret_cast<void*>(text));
+    } break;
+    case GVR_KEYBOARD_TEXT_COMMITTED: {
+      char* text = gvr_keyboard_get_text(keyboard_context);
+      DVLOG(1) << "GVR keyboard text updated: " << text;
+      free(reinterpret_cast<void*>(text));
+      gvr_keyboard_set_text(keyboard_context, "");
+    } break;
+    default:
+      NOTREACHED();
+  }
+}
+
 gfx::Transform PerspectiveMatrixFromView(const gvr::Rectf& fov,
                                          float z_near,
                                          float z_far) {
@@ -196,6 +236,9 @@
 
 VrShellGl::~VrShellGl() {
   ClosePresentationBindings();
+  if (keyboard_enabled_) {
+    gvr_keyboard_destroy(&gvr_keyboard_);
+  }
 }
 
 void VrShellGl::Initialize() {
@@ -465,6 +508,7 @@
 
 void VrShellGl::InitializeRenderer() {
   gvr_api_->InitializeGl();
+  CreateKeyboard();
   gfx::Transform head_pose;
   device::GvrDelegate::GetGvrPoseWithNeckModel(gvr_api_.get(), &head_pose);
   webvr_head_pose_.assign(kPoseRingBufferSize, head_pose);
@@ -614,6 +658,7 @@
   controller_model.opacity = controller_->GetOpacity();
   controller_model.laser_direction = controller_direction;
   controller_model.laser_origin = laser_origin;
+  controller_model_ = controller_model;
 
   vr::ReticleModel reticle_model;
   ui_->input_manager()->HandleInput(current_time, controller_model,
@@ -645,6 +690,18 @@
 
   if (controller_->ButtonUpHappened(
           gvr::ControllerButton::GVR_CONTROLLER_BUTTON_APP)) {
+    // TODO(ymalik,crbug.com/780318): We temporarily show and hide the keyboard
+    // when the app button is pressed. This behavior is behind a runtime enabled
+    // feature and should go away as soon as we have editable input fields.
+    show_keyboard_ = keyboard_enabled_ && !show_keyboard_;
+    if (keyboard_enabled_) {
+      if (show_keyboard_) {
+        gvr_keyboard_show(gvr_keyboard_);
+      } else {
+        gvr_keyboard_hide(gvr_keyboard_);
+      }
+    }
+
     // A gesture is a movement of the controller while holding the App button.
     // If the angle of the movement is within a threshold, the action is
     // considered a regular click
@@ -892,6 +949,10 @@
   // screen showing in WebVR mode that must also fill the screen.
   ui_->ui_renderer()->Draw(render_info_primary_);
 
+  // Draw keyboard. TODO(ymalik,crbug.com/780135): Keyboard should be a UI
+  // element and this special rendering logic should move out of here.
+  DrawKeyboard();
+
   content_frame_available_ = false;
   acquired_frame_.Unbind();
 
@@ -970,6 +1031,77 @@
   }
 }
 
+void VrShellGl::CreateKeyboard() {
+  if (gvr_keyboard_)
+    return;
+
+  keyboard_enabled_ =
+      base::FeatureList::IsEnabled(features::kVrBrowserKeyboard);
+  if (!keyboard_enabled_)
+    return;
+
+  gvr_keyboard_ = gvr_keyboard_create(nullptr, OnKeyboardEvent);
+  if (!gvr_keyboard_) {
+    keyboard_enabled_ = false;
+    return;
+  }
+  keyboard_context = gvr_keyboard_;
+
+  gvr_mat4f matrix;
+  gvr_keyboard_get_recommended_world_from_keyboard_matrix(2.0f, &matrix);
+  gvr_keyboard_set_world_from_keyboard_matrix(gvr_keyboard_, &matrix);
+}
+
+void VrShellGl::DrawKeyboard() {
+  if (!keyboard_enabled_)
+    return;
+
+  // Note that according to the keyboard API, these functions must be called
+  // every frame after the keyboard is created to process events, regardless of
+  // keyboard visibility.
+  gvr::ClockTimePoint target_time = gvr::GvrApi::GetTimePointNow();
+  gvr_keyboard_set_frame_time(gvr_keyboard_, &target_time);
+  gvr_keyboard_advance_frame(gvr_keyboard_);
+
+  if (!show_keyboard_)
+    return;
+
+  bool pressed = controller_->ButtonUpHappened(
+      gvr::ControllerButton::GVR_CONTROLLER_BUTTON_CLICK);
+  gvr_keyboard_update_button_state(
+      gvr_keyboard_, gvr::ControllerButton::GVR_CONTROLLER_BUTTON_CLICK,
+      pressed);
+
+  gvr_vec3f start;
+  start.x = controller_model_.laser_origin.x();
+  start.y = controller_model_.laser_origin.y();
+  start.z = controller_model_.laser_origin.z();
+  gvr_vec3f end;
+  end.x = start.x + controller_model_.laser_direction.x();
+  end.y = start.y + controller_model_.laser_direction.y();
+  end.z = start.z + controller_model_.laser_direction.z();
+  gvr_vec3f hit_point;
+  gvr_keyboard_update_controller_ray(gvr_keyboard_, &start, &end, &hit_point);
+  for (auto eye : {GVR_LEFT_EYE, GVR_RIGHT_EYE}) {
+    vr::CameraModel& eye_info = (eye == GVR_LEFT_EYE)
+                                    ? render_info_primary_.left_eye_model
+                                    : render_info_primary_.right_eye_model;
+    gvr::Mat4f view_matrix;
+    TransformToGvrMat(eye_info.view_matrix, &view_matrix);
+    gvr_keyboard_set_eye_from_world_matrix(gvr_keyboard_, eye, &view_matrix);
+
+    gvr::Mat4f proj_matrix;
+    TransformToGvrMat(eye_info.proj_matrix, &proj_matrix);
+    gvr_keyboard_set_projection_matrix(gvr_keyboard_, eye, &proj_matrix);
+
+    gfx::Rect viewport_rect = eye_info.viewport;
+    const gvr::Recti viewport = {viewport_rect.x(), viewport_rect.right(),
+                                 viewport_rect.y(), viewport_rect.bottom()};
+    gvr_keyboard_set_viewport(gvr_keyboard_, eye, &viewport);
+    gvr_keyboard_render(gvr_keyboard_, eye);
+  }
+}
+
 void VrShellGl::DrawFrameSubmitWhenReady(
     int16_t frame_index,
     const gfx::Transform& head_pose,
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.h b/chrome/browser/android/vr_shell/vr_shell_gl.h
index 3c2f21a..1f6fa1e 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.h
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.h
@@ -24,6 +24,7 @@
 #include "chrome/browser/vr/ui_renderer.h"
 #include "device/vr/vr_service.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
+#include "third_party/gvr-android-keyboard/src/libraries/headers/vr/gvr/capi/include/gvr_keyboard.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr_types.h"
 #include "ui/gfx/geometry/quaternion.h"
@@ -85,6 +86,8 @@
   void OnTriggerEvent();
   void OnPause();
   void OnResume();
+  void DrawKeyboard();
+  void CreateKeyboard();
 
   base::WeakPtr<vr::BrowserUiInterface> GetBrowserUiWeakPtr();
 
@@ -221,6 +224,7 @@
   bool is_exiting_ = false;
 
   std::unique_ptr<VrController> controller_;
+  gvr_keyboard_context* gvr_keyboard_ = nullptr;
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
@@ -264,6 +268,10 @@
   bool skips_redraw_when_not_dirty_;
   gfx::Transform last_used_head_pose_;
 
+  bool keyboard_enabled_ = false;
+  bool show_keyboard_ = false;
+  vr::ControllerModel controller_model_;
+
   base::WeakPtrFactory<VrShellGl> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(VrShellGl);
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 3d404fe..1a02535 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -109,6 +109,7 @@
 #include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
@@ -807,6 +808,8 @@
     MagnificationManager::Initialize();
   }
 
+  // TODO(crbug.com/776464): Remove WallpaperManager after everything is
+  // migrated to WallpaperController.
   WallpaperManager::SetPathIds(chrome::DIR_USER_DATA,
                                chrome::DIR_CHROMEOS_WALLPAPERS,
                                chrome::DIR_CHROMEOS_CUSTOM_WALLPAPERS);
@@ -840,6 +843,9 @@
   tablet_mode_client_ = std::make_unique<TabletModeClient>();
   tablet_mode_client_->Init();
 
+  wallpaper_controller_client_ = std::make_unique<WallpaperControllerClient>();
+  wallpaper_controller_client_->Init();
+
   if (lock_screen_apps::StateController::IsEnabled()) {
     lock_screen_apps_state_controller_ =
         base::MakeUnique<lock_screen_apps::StateController>();
@@ -1165,6 +1171,8 @@
   g_browser_process->platform_part()->user_manager()->Shutdown();
   WallpaperManager::Shutdown();
 
+  wallpaper_controller_client_.reset();
+
   // Let the DeviceDisablingManager unregister itself as an observer of the
   // CrosSettings singleton before it is destroyed.
   g_browser_process->platform_part()->ShutdownDeviceDisablingManager();
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.h b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
index 937d48f..df74642f 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.h
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
@@ -17,6 +17,7 @@
 class NightLightClient;
 class NotificationPlatformBridge;
 class TabletModeClient;
+class WallpaperControllerClient;
 
 namespace lock_screen_apps {
 class StateController;
@@ -115,6 +116,7 @@
       lock_screen_apps_state_controller_;
 
   std::unique_ptr<NightLightClient> night_light_client_;
+  std::unique_ptr<WallpaperControllerClient> wallpaper_controller_client_;
 
   // TODO(estade): Remove this when Chrome OS uses native notifications by
   // default (as it will be instantiated elsewhere). For now it's necessary to
diff --git a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc
index ebef2b89..34b0add 100644
--- a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc
+++ b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc
@@ -200,7 +200,7 @@
 // purpose, but it has two caveats:
 // 1. username_hash() is defined only after user has logged in.
 // 2. If cryptohome identifier changes, username_hash() will also change,
-//    and we may loose user => wallpaper files mapping at that point.
+//    and we may lose user => wallpaper files mapping at that point.
 // So this function gives WallpaperManager independent hashing method to break
 // this dependency.
 //
@@ -743,15 +743,16 @@
 base::FilePath WallpaperManager::GetCustomWallpaperPath(
     const char* sub_dir,
     const wallpaper::WallpaperFilesId& wallpaper_files_id,
-    const std::string& file) {
+    const std::string& file_name) {
   base::FilePath custom_wallpaper_path = GetCustomWallpaperDir(sub_dir);
-  return custom_wallpaper_path.Append(wallpaper_files_id.id()).Append(file);
+  return custom_wallpaper_path.Append(wallpaper_files_id.id())
+      .Append(file_name);
 }
 
 void WallpaperManager::SetCustomWallpaper(
     const AccountId& account_id,
     const wallpaper::WallpaperFilesId& wallpaper_files_id,
-    const std::string& file,
+    const std::string& file_name,
     wallpaper::WallpaperLayout layout,
     wallpaper::WallpaperType type,
     const gfx::ImageSkia& image,
@@ -774,7 +775,7 @@
   }
 
   base::FilePath wallpaper_path = GetCustomWallpaperPath(
-      kOriginalWallpaperSubDir, wallpaper_files_id, file);
+      kOriginalWallpaperSubDir, wallpaper_files_id, file_name);
 
   const user_manager::User* user =
       user_manager::UserManager::Get()->FindUser(account_id);
@@ -802,7 +803,7 @@
   }
 
   std::string relative_path =
-      base::FilePath(wallpaper_files_id.id()).Append(file).value();
+      base::FilePath(wallpaper_files_id.id()).Append(file_name).value();
   // User's custom wallpaper path is determined by relative path and the
   // appropriate wallpaper resolution in GetCustomWallpaperInternal.
   WallpaperInfo info = {relative_path, layout, type,
@@ -851,7 +852,7 @@
 
 void WallpaperManager::SetCustomizedDefaultWallpaper(
     const GURL& wallpaper_url,
-    const base::FilePath& downloaded_file,
+    const base::FilePath& file_path,
     const base::FilePath& resized_directory) {
   // Should fail if this ever happens in tests.
   DCHECK(wallpaper_url.is_valid());
@@ -862,10 +863,10 @@
     }
     return;
   }
-  std::string downloaded_file_name = downloaded_file.BaseName().value();
+  std::string downloaded_file_name = file_path.BaseName().value();
   std::unique_ptr<CustomizedWallpaperRescaledFiles> rescaled_files(
       new CustomizedWallpaperRescaledFiles(
-          downloaded_file,
+          file_path,
           resized_directory.Append(downloaded_file_name +
                                    kSmallWallpaperSuffix),
           resized_directory.Append(downloaded_file_name +
@@ -874,7 +875,7 @@
   base::Closure check_file_exists = rescaled_files->CreateCheckerClosure();
   base::Closure on_checked_closure =
       base::Bind(&WallpaperManager::SetCustomizedDefaultWallpaperAfterCheck,
-                 weak_factory_.GetWeakPtr(), wallpaper_url, downloaded_file,
+                 weak_factory_.GetWeakPtr(), wallpaper_url, file_path,
                  base::Passed(std::move(rescaled_files)));
   if (!task_runner_->PostTaskAndReply(FROM_HERE, check_file_exists,
                                       on_checked_closure)) {
@@ -1234,7 +1235,7 @@
   observers_.RemoveObserver(observer);
 }
 
-void WallpaperManager::Open() {
+void WallpaperManager::OpenWallpaperPicker() {
   if (wallpaper_manager_util::ShouldUseAndroidWallpapersApp(
           ProfileHelper::Get()->GetProfileByUser(
               user_manager::UserManager::Get()->GetActiveUser())) &&
@@ -1329,8 +1330,7 @@
 // WallpaperManager, private: --------------------------------------------------
 
 WallpaperManager::WallpaperManager()
-    : binding_(this),
-      activation_client_observer_(this),
+    : activation_client_observer_(this),
       window_observer_(this),
       weak_factory_(this) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -1349,19 +1349,6 @@
        base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
 
   user_manager::UserManager::Get()->AddObserver(this);
-
-  content::ServiceManagerConnection* connection =
-      content::ServiceManagerConnection::GetForProcess();
-  if (connection && connection->GetConnector()) {
-    // Connect to the wallpaper controller interface in the ash service.
-    ash::mojom::WallpaperControllerPtr wallpaper_controller_ptr;
-    connection->GetConnector()->BindInterface(ash::mojom::kServiceName,
-                                              &wallpaper_controller_ptr);
-    // Register this object as the wallpaper picker.
-    ash::mojom::WallpaperPickerPtr picker;
-    binding_.Bind(mojo::MakeRequest(&picker));
-    wallpaper_controller_ptr->SetWallpaperPicker(std::move(picker));
-  }
 }
 
 // static
@@ -2025,7 +2012,7 @@
 
 void WallpaperManager::SetCustomizedDefaultWallpaperAfterCheck(
     const GURL& wallpaper_url,
-    const base::FilePath& downloaded_file,
+    const base::FilePath& file_path,
     std::unique_ptr<CustomizedWallpaperRescaledFiles> rescaled_files) {
   PrefService* pref_service = g_browser_process->local_state();
 
@@ -2037,7 +2024,7 @@
     // Either resized images do not exist or cached version is incorrect.
     // Need to start resize again.
     user_image_loader::StartWithFilePath(
-        task_runner_, downloaded_file, ImageDecoder::ROBUST_JPEG_CODEC,
+        task_runner_, file_path, ImageDecoder::ROBUST_JPEG_CODEC,
         0,  // Do not crop.
         base::Bind(&WallpaperManager::OnCustomizedDefaultWallpaperDecoded,
                    weak_factory_.GetWeakPtr(), wallpaper_url,
diff --git a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h
index c70894a..6bbb4a7 100644
--- a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h
+++ b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h
@@ -12,7 +12,6 @@
 #include <string>
 #include <vector>
 
-#include "ash/public/interfaces/wallpaper.mojom.h"
 #include "base/containers/circular_deque.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
@@ -33,7 +32,6 @@
 #include "components/wallpaper/wallpaper_info.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
-#include "mojo/public/cpp/bindings/binding.h"
 #include "ui/aura/window_observer.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/wm/public/activation_change_observer.h"
@@ -96,8 +94,7 @@
 // A dictionary pref that maps usernames to wallpaper info.
 extern const char kUsersWallpaperInfo[];
 
-class WallpaperManager : public ash::mojom::WallpaperPicker,
-                         public content::NotificationObserver,
+class WallpaperManager : public content::NotificationObserver,
                          public user_manager::UserManager::Observer,
                          public wm::ActivationChangeObserver,
                          public aura::WindowObserver {
@@ -240,26 +237,39 @@
                                      gfx::ImageSkia* output_skia);
 
   // Returns custom wallpaper path. Append |sub_dir|, |wallpaper_files_id| and
-  // |file| to custom wallpaper directory.
+  // |file_name| to custom wallpaper directory.
   static base::FilePath GetCustomWallpaperPath(
       const char* sub_dir,
       const wallpaper::WallpaperFilesId& wallpaper_files_id,
-      const std::string& file);
+      const std::string& file_name);
 
   // Sets wallpaper from policy or from a local file. Saves the custom wallpaper
-  // to file, posts task to generate thumbnail and updates local state. If
-  // |show_wallpaper| is false, don't show the new wallpaper now but only update
-  // cache.
+  // to file, posts task to generate thumbnail and updates local state.
+  // |account_id|: The user's account id.
+  // |wallpaper_files_id|: The unique id of each wallpaper file.
+  // |file_name|: The name of the wallpaper file.
+  // |layout|: The layout of the wallpaper, used for wallpaper resizing.
+  // |type|: The type of the wallpaper, e.g., default, policy etc.
+  // |image|: The wallpaper image.
+  // |show_wallpaper|: If false, don't show the new wallpaper now but only
+  //                   update cache.
   void SetCustomWallpaper(const AccountId& account_id,
                           const wallpaper::WallpaperFilesId& wallpaper_files_id,
-                          const std::string& file,
+                          const std::string& file_name,
                           wallpaper::WallpaperLayout layout,
                           wallpaper::WallpaperType type,
                           const gfx::ImageSkia& image,
                           bool show_wallpaper);
 
-  // Sets wallpaper from the wallpaper picker selection. If |show_wallpaper|
-  // is false, don't show the new wallpaper now but only update cache.
+  // Sets wallpaper from the wallpaper picker selection, i.e., the wallpaper
+  // type is ONLINE.
+  // |account_id|: The user's account id.
+  // |image|: The wallpaper image.
+  // |url|: The url corresponding to this wallpaper. Used as a placeholder for
+  //        the location in WallpaperInfo.
+  // |layout|: The layout of the wallpaper, used for wallpaper resizing.
+  // |show_wallpaper|: If false, don't show the new wallpaper now but only
+  //                   update cache.
   void SetOnlineWallpaper(const AccountId& account_id,
                           const gfx::ImageSkia& image,
                           const std::string& url,
@@ -271,10 +281,15 @@
   // false, don't show the default wallpaper now.
   void SetDefaultWallpaper(const AccountId& account_id, bool show_wallpaper);
 
-  // Called from CustomizationDocument. |resized_directory| is the directory
-  // where resized versions are stored and it must be writable.
+  // Sets a customized default wallpaper to be used wherever a default wallpaper
+  // is needed. Note: it doesn't change the default wallpaper for guest and
+  // child accounts.
+  // |wallpaper_url|: The url corresponding to this wallpaper.
+  // |file_path|: The path of the wallpaper file.
+  // |resized_directory|: The directory where resized versions are stored. Must
+  //                      be writable.
   void SetCustomizedDefaultWallpaper(const GURL& wallpaper_url,
-                                     const base::FilePath& downloaded_file,
+                                     const base::FilePath& file_path,
                                      const base::FilePath& resized_directory);
 
   // Shows |account_id|'s wallpaper, which is determined in the following order:
@@ -357,8 +372,8 @@
   // Removes given observer from the list.
   void RemoveObserver(Observer* observer);
 
-  // ash::mojom::WallpaperPicker:
-  void Open() override;
+  // Opens the wallpaper picker window.
+  void OpenWallpaperPicker();
 
   // content::NotificationObserver:
   void Observe(int type,
@@ -549,7 +564,7 @@
   // This is called after we check that supplied default wallpaper files exist.
   void SetCustomizedDefaultWallpaperAfterCheck(
       const GURL& wallpaper_url,
-      const base::FilePath& downloaded_file,
+      const base::FilePath& file_path,
       std::unique_ptr<CustomizedWallpaperRescaledFiles> rescaled_files);
 
   // Starts rescaling of customized wallpaper.
@@ -656,8 +671,6 @@
       const base::FilePath& customized_default_wallpaper_file_large,
       std::unique_ptr<gfx::ImageSkia> large_wallpaper_image);
 
-  mojo::Binding<ash::mojom::WallpaperPicker> binding_;
-
   std::unique_ptr<CrosSettings::ObserverSubscription>
       show_user_name_on_signin_subscription_;
 
diff --git a/chrome/browser/chromeos/ui/low_disk_notification.cc b/chrome/browser/chromeos/ui/low_disk_notification.cc
index 349e090..4fef6d9 100644
--- a/chrome/browser/chromeos/ui/low_disk_notification.cc
+++ b/chrome/browser/chromeos/ui/low_disk_notification.cc
@@ -13,6 +13,7 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chrome/browser/notifications/notification_display_service.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/grit/generated_resources.h"
@@ -23,7 +24,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
-#include "ui/message_center/message_center.h"
 #include "ui/message_center/notification.h"
 #include "ui/message_center/notification_types.h"
 #include "ui/message_center/notifier_id.h"
@@ -37,31 +37,12 @@
 constexpr base::TimeDelta kNotificationInterval =
     base::TimeDelta::FromMinutes(2);
 
-class LowDiskNotificationDelegate
-    : public message_center::NotificationDelegate {
- public:
-  LowDiskNotificationDelegate() {}
-
-  // message_center::NotificationDelegate
-  void ButtonClick(int button_index) override {
-    chrome::ShowSettingsSubPageForProfile(
-        ProfileManager::GetActiveUserProfile(), kStoragePage);
-  }
-
- private:
-  ~LowDiskNotificationDelegate() override {}
-
-  DISALLOW_COPY_AND_ASSIGN(LowDiskNotificationDelegate);
-};
-
 }  // namespace
 
 namespace chromeos {
 
 LowDiskNotification::LowDiskNotification()
-    : message_center_(g_browser_process->message_center()),
-      notification_interval_(kNotificationInterval),
-      weak_ptr_factory_(this) {
+    : notification_interval_(kNotificationInterval), weak_ptr_factory_(this) {
   DCHECK(DBusThreadManager::Get()->GetCryptohomeClient());
   DBusThreadManager::Get()->GetCryptohomeClient()->AddObserver(this);
 }
@@ -88,14 +69,16 @@
   if (severity != last_notification_severity_ ||
       (severity == HIGH &&
        now - last_notification_time_ > notification_interval_)) {
-    message_center_->AddNotification(CreateNotification(severity));
+    Profile* profile = ProfileManager::GetLastUsedProfile();
+    NotificationDisplayService::GetForProfile(profile)->Display(
+        NotificationCommon::TRANSIENT, *CreateNotification(severity, profile));
     last_notification_time_ = now;
     last_notification_severity_ = severity;
   }
 }
 
 std::unique_ptr<message_center::Notification>
-LowDiskNotification::CreateNotification(Severity severity) {
+LowDiskNotification::CreateNotification(Severity severity, Profile* profile) {
   base::string16 title;
   base::string16 message;
   gfx::Image icon;
@@ -128,12 +111,17 @@
       message_center::NotifierId::SYSTEM_COMPONENT,
       ash::system_notifier::kNotifierDisk);
 
+  auto on_click = base::Bind(
+      [](Profile* profile, int button_index) {
+        chrome::ShowSettingsSubPageForProfile(profile, kStoragePage);
+      },
+      profile);
   std::unique_ptr<message_center::Notification> notification =
       ash::system_notifier::CreateSystemNotification(
           message_center::NOTIFICATION_TYPE_SIMPLE, kLowDiskId, title, message,
           icon, base::string16(), GURL(), notifier_id, optional_fields,
-          new LowDiskNotificationDelegate(), kNotificationStorageFullIcon,
-          warning_level);
+          new message_center::HandleNotificationButtonClickDelegate(on_click),
+          kNotificationStorageFullIcon, warning_level);
 
   return notification;
 }
@@ -147,11 +135,6 @@
   return Severity::NONE;
 }
 
-void LowDiskNotification::SetMessageCenterForTest(
-    message_center::MessageCenter* message_center) {
-  message_center_ = message_center;
-}
-
 void LowDiskNotification::SetNotificationIntervalForTest(
     base::TimeDelta notification_interval) {
   notification_interval_ = notification_interval;
diff --git a/chrome/browser/chromeos/ui/low_disk_notification.h b/chrome/browser/chromeos/ui/low_disk_notification.h
index 2e4a663..e8bc265 100644
--- a/chrome/browser/chromeos/ui/low_disk_notification.h
+++ b/chrome/browser/chromeos/ui/low_disk_notification.h
@@ -15,9 +15,10 @@
 #include "base/time/time.h"
 #include "chromeos/dbus/cryptohome_client.h"
 
+class Profile;
+
 namespace message_center {
 class Notification;
-class MessageCenter;
 }
 
 namespace chromeos {
@@ -50,22 +51,19 @@
   // Creates a notification for the specified severity.  If the severity does
   // not match a known value MEDIUM is used by default.
   std::unique_ptr<message_center::Notification> CreateNotification(
-      Severity severity);
+      Severity severity,
+      Profile* profile);
 
   // Gets the severity of the low disk status based on the amount of free space
   // left on the disk.
   Severity GetSeverity(uint64_t free_disk_bytes);
 
-  // Sets the MessageCenter instance to use.  Should only be used in tests.
-  void SetMessageCenterForTest(message_center::MessageCenter* message_center);
-
   // Sets the minimum time to wait between notifications of the same severity.
   // Should only be used in tests.
   void SetNotificationIntervalForTest(base::TimeDelta interval);
 
   base::Time last_notification_time_;
   Severity last_notification_severity_ = NONE;
-  message_center::MessageCenter* message_center_;
   base::TimeDelta notification_interval_;
   base::ThreadChecker thread_checker_;
   base::WeakPtrFactory<LowDiskNotification> weak_ptr_factory_;
diff --git a/chrome/browser/chromeos/ui/low_disk_notification_unittest.cc b/chrome/browser/chromeos/ui/low_disk_notification_unittest.cc
index ab009f4..ddb8e23 100644
--- a/chrome/browser/chromeos/ui/low_disk_notification_unittest.cc
+++ b/chrome/browser/chromeos/ui/low_disk_notification_unittest.cc
@@ -8,17 +8,17 @@
 
 #include <utility>
 
-#include "base/test/scoped_task_environment.h"
-#include "base/threading/platform_thread.h"
 #include "base/time/time.h"
+#include "chrome/browser/notifications/notification_display_service_tester.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/grit/generated_resources.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/testing_profile_manager.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/fake_cryptohome_client.h"
-#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/message_center/fake_message_center.h"
-#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
 
 namespace {
 
@@ -30,39 +30,44 @@
 
 namespace chromeos {
 
-class LowDiskNotificationTest : public testing::Test,
-                                public message_center::FakeMessageCenter {
+class LowDiskNotificationTest : public BrowserWithTestWindowTest {
  public:
   LowDiskNotificationTest() {}
+  ~LowDiskNotificationTest() override {}
 
   void SetUp() override {
     DBusThreadManager::GetSetterForTesting()->SetCryptohomeClient(
         std::unique_ptr<CryptohomeClient>(new FakeCryptohomeClient));
-    message_center::MessageCenter::Initialize();
+
+    BrowserWithTestWindowTest::SetUp();
+
+    tester_ = std::make_unique<NotificationDisplayServiceTester>(
+        profile_manager()->profile_manager()->GetLastUsedProfile());
+    tester_->SetNotificationAddedClosure(base::Bind(
+        &LowDiskNotificationTest::OnNotificationAdded, base::Unretained(this)));
     low_disk_notification_.reset(new LowDiskNotification());
-    low_disk_notification_->SetMessageCenterForTest(this);
-    low_disk_notification_->SetNotificationIntervalForTest(
-        base::TimeDelta::FromMilliseconds(10));
     notification_count_ = 0;
   }
 
   void TearDown() override {
     low_disk_notification_.reset();
-    last_notification_.reset();
-    message_center::MessageCenter::Shutdown();
-    DBusThreadManager::Shutdown();
+    BrowserWithTestWindowTest::TearDown();
   }
 
-  void AddNotification(
-      std::unique_ptr<message_center::Notification> notification) override {
-    last_notification_ = std::move(notification);
-    notification_count_++;
+  base::Optional<message_center::Notification> GetNotification() {
+    return tester_->GetNotification("low_disk");
   }
 
+  void SetNotificationThrottlingInterval(int ms) {
+    low_disk_notification_->SetNotificationIntervalForTest(
+        base::TimeDelta::FromMilliseconds(ms));
+  }
+
+  void OnNotificationAdded() { notification_count_++; }
+
  protected:
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  std::unique_ptr<NotificationDisplayServiceTester> tester_;
   std::unique_ptr<LowDiskNotification> low_disk_notification_;
-  std::unique_ptr<message_center::Notification> last_notification_;
   int notification_count_;
 };
 
@@ -70,8 +75,9 @@
   base::string16 expected_title =
       l10n_util::GetStringUTF16(IDS_LOW_DISK_NOTIFICATION_TITLE);
   low_disk_notification_->LowDiskSpace(kMediumNotification);
-  EXPECT_NE(nullptr, last_notification_);
-  EXPECT_EQ(expected_title, last_notification_->title());
+  auto notification = GetNotification();
+  ASSERT_TRUE(notification);
+  EXPECT_EQ(expected_title, notification->title());
   EXPECT_EQ(1, notification_count_);
 }
 
@@ -80,28 +86,29 @@
       l10n_util::GetStringUTF16(IDS_CRITICALLY_LOW_DISK_NOTIFICATION_TITLE);
   low_disk_notification_->LowDiskSpace(kMediumNotification);
   low_disk_notification_->LowDiskSpace(kHighNotification);
-  EXPECT_NE(nullptr, last_notification_);
-  EXPECT_EQ(expected_title, last_notification_->title());
+  auto notification = GetNotification();
+  ASSERT_TRUE(notification);
+  EXPECT_EQ(expected_title, notification->title());
   EXPECT_EQ(2, notification_count_);
 }
 
 TEST_F(LowDiskNotificationTest, NotificationsAreThrottled) {
+  SetNotificationThrottlingInterval(10000000);
   low_disk_notification_->LowDiskSpace(kHighNotification);
-  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(5));
   low_disk_notification_->LowDiskSpace(kHighNotification);
   EXPECT_EQ(1, notification_count_);
 }
 
 TEST_F(LowDiskNotificationTest, HighNotificationsAreShownAfterThrottling) {
+  SetNotificationThrottlingInterval(-1);
   low_disk_notification_->LowDiskSpace(kHighNotification);
-  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(15));
   low_disk_notification_->LowDiskSpace(kHighNotification);
   EXPECT_EQ(2, notification_count_);
 }
 
 TEST_F(LowDiskNotificationTest, MediumNotificationsAreNotShownAfterThrottling) {
+  SetNotificationThrottlingInterval(-1);
   low_disk_notification_->LowDiskSpace(kMediumNotification);
-  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(15));
   low_disk_notification_->LowDiskSpace(kMediumNotification);
   EXPECT_EQ(1, notification_count_);
 }
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8d9ef79d..04b2a93 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2554,9 +2554,9 @@
 const char kMultideviceDescription[] =
     "Enables UI for controlling multidevice features.";
 
-const char kNativeSambaName[] = "Native Samba Client";
-const char kNativeSambaDescription[] =
-    "If enabled, allows connections to a Samba share via Files app";
+const char kNativeSmbName[] = "Native Smb Client";
+const char kNativeSmbDescription[] =
+    "If enabled, allows connections to an smb filesystem via Files app";
 
 const char kNetworkPortalNotificationName[] =
     "Notifications about captive portals";
@@ -2718,6 +2718,10 @@
 const char kVrBrowsingDescription[] =
     "Browsing within a VR headset if available for this device.";
 
+const char kVrBrowserKeyboardName[] = "Chrome VR virtual keyboard.";
+const char kVrBrowserKeyboardDescription[] =
+    "Enable a virtual keyboard for Chrome VR.";
+
 const char kVrBrowsingExperimentalFeaturesName[] =
     "VR browsing experimental features";
 const char kVrBrowsingExperimentalFeaturesDescription[] =
@@ -2744,6 +2748,12 @@
 
 #endif  // OS_ANDROID
 
+#if BUILDFLAG(ENABLE_OPENVR)
+const char kOpenVRName[] = "OpenVR hardware support";
+const char kOpenVRDescription[] =
+    "If enabled, Chrome will use OpenVR devices for VR.";
+#endif  // ENABLE_OPENVR
+
 #endif  // ENABLE_VR
 
 #if BUILDFLAG(ENABLE_NACL)
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1850a7b0..71e93370 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1583,8 +1583,8 @@
 extern const char kMultideviceName[];
 extern const char kMultideviceDescription[];
 
-extern const char kNativeSambaName[];
-extern const char kNativeSambaDescription[];
+extern const char kNativeSmbName[];
+extern const char kNativeSmbDescription[];
 
 extern const char kNetworkPortalNotificationName[];
 extern const char kNetworkPortalNotificationDescription[];
@@ -1679,6 +1679,9 @@
 extern const char kVrBrowsingName[];
 extern const char kVrBrowsingDescription[];
 
+extern const char kVrBrowserKeyboardName[];
+extern const char kVrBrowserKeyboardDescription[];
+
 extern const char kVrBrowsingExperimentalFeaturesName[];
 extern const char kVrBrowsingExperimentalFeaturesDescription[];
 
@@ -1696,6 +1699,11 @@
 
 #endif  // OS_ANDROID
 
+#if BUILDFLAG(ENABLE_OPENVR)
+extern const char kOpenVRName[];
+extern const char kOpenVRDescription[];
+#endif  // ENABLE_OPENVR
+
 #endif  // ENABLE_VR
 
 #if BUILDFLAG(ENABLE_NACL)
diff --git a/chrome/browser/media/router/discovery/media_sink_discovery_metrics.cc b/chrome/browser/media/router/discovery/media_sink_discovery_metrics.cc
index 0401a8c..fdf8e36 100644
--- a/chrome/browser/media/router/discovery/media_sink_discovery_metrics.cc
+++ b/chrome/browser/media/router/discovery/media_sink_discovery_metrics.cc
@@ -114,4 +114,16 @@
   }
 }
 
+void WiredDisplayDeviceCountMetrics::RecordDeviceCounts(
+    size_t available_device_count,
+    size_t known_device_count) {
+  // Record just the available device count, because for wired displays, all
+  // the known displays are available.
+  UMA_HISTOGRAM_COUNTS_100(kHistogramWiredDisplayDeviceCount,
+                           available_device_count);
+}
+
+const char WiredDisplayDeviceCountMetrics::kHistogramWiredDisplayDeviceCount[] =
+    "MediaRouter.WiredDisplay.AvailableDevicesCount";
+
 }  // namespace media_router
diff --git a/chrome/browser/media/router/discovery/media_sink_discovery_metrics.h b/chrome/browser/media/router/discovery/media_sink_discovery_metrics.h
index c89845a..05ed917 100644
--- a/chrome/browser/media/router/discovery/media_sink_discovery_metrics.h
+++ b/chrome/browser/media/router/discovery/media_sink_discovery_metrics.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/gtest_prod_util.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
 
@@ -111,6 +112,21 @@
                                               const base::TimeDelta& duration);
 };
 
+// Metrics for wired display (local screen) sink counts.
+class WiredDisplayDeviceCountMetrics : public DeviceCountMetrics {
+ protected:
+  // |known_device_count| is not recorded, since it should be the same as
+  // |available_device_count|.
+  void RecordDeviceCounts(size_t available_device_count,
+                          size_t known_device_count) override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(WiredDisplayDeviceCountMetricsTest,
+                           RecordWiredDisplaySinkCount);
+
+  static const char kHistogramWiredDisplayDeviceCount[];
+};
+
 }  // namespace media_router
 
 #endif  // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_DISCOVERY_METRICS_H_
diff --git a/chrome/browser/media/router/discovery/media_sink_discovery_metrics_unittest.cc b/chrome/browser/media/router/discovery/media_sink_discovery_metrics_unittest.cc
index 63e2d3c..83b4d34 100644
--- a/chrome/browser/media/router/discovery/media_sink_discovery_metrics_unittest.cc
+++ b/chrome/browser/media/router/discovery/media_sink_discovery_metrics_unittest.cc
@@ -148,4 +148,27 @@
                             delta.InMilliseconds(), 1);
 }
 
+TEST(WiredDisplayDeviceCountMetricsTest, RecordWiredDisplaySinkCount) {
+  base::HistogramTester tester;
+  WiredDisplayDeviceCountMetrics metrics;
+  tester.ExpectTotalCount(
+      WiredDisplayDeviceCountMetrics::kHistogramWiredDisplayDeviceCount, 0);
+
+  // Only the first argument, the available sink count, is recorded.
+  metrics.RecordDeviceCounts(1, 1);
+  metrics.RecordDeviceCounts(200, 200);
+  metrics.RecordDeviceCounts(0, 0);
+  metrics.RecordDeviceCounts(25, 30);
+  metrics.RecordDeviceCounts(1, 0);
+
+  tester.ExpectTotalCount(
+      WiredDisplayDeviceCountMetrics::kHistogramWiredDisplayDeviceCount, 5);
+  EXPECT_THAT(
+      tester.GetAllSamples(
+          WiredDisplayDeviceCountMetrics::kHistogramWiredDisplayDeviceCount),
+      ElementsAre(
+          Bucket(0, 1), Bucket(1, 2), Bucket(25, 1),
+          Bucket(100, 1)));  // Counts over 100 are all put in the 100 bucket.
+}
+
 }  // namespace media_router
diff --git a/chrome/browser/media/router/media_router_metrics.cc b/chrome/browser/media/router/media_router_metrics.cc
index 7d729b0..714082d 100644
--- a/chrome/browser/media/router/media_router_metrics.cc
+++ b/chrome/browser/media/router/media_router_metrics.cc
@@ -108,12 +108,14 @@
   UMA_HISTOGRAM_SPARSE_SLOWLY(kHistogramMediaRouterCastingSource, source);
 }
 
+// static
 void MediaRouterMetrics::RecordMediaRouterFileFormat(
     const media::container_names::MediaContainerName format) {
   UMA_HISTOGRAM_ENUMERATION(kHistogramMediaRouterFileFormat, format,
                             media::container_names::CONTAINER_MAX);
 }
 
+// static
 void MediaRouterMetrics::RecordMediaRouterFileSize(int64_t size) {
   UMA_HISTOGRAM_MEMORY_LARGE_MB(kHistogramMediaRouterFileSize, size);
 }
diff --git a/chrome/browser/media/router/mojo/wired_display_media_route_provider.cc b/chrome/browser/media/router/mojo/wired_display_media_route_provider.cc
index eb639460..8fad64c 100644
--- a/chrome/browser/media/router/mojo/wired_display_media_route_provider.cc
+++ b/chrome/browser/media/router/mojo/wired_display_media_route_provider.cc
@@ -263,8 +263,9 @@
     media_router_->OnRoutesUpdated(kProviderId, route_list, route_query, {});
 }
 
-void WiredDisplayMediaRouteProvider::NotifySinkObservers() const {
+void WiredDisplayMediaRouteProvider::NotifySinkObservers() {
   std::vector<MediaSinkInternal> sinks = GetSinks();
+  device_count_metrics_.RecordDeviceCountsIfNeeded(sinks.size(), sinks.size());
   for (const auto& sink_query : sink_queries_)
     media_router_->OnSinksReceived(kProviderId, sink_query, sinks, {});
 }
diff --git a/chrome/browser/media/router/mojo/wired_display_media_route_provider.h b/chrome/browser/media/router/mojo/wired_display_media_route_provider.h
index 6d3ef46..ca0c61d9 100644
--- a/chrome/browser/media/router/mojo/wired_display_media_route_provider.h
+++ b/chrome/browser/media/router/mojo/wired_display_media_route_provider.h
@@ -7,6 +7,7 @@
 
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
+#include "chrome/browser/media/router/discovery/media_sink_discovery_metrics.h"
 #include "chrome/common/media_router/mojo/media_router.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "ui/display/display_observer.h"
@@ -102,7 +103,7 @@
   void NotifyRouteObservers() const;
 
   // Sends the current list of sinks to each query in |sink_queries_|.
-  void NotifySinkObservers() const;
+  void NotifySinkObservers();
 
   // Returns a list of available sinks. A display can be a sink if it is
   // secondary and does not mirror a primary display.
@@ -125,6 +126,9 @@
 
   // A set of MediaSource IDs associated with queries for MediaSink updates.
   base::flat_set<std::string> sink_queries_;
+
+  // Used for recording UMA metrics for the number of sinks available.
+  WiredDisplayDeviceCountMetrics device_count_metrics_;
 };
 
 }  // namespace media_router
diff --git a/chrome/browser/ntp_snippets/download_suggestions_provider.cc b/chrome/browser/ntp_snippets/download_suggestions_provider.cc
index 9b1649e..3537bdb 100644
--- a/chrome/browser/ntp_snippets/download_suggestions_provider.cc
+++ b/chrome/browser/ntp_snippets/download_suggestions_provider.cc
@@ -722,7 +722,6 @@
     bool notify,
     const std::vector<offline_pages::OfflinePageItem>&
         all_download_offline_pages) {
-  DCHECK(!offline_page_model_ || offline_page_model_->is_loaded());
 
   std::set<std::string> old_dismissed_ids =
       ReadOfflinePageDismissedIDsFromPrefs();
diff --git a/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc b/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc
index 1e7acffb..f94ed87b 100644
--- a/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc
+++ b/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc
@@ -364,8 +364,6 @@
 }
 
 void OfflinePageEvaluationBridge::NotifyIfDoneLoading() const {
-  if (!offline_page_model_->is_loaded())
-    return;
   JNIEnv* env = base::android::AttachCurrentThread();
   ScopedJavaLocalRef<jobject> obj = weak_java_ref_.get(env);
   if (obj.is_null())
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.cc b/chrome/browser/offline_pages/android/offline_page_bridge.cc
index 941b8c83..51238b71 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.cc
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.cc
@@ -740,8 +740,6 @@
 }
 
 void OfflinePageBridge::NotifyIfDoneLoading() const {
-  if (!offline_page_model_->is_loaded())
-    return;
   JNIEnv* env = base::android::AttachCurrentThread();
   Java_OfflinePageBridge_offlinePageModelLoaded(env, java_ref_);
 }
diff --git a/chrome/browser/offline_pages/recent_tab_helper_unittest.cc b/chrome/browser/offline_pages/recent_tab_helper_unittest.cc
index c0216ef0..be8169f 100644
--- a/chrome/browser/offline_pages/recent_tab_helper_unittest.cc
+++ b/chrome/browser/offline_pages/recent_tab_helper_unittest.cc
@@ -293,14 +293,12 @@
   recent_tab_helper()->DocumentOnLoadCompletedInMainFrame();
   // Move the snapshot controller's time forward so it gets past timeouts.
   FastForwardSnapshotController();
-  EXPECT_TRUE(model()->is_loaded());
   EXPECT_EQ(0U, page_added_count());
   ASSERT_EQ(0U, GetAllPages().size());
 
   // Tab is hidden with a fully loaded page. A snapshot save should happen.
   recent_tab_helper()->WasHidden();
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   EXPECT_EQ(1U, page_added_count());
   ASSERT_EQ(1U, GetAllPages().size());
   EXPECT_EQ(kTestPageUrl, GetAllPages()[0].url);
@@ -314,7 +312,6 @@
   NavigateAndCommit(kTestPageUrl);
   recent_tab_helper()->WasHidden();
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   EXPECT_EQ(0U, page_added_count());
   ASSERT_EQ(0U, GetAllPages().size());
 
@@ -340,7 +337,6 @@
   recent_tab_helper()->ObserveAndDownloadCurrentPage(NewDownloadClientId(),
                                                      123L, "");
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   // No page should be captured.
   EXPECT_EQ(0U, page_added_count());
   ASSERT_EQ(0U, GetAllPages().size());
@@ -358,7 +354,6 @@
   FastForwardSnapshotController();
   recent_tab_helper()->WasHidden();
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   EXPECT_EQ(0U, page_added_count());
   ASSERT_EQ(0U, GetAllPages().size());
 
@@ -382,7 +377,6 @@
   FastForwardSnapshotController();
   recent_tab_helper()->WasHidden();
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   EXPECT_EQ(0U, page_added_count());
   ASSERT_EQ(0U, GetAllPages().size());
 
@@ -400,7 +394,6 @@
   // Upon the next hide a last_n snapshot should be saved.
   recent_tab_helper()->WasHidden();
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   EXPECT_EQ(2U, page_added_count());
   ASSERT_EQ(2U, GetAllPages().size());
 }
@@ -412,7 +405,6 @@
   // Set page loading state to the 1st snapshot-able stage. No capture so far.
   recent_tab_helper()->DocumentAvailableInMainFrame();
   FastForwardSnapshotController();
-  EXPECT_TRUE(model()->is_loaded());
   EXPECT_EQ(0U, page_added_count());
 
   // Tab is hidden and a snapshot should be saved.
@@ -642,7 +634,6 @@
   recent_tab_helper()->ObserveAndDownloadCurrentPage(NewDownloadClientId(),
                                                      123L, "");
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
 }
 
@@ -656,7 +647,6 @@
   FastForwardSnapshotController();
   recent_tab_helper()->WasHidden();
   RunUntilIdle();
-  EXPECT_TRUE(model()->is_loaded());
   // No page should be captured.
   ASSERT_EQ(0U, GetAllPages().size());
 
@@ -676,7 +666,6 @@
   const ClientId client_id = NewDownloadClientId();
   recent_tab_helper()->ObserveAndDownloadCurrentPage(client_id, 153L, "");
   FastForwardSnapshotController();
-  EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
 
   // Minimally load the page. First capture should occur.
@@ -707,7 +696,6 @@
   NavigateAndCommit(kTestPageUrl);
   recent_tab_helper()->DocumentAvailableInMainFrame();
   FastForwardSnapshotController();
-  EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
 
   const ClientId client_id = NewDownloadClientId();
@@ -732,7 +720,6 @@
   NavigateAndCommit(kTestPageUrl);
   recent_tab_helper()->DocumentOnLoadCompletedInMainFrame();
   FastForwardSnapshotController();
-  EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
 
   const ClientId client_id = NewDownloadClientId();
@@ -752,7 +739,6 @@
   NavigateAndCommit(kTestPageUrl);
   recent_tab_helper()->DocumentOnLoadCompletedInMainFrame();
   FastForwardSnapshotController();
-  EXPECT_TRUE(model()->is_loaded());
   ASSERT_EQ(0U, GetAllPages().size());
 
   const ClientId client_id = NewDownloadClientId();
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.css b/chrome/browser/resources/media_router/elements/route_controls/route_controls.css
index 5c3c658..0fcf48a 100644
--- a/chrome/browser/resources/media_router/elements/route_controls/route_controls.css
+++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.css
@@ -16,13 +16,6 @@
   right: 20px;
 }
 
-.ellipsis {
-  padding: 0 1%;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  width: 90%;
-}
-
 :host-context([dir='rtl']) #play-pause-volume-hangouts-controls {
   transform: scaleX(-1);
 }
@@ -48,7 +41,7 @@
 
 #route-description {
   margin: 15px 8px 3px 8px;
-  overflow: hidden;
+  width: 90%;
 }
 
 #route-time-controls {
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.html b/chrome/browser/resources/media_router/elements/route_controls/route_controls.html
index 64e37d0..f6184a8 100644
--- a/chrome/browser/resources/media_router/elements/route_controls/route_controls.html
+++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.html
@@ -8,9 +8,14 @@
   <link rel="import" type="css" href="route_controls.css">
   <template>
     <div id="media-controls">
+      <!--
+        TODO(crbug.com/786208): Remove the div below and always render the
+        description in the details element.  And, possibly combine details and
+        controls elements.
+      -->
       <div class="ellipsis" id="route-description"
-           title="[[displayedDescription_]]">
-        [[displayedDescription_]]
+           title="[[routeDescription_]]">
+        [[routeDescription_]]
       </div>
       <div class="ellipsis" id="route-title" title="[[routeStatus.title]]">
         [[routeStatus.title]]
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.js b/chrome/browser/resources/media_router/elements/route_controls/route_controls.js
index c11e97e..ce15e1e 100644
--- a/chrome/browser/resources/media_router/elements/route_controls/route_controls.js
+++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.js
@@ -21,11 +21,11 @@
     },
 
     /**
-     * The media description to display. Uses route description if none is
-     * provided by the route status object.
+     * The route description to display. Uses the media route description if
+     * none is provided by the media route status object.
      * @private {string}
      */
-    displayedDescription_: {
+    routeDescription_: {
       type: String,
       value: '',
     },
@@ -357,7 +357,7 @@
       this.displayedVolume_ = Math.round(newRouteStatus.volume * 100) / 100;
     }
     if (newRouteStatus.description !== '') {
-      this.displayedDescription_ = newRouteStatus.description;
+      this.routeDescription_ = newRouteStatus.description;
     }
     if (!this.initialLoadTime_) {
       this.initialLoadTime_ = Date.now();
@@ -386,8 +386,7 @@
       this.stopIncrementingCurrentTime_();
     }
     if (route && this.routeStatus.description === '') {
-      this.displayedDescription_ =
-          loadTimeData.getStringF('castingActivityStatus', route.description);
+      this.routeDescription_ = route.description;
     }
   },
 
diff --git a/chrome/browser/resources/media_router/elements/route_details/route_details.css b/chrome/browser/resources/media_router/elements/route_details/route_details.css
index 742647d..92f8b1a 100644
--- a/chrome/browser/resources/media_router/elements/route_details/route_details.css
+++ b/chrome/browser/resources/media_router/elements/route_details/route_details.css
@@ -17,12 +17,10 @@
   text-align: end;
 }
 
-#route-information {
+#route-description {
   -webkit-padding-end: var(--dialog-padding-end);
   -webkit-padding-start: 44px;
-  background-color: white;
   font-size: 1.2em;
   line-height: 1.5em;
   margin-top: 16px;
-  overflow: hidden;
 }
diff --git a/chrome/browser/resources/media_router/elements/route_details/route_details.html b/chrome/browser/resources/media_router/elements/route_details/route_details.html
index 6bc2093..8d91b1be 100644
--- a/chrome/browser/resources/media_router/elements/route_details/route_details.html
+++ b/chrome/browser/resources/media_router/elements/route_details/route_details.html
@@ -7,9 +7,9 @@
   <link rel="import" type="css" href="../../media_router_common.css">
   <link rel="import" type="css" href="route_details.css">
   <template>
-    <div id="route-information"
-        hidden$="[[!shouldShowRouteInfoOnly_(controllerType_)]]">
-      <span>[[activityStatus_]]</span>
+    <div class="ellipsis" id="route-description" title="[[routeDescription_]]"
+        hidden$="[[!shouldShowRouteDescription_(controllerType_)]]">
+      [[routeDescription_]]
     </div>
     <template is="dom-if" if="[[shouldAttemptLoadingExtensionView_(route)]]">
       <extension-view-wrapper id="extension-view-wrapper" route="[[route]]"
diff --git a/chrome/browser/resources/media_router/elements/route_details/route_details.js b/chrome/browser/resources/media_router/elements/route_details/route_details.js
index b260d81..8724cd2 100644
--- a/chrome/browser/resources/media_router/elements/route_details/route_details.js
+++ b/chrome/browser/resources/media_router/elements/route_details/route_details.js
@@ -9,10 +9,10 @@
 
   properties: {
     /**
-     * The text for the current casting activity status.
+     * Description of the current casting activity, e.g. "Casting YouTube".
      * @private {string|undefined}
      */
-    activityStatus_: {
+    routeDescription_: {
       type: String,
     },
 
@@ -191,15 +191,12 @@
   },
 
   /**
-   * Updates |activityStatus_| for the default view.
+   * Updates |routeDescription_| for the default view.
    *
    * @private
    */
-  updateActivityStatus_: function() {
-    this.activityStatus_ = this.route ?
-        loadTimeData.getStringF(
-            'castingActivityStatus', this.route.description) :
-        '';
+  updateRouteDescription_: function() {
+    this.routeDescription_ = this.route ? this.route.description : '';
   },
 
   /**
@@ -231,7 +228,7 @@
    */
   onRouteChange_: function(newRoute) {
     if (this.controllerType_ !== media_router.ControllerType.WEBUI) {
-      this.updateActivityStatus_();
+      this.updateRouteDescription_();
     }
   },
 
@@ -260,7 +257,7 @@
    *     the extensionview or the WebUI route controller.
    * @private
    */
-  shouldShowRouteInfoOnly_: function(controllerType) {
+  shouldShowRouteDescription_: function(controllerType) {
     return controllerType === media_router.ControllerType.NONE;
   },
 
diff --git a/chrome/browser/resources/media_router/media_router_common.css b/chrome/browser/resources/media_router/media_router_common.css
index dd22f1cc..d944684 100644
--- a/chrome/browser/resources/media_router/media_router_common.css
+++ b/chrome/browser/resources/media_router/media_router_common.css
@@ -19,3 +19,14 @@
 [hidden] {
   display: none !important;
 }
+
+.ellipsis {
+  padding: 0 1%;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+#route-description {
+  background-color: white;
+  overflow: hidden;
+}
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
index 03ebf64..d5a0022 100644
--- a/chrome/browser/resources/settings/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
@@ -120,12 +120,10 @@
     proxyExpanded_: Boolean,
   },
 
-  /**
-   * Listener function for chrome.networkingPrivate.onNetworksChanged event.
-   * @type {?function(!Array<string>)}
-   * @private
-   */
-  networksChangedListener_: null,
+  listeners: {
+    'network-list-changed': 'checkNetworkExists_',
+    'networks-changed': 'updateNetworkDetails_',
+  },
 
   /** @private {boolean} */
   didSetFocus_: false,
@@ -133,6 +131,8 @@
   /**
    * Set to true to once the initial properties have been received. This
    * prevents setProperties from being called when setting default properties.
+   * This will also be set to false if the network no longer exists in the
+   * list of networks (e.g. it goes out of range).
    * @private {boolean}
    */
   networkPropertiesReceived_: false,
@@ -152,19 +152,9 @@
    * @protected
    */
   currentRouteChanged: function(route, oldRoute) {
-    if (route != settings.routes.NETWORK_DETAIL) {
-      if (this.networksChangedListener_) {
-        this.networkingPrivate.onNetworksChanged.removeListener(
-            this.networksChangedListener_);
-        this.networksChangedListener_ = null;
-      }
+    if (route != settings.routes.NETWORK_DETAIL)
       return;
-    }
-    if (!this.networksChangedListener_) {
-      this.networksChangedListener_ = this.onNetworksChangedEvent_.bind(this);
-      this.networkingPrivate.onNetworksChanged.addListener(
-          this.networksChangedListener_);
-    }
+
     var queryParams = settings.getQueryParameters();
     this.guid = queryParams.get('guid') || '';
     if (!this.guid) {
@@ -263,11 +253,20 @@
   },
 
   /**
-   * networkingPrivate.onNetworksChanged event callback.
-   * @param {!Array<string>} networkIds The list of changed network GUIDs.
+   * @param {{detail: !Array<string>}} event
    * @private
    */
-  onNetworksChangedEvent_: function(networkIds) {
+  checkNetworkExists_: function(event) {
+    var networkIds = event.detail;
+    this.networkPropertiesReceived_ = networkIds.indexOf(this.guid) != -1;
+  },
+
+  /**
+   * @param {{detail: !Array<string>}} event
+   * @private
+   */
+  updateNetworkDetails_: function(event) {
+    var networkIds = event.detail;
     if (networkIds.indexOf(this.guid) != -1)
       this.getNetworkDetails_();
   },
@@ -541,6 +540,8 @@
   enableConnect_: function(networkProperties, defaultNetwork, globalPolicy) {
     if (!this.showConnect_(networkProperties, globalPolicy))
       return false;
+    if (!this.networkPropertiesReceived_)
+      return false;
     if ((networkProperties.Type == CrOnc.Type.CELLULAR) &&
         (CrOnc.isSimLocked(networkProperties) ||
          this.get('Cellular.Scanning', networkProperties))) {
diff --git a/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js b/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js
index 5ec39d7..b1dc5e6 100644
--- a/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_known_networks_page.js
@@ -54,44 +54,17 @@
     enableForget_: Boolean,
   },
 
+  listeners: {'network-list-changed': 'refreshNetworks_'},
+
   /** @private {string} */
   selectedGuid_: '',
 
-  /**
-   * Listener function for chrome.networkingPrivate.onNetworksChanged event.
-   * @type {function(!Array<string>)}
-   * @private
-   */
-  networksChangedListener_: function() {},
-
-  /** @override */
-  attached: function() {
-    this.networksChangedListener_ = this.onNetworksChangedEvent_.bind(this);
-    this.networkingPrivate.onNetworksChanged.addListener(
-        this.networksChangedListener_);
-  },
-
-  /** @override */
-  detached: function() {
-    this.networkingPrivate.onNetworksChanged.removeListener(
-        this.networksChangedListener_);
-  },
-
   /** @private */
   networkTypeChanged_: function() {
     this.refreshNetworks_();
   },
 
   /**
-   * networkingPrivate.onNetworksChanged event callback.
-   * @param {!Array<string>} networkIds The list of changed network GUIDs.
-   * @private
-   */
-  onNetworksChangedEvent_: function(networkIds) {
-    this.refreshNetworks_();
-  },
-
-  /**
    * Requests the list of network states from Chrome. Updates networkStates
    * once the results are returned from Chrome.
    * @private
diff --git a/chrome/browser/resources/settings/internet_page/internet_page.html b/chrome/browser/resources/settings/internet_page/internet_page.html
index ba794ee..fdee5441 100644
--- a/chrome/browser/resources/settings/internet_page/internet_page.html
+++ b/chrome/browser/resources/settings/internet_page/internet_page.html
@@ -84,7 +84,7 @@
         </template>
       </neon-animatable>
 
-      <template is="dom-if" route-path="/networkDetail" no-search>
+      <template is="dom-if" route-path="/networkDetail" no-search restamp>
         <settings-subpage page-title="$i18n{internetDetailPageTitle}">
           <settings-internet-detail-page prefs="{{prefs}}"
               default-network="[[defaultNetwork]]"
@@ -94,7 +94,7 @@
         </settings-subpage>
       </template>
 
-      <template is="dom-if" route-path="/knownNetworks" no-search>
+      <template is="dom-if" route-path="/knownNetworks" no-search restamp>
         <settings-subpage page-title="$i18n{internetKnownNetworksPageTitle}">
           <settings-internet-known-networks-page
               network-type="[[knownNetworksType_]]"
@@ -103,7 +103,7 @@
         </settings-subpage>
       </template>
 
-      <template is="dom-if" route-path="/networks" no-search>
+      <template is="dom-if" route-path="/networks" no-search restamp>
         <settings-subpage page-title="[[getNetworksPageTitle_(subpageType_)]]"
             show-spinner="[[showSpinner_]]">
           <settings-internet-subpage
diff --git a/chrome/browser/resources/settings/internet_page/internet_page.js b/chrome/browser/resources/settings/internet_page/internet_page.js
index 7ea0e3c9..ad0c6fb 100644
--- a/chrome/browser/resources/settings/internet_page/internet_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_page.js
@@ -112,6 +112,13 @@
   },
 
   // chrome.networkingPrivate listeners
+  /** @private {?function(!Array<string>)} */
+  networkListChangedListener_: null,
+
+  /** @private {?function(!Array<string>)} */
+  networksChangedListener_: null,
+
+  // chrome.management listeners
   /** @private {Function} */
   onExtensionAddedListener_: null,
 
@@ -123,6 +130,16 @@
 
   /** @override */
   attached: function() {
+    this.networkListChangedListener_ = this.networkListChangedListener_ ||
+        this.onNetworkListChanged_.bind(this);
+    this.networkingPrivate.onNetworkListChanged.addListener(
+        this.networkListChangedListener_);
+
+    this.networksChangedListener_ =
+        this.networksChangedListener_ || this.onNetworksChanged_.bind(this);
+    this.networkingPrivate.onNetworksChanged.addListener(
+        this.networksChangedListener_);
+
     this.onExtensionAddedListener_ =
         this.onExtensionAddedListener_ || this.onExtensionAdded_.bind(this);
     chrome.management.onInstalled.addListener(this.onExtensionAddedListener_);
@@ -146,6 +163,11 @@
 
   /** @override */
   detached: function() {
+    this.networkingPrivate.onNetworkListChanged.removeListener(
+        assert(this.networkListChangedListener_));
+    this.networkingPrivate.onNetworksChanged.removeListener(
+        assert(this.networksChangedListener_));
+
     chrome.management.onInstalled.removeListener(
         assert(this.onExtensionAddedListener_));
     chrome.management.onEnabled.removeListener(
@@ -393,6 +415,45 @@
   },
 
   /**
+   * This event is triggered when the list of networks changes.
+   * |networkIds| contains the ids for all visible or configured networks.
+   * networkingPrivate.onNetworkListChanged event callback.
+   * @param {!Array<string>} networkIds
+   * @private
+   */
+  onNetworkListChanged_: function(networkIds) {
+    var event = new CustomEvent('network-list-changed', {detail: networkIds});
+    this.maybeDispatchEvent_('network-summary', event);
+    this.maybeDispatchEvent_('settings-internet-detail-page', event);
+    this.maybeDispatchEvent_('settings-internet-known-networks-page', event);
+    this.maybeDispatchEvent_('settings-internet-subpage', event);
+  },
+
+  /**
+   * This event is triggered when interesting properties of a network change.
+   * |networkIds| contains the ids for networks whose properties have changed.
+   * networkingPrivate.onNetworksChanged event callback.
+   * @param {!Array<string>} networkIds
+   * @private
+   */
+  onNetworksChanged_: function(networkIds) {
+    var event = new CustomEvent('networks-changed', {detail: networkIds});
+    this.maybeDispatchEvent_('network-summary', event);
+    this.maybeDispatchEvent_('settings-internet-detail-page', event);
+  },
+
+  /**
+   * @param {!Event} event
+   * @private
+   */
+  maybeDispatchEvent_: function(identifier, event) {
+    var element = this.$$(identifier);
+    if (!element)
+      return;
+    element.dispatchEvent(event);
+  },
+
+  /**
    * chrome.management.onInstalled or onEnabled event.
    * @param {!chrome.management.ExtensionInfo} extension
    * @private
diff --git a/chrome/browser/resources/settings/internet_page/internet_subpage.js b/chrome/browser/resources/settings/internet_page/internet_subpage.js
index 0434b0e..af364579 100644
--- a/chrome/browser/resources/settings/internet_page/internet_subpage.js
+++ b/chrome/browser/resources/settings/internet_page/internet_subpage.js
@@ -84,31 +84,16 @@
     },
   },
 
+  listeners: {'network-list-changed': 'getNetworkStateList_'},
+
   observers: ['deviceStateChanged_(networkingPrivate, deviceState)'],
 
   /** @private {number|null} */
   scanIntervalId_: null,
 
-  /**
-   * Listener function for chrome.networkingPrivate.onNetworkListChanged event.
-   * @type {?function(!Array<string>)}
-   * @private
-   */
-  networkListChangedListener_: null,
-
-  /** override */
-  attached: function() {
-    this.networkListChangedListener_ = this.networkListChangedListener_ ||
-        this.onNetworkListChangedEvent_.bind(this);
-    this.networkingPrivate.onNetworkListChanged.addListener(
-        this.networkListChangedListener_);
-  },
-
   /** override */
   detached: function() {
     this.stopScanning_();
-    this.networkingPrivate.onNetworkListChanged.removeListener(
-        assert(this.networkListChangedListener_));
   },
 
   /**
@@ -195,14 +180,6 @@
     this.scanIntervalId_ = null;
   },
 
-  /**
-   * networkingPrivate.onNetworkListChanged event callback.
-   * @private
-   */
-  onNetworkListChangedEvent_: function() {
-    this.getNetworkStateList_();
-  },
-
   /** @private */
   getNetworkStateList_: function() {
     if (!this.deviceState)
diff --git a/chrome/browser/resources/settings/internet_page/network_summary.js b/chrome/browser/resources/settings/internet_page/network_summary.js
index a1c6dea..a755721b 100644
--- a/chrome/browser/resources/settings/internet_page/network_summary.js
+++ b/chrome/browser/resources/settings/internet_page/network_summary.js
@@ -91,11 +91,10 @@
     },
   },
 
-  /**
-   * Listener function for chrome.networkingPrivate.onNetworkListChanged event.
-   * @private {?function(!Array<string>)}
-   */
-  networkListChangedListener_: null,
+  listeners: {
+    'network-list-changed': 'getNetworkLists_',
+    'networks-changed': 'updateActiveNetworks_',
+  },
 
   /**
    * Listener function for chrome.networkingPrivate.onDeviceStateListChanged
@@ -105,12 +104,6 @@
   deviceStateListChangedListener_: null,
 
   /**
-   * Listener function for chrome.networkingPrivate.onNetworksChanged event.
-   * @private {?function(!Array<string>)}
-   */
-  networksChangedListener_: null,
-
-  /**
    * Set of GUIDs identifying active networks, one for each type.
    * @private {?Set<string>}
    */
@@ -120,41 +113,17 @@
   attached: function() {
     this.getNetworkLists_();
 
-    this.networkListChangedListener_ = this.networkListChangedListener_ ||
-        this.onNetworkListChangedEvent_.bind(this);
-    this.networkingPrivate.onNetworkListChanged.addListener(
-        this.networkListChangedListener_);
-
     this.deviceStateListChangedListener_ =
         this.deviceStateListChangedListener_ ||
         this.onDeviceStateListChangedEvent_.bind(this);
     this.networkingPrivate.onDeviceStateListChanged.addListener(
         this.deviceStateListChangedListener_);
-
-    this.networksChangedListener_ = this.networksChangedListener_ ||
-        this.onNetworksChangedEvent_.bind(this);
-    this.networkingPrivate.onNetworksChanged.addListener(
-        this.networksChangedListener_);
   },
 
   /** @override */
   detached: function() {
-    this.networkingPrivate.onNetworkListChanged.removeListener(
-        assert(this.networkListChangedListener_));
-
     this.networkingPrivate.onDeviceStateListChanged.removeListener(
         assert(this.deviceStateListChangedListener_));
-
-    this.networkingPrivate.onNetworksChanged.removeListener(
-        assert(this.networksChangedListener_));
-  },
-
-  /**
-   * networkingPrivate.onNetworkListChanged event callback.
-   * @private
-   */
-  onNetworkListChangedEvent_: function() {
-    this.getNetworkLists_();
   },
 
   /**
@@ -166,13 +135,13 @@
   },
 
   /**
-   * networkingPrivate.onNetworksChanged event callback.
-   * @param {!Array<string>} networkIds The list of changed network GUIDs.
+   * @param {{detail: !Array<string>}} event
    * @private
    */
-  onNetworksChangedEvent_: function(networkIds) {
+  updateActiveNetworks_: function(event) {
     if (!this.activeNetworkIds_)
       return;  // Initial list of networks not received yet.
+    var networkIds = event.detail;
     networkIds.forEach(function(id) {
       if (this.activeNetworkIds_.has(id)) {
         this.networkingPrivate.getState(
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html
index f6e65e7..4da90c01 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
+<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
 <link rel="import" href="chrome://resources/html/action_link.html">
 <link rel="import" href="chrome://resources/html/action_link_css.html">
 <link rel="import" href="chrome://resources/html/assert.html">
@@ -57,6 +58,11 @@
         text-overflow: ellipsis;
         white-space: nowrap;
       }
+
+      cr-policy-indicator {
+        padding-right: 20px;
+        width: 20px;
+      }
     </style>
     <settings-toggle-button id="autofillToggle"
         class="first primary-toggle"
@@ -133,51 +139,62 @@
       <h2 class="start">$i18n{creditCards}</h2>
       <paper-button id="addCreditCard"
           class="secondary-button header-aligned-button"
-          on-tap="onAddCreditCardTap_">
+          on-tap="onAddCreditCardTap_"
+          hidden$="[[isDisabled_(prefs.autofill.credit_card_enabled)]]">
         $i18n{add}
       </paper-button>
     </div>
     <div class="list-frame">
-      <div id="creditCardsHeading" class="list-item column-header"
-          hidden$="[[!hasSome_(creditCards)]]">
-        <div class="type-column">$i18n{creditCardType}</div>
-        <div class="expiration-column">$i18n{creditCardExpiration}</div>
-      </div>
-      <div id="creditCardList" class="vertical-list list-with-header">
-        <template is="dom-repeat" items="[[creditCards]]">
-          <div class="list-item">
-            <div class="type-column">
-              <span id="creditCardLabel">[[item.metadata.summaryLabel]]</span>
-              <span class="payments-label" hidden$="[[item.metadata.isLocal]]">
-                <span hidden$="[[item.metadata.isCached]]">
-                  $i18n{googlePayments}
+      <template is="dom-if"
+          if="[[!isDisabled_(prefs.autofill.credit_card_enabled)]]">
+        <div id="creditCardsHeading" class="list-item column-header"
+            hidden$="[[!hasSome_(creditCards)]]">
+          <div class="type-column">$i18n{creditCardType}</div>
+          <div class="expiration-column">$i18n{creditCardExpiration}</div>
+        </div>
+        <div id="creditCardList" class="vertical-list list-with-header">
+          <template is="dom-repeat" items="[[creditCards]]">
+            <div class="list-item">
+              <div class="type-column">
+                <span id="creditCardLabel">[[item.metadata.summaryLabel]]</span>
+                <span class="payments-label"
+                    hidden$="[[item.metadata.isLocal]]">
+                  <span hidden$="[[item.metadata.isCached]]">
+                    $i18n{googlePayments}
+                  </span>
+                  <span hidden$="[[!item.metadata.isCached]]">
+                    $i18n{googlePaymentsCached}
+                  </span>
                 </span>
-                <span hidden$="[[!item.metadata.isCached]]">
-                  $i18n{googlePaymentsCached}
-                </span>
-              </span>
+              </div>
+              <div class="expiration-column">
+                <div id="creditCardExpiration"
+                    class="expiration-date">[[expiration_(item)]]</div>
+                <template is="dom-if" if="[[showDots_(item.metadata)]]">
+                  <button is="paper-icon-button-light" id="creditCardMenu"
+                      class="icon-more-vert" title="$i18n{moreActions}"
+                      on-tap="onCreditCardMenuTap_">
+                  </button>
+                </template>
+                <template is="dom-if" if="[[!showDots_(item.metadata)]]">
+                  <button is="paper-icon-button-light" id="remoteCreditCardLink"
+                      class="icon-external"
+                      on-tap="onRemoteEditCreditCardTap_" actionable></button>
+                </template>
+              </div>
             </div>
-            <div class="expiration-column">
-              <div id="creditCardExpiration"
-                  class="expiration-date">[[expiration_(item)]]</div>
-              <template is="dom-if" if="[[showDots_(item.metadata)]]">
-                <button is="paper-icon-button-light" id="creditCardMenu"
-                    class="icon-more-vert" title="$i18n{moreActions}"
-                    on-tap="onCreditCardMenuTap_">
-                </button>
-              </template>
-              <template is="dom-if" if="[[!showDots_(item.metadata)]]">
-                <button is="paper-icon-button-light" id="remoteCreditCardLink"
-                    class="icon-external"
-                    on-tap="onRemoteEditCreditCardTap_" actionable></button>
-              </template>
-            </div>
-          </div>
-        </template>
-      </div>
-      <div id="noCreditCardsLabel" class="list-item"
-          hidden$="[[hasSome_(creditCards)]]">
-        $i18n{noCreditCardsFound}
+          </template>
+        </div>
+        <div id="noCreditCardsLabel" class="list-item"
+            hidden$="[[hasSome_(creditCards)]]">
+          $i18n{noCreditCardsFound}
+        </div>
+      </template>
+      <div id="CreditCardsDisabledLabel" class="list-item"
+          hidden$="[[!isDisabled_(prefs.autofill.credit_card_enabled)]]">
+        <cr-policy-indicator indicator-type="userPolicy"
+            icon-aria-label="$i18n{noCreditCardsPolicy}"></cr-policy-indicator>
+        $i18n{noCreditCardsPolicy}
       </div>
     </div>
     <dialog is="cr-action-menu" id="creditCardSharedMenu">
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js
index ffad7d4..7c8197d1 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js
@@ -435,6 +435,17 @@
   },
 
   /**
+   * Returns true if the pref has been explicitly disabled.
+   * @param {Object} pref
+   * @return {boolean}
+   * @private
+   */
+  isDisabled_: function(pref) {
+    return !!(pref.value === false);
+  },
+
+
+  /**
    * Listens for the save-address event, and calls the private API.
    * @param {!Event} event
    * @private
diff --git a/chrome/browser/resources/settings/site_settings/compiled_resources2.gyp b/chrome/browser/resources/settings/site_settings/compiled_resources2.gyp
index fbc1a37..952595b 100644
--- a/chrome/browser/resources/settings/site_settings/compiled_resources2.gyp
+++ b/chrome/browser/resources/settings/site_settings/compiled_resources2.gyp
@@ -88,6 +88,7 @@
         '<(DEPTH)/ui/webui/resources/cr_elements/cr_action_menu/compiled_resources2.gyp:cr_action_menu',
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:web_ui_listener_behavior',
         'site_settings_behavior',
+        '../android_apps_page/compiled_resources2.gyp:android_apps_browser_proxy',
       ],
       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
     },
diff --git a/chrome/browser/resources/settings/site_settings/protocol_handlers.js b/chrome/browser/resources/settings/site_settings/protocol_handlers.js
index cc6075b..7da371a9a 100644
--- a/chrome/browser/resources/settings/site_settings/protocol_handlers.js
+++ b/chrome/browser/resources/settings/site_settings/protocol_handlers.js
@@ -18,18 +18,6 @@
 };
 
 /**
- * Type definition of AndroidAppsInfo entry. |playStoreEnabled| indicates that
- * Play Store is enabled. |settingsAppAvailable| indicates that Android settings
- * app is registered in the system.
- * @typedef {{
- *   playStoreEnabled: boolean,
- *   settingsAppAvailable: boolean,
- * }}
- * @see chrome/browser/ui/webui/settings/chromeos/android_apps_handler.cc
- */
-var AndroidAppsInfo;
-
-/**
  * @typedef {{host: string,
  *            protocol: string,
  *            spec: string}}
diff --git a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
index 511e35e2..cf00513 100644
--- a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
@@ -287,7 +287,7 @@
   UMA_HISTOGRAM_SPARSE_SLOWLY("SBClientDownload.PPAPIDownloadRequest.Result",
                               static_cast<int>(response));
   UMA_HISTOGRAM_TIMES("SBClientDownload.PPAPIDownloadRequest.RequestDuration",
-                      start_time_ - base::TimeTicks::Now());
+                      base::TimeTicks::Now() - start_time_);
   if (!callback_.is_null())
     base::ResetAndReturn(&callback_).Run(response);
   fetcher_.reset();
diff --git a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
index 00c8205..fc63b07 100644
--- a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
@@ -53,7 +53,7 @@
 
   server->InjectEntity(
       syncer::PersistentUniqueClientEntity::CreateFromEntitySpecifics(
-          kDefaultCardID, specifics));
+          kDefaultCardID, specifics, 12345, 12345));
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 3066185..5116496 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1274,6 +1274,8 @@
       "ash/volume_controller.h",
       "ash/vpn_list_forwarder.cc",
       "ash/vpn_list_forwarder.h",
+      "ash/wallpaper_controller_client.cc",
+      "ash/wallpaper_controller_client.h",
       "browser_commands_chromeos.cc",
       "browser_commands_chromeos.h",
       "extensions/extension_installed_notification.cc",
@@ -1704,6 +1706,7 @@
       "cocoa/chrome_command_dispatcher_delegate.mm",
       "cocoa/chrome_style.cc",
       "cocoa/chrome_style.h",
+      "cocoa/color_chooser_mac.h",
       "cocoa/color_chooser_mac.mm",
       "cocoa/confirm_quit.h",
       "cocoa/confirm_quit_panel_controller.h",
@@ -3724,6 +3727,8 @@
     sources += [
       "ash/fake_tablet_mode_controller.cc",
       "ash/fake_tablet_mode_controller.h",
+      "ash/test_wallpaper_controller.cc",
+      "ash/test_wallpaper_controller.h",
     ]
     deps += [ "//ash/public/cpp:ash_public_cpp" ]
   }
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.cc b/chrome/browser/ui/ash/test_wallpaper_controller.cc
new file mode 100644
index 0000000..2323147
--- /dev/null
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.cc
@@ -0,0 +1,84 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/test_wallpaper_controller.h"
+
+TestWallpaperController::TestWallpaperController() : binding_(this) {}
+
+TestWallpaperController::~TestWallpaperController() = default;
+
+ash::mojom::WallpaperControllerPtr
+TestWallpaperController::CreateInterfacePtr() {
+  ash::mojom::WallpaperControllerPtr ptr;
+  binding_.Bind(mojo::MakeRequest(&ptr));
+  return ptr;
+}
+
+void TestWallpaperController::SetClient(
+    ash::mojom::WallpaperControllerClientPtr client) {
+  was_client_set_ = true;
+}
+
+void TestWallpaperController::SetCustomWallpaper(
+    ash::mojom::WallpaperUserInfoPtr user_info,
+    const std::string& wallpaper_files_id,
+    const std::string& file_name,
+    wallpaper::WallpaperLayout layout,
+    wallpaper::WallpaperType type,
+    const SkBitmap& image,
+    bool show_wallpaper) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::SetOnlineWallpaper(
+    ash::mojom::WallpaperUserInfoPtr user_info,
+    const SkBitmap& image,
+    const std::string& url,
+    wallpaper::WallpaperLayout layout,
+    bool show_wallpaper) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::SetDefaultWallpaper(
+    ash::mojom::WallpaperUserInfoPtr user_info,
+    bool show_wallpaper) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::SetCustomizedDefaultWallpaper(
+    const GURL& wallpaper_url,
+    const base::FilePath& file_path,
+    const base::FilePath& resized_directory) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::ShowUserWallpaper(
+    ash::mojom::WallpaperUserInfoPtr user_info) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::ShowSigninWallpaper() {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::RemoveUserWallpaper(
+    ash::mojom::WallpaperUserInfoPtr user_info) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::SetWallpaper(
+    const SkBitmap& wallpaper,
+    const wallpaper::WallpaperInfo& wallpaper_info) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::AddObserver(
+    ash::mojom::WallpaperObserverAssociatedPtrInfo observer) {
+  NOTIMPLEMENTED();
+}
+
+void TestWallpaperController::GetWallpaperColors(
+    GetWallpaperColorsCallback callback) {
+  NOTIMPLEMENTED();
+}
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.h b/chrome/browser/ui/ash/test_wallpaper_controller.h
new file mode 100644
index 0000000..6b3f8fa4
--- /dev/null
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.h
@@ -0,0 +1,63 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_TEST_WALLPAPER_CONTROLLER_H_
+#define CHROME_BROWSER_UI_ASH_TEST_WALLPAPER_CONTROLLER_H_
+
+#include "ash/public/interfaces/wallpaper.mojom.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+// Simulates WallpaperController in ash.
+// TODO(crbug.com/776464): Maybe create an enum to represent each function to
+// avoid having lots of counters and getters.
+class TestWallpaperController : ash::mojom::WallpaperController {
+ public:
+  TestWallpaperController();
+
+  ~TestWallpaperController() override;
+
+  bool was_client_set() const { return was_client_set_; }
+
+  // Returns a mojo interface pointer bound to this object.
+  ash::mojom::WallpaperControllerPtr CreateInterfacePtr();
+
+  // ash::mojom::WallpaperController:
+  void SetClient(ash::mojom::WallpaperControllerClientPtr client) override;
+  void SetCustomWallpaper(ash::mojom::WallpaperUserInfoPtr user_info,
+                          const std::string& wallpaper_files_id,
+                          const std::string& file_name,
+                          wallpaper::WallpaperLayout layout,
+                          wallpaper::WallpaperType type,
+                          const SkBitmap& image,
+                          bool show_wallpaper) override;
+  void SetOnlineWallpaper(ash::mojom::WallpaperUserInfoPtr user_info,
+                          const SkBitmap& image,
+                          const std::string& url,
+                          wallpaper::WallpaperLayout layout,
+                          bool show_wallpaper) override;
+  void SetDefaultWallpaper(ash::mojom::WallpaperUserInfoPtr user_info,
+                           bool show_wallpaper) override;
+  void SetCustomizedDefaultWallpaper(
+      const GURL& wallpaper_url,
+      const base::FilePath& file_path,
+      const base::FilePath& resized_directory) override;
+  void ShowUserWallpaper(ash::mojom::WallpaperUserInfoPtr user_info) override;
+  void ShowSigninWallpaper() override;
+  void RemoveUserWallpaper(ash::mojom::WallpaperUserInfoPtr user_info) override;
+  void SetWallpaper(const SkBitmap& wallpaper,
+                    const wallpaper::WallpaperInfo& wallpaper_info) override;
+  void AddObserver(
+      ash::mojom::WallpaperObserverAssociatedPtrInfo observer) override;
+  void GetWallpaperColors(GetWallpaperColorsCallback callback) override;
+
+ private:
+  mojo::Binding<ash::mojom::WallpaperController> binding_;
+
+  bool was_client_set_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(TestWallpaperController);
+};
+
+#endif  // CHROME_BROWSER_UI_ASH_TEST_WALLPAPER_CONTROLLER_H_
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.cc b/chrome/browser/ui/ash/wallpaper_controller_client.cc
new file mode 100644
index 0000000..9fe8f0e
--- /dev/null
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.cc
@@ -0,0 +1,152 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/wallpaper_controller_client.h"
+
+#include "ash/public/interfaces/constants.mojom.h"
+#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
+#include "components/wallpaper/wallpaper_files_id.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace {
+
+WallpaperControllerClient* g_instance = nullptr;
+
+// Creates a mojom::WallpaperUserInfo for the account id. Returns nullptr if
+// user manager cannot find the user.
+ash::mojom::WallpaperUserInfoPtr AccountIdToWallpaperUserInfo(
+    const AccountId& account_id) {
+  const user_manager::User* user =
+      user_manager::UserManager::Get()->FindUser(account_id);
+  if (!user)
+    return nullptr;
+
+  ash::mojom::WallpaperUserInfoPtr wallpaper_user_info =
+      ash::mojom::WallpaperUserInfo::New();
+  wallpaper_user_info->account_id = account_id;
+  wallpaper_user_info->type = user->GetType();
+  wallpaper_user_info->is_ephemeral =
+      user_manager::UserManager::Get()->IsUserNonCryptohomeDataEphemeral(
+          account_id);
+  wallpaper_user_info->has_gaia_account = user->HasGaiaAccount();
+
+  return wallpaper_user_info;
+}
+
+}  // namespace
+
+WallpaperControllerClient::WallpaperControllerClient() : binding_(this) {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
+
+WallpaperControllerClient::~WallpaperControllerClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
+
+void WallpaperControllerClient::Init() {
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->BindInterface(ash::mojom::kServiceName, &wallpaper_controller_);
+  BindAndSetClient();
+}
+
+void WallpaperControllerClient::InitForTesting(
+    ash::mojom::WallpaperControllerPtr controller) {
+  wallpaper_controller_ = std::move(controller);
+  BindAndSetClient();
+}
+
+// static
+WallpaperControllerClient* WallpaperControllerClient::Get() {
+  return g_instance;
+}
+
+void WallpaperControllerClient::SetCustomWallpaper(
+    const AccountId& account_id,
+    const wallpaper::WallpaperFilesId& wallpaper_files_id,
+    const std::string& file_name,
+    wallpaper::WallpaperLayout layout,
+    wallpaper::WallpaperType type,
+    const gfx::ImageSkia& image,
+    bool show_wallpaper) {
+  ash::mojom::WallpaperUserInfoPtr user_info =
+      AccountIdToWallpaperUserInfo(account_id);
+  if (!user_info)
+    return;
+  wallpaper_controller_->SetCustomWallpaper(
+      std::move(user_info), wallpaper_files_id.id(), file_name, layout, type,
+      *image.bitmap(), show_wallpaper);
+}
+
+void WallpaperControllerClient::SetOnlineWallpaper(
+    const AccountId& account_id,
+    const gfx::ImageSkia& image,
+    const std::string& url,
+    wallpaper::WallpaperLayout layout,
+    bool show_wallpaper) {
+  ash::mojom::WallpaperUserInfoPtr user_info =
+      AccountIdToWallpaperUserInfo(account_id);
+  if (!user_info)
+    return;
+  wallpaper_controller_->SetOnlineWallpaper(
+      std::move(user_info), *image.bitmap(), url, layout, show_wallpaper);
+}
+
+void WallpaperControllerClient::SetDefaultWallpaper(const AccountId& account_id,
+                                                    bool show_wallpaper) {
+  ash::mojom::WallpaperUserInfoPtr user_info =
+      AccountIdToWallpaperUserInfo(account_id);
+  if (!user_info)
+    return;
+  wallpaper_controller_->SetDefaultWallpaper(std::move(user_info),
+                                             show_wallpaper);
+}
+
+void WallpaperControllerClient::SetCustomizedDefaultWallpaper(
+    const GURL& wallpaper_url,
+    const base::FilePath& file_path,
+    const base::FilePath& resized_directory) {
+  wallpaper_controller_->SetCustomizedDefaultWallpaper(wallpaper_url, file_path,
+                                                       resized_directory);
+}
+
+void WallpaperControllerClient::ShowUserWallpaper(const AccountId& account_id) {
+  ash::mojom::WallpaperUserInfoPtr user_info =
+      AccountIdToWallpaperUserInfo(account_id);
+  if (!user_info)
+    return;
+  wallpaper_controller_->ShowUserWallpaper(std::move(user_info));
+}
+
+void WallpaperControllerClient::ShowSigninWallpaper() {
+  wallpaper_controller_->ShowSigninWallpaper();
+}
+
+void WallpaperControllerClient::RemoveUserWallpaper(
+    const AccountId& account_id) {
+  ash::mojom::WallpaperUserInfoPtr user_info =
+      AccountIdToWallpaperUserInfo(account_id);
+  if (!user_info)
+    return;
+  wallpaper_controller_->RemoveUserWallpaper(std::move(user_info));
+}
+
+void WallpaperControllerClient::OpenWallpaperPicker() {
+  // TODO(crbug.com/776464): Inline the implementation after WallpaperManager
+  // is removed.
+  chromeos::WallpaperManager::Get()->OpenWallpaperPicker();
+}
+
+void WallpaperControllerClient::FlushForTesting() {
+  wallpaper_controller_.FlushForTesting();
+}
+
+void WallpaperControllerClient::BindAndSetClient() {
+  ash::mojom::WallpaperControllerClientPtr client;
+  binding_.Bind(mojo::MakeRequest(&client));
+  wallpaper_controller_->SetClient(std::move(client));
+}
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.h b/chrome/browser/ui/ash/wallpaper_controller_client.h
new file mode 100644
index 0000000..f9a0af2
--- /dev/null
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.h
@@ -0,0 +1,73 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_WALLPAPER_CONTROLLER_CLIENT_H_
+#define CHROME_BROWSER_UI_ASH_WALLPAPER_CONTROLLER_CLIENT_H_
+
+#include "ash/public/interfaces/wallpaper.mojom.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace wallpaper {
+class WallpaperFilesId;
+enum WallpaperLayout;
+enum WallpaperType;
+}  // namespace wallpaper
+
+// Handles method calls sent from ash to chrome. Also sends messages from chrome
+// to ash.
+class WallpaperControllerClient : public ash::mojom::WallpaperControllerClient {
+ public:
+  WallpaperControllerClient();
+  ~WallpaperControllerClient() override;
+
+  // Initializes and connects to ash.
+  void Init();
+
+  // Tests can provide a mock mojo interface for the ash controller.
+  void InitForTesting(ash::mojom::WallpaperControllerPtr controller);
+
+  static WallpaperControllerClient* Get();
+
+  // Wrappers around the ash::mojom::WallpaperController interface.
+  void SetCustomWallpaper(const AccountId& account_id,
+                          const wallpaper::WallpaperFilesId& wallpaper_files_id,
+                          const std::string& file_name,
+                          wallpaper::WallpaperLayout layout,
+                          wallpaper::WallpaperType type,
+                          const gfx::ImageSkia& image,
+                          bool show_wallpaper);
+  void SetOnlineWallpaper(const AccountId& account_id,
+                          const gfx::ImageSkia& image,
+                          const std::string& url,
+                          wallpaper::WallpaperLayout layout,
+                          bool show_wallpaper);
+  void SetDefaultWallpaper(const AccountId& account_id, bool show_wallpaper);
+  void SetCustomizedDefaultWallpaper(const GURL& wallpaper_url,
+                                     const base::FilePath& file_path,
+                                     const base::FilePath& resized_directory);
+  void ShowUserWallpaper(const AccountId& account_id);
+  void ShowSigninWallpaper();
+  void RemoveUserWallpaper(const AccountId& account_id);
+
+  // ash::mojom::WallpaperControllerClient:
+  void OpenWallpaperPicker() override;
+
+  // Flushes the mojo pipe to ash.
+  void FlushForTesting();
+
+ private:
+  // Binds this object to its mojo interface and sets it as the ash client.
+  void BindAndSetClient();
+
+  // WallpaperController interface in ash.
+  ash::mojom::WallpaperControllerPtr wallpaper_controller_;
+
+  // Binds to the client interface.
+  mojo::Binding<ash::mojom::WallpaperControllerClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(WallpaperControllerClient);
+};
+
+#endif  // CHROME_BROWSER_UI_ASH_WALLPAPER_CONTROLLER_CLIENT_H_
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_unittest.cc b/chrome/browser/ui/ash/wallpaper_controller_client_unittest.cc
new file mode 100644
index 0000000..4c990ae3
--- /dev/null
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_unittest.cc
@@ -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.
+
+#include "chrome/browser/ui/ash/wallpaper_controller_client.h"
+
+#include "base/test/scoped_task_environment.h"
+#include "chrome/browser/ui/ash/test_wallpaper_controller.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class WallpaperControllerClientTest : public testing::Test {
+ public:
+  WallpaperControllerClientTest() = default;
+  ~WallpaperControllerClientTest() override = default;
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  DISALLOW_COPY_AND_ASSIGN(WallpaperControllerClientTest);
+};
+
+TEST_F(WallpaperControllerClientTest, Construction) {
+  WallpaperControllerClient client;
+  TestWallpaperController controller;
+  client.InitForTesting(controller.CreateInterfacePtr());
+  client.FlushForTesting();
+
+  // Singleton was initialized.
+  EXPECT_EQ(&client, WallpaperControllerClient::Get());
+
+  // Object was set as client.
+  EXPECT_TRUE(controller.was_client_set());
+}
+
+}  // namespace
\ No newline at end of file
diff --git a/chrome/browser/ui/browser_navigator_params.cc b/chrome/browser/ui/browser_navigator_params.cc
index f4bf1e1..43e9569 100644
--- a/chrome/browser/ui/browser_navigator_params.cc
+++ b/chrome/browser/ui/browser_navigator_params.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/ui/browser_navigator_params.h"
 
 #include "build/build_config.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/page_navigator.h"
 
@@ -21,102 +20,26 @@
 
 #if defined(OS_ANDROID)
 NavigateParams::NavigateParams(WebContents* a_target_contents)
-    : frame_tree_node_id(-1),
-      uses_post(false),
-      target_contents(a_target_contents),
-      source_contents(nullptr),
-      disposition(WindowOpenDisposition::CURRENT_TAB),
-      opener(nullptr),
-      trusted_source(false),
-      transition(ui::PAGE_TRANSITION_LINK),
-      is_renderer_initiated(false),
-      tabstrip_index(-1),
-      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
-      window_action(NO_ACTION),
-      user_gesture(true),
-      path_behavior(RESPECT),
-      ref_behavior(IGNORE_REF),
-      initiating_profile(nullptr),
-      should_replace_current_entry(false),
-      created_with_opener(false),
-      started_from_context_menu(false) {}
+    : target_contents(a_target_contents) {}
 #else
 NavigateParams::NavigateParams(Browser* a_browser,
                                const GURL& a_url,
                                ui::PageTransition a_transition)
-    : url(a_url),
-      frame_tree_node_id(-1),
-      uses_post(false),
-      target_contents(NULL),
-      source_contents(NULL),
-      disposition(WindowOpenDisposition::CURRENT_TAB),
-      opener(nullptr),
-      trusted_source(false),
-      transition(a_transition),
-      is_renderer_initiated(false),
-      tabstrip_index(-1),
-      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
-      window_action(NO_ACTION),
-      user_gesture(true),
-      path_behavior(RESPECT),
-      ref_behavior(IGNORE_REF),
-      browser(a_browser),
-      initiating_profile(NULL),
-      should_replace_current_entry(false),
-      created_with_opener(false),
-      started_from_context_menu(false) {}
+    : url(a_url), transition(a_transition), browser(a_browser) {}
 
 NavigateParams::NavigateParams(Browser* a_browser,
                                WebContents* a_target_contents)
-    : frame_tree_node_id(-1),
-      uses_post(false),
-      target_contents(a_target_contents),
-      source_contents(NULL),
-      disposition(WindowOpenDisposition::CURRENT_TAB),
-      opener(nullptr),
-      trusted_source(false),
-      transition(ui::PAGE_TRANSITION_LINK),
-      is_renderer_initiated(false),
-      tabstrip_index(-1),
-      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
-      window_action(NO_ACTION),
-      user_gesture(true),
-      path_behavior(RESPECT),
-      ref_behavior(IGNORE_REF),
-      browser(a_browser),
-      initiating_profile(NULL),
-      should_replace_current_entry(false),
-      created_with_opener(false),
-      started_from_context_menu(false) {}
+    : target_contents(a_target_contents), browser(a_browser) {}
 #endif  // !defined(OS_ANDROID)
 
 NavigateParams::NavigateParams(Profile* a_profile,
                                const GURL& a_url,
                                ui::PageTransition a_transition)
     : url(a_url),
-      frame_tree_node_id(-1),
-      uses_post(false),
-      target_contents(NULL),
-      source_contents(NULL),
       disposition(WindowOpenDisposition::NEW_FOREGROUND_TAB),
-      opener(nullptr),
-      trusted_source(false),
       transition(a_transition),
-      is_renderer_initiated(false),
-      tabstrip_index(-1),
-      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
       window_action(SHOW_WINDOW),
-      user_gesture(true),
-      path_behavior(RESPECT),
-      ref_behavior(IGNORE_REF),
-#if !defined(OS_ANDROID)
-      browser(NULL),
-#endif
-      initiating_profile(a_profile),
-      should_replace_current_entry(false),
-      created_with_opener(false),
-      started_from_context_menu(false) {
-}
+      initiating_profile(a_profile) {}
 
 NavigateParams::NavigateParams(const NavigateParams& other) = default;
 
diff --git a/chrome/browser/ui/browser_navigator_params.h b/chrome/browser/ui/browser_navigator_params.h
index 007e8d52..d79db8e 100644
--- a/chrome/browser/ui/browser_navigator_params.h
+++ b/chrome/browser/ui/browser_navigator_params.h
@@ -10,6 +10,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "build/build_config.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "content/public/browser/global_request_id.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/common/referrer.h"
@@ -77,14 +78,14 @@
   std::string frame_name;
 
   // The browser-global ID of the frame to navigate, or -1 for the main frame.
-  int frame_tree_node_id;
+  int frame_tree_node_id = -1;
 
   // Any redirect URLs that occurred for this navigation before |url|.
   // Usually empty.
   std::vector<GURL> redirect_chain;
 
   // Indicates whether this navigation will be sent using POST.
-  bool uses_post;
+  bool uses_post = false;
 
   // The post data when the navigation uses POST.
   scoped_refptr<content::ResourceRequestBody> post_data;
@@ -106,7 +107,7 @@
   //       a new WebContents, this field will remain NULL and the
   //       WebContents deleted if the WebContents it created is
   //       not added to a TabStripModel before Navigate() returns.
-  content::WebContents* target_contents;
+  content::WebContents* target_contents = nullptr;
 
   // [in]  The WebContents that initiated the Navigate() request if such
   //       context is necessary. Default is NULL, i.e. no context.
@@ -115,7 +116,7 @@
   //       Navigate(). However, if the originating page is from a different
   //       profile (e.g. an OFF_THE_RECORD page originating from a non-OTR
   //       window), then |source_contents| is reset to NULL.
-  content::WebContents* source_contents;
+  content::WebContents* source_contents = nullptr;
 
   // The disposition requested by the navigation source. Default is
   // CURRENT_TAB. What follows is a set of coercions that happen to this value
@@ -132,34 +133,30 @@
   // If disposition is one of NEW_WINDOW, NEW_POPUP, NEW_FOREGROUND_TAB or
   // SINGLETON_TAB, then TabStripModel::ADD_ACTIVE is automatically added to
   // |tabstrip_add_types|.
-  WindowOpenDisposition disposition;
+  WindowOpenDisposition disposition = WindowOpenDisposition::CURRENT_TAB;
 
   // Allows setting the opener for the case when new WebContents are created
   // (i.e. when |disposition| asks for a new tab or window).
-  content::RenderFrameHost* opener;
+  content::RenderFrameHost* opener = nullptr;
 
-  // Sets browser->is_trusted_source. Default is false.
-  bool trusted_source;
+  // Sets browser->is_trusted_source.
+  bool trusted_source = false;
 
-  // The transition type of the navigation. Default is
-  // ui::PAGE_TRANSITION_LINK when target_contents is specified in the
-  // constructor.
-  ui::PageTransition transition;
+  // The transition type of the navigation.
+  ui::PageTransition transition = ui::PAGE_TRANSITION_LINK;
 
-  // Whether this navigation was initiated by the renderer process. Default is
-  // false.
-  bool is_renderer_initiated;
+  // Whether this navigation was initiated by the renderer process.
+  bool is_renderer_initiated = false;
 
   // The index the caller would like the tab to be positioned at in the
   // TabStrip. The actual index will be determined by the TabHandler in
-  // accordance with |add_types|. Defaults to -1 (allows the TabHandler to
-  // decide).
-  int tabstrip_index;
+  // accordance with |add_types|. The default allows the TabHandler to decide.
+  int tabstrip_index = -1;
 
   // A bitmask of values defined in TabStripModel::AddTabTypes. Helps
   // determine where to insert a new tab and whether or not it should be
-  // selected, among other properties. Default is ADD_ACTIVE.
-  int tabstrip_add_types;
+  // selected, among other properties.
+  int tabstrip_add_types = TabStripModel::ADD_ACTIVE;
 
   // If non-empty, the new tab is an app tab.
   std::string extension_app_id;
@@ -184,11 +181,10 @@
   // Default is NO_ACTION (don't show or activate the window).
   // If disposition is NEW_WINDOW or NEW_POPUP, and |window_action| is set to
   // NO_ACTION, |window_action| will be set to SHOW_WINDOW.
-  WindowAction window_action;
+  WindowAction window_action = NO_ACTION;
 
   // If false then the navigation was not initiated by a user gesture.
-  // Default is true.
-  bool user_gesture;
+  bool user_gesture = true;
 
   // What to do with the path component of the URL for singleton navigations.
   enum PathBehavior {
@@ -199,8 +195,7 @@
     // Ignore path when finding existing tab, don't navigate tab.
     IGNORE_AND_STAY_PUT,
   };
-  // Default is RESPECT.
-  PathBehavior path_behavior;
+  PathBehavior path_behavior = RESPECT;
 
   // What to do with the ref component of the URL for singleton navigations.
   enum RefBehavior {
@@ -209,8 +204,7 @@
     // Two URLs with differing refs are different.
     RESPECT_REF,
   };
-  // Default is IGNORE.
-  RefBehavior ref_behavior;
+  RefBehavior ref_behavior = IGNORE_REF;
 
 #if !defined(OS_ANDROID)
   // [in]  Specifies a Browser object where the navigation could occur or the
@@ -225,22 +219,22 @@
   //       Navigate(), the caller is responsible for showing it so that its
   //       window can assume responsibility for the Browser's lifetime (Browser
   //       objects are deleted when the user closes a visible browser window).
-  Browser* browser;
+  Browser* browser = nullptr;
 #endif
 
   // The profile that is initiating the navigation. If there is a non-NULL
   // browser passed in via |browser|, it's profile will be used instead.
-  Profile* initiating_profile;
+  Profile* initiating_profile = nullptr;
 
   // Indicates whether this navigation  should replace the current
   // navigation entry.
-  bool should_replace_current_entry;
+  bool should_replace_current_entry = false;
 
   // Indicates whether |target_contents| is being created with a window.opener.
-  bool created_with_opener;
+  bool created_with_opener = false;
 
   // Whether or not the related navigation was started in the context menu.
-  bool started_from_context_menu;
+  bool started_from_context_menu = false;
 
   // SiteInstance of the frame that initiated the navigation or null if we
   // don't know it. This should be assigned from the OpenURLParams of the
diff --git a/chrome/browser/ui/cocoa/color_chooser_mac.h b/chrome/browser/ui/cocoa/color_chooser_mac.h
new file mode 100644
index 0000000..511000dc
--- /dev/null
+++ b/chrome/browser/ui/cocoa/color_chooser_mac.h
@@ -0,0 +1,71 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_COLOR_CHOOSER_MAC_H_
+#define CHROME_BROWSER_UI_COCOA_COLOR_CHOOSER_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#import "base/mac/scoped_nsobject.h"
+#include "content/public/browser/color_chooser.h"
+#include "content/public/browser/web_contents.h"
+
+class ColorChooserMac;
+
+// A Listener class to act as a event target for NSColorPanel and send
+// the results to the C++ class, ColorChooserMac.
+@interface ColorPanelCocoa : NSObject<NSWindowDelegate> {
+ @protected
+  // We don't call DidChooseColor if the change wasn't caused by the user
+  // interacting with the panel.
+  BOOL nonUserChange_;
+ @private
+  ColorChooserMac* chooser_;  // weak, owns this
+}
+
+- (id)initWithChooser:(ColorChooserMac*)chooser;
+
+// Called from NSColorPanel.
+- (void)didChooseColor:(NSColorPanel*)panel;
+
+// Sets color to the NSColorPanel as a non user change.
+- (void)setColor:(NSColor*)color;
+
+@end
+
+class ColorChooserMac : public content::ColorChooser {
+ public:
+  // Returns a ColorChooserMac instance owned by the ColorChooserMac class -
+  // call End() when done to free it. Each call to Open() returns a new
+  // instance after freeing the previous one (i.e. it does not reuse the
+  // previous instance even if it still exists).
+  static ColorChooserMac* Open(content::WebContents* web_contents,
+                               SkColor initial_color);
+
+  // Called from ColorPanelCocoa.
+  void DidChooseColorInColorPanel(SkColor color);
+  void DidCloseColorPabel();
+
+  // Set the color programmatically.
+  void SetSelectedColor(SkColor color) override;
+
+  // Call when done with the ColorChooserMac.
+  void End() override;
+
+ private:
+  ColorChooserMac(content::WebContents* tab, SkColor initial_color);
+
+  ~ColorChooserMac() override;
+
+  static ColorChooserMac* current_color_chooser_;
+
+  // The web contents invoking the color chooser.  No ownership because it will
+  // outlive this class.
+  content::WebContents* web_contents_;
+  base::scoped_nsobject<ColorPanelCocoa> panel_;
+
+  DISALLOW_COPY_AND_ASSIGN(ColorChooserMac);
+};
+
+#endif  // CHROME_BROWSER_UI_COCOA_COLOR_CHOOSER_MAC_H_
diff --git a/chrome/browser/ui/cocoa/color_chooser_mac.mm b/chrome/browser/ui/cocoa/color_chooser_mac.mm
index ed5a26b5..fee82a6 100644
--- a/chrome/browser/ui/cocoa/color_chooser_mac.mm
+++ b/chrome/browser/ui/cocoa/color_chooser_mac.mm
@@ -2,61 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import <Cocoa/Cocoa.h>
+#import "chrome/browser/ui/cocoa/color_chooser_mac.h"
 
 #include "base/logging.h"
-#import "base/mac/scoped_nsobject.h"
 #include "chrome/browser/ui/browser_dialogs.h"
-#include "content/public/browser/color_chooser.h"
-#include "content/public/browser/web_contents.h"
 #include "skia/ext/skia_utils_mac.h"
 
-class ColorChooserMac;
-
-// A Listener class to act as a event target for NSColorPanel and send
-// the results to the C++ class, ColorChooserMac.
-@interface ColorPanelCocoa : NSObject<NSWindowDelegate> {
- @private
-  // We don't call DidChooseColor if the change wasn't caused by the user
-  // interacting with the panel.
-  BOOL nonUserChange_;
-  ColorChooserMac* chooser_;  // weak, owns this
-}
-
-- (id)initWithChooser:(ColorChooserMac*)chooser;
-
-// Called from NSColorPanel.
-- (void)didChooseColor:(NSColorPanel*)panel;
-
-// Sets color to the NSColorPanel as a non user change.
-- (void)setColor:(NSColor*)color;
-
-@end
-
-class ColorChooserMac : public content::ColorChooser {
- public:
-  static ColorChooserMac* Open(content::WebContents* web_contents,
-                               SkColor initial_color);
-
-  ColorChooserMac(content::WebContents* tab, SkColor initial_color);
-  ~ColorChooserMac() override;
-
-  // Called from ColorPanelCocoa.
-  void DidChooseColorInColorPanel(SkColor color);
-  void DidCloseColorPabel();
-
-  void End() override;
-  void SetSelectedColor(SkColor color) override;
-
- private:
-  static ColorChooserMac* current_color_chooser_;
-
-  // The web contents invoking the color chooser.  No ownership because it will
-  // outlive this class.
-  content::WebContents* web_contents_;
-  base::scoped_nsobject<ColorPanelCocoa> panel_;
-};
-
 ColorChooserMac* ColorChooserMac::current_color_chooser_ = NULL;
 
 // static
@@ -104,6 +55,11 @@
   [panel_ setColor:skia::SkColorToDeviceNSColor(color)];
 }
 
+@interface NSColorPanel (Private)
+// Private method returning the NSColorPanel's target.
+- (id)__target;
+@end
+
 @implementation ColorPanelCocoa
 
 - (id)initWithChooser:(ColorChooserMac*)chooser {
@@ -120,10 +76,18 @@
 
 - (void)dealloc {
   NSColorPanel* panel = [NSColorPanel sharedColorPanel];
-  if ([panel delegate] == self) {
+
+  // On macOS 10.13 the NSColorPanel delegate can apparently get reset to nil
+  // with the target left unchanged. Use the private __target method to see if
+  // the ColorPanelCocoa is still the target.
+  BOOL respondsToPrivateTargetMethod =
+      [panel respondsToSelector:@selector(__target)];
+
+  if ([panel delegate] == self ||
+      (respondsToPrivateTargetMethod && [panel __target] == self)) {
     [panel setDelegate:nil];
     [panel setTarget:nil];
-    [panel setAction:nil];
+    [panel setAction:nullptr];
   }
 
   [super dealloc];
diff --git a/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
new file mode 100644
index 0000000..1985d80
--- /dev/null
+++ b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
@@ -0,0 +1,104 @@
+// 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 "chrome/browser/ui/cocoa/color_chooser_mac.h"
+
+#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
+#include "skia/ext/skia_utils_mac.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+
+@interface NSColorPanel (Private)
+// Private method returning the NSColorPanel's target.
+- (id)__target;
+@end
+
+namespace {
+
+class ColorPanelCocoaTest : public CocoaTest {
+  void SetUp() override {
+    // Create the color panel and call Init() again to update its initial
+    // window list to include it. The NSColorPanel cannot be dealloced, so
+    // without this step the tests will fail complaining that not all windows
+    // were closed.
+    [[NSColorPanel sharedColorPanel] makeKeyAndOrderFront:nil];
+    Init();
+  }
+};
+
+TEST_F(ColorPanelCocoaTest, ClearTargetAndDelegateOnEnd) {
+  NSColorPanel* nscolor_panel = [NSColorPanel sharedColorPanel];
+  EXPECT_TRUE([nscolor_panel respondsToSelector:@selector(__target)]);
+
+  // Create a ColorPanelCocoa.
+  ColorChooserMac* color_chooser_mac =
+      ColorChooserMac::Open(nullptr, SK_ColorBLACK);
+
+  // Confirm the NSColorPanel's configuration by the ColorChooserMac's
+  // ColorPanelCocoa.
+  EXPECT_TRUE([nscolor_panel delegate]);
+  EXPECT_TRUE([nscolor_panel __target]);
+
+  // Release the ColorPanelCocoa and confirm it's no longer the NSColorPanel's
+  // target or delegate.
+  color_chooser_mac->End();
+
+  EXPECT_EQ([nscolor_panel delegate], nil);
+  EXPECT_EQ([nscolor_panel __target], nil);
+}
+
+TEST_F(ColorPanelCocoaTest, ClearTargetOnEnd) {
+  NSColorPanel* nscolor_panel = [NSColorPanel sharedColorPanel];
+  EXPECT_TRUE([nscolor_panel respondsToSelector:@selector(__target)]);
+
+  // Create a ColorPanelCocoa.
+  ColorChooserMac* color_chooser_mac =
+      ColorChooserMac::Open(nullptr, SK_ColorBLACK);
+
+  // Confirm the NSColorPanel's configuration by the ColorChooserMac's
+  // ColorPanelCocoa.
+  EXPECT_TRUE([nscolor_panel delegate]);
+  EXPECT_TRUE([nscolor_panel __target]);
+
+  // Clear the delegate and release the ColorPanelCocoa.
+  [nscolor_panel setDelegate:nil];
+
+  // Release the ColorPanelCocoa.
+  color_chooser_mac->End();
+
+  // Confirm the ColorPanelCocoa is no longer the NSColorPanel's target or
+  // delegate. Previously the ColorPanelCocoa would not clear the target if
+  // the delegate had already been cleared.
+  EXPECT_EQ([nscolor_panel delegate], nil);
+  EXPECT_EQ([nscolor_panel __target], nil);
+}
+
+TEST_F(ColorPanelCocoaTest, SetColor) {
+  // Set the NSColor panel up with an intial color.
+  NSColor* blue_color = [NSColor blueColor];
+  NSColorPanel* nscolor_panel = [NSColorPanel sharedColorPanel];
+  [nscolor_panel setColor:blue_color];
+  EXPECT_TRUE([[nscolor_panel color] isEqual:blue_color]);
+
+  // Create a ColorChooserMac and confirm the NSColorPanel gets its initial
+  // color.
+  SkColor initial_color = SK_ColorBLACK;
+  ColorChooserMac* color_chooser_mac =
+      ColorChooserMac::Open(nullptr, SK_ColorBLACK);
+
+  EXPECT_NSEQ([nscolor_panel color],
+              skia::SkColorToDeviceNSColor(initial_color));
+
+  // Confirm that -[ColorPanelCocoa setColor:] sets the NSColorPanel's color.
+  SkColor test_color = SK_ColorRED;
+  color_chooser_mac->SetSelectedColor(test_color);
+
+  EXPECT_NSEQ([nscolor_panel color], skia::SkColorToDeviceNSColor(test_color));
+
+  // Clean up.
+  color_chooser_mac->End();
+}
+
+}  // namespace
diff --git a/chrome/browser/ui/webui/settings/appearance_handler.cc b/chrome/browser/ui/webui/settings/appearance_handler.cc
index dae38e4..3209963 100644
--- a/chrome/browser/ui/webui/settings/appearance_handler.cc
+++ b/chrome/browser/ui/webui/settings/appearance_handler.cc
@@ -14,6 +14,7 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
+#include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chromeos/login/login_state.h"
 #endif
 
@@ -114,7 +115,7 @@
     const base::ListValue* /*args*/) {
   if (!chromeos::WallpaperManager::Get()->IsPolicyControlled(
           user_manager::UserManager::Get()->GetActiveUser()->GetAccountId())) {
-    chromeos::WallpaperManager::Get()->Open();
+    WallpaperControllerClient::Get()->OpenWallpaperPicker();
   }
 }
 #endif
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index 06433a4..476949f 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -35,8 +35,10 @@
 
 #if defined(OS_CHROMEOS)
 #include "ash/public/cpp/ash_switches.h"
+#include "ash/public/interfaces/voice_interaction_controller.mojom.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "base/sys_info.h"
+#include "chrome/browser/chromeos/arc/arc_util.h"
 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos.h"
 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
@@ -1169,6 +1171,7 @@
       {"editPasswordPasswordLabel", IDS_SETTINGS_PASSWORDS_PASSWORD},
       {"noAddressesFound", IDS_SETTINGS_ADDRESS_NONE},
       {"noCreditCardsFound", IDS_SETTINGS_CREDIT_CARD_NONE},
+      {"noCreditCardsPolicy", IDS_SETTINGS_CREDIT_CARD_DISABLED},
       {"noPasswordsFound", IDS_SETTINGS_PASSWORDS_NONE},
       {"noExceptionsFound", IDS_SETTINGS_PASSWORDS_EXCEPTIONS_NONE},
       {"import", IDS_PASSWORD_MANAGER_IMPORT_BUTTON},
@@ -1593,17 +1596,15 @@
 
 void AddSearchStrings(content::WebUIDataSource* html_source, Profile* profile) {
 #if defined(OS_CHROMEOS)
-  const bool is_user_primary =
-      chromeos::ProfileHelper::Get()->GetUserByProfile(profile) ==
-      user_manager::UserManager::Get()->GetPrimaryUser();
+  const bool is_assistant_allowed =
+      arc::IsAssistantAllowedForProfile(profile) ==
+      ash::mojom::AssistantAllowedState::ALLOWED;
 #endif
 
   LocalizedString localized_strings[] = {
 #if defined(OS_CHROMEOS)
-    {"searchPageTitle", !profile->IsSupervised() && is_user_primary &&
-                                chromeos::switches::IsVoiceInteractionEnabled()
-                            ? IDS_SETTINGS_SEARCH_AND_ASSISTANT
-                            : IDS_SETTINGS_SEARCH},
+    {"searchPageTitle", is_assistant_allowed ? IDS_SETTINGS_SEARCH_AND_ASSISTANT
+                                             : IDS_SETTINGS_SEARCH},
 #else
     {"searchPageTitle", IDS_SETTINGS_SEARCH},
 #endif
@@ -1641,9 +1642,7 @@
       base::ASCIIToUTF16(chrome::kOmniboxLearnMoreURL));
   html_source->AddString("searchExplanation", search_explanation_text);
 #if defined(OS_CHROMEOS)
-  html_source->AddBoolean(
-      "enableVoiceInteraction",
-      chromeos::switches::IsVoiceInteractionEnabled() && is_user_primary);
+  html_source->AddBoolean("enableVoiceInteraction", is_assistant_allowed);
 #endif
 }
 
diff --git a/chrome/browser/vr/service/vr_device_manager.cc b/chrome/browser/vr/service/vr_device_manager.cc
index 45b77fc..3948672 100644
--- a/chrome/browser/vr/service/vr_device_manager.cc
+++ b/chrome/browser/vr/service/vr_device_manager.cc
@@ -10,6 +10,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/singleton.h"
 #include "build/build_config.h"
+#include "chrome/common/chrome_features.h"
 #include "device/vr/features/features.h"
 
 #if defined(OS_ANDROID)
@@ -35,7 +36,8 @@
 #endif
 
 #if BUILDFLAG(ENABLE_OPENVR)
-    providers.emplace_back(std::make_unique<device::OpenVRDeviceProvider>());
+    if (base::FeatureList::IsEnabled(features::kOpenVR))
+      providers.emplace_back(std::make_unique<device::OpenVRDeviceProvider>());
 #endif
     new VRDeviceManager(std::move(providers));
   }
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 0bafcbc..f864568 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -227,6 +227,10 @@
 #endif  // BUILDFLAG(ENABLE_VR) || defined(OS_ANDROID)
 
 #if BUILDFLAG(ENABLE_VR)
+// Enables the virtual keyboard for Chrome VR.
+const base::Feature kVrBrowserKeyboard{"VrBrowserKeyboard",
+                                       base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls features related to VR browsing that are under development.
 const base::Feature kVrBrowsingExperimentalFeatures{
     "VrBrowsingExperimentalFeatures", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -234,6 +238,12 @@
 // Controls experimental rendering features for VR browsing.
 const base::Feature kVrBrowsingExperimentalRendering{
     "VrBrowsingExperimentalRendering", base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if BUILDFLAG(ENABLE_OPENVR)
+// Controls OpenVR support.
+const base::Feature kOpenVR{"OpenVR", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif  // ENABLE_OPENVR
+
 #endif  // BUILDFLAG(ENABLE_VR)
 
 #if defined(OS_WIN)
@@ -485,8 +495,7 @@
 
 #if defined(OS_CHROMEOS)
 // Enables or disables the ability to add a Samba Share to the Files app
-const base::Feature kNativeSamba{"NativeSamba",
-                                 base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kNativeSmb{"NativeSmb", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // defined(OS_CHROMEOS)
 
 // Enables or disables the ability to use the sound content setting to mute a
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 98866ad..2d4341d 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -118,9 +118,15 @@
 extern const base::Feature kVrBrowsing;
 #endif
 #if BUILDFLAG(ENABLE_VR)
+extern const base::Feature kVrBrowserKeyboard;
 extern const base::Feature kVrBrowsingExperimentalFeatures;
 extern const base::Feature kVrBrowsingExperimentalRendering;
-#endif
+
+#if BUILDFLAG(ENABLE_OPENVR)
+extern const base::Feature kOpenVR;
+#endif  // ENABLE_OPENVR
+
+#endif  // ENABLE_VR
 
 #if defined(OS_MACOSX)
 extern const base::Feature kFullscreenToolbarReveal;
@@ -253,7 +259,7 @@
 #endif
 
 #if defined(OS_CHROMEOS)
-extern const base::Feature kNativeSamba;
+extern const base::Feature kNativeSmb;
 #endif
 
 extern const base::Feature kSoundContentSetting;
diff --git a/chrome/common/extensions/docs/examples/api/water_alarm_notification/background.js b/chrome/common/extensions/docs/examples/api/water_alarm_notification/background.js
index 5427d468..f3fb248 100644
--- a/chrome/common/extensions/docs/examples/api/water_alarm_notification/background.js
+++ b/chrome/common/extensions/docs/examples/api/water_alarm_notification/background.js
@@ -7,7 +7,7 @@
   chrome.browserAction.setBadgeText({text: ''});
   chrome.notifications.create({
       type:     'basic',
-      iconUrl:  'drink_water.png',
+      iconUrl:  'stay_hydrated.png',
       title:    'Time to Hydrate',
       message:  'Everyday I\'m Guzzlin\'!',
       buttons: [
diff --git a/chrome/common/media_router/media_route.h b/chrome/common/media_router/media_route.h
index 15b2e4d..f89789a0 100644
--- a/chrome/common/media_router/media_route.h
+++ b/chrome/common/media_router/media_route.h
@@ -32,7 +32,7 @@
   // |media_route_id|: ID of the route.
   // |media_source|: Description of source of the route.
   // |media_sink|: The sink that is receiving the media.
-  // |description|: Description of the route to be displayed.
+  // |description|: Human readable description of the casting activity.
   // |is_local|: true if the route was created from this browser.
   // |custom_controller_path|: custom controller path if it is given by route
   //     provider. empty otherwise.
@@ -126,8 +126,8 @@
   // The ID of sink being routed to.
   MediaSink::Id media_sink_id_;
 
-  // The description of the media route activity, for example
-  // "Playing Foo Bar Music All Access."
+  // Human readable description of the casting activity.  Examples:
+  // "Mirroring tab (www.example.com)", "Casting media", "Casting YouTube"
   std::string description_;
 
   // |true| if the route is created locally (versus discovered by a media route
diff --git a/chrome/common/media_router/media_status.h b/chrome/common/media_router/media_status.h
index dc1d988..cac78a8 100644
--- a/chrome/common/media_router/media_status.h
+++ b/chrome/common/media_router/media_status.h
@@ -36,6 +36,8 @@
 
   // Text describing the media, or a secondary title. For example, in a
   // MediaStatus representing a YouTube Cast session, this could be "YouTube".
+  //
+  // DEPRECATED.  TODO(crbug.com/786208): Remove this when no longer used.
   std::string description;
 
   // If this is true, the media can be played and paused.
diff --git a/chrome/common/media_router/mojo/media_router.mojom b/chrome/common/media_router/mojo/media_router.mojom
index fd07ad3..97e4a434 100644
--- a/chrome/common/media_router/mojo/media_router.mojom
+++ b/chrome/common/media_router/mojo/media_router.mojom
@@ -30,6 +30,11 @@
   // The human-readable name, e.g. "Janet's Chromecast".
   string name;
   // Optional description of the sink.
+  //
+  // DEPRECATED.  This is currently used in the Media Router UX to relay a
+  // Hangouts meeting name.  It should not be used for other purposes.
+  // TODO(crbug.com/786208): Remove this at a future date when Hangouts names
+  // are no longer required.
   string? description;
   // Optional domain of the sink if this sink is associated with an identity.
   string? domain;
@@ -93,7 +98,11 @@
   string? media_source;
   // The ID of sink that is rendering the media content.
   string media_sink_id;
-  // Human readable description of this route, e.g. "Tab casting".
+  // Human readable description of the casting activity.  Examples:
+  // "Mirroring tab (www.example.com)", "Casting media", "Casting YouTube"
+  //
+  // TODO(crbug.com/786208): Localize this properly by passing it in a way that
+  // permits use of template strings and placeholders.
   string description;
   // Specifies that the route is requested locally.
   bool is_local;
diff --git a/chrome/common/media_router/mojo/media_status.mojom b/chrome/common/media_router/mojo/media_status.mojom
index b70fc812..09b6684 100644
--- a/chrome/common/media_router/mojo/media_status.mojom
+++ b/chrome/common/media_router/mojo/media_status.mojom
@@ -16,12 +16,19 @@
     BUFFERING
   };
 
-  // The main title of the media. For example, in a MediaStatus representing
-  // a YouTube Cast session, this could be the title of the video.
+  // The title of the currently playing media. For example, in a MediaStatus
+  // representing a YouTube Cast route, this is the title of the video.
+  // For a tab mirroring or media remoting route, it's the page title.
+  // For a Presentation API route, it is the presentation page title.
   string title;
 
   // Text describing the media, or a secondary title. For example, in a
   // MediaStatus representing a YouTube Cast session, this could be "YouTube".
+  //
+  // DEPRECATED.  This is used to override MediaRoute.description (defined in
+  // media_router.mojom) in the Media Router UX.  We will be using
+  // MediaRoute.description exclusively once all MRPs have been updated.
+  // TODO(crbug.com/786208): Remove this when that is done.
   string description;
 
   // If this is true, the media can be played and paused through its
diff --git a/chrome/installer/linux/rpm/dist_package_provides.json b/chrome/installer/linux/rpm/dist_package_provides.json
index 1fa6a5b..7866294 100644
--- a/chrome/installer/linux/rpm/dist_package_provides.json
+++ b/chrome/installer/linux/rpm/dist_package_provides.json
@@ -483,6 +483,252 @@
         "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)",
         "libxcb.so.1()(64bit)"
     ],
+    "Fedora 27": [
+        "ld-linux-x86-64.so.2()(64bit)",
+        "ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)",
+        "ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)",
+        "ld-linux-x86-64.so.2(GLIBC_2.4)(64bit)",
+        "libX11-xcb.so.1()(64bit)",
+        "libX11.so.6()(64bit)",
+        "libXcomposite.so.1()(64bit)",
+        "libXcursor.so.1()(64bit)",
+        "libXdamage.so.1()(64bit)",
+        "libXext.so.6()(64bit)",
+        "libXfixes.so.3()(64bit)",
+        "libXi.so.6()(64bit)",
+        "libXrandr.so.2()(64bit)",
+        "libXrender.so.1()(64bit)",
+        "libXss.so.1()(64bit)",
+        "libXtst.so.6()(64bit)",
+        "libasound.so.2()(64bit)",
+        "libasound.so.2(ALSA_0.9)(64bit)",
+        "libasound.so.2(ALSA_0.9.0)(64bit)",
+        "libasound.so.2(ALSA_0.9.0rc4)(64bit)",
+        "libasound.so.2(ALSA_0.9.0rc8)(64bit)",
+        "libasound.so.2(ALSA_0.9.3)(64bit)",
+        "libasound.so.2(ALSA_0.9.5)(64bit)",
+        "libasound.so.2(ALSA_0.9.7)(64bit)",
+        "libatk-1.0.so.0()(64bit)",
+        "libatk-bridge-2.0.so.0()(64bit)",
+        "libc.so.6()(64bit)",
+        "libc.so.6(GLIBC_2.10)(64bit)",
+        "libc.so.6(GLIBC_2.11)(64bit)",
+        "libc.so.6(GLIBC_2.12)(64bit)",
+        "libc.so.6(GLIBC_2.13)(64bit)",
+        "libc.so.6(GLIBC_2.14)(64bit)",
+        "libc.so.6(GLIBC_2.15)(64bit)",
+        "libc.so.6(GLIBC_2.16)(64bit)",
+        "libc.so.6(GLIBC_2.17)(64bit)",
+        "libc.so.6(GLIBC_2.18)(64bit)",
+        "libc.so.6(GLIBC_2.2.5)(64bit)",
+        "libc.so.6(GLIBC_2.2.6)(64bit)",
+        "libc.so.6(GLIBC_2.22)(64bit)",
+        "libc.so.6(GLIBC_2.23)(64bit)",
+        "libc.so.6(GLIBC_2.24)(64bit)",
+        "libc.so.6(GLIBC_2.25)(64bit)",
+        "libc.so.6(GLIBC_2.26)(64bit)",
+        "libc.so.6(GLIBC_2.3)(64bit)",
+        "libc.so.6(GLIBC_2.3.2)(64bit)",
+        "libc.so.6(GLIBC_2.3.3)(64bit)",
+        "libc.so.6(GLIBC_2.3.4)(64bit)",
+        "libc.so.6(GLIBC_2.4)(64bit)",
+        "libc.so.6(GLIBC_2.5)(64bit)",
+        "libc.so.6(GLIBC_2.6)(64bit)",
+        "libc.so.6(GLIBC_2.7)(64bit)",
+        "libc.so.6(GLIBC_2.8)(64bit)",
+        "libc.so.6(GLIBC_2.9)(64bit)",
+        "libcairo.so()(64bit)",
+        "libcairo.so.2()(64bit)",
+        "libcups.so.2()(64bit)",
+        "libdbus-1.so.3()(64bit)",
+        "libdbus-1.so.3(LIBDBUS_1_3)(64bit)",
+        "libdbus-1.so.3(LIBDBUS_PRIVATE_1.12.0)(64bit)",
+        "libdl.so.2()(64bit)",
+        "libdl.so.2(GLIBC_2.2.5)(64bit)",
+        "libdl.so.2(GLIBC_2.3.3)(64bit)",
+        "libdl.so.2(GLIBC_2.3.4)(64bit)",
+        "libexpat.so.1()(64bit)",
+        "libfontconfig.so.1()(64bit)",
+        "libgcc_s.so.1()(64bit)",
+        "libgcc_s.so.1(GCC_3.0)(64bit)",
+        "libgcc_s.so.1(GCC_3.3)(64bit)",
+        "libgcc_s.so.1(GCC_3.3.1)(64bit)",
+        "libgcc_s.so.1(GCC_3.4)(64bit)",
+        "libgcc_s.so.1(GCC_3.4.2)(64bit)",
+        "libgcc_s.so.1(GCC_3.4.4)(64bit)",
+        "libgcc_s.so.1(GCC_4.0.0)(64bit)",
+        "libgcc_s.so.1(GCC_4.2.0)(64bit)",
+        "libgcc_s.so.1(GCC_4.3.0)(64bit)",
+        "libgcc_s.so.1(GCC_4.7.0)(64bit)",
+        "libgcc_s.so.1(GCC_4.8.0)(64bit)",
+        "libgcc_s.so.1(GCC_7.0.0)(64bit)",
+        "libgconf-2.so.4()(64bit)",
+        "libgdk-3.so.0()(64bit)",
+        "libgdk_pixbuf-2.0.so.0()(64bit)",
+        "libgio-2.0.so.0()(64bit)",
+        "libglib-2.0.so.0()(64bit)",
+        "libgmodule-2.0.so.0()(64bit)",
+        "libgobject-2.0.so.0()(64bit)",
+        "libgtk-3.so.0()(64bit)",
+        "libm.so.6()(64bit)",
+        "libm.so.6(GLIBC_2.15)(64bit)",
+        "libm.so.6(GLIBC_2.18)(64bit)",
+        "libm.so.6(GLIBC_2.2.5)(64bit)",
+        "libm.so.6(GLIBC_2.23)(64bit)",
+        "libm.so.6(GLIBC_2.24)(64bit)",
+        "libm.so.6(GLIBC_2.25)(64bit)",
+        "libm.so.6(GLIBC_2.26)(64bit)",
+        "libm.so.6(GLIBC_2.4)(64bit)",
+        "libnspr4.so()(64bit)",
+        "libnss3.so()(64bit)",
+        "libnss3.so(NSS_3.10)(64bit)",
+        "libnss3.so(NSS_3.10.2)(64bit)",
+        "libnss3.so(NSS_3.11)(64bit)",
+        "libnss3.so(NSS_3.11.1)(64bit)",
+        "libnss3.so(NSS_3.11.2)(64bit)",
+        "libnss3.so(NSS_3.11.7)(64bit)",
+        "libnss3.so(NSS_3.11.9)(64bit)",
+        "libnss3.so(NSS_3.12)(64bit)",
+        "libnss3.so(NSS_3.12.1)(64bit)",
+        "libnss3.so(NSS_3.12.10)(64bit)",
+        "libnss3.so(NSS_3.12.3)(64bit)",
+        "libnss3.so(NSS_3.12.4)(64bit)",
+        "libnss3.so(NSS_3.12.5)(64bit)",
+        "libnss3.so(NSS_3.12.6)(64bit)",
+        "libnss3.so(NSS_3.12.7)(64bit)",
+        "libnss3.so(NSS_3.12.9)(64bit)",
+        "libnss3.so(NSS_3.13)(64bit)",
+        "libnss3.so(NSS_3.13.2)(64bit)",
+        "libnss3.so(NSS_3.14)(64bit)",
+        "libnss3.so(NSS_3.14.1)(64bit)",
+        "libnss3.so(NSS_3.14.3)(64bit)",
+        "libnss3.so(NSS_3.15)(64bit)",
+        "libnss3.so(NSS_3.15.4)(64bit)",
+        "libnss3.so(NSS_3.16.1)(64bit)",
+        "libnss3.so(NSS_3.16.2)(64bit)",
+        "libnss3.so(NSS_3.18)(64bit)",
+        "libnss3.so(NSS_3.19)(64bit)",
+        "libnss3.so(NSS_3.19.1)(64bit)",
+        "libnss3.so(NSS_3.2)(64bit)",
+        "libnss3.so(NSS_3.2.1)(64bit)",
+        "libnss3.so(NSS_3.21)(64bit)",
+        "libnss3.so(NSS_3.22)(64bit)",
+        "libnss3.so(NSS_3.3)(64bit)",
+        "libnss3.so(NSS_3.3.1)(64bit)",
+        "libnss3.so(NSS_3.30)(64bit)",
+        "libnss3.so(NSS_3.31)(64bit)",
+        "libnss3.so(NSS_3.33)(64bit)",
+        "libnss3.so(NSS_3.4)(64bit)",
+        "libnss3.so(NSS_3.5)(64bit)",
+        "libnss3.so(NSS_3.6)(64bit)",
+        "libnss3.so(NSS_3.7)(64bit)",
+        "libnss3.so(NSS_3.7.1)(64bit)",
+        "libnss3.so(NSS_3.8)(64bit)",
+        "libnss3.so(NSS_3.9)(64bit)",
+        "libnss3.so(NSS_3.9.2)(64bit)",
+        "libnss3.so(NSS_3.9.3)(64bit)",
+        "libnssutil3.so()(64bit)",
+        "libnssutil3.so(NSSUTIL_3.12)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.12.3)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.12.5)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.12.7)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.13)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.14)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.15)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.17.1)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.21)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.24)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.25)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.31)(64bit)",
+        "libnssutil3.so(NSSUTIL_3.33)(64bit)",
+        "libpango-1.0.so.0()(64bit)",
+        "libpangocairo-1.0.so.0()(64bit)",
+        "libpthread.so.0()(64bit)",
+        "libpthread.so.0(GLIBC_2.11)(64bit)",
+        "libpthread.so.0(GLIBC_2.12)(64bit)",
+        "libpthread.so.0(GLIBC_2.18)(64bit)",
+        "libpthread.so.0(GLIBC_2.2.5)(64bit)",
+        "libpthread.so.0(GLIBC_2.2.6)(64bit)",
+        "libpthread.so.0(GLIBC_2.3.2)(64bit)",
+        "libpthread.so.0(GLIBC_2.3.3)(64bit)",
+        "libpthread.so.0(GLIBC_2.3.4)(64bit)",
+        "libpthread.so.0(GLIBC_2.4)(64bit)",
+        "librt.so.1()(64bit)",
+        "librt.so.1(GLIBC_2.2.5)(64bit)",
+        "librt.so.1(GLIBC_2.3.3)(64bit)",
+        "librt.so.1(GLIBC_2.3.4)(64bit)",
+        "librt.so.1(GLIBC_2.4)(64bit)",
+        "librt.so.1(GLIBC_2.7)(64bit)",
+        "libsmime3.so()(64bit)",
+        "libsmime3.so(NSS_3.10)(64bit)",
+        "libsmime3.so(NSS_3.12.10)(64bit)",
+        "libsmime3.so(NSS_3.12.2)(64bit)",
+        "libsmime3.so(NSS_3.13)(64bit)",
+        "libsmime3.so(NSS_3.15)(64bit)",
+        "libsmime3.so(NSS_3.16)(64bit)",
+        "libsmime3.so(NSS_3.18)(64bit)",
+        "libsmime3.so(NSS_3.2)(64bit)",
+        "libsmime3.so(NSS_3.2.1)(64bit)",
+        "libsmime3.so(NSS_3.3)(64bit)",
+        "libsmime3.so(NSS_3.4)(64bit)",
+        "libsmime3.so(NSS_3.4.1)(64bit)",
+        "libsmime3.so(NSS_3.6)(64bit)",
+        "libsmime3.so(NSS_3.7)(64bit)",
+        "libsmime3.so(NSS_3.7.2)(64bit)",
+        "libsmime3.so(NSS_3.8)(64bit)",
+        "libsmime3.so(NSS_3.9)(64bit)",
+        "libsmime3.so(NSS_3.9.3)(64bit)",
+        "libstdc++.so.5()(64bit)",
+        "libstdc++.so.5(CXXABI_1.2)(64bit)",
+        "libstdc++.so.5(CXXABI_1.2.1)(64bit)",
+        "libstdc++.so.5(CXXABI_1.2.2)(64bit)",
+        "libstdc++.so.5(GLIBCPP_3.2)(64bit)",
+        "libstdc++.so.5(GLIBCPP_3.2.1)(64bit)",
+        "libstdc++.so.5(GLIBCPP_3.2.2)(64bit)",
+        "libstdc++.so.5(GLIBCPP_3.2.3)(64bit)",
+        "libstdc++.so.5(GLIBCPP_3.2.4)(64bit)",
+        "libstdc++.so.6()(64bit)",
+        "libstdc++.so.6(CXXABI_1.3)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.1)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.10)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.11)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.2)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.3)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.4)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.5)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.6)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.7)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.8)(64bit)",
+        "libstdc++.so.6(CXXABI_1.3.9)(64bit)",
+        "libstdc++.so.6(CXXABI_FLOAT128)(64bit)",
+        "libstdc++.so.6(CXXABI_TM_1)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.1)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.10)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.11)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.12)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.13)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.14)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.15)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.16)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.17)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.18)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.19)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.2)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.20)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.21)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.22)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.23)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.24)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.3)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.4)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.5)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.6)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.7)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.8)(64bit)",
+        "libstdc++.so.6(GLIBCXX_3.4.9)(64bit)",
+        "libxcb.so.1()(64bit)"
+    ],
     "openSUSE Leap 42.2": [
         "ld-linux-x86-64.so.2()(64bit)",
         "ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)",
diff --git a/chrome/installer/linux/rpm/update_package_provides.py b/chrome/installer/linux/rpm/update_package_provides.py
index 88d9ae6..feacf8da 100755
--- a/chrome/installer/linux/rpm/update_package_provides.py
+++ b/chrome/installer/linux/rpm/update_package_provides.py
@@ -58,7 +58,7 @@
     "libxcb.so",
 ]
 
-SUPPORTED_FEDORA_RELEASES = ['25', '26']
+SUPPORTED_FEDORA_RELEASES = ['25', '26', '27']
 SUPPORTED_OPENSUSE_LEAP_RELEASES = ['42.2', '42.3']
 
 COMMON_NS = "http://linux.duke.edu/metadata/common"
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4823dd6..fd0f8c56 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2990,6 +2990,7 @@
       "../browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc",
       "../browser/ui/ash/session_controller_client_unittest.cc",
       "../browser/ui/ash/tablet_mode_client_unittest.cc",
+      "../browser/ui/ash/wallpaper_controller_client_unittest.cc",
       "../browser/ui/ash/window_positioner_unittest.cc",
       "../browser/ui/window_sizer/window_sizer_ash_unittest.cc",
     ]
@@ -3720,6 +3721,7 @@
         "../browser/ui/cocoa/bubble_view_unittest.mm",
         "../browser/ui/cocoa/chrome_browser_window_unittest.mm",
         "../browser/ui/cocoa/clickhold_button_cell_unittest.mm",
+        "../browser/ui/cocoa/color_panel_cocoa_unittest.mm",
         "../browser/ui/cocoa/confirm_bubble_controller_unittest.mm",
         "../browser/ui/cocoa/confirm_quit_panel_controller_unittest.mm",
         "../browser/ui/cocoa/constrained_window/constrained_window_alert_unittest.mm",
diff --git a/chrome/test/data/webui/media_router/route_controls_tests.js b/chrome/test/data/webui/media_router/route_controls_tests.js
index b0ca648..c681514 100644
--- a/chrome/test/data/webui/media_router/route_controls_tests.js
+++ b/chrome/test/data/webui/media_router/route_controls_tests.js
@@ -82,17 +82,11 @@
       test('initial text setting', function() {
         // Set |route|.
         controls.onRouteUpdated_(fakeRouteOne);
-        assertElementText(
-            loadTimeData.getStringF(
-                'castingActivityStatus', fakeRouteOne.description),
-            'route-description');
+        assertElementText(fakeRouteOne.description, 'route-description');
 
         // Set |route| to a different route.
         controls.onRouteUpdated_(fakeRouteTwo);
-        assertElementText(
-            loadTimeData.getStringF(
-                'castingActivityStatus', fakeRouteTwo.description),
-            'route-description');
+        assertElementText(fakeRouteTwo.description, 'route-description');
       });
 
       // Tests that the route title and status are shown when RouteStatus is
diff --git a/chrome/test/data/webui/media_router/route_details_tests.js b/chrome/test/data/webui/media_router/route_details_tests.js
index a07cbb6..9ccd085 100644
--- a/chrome/test/data/webui/media_router/route_details_tests.js
+++ b/chrome/test/data/webui/media_router/route_details_tests.js
@@ -45,9 +45,17 @@
             details.$$('#' + elementId).querySelector('span').innerText);
       };
 
+      // Checks whether |expected| and the text in the element in the
+      // |elementId| element are equal.
+      var checkElementText = function(expected, elementId) {
+        assertEquals(
+            expected,
+            details.$$('#' + elementId).innerText);
+      };
+
       // Checks the default route view is shown.
       var checkDefaultViewIsShown = function() {
-        assertFalse(details.$$('#route-information').hasAttribute('hidden'));
+        assertFalse(details.$$('#route-description').hasAttribute('hidden'));
         assertTrue(
             !details.$$('extension-view-wrapper') ||
             details.$$('extension-view-wrapper').hasAttribute('hidden'));
@@ -67,7 +75,7 @@
 
       // Checks the custom controller is shown.
       var checkCustomControllerIsShown = function() {
-        assertTrue(details.$$('#route-information').hasAttribute('hidden'));
+        assertTrue(details.$$('#route-description').hasAttribute('hidden'));
         assertFalse(
             details.$$('extension-view-wrapper').hasAttribute('hidden'));
       };
@@ -182,7 +190,7 @@
         checkSpanText(
             loadTimeData.getString('startCastingButtonText').toUpperCase(),
             'start-casting-to-route-button');
-        checkSpanText('', 'route-information');
+        checkElementText('', 'route-description');
       });
 
       // Tests when |route| is undefined or set.
@@ -194,16 +202,14 @@
         // Set |route|.
         details.route = fakeRouteOne;
         assertEquals(fakeRouteOne, details.route);
-        checkSpanText(loadTimeData.getStringF('castingActivityStatus',
-            fakeRouteOne.description), 'route-information');
+        checkElementText(fakeRouteOne.description, 'route-description');
         checkDefaultViewIsShown();
         checkStartCastButtonIsNotShown();
 
         // Set |route| to a different route.
         details.route = fakeRouteTwo;
         assertEquals(fakeRouteTwo, details.route);
-        checkSpanText(loadTimeData.getStringF('castingActivityStatus',
-            fakeRouteTwo.description), 'route-information');
+        checkElementText(fakeRouteTwo.description, 'route-description');
         checkDefaultViewIsShown();
         checkStartCastButtonIsShown();
       });
diff --git a/chrome/test/data/webui/settings/passwords_and_forms_browsertest.js b/chrome/test/data/webui/settings/passwords_and_forms_browsertest.js
index 2ee6103d..cbbdee0 100644
--- a/chrome/test/data/webui/settings/passwords_and_forms_browsertest.js
+++ b/chrome/test/data/webui/settings/passwords_and_forms_browsertest.js
@@ -75,21 +75,26 @@
       CrSettingsPrefs.deferInitialization = true;
       var prefs = document.createElement('settings-prefs');
       prefs.initialize(new settings.FakeSettingsPrivate([
-            {
-              key: 'autofill.enabled',
-              type: chrome.settingsPrivate.PrefType.BOOLEAN,
-              value: autofill,
-            },
-            {
-              key: 'credentials_enable_service',
-              type: chrome.settingsPrivate.PrefType.BOOLEAN,
-              value: passwords,
-            },
-            {
-              key: 'credentials_enable_autosignin',
-              type: chrome.settingsPrivate.PrefType.BOOLEAN,
-              value: true,
-            },
+        {
+          key: 'autofill.enabled',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: autofill,
+        },
+        {
+          key: 'autofill.credit_card_enabled',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: true,
+        },
+        {
+          key: 'credentials_enable_service',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: passwords,
+        },
+        {
+          key: 'credentials_enable_autosignin',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: true,
+        },
       ]));
 
       CrSettingsPrefs.initialized.then(function() {
diff --git a/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js b/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js
index 95aa556..9b2ee5b 100644
--- a/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js
+++ b/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js
@@ -116,10 +116,11 @@
    * Creates the autofill section for the given lists.
    * @param {!Array<!chrome.passwordsPrivate.PasswordUiEntry>} passwordList
    * @param {!Array<!chrome.passwordsPrivate.ExceptionEntry>} exceptionList
+   * @param {!Object} pref_value
    * @return {!Object}
    * @private
    */
-  createAutofillSection_: function(addresses, creditCards) {
+  createAutofillSection_: function(addresses, creditCards, pref_value) {
     // Override the AutofillManagerImpl for testing.
     this.autofillManager = new TestAutofillManager();
     this.autofillManager.data.addresses = addresses;
@@ -127,8 +128,10 @@
     AutofillManagerImpl.instance_ = this.autofillManager;
 
     var section = document.createElement('settings-autofill-section');
+    section.prefs = {autofill: {credit_card_enabled: pref_value}};
     document.body.appendChild(section);
     Polymer.dom.flush();
+
     return section;
   },
 
@@ -168,7 +171,7 @@
     test('testAutofillExtensionIndicator', function() {
       // Initializing with fake prefs
       var section = document.createElement('settings-autofill-section');
-      section.prefs = {autofill: {enabled: {}}};
+      section.prefs = {autofill: {enabled: {}, credit_card_enabled: {}}};
       document.body.appendChild(section);
 
       assertFalse(!!section.$$('#autofillExtensionIndicator'));
@@ -196,14 +199,22 @@
     });
 
     test('verifyCreditCardCount', function() {
-      var section = self.createAutofillSection_([], []);
+      var section = self.createAutofillSection_([], [], {});
 
-      var creditCardList = section.$.creditCardList;
+      var creditCardList = section.$$('#creditCardList');
       assertTrue(!!creditCardList);
       assertEquals(0, creditCardList.querySelectorAll('.list-item').length);
 
-      assertFalse(section.$.noCreditCardsLabel.hidden);
-      assertTrue(section.$.creditCardsHeading.hidden);
+      assertFalse(section.$$('#noCreditCardsLabel').hidden);
+      assertTrue(section.$$('#creditCardsHeading').hidden);
+      assertTrue(section.$$('#CreditCardsDisabledLabel').hidden);
+    });
+
+    test('verifyCreditCardsDisabled', function() {
+      var section = self.createAutofillSection_([], [], {value: false});
+
+      assertEquals(0, section.querySelectorAll('#creditCardList').length);
+      assertFalse(section.$$('#CreditCardsDisabledLabel').hidden);
     });
 
     test('verifyCreditCardCount', function() {
@@ -216,21 +227,21 @@
         FakeDataMaker.creditCardEntry(),
       ];
 
-      var section = self.createAutofillSection_([], creditCards);
-
-      var creditCardList = section.$.creditCardList;
+      var section = self.createAutofillSection_([], creditCards, {});
+      var creditCardList = section.$$('#creditCardList');
       assertTrue(!!creditCardList);
       assertEquals(creditCards.length,
           creditCardList.querySelectorAll('.list-item').length);
 
-      assertTrue(section.$.noCreditCardsLabel.hidden);
-      assertFalse(section.$.creditCardsHeading.hidden);
+      assertTrue(section.$$('#noCreditCardsLabel').hidden);
+      assertFalse(section.$$('#creditCardsHeading').hidden);
+      assertTrue(section.$$('#CreditCardsDisabledLabel').hidden);
     });
 
     test('verifyCreditCardFields', function() {
       var creditCard = FakeDataMaker.creditCardEntry();
-      var section = self.createAutofillSection_([], [creditCard]);
-      var creditCardList = section.$.creditCardList;
+      var section = self.createAutofillSection_([], [creditCard], {});
+      var creditCardList = section.$$('#creditCardList');
       var row = creditCardList.children[0];
       assertTrue(!!row);
 
@@ -243,8 +254,8 @@
     test('verifyCreditCardRowButtonIsDropdownWhenLocal', function() {
       var creditCard = FakeDataMaker.creditCardEntry();
       creditCard.metadata.isLocal = true;
-      var section = self.createAutofillSection_([], [creditCard]);
-      var creditCardList = section.$.creditCardList;
+      var section = self.createAutofillSection_([], [creditCard], {});
+      var creditCardList = section.$$('#creditCardList');
       var row = creditCardList.children[0];
       assertTrue(!!row);
       var menuButton = row.querySelector('#creditCardMenu');
@@ -256,8 +267,8 @@
     test('verifyCreditCardRowButtonIsOutlinkWhenRemote', function() {
       var creditCard = FakeDataMaker.creditCardEntry();
       creditCard.metadata.isLocal = false;
-      var section = self.createAutofillSection_([], [creditCard]);
-      var creditCardList = section.$.creditCardList;
+      var section = self.createAutofillSection_([], [creditCard], {});
+      var creditCardList = section.$$('#creditCardList');
       var row = creditCardList.children[0];
       assertTrue(!!row);
       var menuButton = row.querySelector('#creditCardMenu');
@@ -431,8 +442,8 @@
       creditCard.metadata.isLocal = true;
       creditCard.metadata.isCached = undefined;
 
-      var section = self.createAutofillSection_([], [creditCard]);
-      var creditCardList = section.$.creditCardList;
+      var section = self.createAutofillSection_([], [creditCard], {});
+      var creditCardList = section.$$('#creditCardList');
       assertTrue(!!creditCardList);
       assertEquals(1, creditCardList.querySelectorAll('.list-item').length);
       var row = creditCardList.children[0];
@@ -462,8 +473,8 @@
       creditCard.metadata.isLocal = false;
       creditCard.metadata.isCached = true;
 
-      var section = self.createAutofillSection_([], [creditCard]);
-      var creditCardList = section.$.creditCardList;
+      var section = self.createAutofillSection_([], [creditCard], {});
+      var creditCardList = section.$$('#creditCardList');
       assertTrue(!!creditCardList);
       assertEquals(1, creditCardList.querySelectorAll('.list-item').length);
       var row = creditCardList.children[0];
@@ -493,8 +504,8 @@
       creditCard.metadata.isLocal = false;
       creditCard.metadata.isCached = false;
 
-      var section = self.createAutofillSection_([], [creditCard]);
-      var creditCardList = section.$.creditCardList;
+      var section = self.createAutofillSection_([], [creditCard], {});
+      var creditCardList = section.$$('#creditCardList');
       assertTrue(!!creditCardList);
       assertEquals(1, creditCardList.querySelectorAll('.list-item').length);
       var row = creditCardList.children[0];
@@ -523,7 +534,7 @@
     });
 
     test('verifyNoAddresses', function() {
-      var section = self.createAutofillSection_([], []);
+      var section = self.createAutofillSection_([], [], {});
 
       var addressList = section.$.addressList;
       assertTrue(!!addressList);
@@ -542,7 +553,7 @@
         FakeDataMaker.addressEntry(),
       ];
 
-      var section = self.createAutofillSection_(addresses, []);
+      var section = self.createAutofillSection_(addresses, [], {});
 
       var addressList = section.$.addressList;
       assertTrue(!!addressList);
@@ -554,7 +565,7 @@
 
     test('verifyAddressFields', function() {
       var address = FakeDataMaker.addressEntry();
-      var section = self.createAutofillSection_([address], []);
+      var section = self.createAutofillSection_([address], [], {});
       var addressList = section.$.addressList;
       var row = addressList.children[0];
       assertTrue(!!row);
@@ -576,7 +587,7 @@
     test('verifyAddressRowButtonIsDropdownWhenLocal', function() {
       var address = FakeDataMaker.addressEntry();
       address.metadata.isLocal = true;
-      var section = self.createAutofillSection_([address], []);
+      var section = self.createAutofillSection_([address], [], {});
       var addressList = section.$.addressList;
       var row = addressList.children[0];
       assertTrue(!!row);
@@ -589,7 +600,7 @@
     test('verifyAddressRowButtonIsOutlinkWhenRemote', function() {
       var address = FakeDataMaker.addressEntry();
       address.metadata.isLocal = false;
-      var section = self.createAutofillSection_([address], []);
+      var section = self.createAutofillSection_([address], [], {});
       var addressList = section.$.addressList;
       var row = addressList.children[0];
       assertTrue(!!row);
diff --git a/chromeos/network/network_state_handler.cc b/chromeos/network/network_state_handler.cc
index f16732a..02cba55 100644
--- a/chromeos/network/network_state_handler.cc
+++ b/chromeos/network/network_state_handler.cc
@@ -6,7 +6,9 @@
 
 #include <stddef.h>
 
+#include <limits>
 #include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -308,10 +310,11 @@
 }
 
 const NetworkState* NetworkStateHandler::ConnectedNetworkByType(
-    const NetworkTypePattern& type) const {
-  const NetworkState* connected_network = nullptr;
+    const NetworkTypePattern& type) {
+  if (!network_list_sorted_)
+    SortNetworkList();  // Sort to ensure visible networks are listed first.
 
-  // Active networks are always listed first by Shill so no need to sort.
+  const NetworkState* connected_network = nullptr;
   for (auto iter = network_list_.begin(); iter != network_list_.end(); ++iter) {
     const NetworkState* network = (*iter)->AsNetworkState();
     DCHECK(network);
@@ -428,7 +431,7 @@
 }
 
 std::string NetworkStateHandler::FormattedHardwareAddressForType(
-    const NetworkTypePattern& type) const {
+    const NetworkTypePattern& type) {
   const NetworkState* network = ConnectedNetworkByType(type);
   if (network && network->type() == kTypeTether) {
     // If this is a Tether network, get the MAC address corresponding to that
@@ -515,7 +518,7 @@
                                                      size_t limit,
                                                      NetworkStateList* list) {
   DCHECK(list);
-  DCHECK(limit != 0);
+  DCHECK_NE(0U, limit);
   if (!IsTechnologyEnabled(NetworkTypePattern::Tether()))
     return;
 
@@ -1301,9 +1304,7 @@
       cellular.push_back(std::move(*iter));
       continue;
     }
-    // Ethernet networks are always considered active.
-    if (network->IsConnectingOrConnected() ||
-        NetworkTypePattern::Ethernet().MatchesType(network->type())) {
+    if (network->IsConnectingOrConnected()) {
       active.push_back(std::move(*iter));
       continue;
     }
diff --git a/chromeos/network/network_state_handler.h b/chromeos/network/network_state_handler.h
index 3027a78..fe8a2d3 100644
--- a/chromeos/network/network_state_handler.h
+++ b/chromeos/network/network_state_handler.h
@@ -161,8 +161,7 @@
   const NetworkState* DefaultNetwork() const;
 
   // Returns the primary connected network of matching |type|, otherwise NULL.
-  const NetworkState* ConnectedNetworkByType(
-      const NetworkTypePattern& type) const;
+  const NetworkState* ConnectedNetworkByType(const NetworkTypePattern& type);
 
   // Like ConnectedNetworkByType() but returns a connecting network or NULL.
   const NetworkState* ConnectingNetworkByType(
@@ -175,8 +174,7 @@
 
   // Returns the aa:bb formatted hardware (MAC) address for the first connected
   // network matching |type|, or an empty string if none is connected.
-  std::string FormattedHardwareAddressForType(
-      const NetworkTypePattern& type) const;
+  std::string FormattedHardwareAddressForType(const NetworkTypePattern& type);
 
   // Convenience method to call GetNetworkListByType(visible=true).
   void GetVisibleNetworkListByType(const NetworkTypePattern& type,
diff --git a/components/flags_ui/resources/flags.css b/components/flags_ui/resources/flags.css
index 400875b..e119df0 100644
--- a/components/flags_ui/resources/flags.css
+++ b/components/flags_ui/resources/flags.css
@@ -363,7 +363,10 @@
   /* Bottom padding should be greater than evaluated height of needs-restart */
   padding-bottom: 200px;
   position: relative;
+  /* iOS does not show unsupported experiments. */
+<if expr="not is_ios">
   width: 200%;
+</if>
 }
 
 .tabs li:nth-child(2) .tab-content {
diff --git a/components/flags_ui/resources/flags.html b/components/flags_ui/resources/flags.html
index 3f7761e..36dda60 100644
--- a/components/flags_ui/resources/flags.html
+++ b/components/flags_ui/resources/flags.html
@@ -2,16 +2,18 @@
 <html dir="$i18n{textdirection}" lang="$i18n{language}">
 <head>
 <meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
 <if expr="not is_ios">
-<link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
 </if>
+
 <link rel="stylesheet" href="flags.css">
 
 <if expr="is_ios">
-<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-<!-- TODO(crbug.com/487000): Remove this once injected by web. -->
-<script src="chrome://resources/js/ios/web_ui.js"></script>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1, maximum-scale=1">
+  <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+  <!-- TODO(crbug.com/487000): Remove this once injected by web. -->
+  <script src="chrome://resources/js/ios/web_ui.js"></script>
 </if>
 
 <script src="chrome://resources/js/util.js"></script>
diff --git a/components/history/core/browser/typed_url_model_type_controller.cc b/components/history/core/browser/typed_url_model_type_controller.cc
index 18398bb..b158782 100644
--- a/components/history/core/browser/typed_url_model_type_controller.cc
+++ b/components/history/core/browser/typed_url_model_type_controller.cc
@@ -25,8 +25,8 @@
 // the tasks we want to run.
 class RunTaskOnHistoryThread : public HistoryDBTask {
  public:
-  explicit RunTaskOnHistoryThread(const ModelTypeController::BridgeTask& task)
-      : task_(task) {}
+  explicit RunTaskOnHistoryThread(ModelTypeController::BridgeTask task)
+      : task_(std::move(task)) {}
 
   bool RunOnDBThread(HistoryBackend* backend, HistoryDatabase* db) override {
     // Invoke the task, then free it immediately so we don't keep a reference
@@ -34,13 +34,9 @@
     // main thread - we want to release references as soon as possible to avoid
     // keeping them around too long during shutdown.
     TypedURLSyncBridge* bridge = backend->GetTypedURLSyncBridge();
-    if (!bridge) {
-      NOTREACHED();
-      return true;
-    }
+    DCHECK(bridge);
 
-    task_.Run(bridge);
-    task_.Reset();
+    std::move(task_).Run(bridge);
     return true;
   }
 
@@ -73,7 +69,7 @@
 }
 
 void TypedURLModelTypeController::PostBridgeTask(const base::Location& location,
-                                                 const BridgeTask& task) {
+                                                 BridgeTask task) {
   history::HistoryService* history = sync_client()->GetHistoryService();
   if (!history) {
     // History must be disabled - don't start.
@@ -81,8 +77,9 @@
     return;
   }
 
-  history->ScheduleDBTask(std::make_unique<RunTaskOnHistoryThread>(task),
-                          &task_tracker_);
+  history->ScheduleDBTask(
+      std::make_unique<RunTaskOnHistoryThread>(std::move(task)),
+      &task_tracker_);
 }
 
 void TypedURLModelTypeController::OnSavingBrowserHistoryDisabledChanged() {
diff --git a/components/history/core/browser/typed_url_model_type_controller.h b/components/history/core/browser/typed_url_model_type_controller.h
index 0ef777d..d528d40 100644
--- a/components/history/core/browser/typed_url_model_type_controller.h
+++ b/components/history/core/browser/typed_url_model_type_controller.h
@@ -23,8 +23,7 @@
 
  private:
   // syncer::ModelTypeController implementation.
-  void PostBridgeTask(const base::Location& location,
-                      const BridgeTask& task) override;
+  void PostBridgeTask(const base::Location& location, BridgeTask task) override;
 
   void OnSavingBrowserHistoryDisabledChanged();
 
diff --git a/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc b/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc
index 92bf0d2..53151cf 100644
--- a/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc
+++ b/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc
@@ -21,10 +21,7 @@
 namespace ntp_snippets {
 namespace test {
 
-FakeOfflinePageModel::FakeOfflinePageModel() {
-  // This is to match StubOfflinePageModel behavior.
-  is_loaded_ = true;
-}
+FakeOfflinePageModel::FakeOfflinePageModel() = default;
 
 FakeOfflinePageModel::~FakeOfflinePageModel() = default;
 
@@ -65,14 +62,6 @@
   return &items_;
 }
 
-bool FakeOfflinePageModel::is_loaded() const {
-  return is_loaded_;
-}
-
-void FakeOfflinePageModel::set_is_loaded(bool value) {
-  is_loaded_ = value;
-}
-
 OfflinePageItem CreateDummyOfflinePageItem(
     int id,
     const offline_pages::ClientId& client_id) {
diff --git a/components/ntp_snippets/offline_pages/offline_pages_test_utils.h b/components/ntp_snippets/offline_pages/offline_pages_test_utils.h
index bbd1ed7..56657af0 100644
--- a/components/ntp_snippets/offline_pages/offline_pages_test_utils.h
+++ b/components/ntp_snippets/offline_pages/offline_pages_test_utils.h
@@ -30,12 +30,8 @@
   const std::vector<offline_pages::OfflinePageItem>& items();
   std::vector<offline_pages::OfflinePageItem>* mutable_items();
 
-  bool is_loaded() const override;
-  void set_is_loaded(bool value);
-
  private:
   std::vector<offline_pages::OfflinePageItem> items_;
-  bool is_loaded_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeOfflinePageModel);
 };
diff --git a/components/offline_pages/core/model/offline_page_model_taskified.cc b/components/offline_pages/core/model/offline_page_model_taskified.cc
index bbb612f0..7299e279 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.cc
+++ b/components/offline_pages/core/model/offline_page_model_taskified.cc
@@ -254,13 +254,6 @@
   return archive_manager_->GetPersistentArchivesDir();
 }
 
-bool OfflinePageModelTaskified::is_loaded() const {
-  NOTIMPLEMENTED();
-  // TODO(romax): Remove the method after switch. No longer needed with
-  // DB.Execute pattern.
-  return false;
-}
-
 ClientPolicyController* OfflinePageModelTaskified::GetPolicyController() {
   return policy_controller_.get();
 }
diff --git a/components/offline_pages/core/model/offline_page_model_taskified.h b/components/offline_pages/core/model/offline_page_model_taskified.h
index 747070b4..d924c06 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.h
+++ b/components/offline_pages/core/model/offline_page_model_taskified.h
@@ -109,8 +109,6 @@
 
   ClientPolicyController* GetPolicyController() override;
 
-  bool is_loaded() const override;
-
   OfflineEventLogger* GetLogger() override;
 
   // Methods for testing only:
diff --git a/components/offline_pages/core/offline_page_model.h b/components/offline_pages/core/offline_page_model.h
index 2b8dfbe..63f999d 100644
--- a/components/offline_pages/core/offline_page_model.h
+++ b/components/offline_pages/core/offline_page_model.h
@@ -201,9 +201,6 @@
   virtual const base::FilePath& GetArchiveDirectory(
       const std::string& name_space) const = 0;
 
-  // TODO(dougarnett): Remove this and its uses.
-  virtual bool is_loaded() const = 0;
-
   // Returns the logger. Ownership is retained by the model.
   virtual OfflineEventLogger* GetLogger() = 0;
 };
diff --git a/components/offline_pages/core/offline_page_model_impl.cc b/components/offline_pages/core/offline_page_model_impl.cc
index 1932493..a5c7bfd1 100644
--- a/components/offline_pages/core/offline_page_model_impl.cc
+++ b/components/offline_pages/core/offline_page_model_impl.cc
@@ -709,10 +709,6 @@
   return storage_manager_.get();
 }
 
-bool OfflinePageModelImpl::is_loaded() const {
-  return is_loaded_;
-}
-
 OfflineEventLogger* OfflinePageModelImpl::GetLogger() {
   return &offline_event_logger_;
 }
diff --git a/components/offline_pages/core/offline_page_model_impl.h b/components/offline_pages/core/offline_page_model_impl.h
index a00f407..7941c179 100644
--- a/components/offline_pages/core/offline_page_model_impl.h
+++ b/components/offline_pages/core/offline_page_model_impl.h
@@ -115,8 +115,6 @@
 
   OfflinePageStorageManager* GetStorageManager();
 
-  bool is_loaded() const override;
-
   OfflineEventLogger* GetLogger() override;
 
   void set_skip_clearing_original_url_for_testing() {
@@ -130,6 +128,7 @@
 
  private:
   FRIEND_TEST_ALL_PREFIXES(OfflinePageModelImplTest, MarkPageForDeletion);
+  FRIEND_TEST_ALL_PREFIXES(OfflinePageModelImplTest, StoreLoadFailurePersists);
 
   typedef std::vector<std::unique_ptr<OfflinePageArchiver>> PendingArchivers;
 
diff --git a/components/offline_pages/core/offline_page_model_impl_unittest.cc b/components/offline_pages/core/offline_page_model_impl_unittest.cc
index 906c176c..30fc4a0 100644
--- a/components/offline_pages/core/offline_page_model_impl_unittest.cc
+++ b/components/offline_pages/core/offline_page_model_impl_unittest.cc
@@ -1468,7 +1468,7 @@
 
   // Model will 'load' but the store underneath it is not functional and
   // will silently fail all sql operations.
-  EXPECT_TRUE(model()->is_loaded());
+  EXPECT_TRUE(model()->is_loaded_);
   EXPECT_EQ(StoreState::FAILED_LOADING, GetStore()->state());
   EXPECT_EQ(0UL, offline_pages.size());
 
diff --git a/components/offline_pages/core/stub_offline_page_model.cc b/components/offline_pages/core/stub_offline_page_model.cc
index 67e51ab..b640a6f 100644
--- a/components/offline_pages/core/stub_offline_page_model.cc
+++ b/components/offline_pages/core/stub_offline_page_model.cc
@@ -63,9 +63,6 @@
 ClientPolicyController* StubOfflinePageModel::GetPolicyController() {
   return &policy_controller_;
 }
-bool StubOfflinePageModel::is_loaded() const {
-  return true;
-}
 OfflineEventLogger* StubOfflinePageModel::GetLogger() {
   return nullptr;
 }
diff --git a/components/offline_pages/core/stub_offline_page_model.h b/components/offline_pages/core/stub_offline_page_model.h
index edd496c..5ebf05e6 100644
--- a/components/offline_pages/core/stub_offline_page_model.h
+++ b/components/offline_pages/core/stub_offline_page_model.h
@@ -65,7 +65,6 @@
   const base::FilePath& GetArchiveDirectory(
       const std::string& name_space) const override;
   ClientPolicyController* GetPolicyController() override;
-  bool is_loaded() const override;
   OfflineEventLogger* GetLogger() override;
 
  private:
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index 2800707..0d97840 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -895,6 +895,11 @@
     "engine_impl/js_mutation_event_observer_unittest.cc",
     "engine_impl/js_sync_encryption_handler_observer_unittest.cc",
     "engine_impl/js_sync_manager_observer_unittest.cc",
+    "engine_impl/loopback_server/loopback_server_unittest.cc",
+    "engine_impl/loopback_server/persistent_bookmark_entity_unittest.cc",
+    "engine_impl/loopback_server/persistent_permanent_entity_unittest.cc",
+    "engine_impl/loopback_server/persistent_tombstone_entity_unittest.cc",
+    "engine_impl/loopback_server/persistent_unique_client_entity_unittest.cc",
     "engine_impl/model_type_registry_unittest.cc",
     "engine_impl/model_type_worker_unittest.cc",
     "engine_impl/net/sync_server_connection_manager_unittest.cc",
@@ -997,10 +1002,6 @@
     sources -= [ "engine/net/http_bridge_unittest.cc" ]
   }
 
-  if (is_win) {
-    sources += [ "engine_impl/loopback_server/loopback_server_unittest.cc" ]
-  }
-
   if (!is_ios) {
     sources += [ "driver/sync_policy_handler_unittest.cc" ]
     deps += [
diff --git a/components/sync/driver/model_type_controller.cc b/components/sync/driver/model_type_controller.cc
index c08dfba..83be1bc9 100644
--- a/components/sync/driver/model_type_controller.cc
+++ b/components/sync/driver/model_type_controller.cc
@@ -52,11 +52,10 @@
   return arg;
 }
 
-void RunBridgeTask(const BridgeProvider& bridge_provider,
-                   const BridgeTask& task) {
-  if (base::WeakPtr<ModelTypeSyncBridge> bridge = bridge_provider.Run()) {
-    task.Run(bridge.get());
-  }
+void RunBridgeTask(BridgeProvider bridge_provider, BridgeTask task) {
+  base::WeakPtr<ModelTypeSyncBridge> bridge = std::move(bridge_provider).Run();
+  if (bridge.get())
+    std::move(task).Run(bridge.get());
 }
 
 }  // namespace
@@ -263,10 +262,11 @@
 }
 
 void ModelTypeController::PostBridgeTask(const base::Location& location,
-                                         const BridgeTask& task) {
+                                         BridgeTask task) {
   DCHECK(model_thread_);
   model_thread_->PostTask(
-      location, base::Bind(&RunBridgeTask, GetBridgeProvider(), task));
+      location, base::Bind(&RunBridgeTask, base::Passed(GetBridgeProvider()),
+                           base::Passed(std::move(task))));
 }
 
 }  // namespace syncer
diff --git a/components/sync/driver/model_type_controller.h b/components/sync/driver/model_type_controller.h
index 02cfb77..ae9007d 100644
--- a/components/sync/driver/model_type_controller.h
+++ b/components/sync/driver/model_type_controller.h
@@ -27,8 +27,9 @@
 // DataTypeController implementation for Unified Sync and Storage model types.
 class ModelTypeController : public DataTypeController {
  public:
-  using BridgeProvider = base::Callback<base::WeakPtr<ModelTypeSyncBridge>()>;
-  using BridgeTask = base::Callback<void(ModelTypeSyncBridge*)>;
+  using BridgeProvider =
+      base::OnceCallback<base::WeakPtr<ModelTypeSyncBridge>()>;
+  using BridgeTask = base::OnceCallback<void(ModelTypeSyncBridge*)>;
 
   ModelTypeController(
       ModelType type,
@@ -76,8 +77,7 @@
 
   // Post the given task that requires the bridge object to run to the model
   // thread, where the bridge lives.
-  virtual void PostBridgeTask(const base::Location& location,
-                              const BridgeTask& task);
+  virtual void PostBridgeTask(const base::Location& location, BridgeTask task);
 
   // The sync client, which provides access to this type's ModelTypeSyncBridge.
   SyncClient* const sync_client_;
diff --git a/components/sync/engine_impl/loopback_server/loopback_server.cc b/components/sync/engine_impl/loopback_server/loopback_server.cc
index 756a6fc..5d049175 100644
--- a/components/sync/engine_impl/loopback_server/loopback_server.cc
+++ b/components/sync/engine_impl/loopback_server/loopback_server.cc
@@ -255,6 +255,9 @@
       *server_status = HttpResponse::SYNC_SERVER_ERROR;
       *response_code = net::ERR_FAILED;
       *response = string();
+      UMA_HISTOGRAM_ENUMERATION(
+          "Sync.Local.RequestTypeOnError", message.message_contents(),
+          sync_pb::ClientToServerMessage_Contents_Contents_MAX);
       return;
     }
 
@@ -289,9 +292,8 @@
   }
 
   bool send_encryption_keys_based_on_nigori = false;
-  for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
-       ++it) {
-    const LoopbackServerEntity& entity = *it->second;
+  for (const auto& kv : entities_) {
+    const LoopbackServerEntity& entity = *kv.second;
     if (sieve->ClientWantsItem(entity)) {
       sync_pb::SyncEntity* response_entity = response->add_entries();
       entity.SerializeAsProto(response_entity);
@@ -306,9 +308,8 @@
 
   if (send_encryption_keys_based_on_nigori ||
       get_updates.need_encryption_key()) {
-    for (vector<string>::iterator it = keystore_keys_.begin();
-         it != keystore_keys_.end(); ++it) {
-      response->add_encryption_keys(*it);
+    for (const string& key : keystore_keys_) {
+      response->add_encryption_keys(key);
     }
   }
 
@@ -329,7 +330,9 @@
   syncer::ModelType type = GetModelType(client_entity);
   if (client_entity.deleted()) {
     entity = PersistentTombstoneEntity::CreateFromEntity(client_entity);
-    DeleteChildren(client_entity.id_string());
+    if (entity) {
+      DeleteChildren(client_entity.id_string());
+    }
   } else if (type == syncer::NIGORI) {
     // NIGORI is the only permanent item type that should be updated by the
     // client.
@@ -351,6 +354,9 @@
     entity = PersistentUniqueClientEntity::CreateFromEntity(client_entity);
   }
 
+  if (!entity)
+    return string();
+
   const std::string id = entity->GetId();
   SaveEntity(std::move(entity));
   BuildEntryResponseForSuccessfulCommit(id, entry_response);
@@ -398,11 +404,11 @@
   return IsChild(entity.GetParentId(), potential_parent_id);
 }
 
-void LoopbackServer::DeleteChildren(const string& id) {
+void LoopbackServer::DeleteChildren(const string& parent_id) {
   std::vector<sync_pb::SyncEntity> tombstones;
-  // Find all the children of id.
+  // Find all the children of |parent_id|.
   for (auto& entity : entities_) {
-    if (IsChild(entity.first, id)) {
+    if (IsChild(entity.first, parent_id)) {
       sync_pb::SyncEntity proto;
       entity.second->SerializeAsProto(&proto);
       tombstones.emplace_back(proto);
@@ -423,12 +429,10 @@
   ModelTypeSet committed_model_types;
 
   // TODO(pvalenzuela): Add validation of CommitMessage.entries.
-  ::google::protobuf::RepeatedPtrField<sync_pb::SyncEntity>::const_iterator it;
-  for (it = commit.entries().begin(); it != commit.entries().end(); ++it) {
+  for (const sync_pb::SyncEntity& client_entity : commit.entries()) {
     sync_pb::CommitResponse_EntryResponse* entry_response =
         response->add_entryresponse();
 
-    sync_pb::SyncEntity client_entity = *it;
     string parent_id = client_entity.parent_id_string();
     if (client_to_server_ids.find(parent_id) != client_to_server_ids.end()) {
       parent_id = client_to_server_ids[parent_id];
@@ -474,9 +478,8 @@
     ModelType model_type) {
   DCHECK(thread_checker_.CalledOnValidThread());
   std::vector<sync_pb::SyncEntity> sync_entities;
-  for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
-       ++it) {
-    const LoopbackServerEntity& entity = *it->second;
+  for (const auto& kv : entities_) {
+    const LoopbackServerEntity& entity = *kv.second;
     if (!(entity.IsDeleted() || entity.IsPermanent()) &&
         entity.GetModelType() == model_type) {
       sync_pb::SyncEntity sync_entity;
@@ -500,9 +503,8 @@
                     std::make_unique<base::ListValue>());
   }
 
-  for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
-       ++it) {
-    const LoopbackServerEntity& entity = *it->second;
+  for (const auto& kv : entities_) {
+    const LoopbackServerEntity& entity = *kv.second;
     if (entity.IsDeleted() || entity.IsPermanent()) {
       // Tombstones are ignored as they don't represent current data. Folders
       // are also ignored as current verification infrastructure does not
@@ -586,10 +588,14 @@
   for (int i = 0; i < proto.keystore_keys_size(); ++i)
     keystore_keys_.push_back(proto.keystore_keys(i));
   for (int i = 0; i < proto.entities_size(); ++i) {
-    entities_[proto.entities(i).entity().id_string()] =
+    std::unique_ptr<LoopbackServerEntity> entity =
         LoopbackServerEntity::CreateEntityFromProto(proto.entities(i));
+    // Silently drop entities that cannot be successfully deserialized.
+    if (entity)
+      entities_[proto.entities(i).entity().id_string()] = std::move(entity);
   }
 
+  // Report success regardless of if some entities were dropped.
   return true;
 }
 
@@ -615,8 +621,7 @@
     if (base::ReadFileToString(filename, &serialized)) {
       sync_pb::LoopbackServerProto proto;
       if (serialized.length() > 0 && proto.ParseFromString(serialized)) {
-        DeSerializeState(proto);
-        return true;
+        return DeSerializeState(proto);
       } else {
         LOG(ERROR) << "Loopback sync can not parse the persistent state file.";
         return false;
diff --git a/components/sync/engine_impl/loopback_server/loopback_server.h b/components/sync/engine_impl/loopback_server/loopback_server.h
index 0f5d5d06..cecc8f2 100644
--- a/components/sync/engine_impl/loopback_server/loopback_server.h
+++ b/components/sync/engine_impl/loopback_server/loopback_server.h
@@ -113,8 +113,8 @@
   bool IsChild(const std::string& id, const std::string& potential_parent_id);
 
   // Creates and saves tombstones for all children of the entity with the given
-  // |id|. A tombstone is not created for the entity itself.
-  void DeleteChildren(const std::string& id);
+  // |parent_id|. A tombstone is not created for the entity itself.
+  void DeleteChildren(const std::string& parent_id);
 
   // Updates the |entity| to a new version and increments the version counter
   // that the server uses to assign versions.
diff --git a/components/sync/engine_impl/loopback_server/loopback_server_entity.cc b/components/sync/engine_impl/loopback_server/loopback_server_entity.cc
index 340b7f4..428ec8b 100644
--- a/components/sync/engine_impl/loopback_server/loopback_server_entity.cc
+++ b/components/sync/engine_impl/loopback_server/loopback_server_entity.cc
@@ -60,7 +60,7 @@
     case sync_pb::LoopbackServerEntity_Type_UNKNOWN:
       NOTREACHED() << "Unknown type encountered";
   }
-  return std::unique_ptr<LoopbackServerEntity>();
+  return nullptr;
 }
 
 const std::string& LoopbackServerEntity::GetId() const {
diff --git a/components/sync/engine_impl/loopback_server/loopback_server_entity.h b/components/sync/engine_impl/loopback_server/loopback_server_entity.h
index 2ad2547..c60bc71 100644
--- a/components/sync/engine_impl/loopback_server/loopback_server_entity.h
+++ b/components/sync/engine_impl/loopback_server/loopback_server_entity.h
@@ -61,11 +61,11 @@
   virtual void SerializeAsLoopbackServerEntity(
       sync_pb::LoopbackServerEntity* entity) const;
 
- protected:
   // Extracts the ModelType from |id|. If |id| is malformed or does not contain
   // a valid ModelType, UNSPECIFIED is returned.
   static syncer::ModelType GetModelTypeFromId(const std::string& id);
 
+ protected:
   LoopbackServerEntity(const std::string& id,
                        const syncer::ModelType& model_type,
                        int64_t version,
diff --git a/components/sync/engine_impl/loopback_server/loopback_server_unittest.cc b/components/sync/engine_impl/loopback_server/loopback_server_unittest.cc
index 99b4ba5d..298e7da 100644
--- a/components/sync/engine_impl/loopback_server/loopback_server_unittest.cc
+++ b/components/sync/engine_impl/loopback_server/loopback_server_unittest.cc
@@ -13,9 +13,61 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using sync_pb::ClientToServerMessage;
+using sync_pb::ClientToServerResponse;
+using sync_pb::EntitySpecifics;
+using sync_pb::SyncEnums;
+using sync_pb::SyncEntity;
 
 namespace syncer {
 
+namespace {
+
+const char kUrl1[] = "http://www.one.com";
+const char kUrl2[] = "http://www.two.com";
+const char kUrl3[] = "http://www.three.com";
+const char kBookmarkBar[] = "bookmark_bar";
+
+SyncEntity NewBookmarkEntity(const std::string& url,
+                             const std::string& parent_id) {
+  SyncEntity entity;
+  entity.mutable_specifics()->mutable_bookmark()->set_url(url);
+  entity.set_parent_id_string(parent_id);
+  return entity;
+}
+
+SyncEntity UpdatedBookmarkEntity(const std::string& url,
+                                 const std::string& id,
+                                 const std::string& parent_id,
+                                 int version) {
+  SyncEntity entity;
+  entity.mutable_specifics()->mutable_bookmark()->set_url(url);
+  entity.set_id_string(id);
+  entity.set_parent_id_string(parent_id);
+  entity.set_version(version);
+  return entity;
+}
+
+SyncEntity DeletedBookmarkEntity(const std::string& id, int version) {
+  SyncEntity entity;
+  entity.mutable_specifics()->mutable_bookmark();
+  entity.set_id_string(id);
+  entity.set_deleted(true);
+  entity.set_version(version);
+  return entity;
+}
+
+std::map<std::string, SyncEntity> ResponseToMap(
+    const ClientToServerResponse& response) {
+  EXPECT_TRUE(response.has_get_updates());
+  std::map<std::string, SyncEntity> results;
+  for (const SyncEntity& entity : response.get_updates().entries()) {
+    results[entity.id_string()] = entity;
+  }
+  return results;
+}
+
+}  // namespace
+
 class LoopbackServerTest : public testing::Test {
  public:
   void SetUp() override {
@@ -24,31 +76,61 @@
         std::make_unique<LoopbackConnectionManager>(&signal_, persistent_file_);
   }
 
-  static bool CallPostAndProcessHeaders(
-      ServerConnectionManager* scm,
-      SyncCycle* cycle,
-      const sync_pb::ClientToServerMessage& msg,
-      sync_pb::ClientToServerResponse* response) {
+  static bool CallPostAndProcessHeaders(ServerConnectionManager* scm,
+                                        SyncCycle* cycle,
+                                        const ClientToServerMessage& msg,
+                                        ClientToServerResponse* response) {
     return SyncerProtoUtil::PostAndProcessHeaders(scm, cycle, msg, response);
   }
 
  protected:
-  ClientToServerMessage CreateCommitMessage() {
-    ClientToServerMessage msg;
-    SyncerProtoUtil::SetProtocolVersion(&msg);
-    msg.set_share("required");
-    msg.set_message_contents(ClientToServerMessage::COMMIT);
-    msg.set_invalidator_client_id("client_id");
-    auto* commit = msg.mutable_commit();
+  ClientToServerResponse GetUpdatesForType(int field_number) {
+    ClientToServerMessage request;
+    SyncerProtoUtil::SetProtocolVersion(&request);
+    request.set_share("required");
+    request.set_message_contents(ClientToServerMessage::GET_UPDATES);
+    request.mutable_get_updates()->add_from_progress_marker()->set_data_type_id(
+        field_number);
+
+    ClientToServerResponse response;
+    EXPECT_TRUE(
+        CallPostAndProcessHeaders(lcm_.get(), nullptr, request, &response));
+    EXPECT_EQ(SyncEnums::SUCCESS, response.error_code());
+    return response;
+  }
+
+  ClientToServerMessage SingleEntryCommit(
+      const std::vector<SyncEntity>& entity_vector) {
+    ClientToServerMessage request;
+    SyncerProtoUtil::SetProtocolVersion(&request);
+    request.set_share("required");
+    request.set_message_contents(ClientToServerMessage::COMMIT);
+    request.set_invalidator_client_id("client_id");
+    auto* commit = request.mutable_commit();
     commit->set_cache_guid("cache_guid");
-    auto* entry = commit->add_entries();
-    // Not quite well formed but enough to fool the server.
-    entry->set_parent_id_string("bookmark_bar");
-    entry->set_id_string("id_string");
-    entry->set_version(0);
-    entry->set_name("google");
-    entry->mutable_specifics()->mutable_bookmark()->set_url("http://google.de");
-    return msg;
+    for (const SyncEntity& entity : entity_vector) {
+      *commit->add_entries() = entity;
+    }
+    return request;
+  }
+
+  std::string CommitVerifySuccess(const SyncEntity& entity) {
+    ClientToServerMessage request = SingleEntryCommit({entity});
+    ClientToServerResponse response;
+    EXPECT_TRUE(
+        CallPostAndProcessHeaders(lcm_.get(), nullptr, request, &response));
+    EXPECT_EQ(SyncEnums::SUCCESS, response.error_code());
+    EXPECT_TRUE(response.has_commit());
+    return response.commit().entryresponse(0).id_string();
+  }
+
+  void CommitVerifyFailure(const SyncEntity& entity) {
+    ClientToServerMessage request = SingleEntryCommit({entity});
+    ClientToServerResponse response;
+    EXPECT_FALSE(
+        CallPostAndProcessHeaders(lcm_.get(), nullptr, request, &response));
+    EXPECT_NE(SyncEnums::SUCCESS, response.error_code());
+    EXPECT_FALSE(response.has_commit());
   }
 
   CancelationSignal signal_;
@@ -63,25 +145,16 @@
   msg.set_store_birthday("not_your_birthday");
   msg.set_message_contents(ClientToServerMessage::GET_UPDATES);
   msg.mutable_get_updates()->add_from_progress_marker()->set_data_type_id(
-      sync_pb::EntitySpecifics::kBookmarkFieldNumber);
-  sync_pb::ClientToServerResponse response;
+      EntitySpecifics::kBookmarkFieldNumber);
+  ClientToServerResponse response;
 
-  EXPECT_TRUE(CallPostAndProcessHeaders(lcm_.get(), NULL, msg, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::NOT_MY_BIRTHDAY, response.error_code());
+  EXPECT_TRUE(CallPostAndProcessHeaders(lcm_.get(), nullptr, msg, &response));
+  EXPECT_EQ(SyncEnums::NOT_MY_BIRTHDAY, response.error_code());
 }
 
 TEST_F(LoopbackServerTest, GetUpdateCommand) {
-  ClientToServerMessage msg;
-  SyncerProtoUtil::SetProtocolVersion(&msg);
-  msg.set_share("required");
-  msg.set_message_contents(ClientToServerMessage::GET_UPDATES);
-  msg.mutable_get_updates()->add_from_progress_marker()->set_data_type_id(
-      sync_pb::EntitySpecifics::kBookmarkFieldNumber);
-  sync_pb::ClientToServerResponse response;
-
-  EXPECT_TRUE(CallPostAndProcessHeaders(lcm_.get(), NULL, msg, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
-  ASSERT_TRUE(response.has_get_updates());
+  ClientToServerResponse response =
+      GetUpdatesForType(EntitySpecifics::kBookmarkFieldNumber);
   // Expect to see the three top-level folders in this update already.
   EXPECT_EQ(3, response.get_updates().entries_size());
 }
@@ -91,31 +164,58 @@
   SyncerProtoUtil::SetProtocolVersion(&msg);
   msg.set_share("required");
   msg.set_message_contents(ClientToServerMessage::CLEAR_SERVER_DATA);
-  sync_pb::ClientToServerResponse response;
+  ClientToServerResponse response;
 
-  EXPECT_TRUE(CallPostAndProcessHeaders(lcm_.get(), NULL, msg, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
+  EXPECT_TRUE(CallPostAndProcessHeaders(lcm_.get(), nullptr, msg, &response));
+  EXPECT_EQ(SyncEnums::SUCCESS, response.error_code());
   EXPECT_TRUE(response.has_clear_server_data());
 }
 
 TEST_F(LoopbackServerTest, CommitCommand) {
-  ClientToServerMessage msg = CreateCommitMessage();
-  sync_pb::ClientToServerResponse response;
+  CommitVerifySuccess(NewBookmarkEntity(kUrl1, kBookmarkBar));
+}
 
-  EXPECT_TRUE(CallPostAndProcessHeaders(lcm_.get(), NULL, msg, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
-  EXPECT_TRUE(response.has_commit());
+TEST_F(LoopbackServerTest, CommitFailureNoTag) {
+  // Non-bookmarks and non-commit only types must have a
+  // client_defined_unique_tag, which we don't set.
+  SyncEntity entity;
+  entity.mutable_specifics()->mutable_preference();
+  CommitVerifyFailure(entity);
+}
+
+TEST_F(LoopbackServerTest, CommitBookmarkTombstoneSuccess) {
+  std::string id1 = CommitVerifySuccess(NewBookmarkEntity(kUrl1, kBookmarkBar));
+  std::string id2 = CommitVerifySuccess(NewBookmarkEntity(kUrl2, id1));
+  std::string id3 = CommitVerifySuccess(NewBookmarkEntity(kUrl3, kBookmarkBar));
+
+  // Because 2 is a child of 1, deleting 1 will also delete 2.
+  CommitVerifySuccess(DeletedBookmarkEntity(id1, 10));
+
+  std::map<std::string, SyncEntity> bookmarks =
+      ResponseToMap(GetUpdatesForType(EntitySpecifics::kBookmarkFieldNumber));
+  EXPECT_TRUE(bookmarks[id1].deleted());
+  EXPECT_TRUE(bookmarks[id2].deleted());
+  EXPECT_FALSE(bookmarks[id3].deleted());
+}
+
+TEST_F(LoopbackServerTest, CommitBookmarkTombstoneFailure) {
+  std::string id1 = CommitVerifySuccess(NewBookmarkEntity(kUrl1, kBookmarkBar));
+  std::string id2 = CommitVerifySuccess(NewBookmarkEntity(kUrl2, "9" + id1));
+
+  // This write is going to fail, the id is supposed to encode the model type as
+  // as prefix, by adding 9 we're creating a fake model type.
+  SyncEntity entity = DeletedBookmarkEntity("9" + id1, 1);
+  CommitVerifyFailure(entity);
+
+  std::map<std::string, SyncEntity> bookmarks =
+      ResponseToMap(GetUpdatesForType(EntitySpecifics::kBookmarkFieldNumber));
+  EXPECT_FALSE(bookmarks[id1].deleted());
+  // This is the point of this test, making sure the child doesn't get deleted.
+  EXPECT_FALSE(bookmarks[id2].deleted());
 }
 
 TEST_F(LoopbackServerTest, LoadSavedState) {
-  ClientToServerMessage commit_msg = CreateCommitMessage();
-
-  sync_pb::ClientToServerResponse response;
-
-  EXPECT_TRUE(
-      CallPostAndProcessHeaders(lcm_.get(), NULL, commit_msg, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
-  EXPECT_TRUE(response.has_commit());
+  std::string id = CommitVerifySuccess(NewBookmarkEntity(kUrl1, kBookmarkBar));
 
   CancelationSignal signal;
   LoopbackConnectionManager second_user(&signal, persistent_file_);
@@ -126,59 +226,31 @@
   get_updates_msg.set_message_contents(ClientToServerMessage::GET_UPDATES);
   get_updates_msg.mutable_get_updates()
       ->add_from_progress_marker()
-      ->set_data_type_id(sync_pb::EntitySpecifics::kBookmarkFieldNumber);
+      ->set_data_type_id(EntitySpecifics::kBookmarkFieldNumber);
 
-  EXPECT_TRUE(CallPostAndProcessHeaders(&second_user, NULL, get_updates_msg,
+  ClientToServerResponse response;
+  EXPECT_TRUE(CallPostAndProcessHeaders(&second_user, nullptr, get_updates_msg,
                                         &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
+  EXPECT_EQ(SyncEnums::SUCCESS, response.error_code());
   ASSERT_TRUE(response.has_get_updates());
   // Expect to see the three top-level folders and the newly added bookmark!
   EXPECT_EQ(4, response.get_updates().entries_size());
+  EXPECT_EQ(1U, ResponseToMap(response).count(id));
 }
 
 TEST_F(LoopbackServerTest, CommitCommandUpdate) {
-  ClientToServerMessage commit_msg_1 = CreateCommitMessage();
-  sync_pb::ClientToServerResponse response;
+  std::string id = CommitVerifySuccess(NewBookmarkEntity(kUrl1, kBookmarkBar));
+  EXPECT_EQ(1U, ResponseToMap(
+                    GetUpdatesForType(EntitySpecifics::kBookmarkFieldNumber))
+                    .count(id));
+  CommitVerifySuccess(UpdatedBookmarkEntity(kUrl2, id, "other_bookmarks", 1));
 
-  EXPECT_TRUE(
-      CallPostAndProcessHeaders(lcm_.get(), NULL, commit_msg_1, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
-  EXPECT_TRUE(response.has_commit());
-  const std::string server_id = response.commit().entryresponse(0).id_string();
-
-  ClientToServerMessage get_updates_msg;
-  SyncerProtoUtil::SetProtocolVersion(&get_updates_msg);
-  get_updates_msg.set_share("required");
-  get_updates_msg.set_message_contents(ClientToServerMessage::GET_UPDATES);
-  get_updates_msg.mutable_get_updates()
-      ->add_from_progress_marker()
-      ->set_data_type_id(sync_pb::EntitySpecifics::kBookmarkFieldNumber);
-
-  EXPECT_TRUE(
-      CallPostAndProcessHeaders(lcm_.get(), NULL, get_updates_msg, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
-  ASSERT_TRUE(response.has_get_updates());
-  // Expect to see the three top-level folders and the newly added bookmark!
-  EXPECT_EQ(4, response.get_updates().entries_size());
-
-  ClientToServerMessage commit_msg_2 = CreateCommitMessage();
-  auto* entry = commit_msg_2.mutable_commit()->mutable_entries()->Mutable(0);
-  entry->set_id_string(server_id);
-  entry->set_version(1);
-  entry->set_parent_id_string("other_bookmarks");
-  entry->mutable_specifics()->mutable_bookmark()->set_url("http://google.bg");
-
-  EXPECT_TRUE(
-      CallPostAndProcessHeaders(lcm_.get(), NULL, commit_msg_2, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
-  EXPECT_TRUE(response.has_commit());
-
-  EXPECT_TRUE(
-      CallPostAndProcessHeaders(lcm_.get(), NULL, get_updates_msg, &response));
-  EXPECT_EQ(sync_pb::SyncEnums::SUCCESS, response.error_code());
+  ClientToServerResponse response =
+      GetUpdatesForType(EntitySpecifics::kBookmarkFieldNumber);
   ASSERT_TRUE(response.has_get_updates());
   // Expect to see no fifth bookmark!
   EXPECT_EQ(4, response.get_updates().entries_size());
+  EXPECT_EQ(kUrl2, ResponseToMap(response)[id].specifics().bookmark().url());
 }
 
 }  // namespace syncer
diff --git a/components/sync/engine_impl/loopback_server/persistent_bookmark_entity.cc b/components/sync/engine_impl/loopback_server/persistent_bookmark_entity.cc
index b20ea8d..7d156bab 100644
--- a/components/sync/engine_impl/loopback_server/persistent_bookmark_entity.cc
+++ b/components/sync/engine_impl/loopback_server/persistent_bookmark_entity.cc
@@ -5,6 +5,7 @@
 #include "components/sync/engine_impl/loopback_server/persistent_bookmark_entity.h"
 
 #include "base/guid.h"
+#include "base/memory/ptr_util.h"
 
 using std::string;
 
@@ -26,16 +27,17 @@
     const sync_pb::SyncEntity& client_entity,
     const string& parent_id,
     const string& client_guid) {
-  DLOG_IF(WARNING, client_entity.version() == 0)
-      << "Possible roaming store corruption. Should self heal.";
-  DCHECK(IsBookmark(client_entity)) << "The given entity must be a bookmark.";
+  if (!IsBookmark(client_entity)) {
+    DLOG(WARNING) << "The given entity must be a bookmark.";
+    return nullptr;
+  }
 
   const string id =
       LoopbackServerEntity::CreateId(syncer::BOOKMARKS, base::GenerateGUID());
   const string originator_cache_guid = client_guid;
   const string originator_client_item_id = client_entity.id_string();
 
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentBookmarkEntity(
+  return base::WrapUnique(new PersistentBookmarkEntity(
       id, 0, client_entity.name(), originator_cache_guid,
       originator_client_item_id, client_entity.unique_position(),
       client_entity.specifics(), client_entity.folder(), parent_id,
@@ -48,9 +50,14 @@
     const sync_pb::SyncEntity& client_entity,
     const LoopbackServerEntity& current_server_entity,
     const string& parent_id) {
-  DCHECK(client_entity.version() != 0) << "Existing entities must not have a "
-                                       << "version = 0.";
-  DCHECK(IsBookmark(client_entity)) << "The given entity must be a bookmark.";
+  if (client_entity.version() == 0) {
+    DLOG(WARNING) << "Existing entities must not have a version = 0.";
+    return nullptr;
+  }
+  if (!IsBookmark(client_entity)) {
+    DLOG(WARNING) << "The given entity must be a bookmark.";
+    return nullptr;
+  }
 
   const PersistentBookmarkEntity& current_bookmark_entity =
       static_cast<const PersistentBookmarkEntity&>(current_server_entity);
@@ -59,7 +66,9 @@
   const string originator_client_item_id =
       current_bookmark_entity.originator_client_item_id_;
 
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentBookmarkEntity(
+  // Using a version of 0 is okay here as it'll be updated before this entity is
+  // actually saved.
+  return base::WrapUnique(new PersistentBookmarkEntity(
       client_entity.id_string(), 0, client_entity.name(), originator_cache_guid,
       originator_client_item_id, client_entity.unique_position(),
       client_entity.specifics(), client_entity.folder(), parent_id,
@@ -70,9 +79,12 @@
 std::unique_ptr<LoopbackServerEntity>
 PersistentBookmarkEntity::CreateFromEntity(
     const sync_pb::SyncEntity& client_entity) {
-  DCHECK(IsBookmark(client_entity)) << "The given entity must be a bookmark.";
+  if (!IsBookmark(client_entity)) {
+    DLOG(WARNING) << "The given entity must be a bookmark.";
+    return nullptr;
+  }
 
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentBookmarkEntity(
+  return base::WrapUnique(new PersistentBookmarkEntity(
       client_entity.id_string(), client_entity.version(), client_entity.name(),
       client_entity.originator_cache_guid(),
       client_entity.originator_client_item_id(),
diff --git a/components/sync/engine_impl/loopback_server/persistent_bookmark_entity_unittest.cc b/components/sync/engine_impl/loopback_server/persistent_bookmark_entity_unittest.cc
new file mode 100644
index 0000000..e2a9e23
--- /dev/null
+++ b/components/sync/engine_impl/loopback_server/persistent_bookmark_entity_unittest.cc
@@ -0,0 +1,61 @@
+// 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/sync/engine_impl/loopback_server/persistent_bookmark_entity.h"
+
+#include "components/sync/protocol/sync.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+TEST(PersistentBookmarkEntityTest, CreateNew) {
+  sync_pb::SyncEntity entity;
+  entity.mutable_specifics()->mutable_preference();
+  EXPECT_FALSE(
+      PersistentBookmarkEntity::CreateNew(entity, "parent_id", "client_guid"));
+
+  entity.clear_specifics();
+  entity.mutable_specifics()->mutable_bookmark();
+  EXPECT_TRUE(
+      PersistentBookmarkEntity::CreateNew(entity, "parent_id", "client_guid"));
+}
+
+TEST(PersistentBookmarkEntityTest, CreateUpdatedVersion) {
+  sync_pb::SyncEntity client_entity;
+  client_entity.mutable_specifics()->mutable_bookmark();
+  auto server_entity =
+      PersistentBookmarkEntity::CreateFromEntity(client_entity);
+  ASSERT_TRUE(server_entity);
+
+  // Fails with since there's no version
+  ASSERT_FALSE(PersistentBookmarkEntity::CreateUpdatedVersion(
+      client_entity, *server_entity, "parent_id"));
+
+  // And now succeeds that we have a version.
+  client_entity.set_version(1);
+  ASSERT_TRUE(PersistentBookmarkEntity::CreateUpdatedVersion(
+      client_entity, *server_entity, "parent_id"));
+
+  // But fails when not actually a bookmark.
+  client_entity.clear_specifics();
+  client_entity.mutable_specifics()->mutable_preference();
+  ASSERT_FALSE(PersistentBookmarkEntity::CreateUpdatedVersion(
+      client_entity, *server_entity, "parent_id"));
+}
+
+TEST(PersistentBookmarkEntityTest, CreateFromEntity) {
+  sync_pb::SyncEntity entity;
+  entity.mutable_specifics()->mutable_preference();
+  EXPECT_FALSE(PersistentBookmarkEntity::CreateFromEntity(entity));
+
+  entity.clear_specifics();
+  entity.mutable_specifics()->mutable_bookmark();
+  EXPECT_TRUE(PersistentBookmarkEntity::CreateFromEntity(entity));
+}
+
+}  // namespace
+
+}  // namespace syncer
diff --git a/components/sync/engine_impl/loopback_server/persistent_permanent_entity.cc b/components/sync/engine_impl/loopback_server/persistent_permanent_entity.cc
index a0203868..54716b2 100644
--- a/components/sync/engine_impl/loopback_server/persistent_permanent_entity.cc
+++ b/components/sync/engine_impl/loopback_server/persistent_permanent_entity.cc
@@ -12,9 +12,11 @@
 using syncer::ModelType;
 
 namespace {
+
 // The parent tag for children of the root entity. Entities with this parent are
 // referred to as top level enities.
 static const char kRootParentTag[] = "0";
+
 }  // namespace
 
 namespace syncer {
@@ -27,38 +29,52 @@
     const string& server_tag,
     const string& name,
     const string& parent_server_tag) {
-  DCHECK(model_type != syncer::UNSPECIFIED) << "The entity's ModelType is "
-                                            << "invalid.";
-  DCHECK(!server_tag.empty())
-      << "A PersistentPermanentEntity must have a server tag.";
-  DCHECK(!name.empty()) << "The entity must have a non-empty name.";
-  DCHECK(!parent_server_tag.empty())
-      << "A PersistentPermanentEntity must have a parent "
-      << "server tag.";
-  DCHECK(parent_server_tag != kRootParentTag)
-      << "Top-level entities should not "
-      << "be created with this factory.";
+  if (model_type == syncer::UNSPECIFIED) {
+    DLOG(WARNING) << "The entity's ModelType is invalid.";
+    return nullptr;
+  }
+  if (server_tag.empty()) {
+    DLOG(WARNING) << "A PersistentPermanentEntity must have a server tag.";
+    return nullptr;
+  }
+  if (name.empty()) {
+    DLOG(WARNING) << "The entity must have a non-empty name.";
+    return nullptr;
+  }
+  if (parent_server_tag.empty()) {
+    DLOG(WARNING)
+        << "A PersistentPermanentEntity must have a parent server tag.";
+    return nullptr;
+  }
+  if (parent_server_tag == kRootParentTag) {
+    DLOG(WARNING)
+        << "Top-level entities should not be created with this factory.";
+    return nullptr;
+  }
 
   string id = LoopbackServerEntity::CreateId(model_type, server_tag);
   string parent_id =
       LoopbackServerEntity::CreateId(model_type, parent_server_tag);
   sync_pb::EntitySpecifics entity_specifics;
   AddDefaultFieldValue(model_type, &entity_specifics);
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentPermanentEntity(
+  return base::WrapUnique(new PersistentPermanentEntity(
       id, 0, model_type, name, parent_id, server_tag, entity_specifics));
 }
 
 // static
 std::unique_ptr<LoopbackServerEntity> PersistentPermanentEntity::CreateTopLevel(
     const ModelType& model_type) {
-  DCHECK(model_type != syncer::UNSPECIFIED) << "The entity's ModelType is "
-                                            << "invalid.";
+  if (model_type == syncer::UNSPECIFIED) {
+    DLOG(WARNING) << "The entity's ModelType is invalid.";
+    return nullptr;
+  }
+
   string server_tag = syncer::ModelTypeToRootTag(model_type);
   string name = syncer::ModelTypeToString(model_type);
   string id = LoopbackServerEntity::GetTopLevelId(model_type);
   sync_pb::EntitySpecifics entity_specifics;
   AddDefaultFieldValue(model_type, &entity_specifics);
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentPermanentEntity(
+  return base::WrapUnique(new PersistentPermanentEntity(
       id, 0, model_type, name, kRootParentTag, server_tag, entity_specifics));
 }
 
@@ -68,10 +84,12 @@
     const sync_pb::SyncEntity& client_entity,
     const LoopbackServerEntity& current_server_entity) {
   ModelType model_type = current_server_entity.GetModelType();
-  DCHECK(model_type == syncer::NIGORI) << "This factory only supports NIGORI "
-                                       << "entities.";
+  if (model_type != syncer::NIGORI) {
+    DLOG(WARNING) << "This factory only supports NIGORI entities.";
+    return nullptr;
+  }
 
-  return base::WrapUnique<LoopbackServerEntity>(new PersistentPermanentEntity(
+  return base::WrapUnique(new PersistentPermanentEntity(
       current_server_entity.GetId(), current_server_entity.GetVersion(),
       model_type, current_server_entity.GetName(),
       current_server_entity.GetParentId(),
diff --git a/components/sync/engine_impl/loopback_server/persistent_permanent_entity_unittest.cc b/components/sync/engine_impl/loopback_server/persistent_permanent_entity_unittest.cc
new file mode 100644
index 0000000..24f0329
--- /dev/null
+++ b/components/sync/engine_impl/loopback_server/persistent_permanent_entity_unittest.cc
@@ -0,0 +1,53 @@
+// 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/sync/engine_impl/loopback_server/persistent_permanent_entity.h"
+
+#include "components/sync/protocol/sync.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+TEST(PersistentPermanentEntityTest, CreateNew) {
+  ASSERT_FALSE(PersistentPermanentEntity::CreateNew(
+      UNSPECIFIED, "server_tag", "name", "parent_server_tag"));
+  ASSERT_FALSE(PersistentPermanentEntity::CreateNew(PREFERENCES, "", "name",
+                                                    "parent_server_tag"));
+  ASSERT_FALSE(PersistentPermanentEntity::CreateNew(PREFERENCES, "server_tag",
+                                                    "", "parent_server_tag"));
+  ASSERT_FALSE(PersistentPermanentEntity::CreateNew(PREFERENCES, "server_tag",
+                                                    "name", ""));
+  ASSERT_FALSE(PersistentPermanentEntity::CreateNew(PREFERENCES, "server_tag",
+                                                    "name", "0"));
+  ASSERT_TRUE(PersistentPermanentEntity::CreateNew(
+      PREFERENCES, "server_tag", "name", "parent_server_tag"));
+}
+
+TEST(PersistentPermanentEntityTest, CreateTopLevel) {
+  ASSERT_FALSE(PersistentPermanentEntity::CreateTopLevel(UNSPECIFIED));
+  ASSERT_TRUE(PersistentPermanentEntity::CreateTopLevel(PREFERENCES));
+}
+
+TEST(PersistentPermanentEntityTest, CreateUpdatedNigoriEntity) {
+  sync_pb::SyncEntity client_entity;
+  client_entity.mutable_specifics()->mutable_nigori();
+
+  auto preferences_server_entity = PersistentPermanentEntity::CreateNew(
+      PREFERENCES, "server_tag", "name", "parent_server_tag");
+  ASSERT_TRUE(preferences_server_entity);
+  ASSERT_FALSE(PersistentPermanentEntity::CreateUpdatedNigoriEntity(
+      client_entity, *preferences_server_entity));
+
+  auto nigori_server_entity = PersistentPermanentEntity::CreateNew(
+      NIGORI, "server_tag", "name", "parent_server_tag");
+  ASSERT_TRUE(nigori_server_entity);
+  ASSERT_TRUE(PersistentPermanentEntity::CreateUpdatedNigoriEntity(
+      client_entity, *nigori_server_entity));
+}
+
+}  // namespace
+
+}  // namespace syncer
diff --git a/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.cc b/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.cc
index 1571e81a..931983e 100644
--- a/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.cc
+++ b/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.cc
@@ -4,6 +4,8 @@
 
 #include "components/sync/engine_impl/loopback_server/persistent_tombstone_entity.h"
 
+#include "base/memory/ptr_util.h"
+
 using std::string;
 
 using syncer::ModelType;
@@ -15,22 +17,31 @@
 // static
 std::unique_ptr<LoopbackServerEntity>
 PersistentTombstoneEntity::CreateFromEntity(const sync_pb::SyncEntity& entity) {
-  const ModelType model_type = GetModelTypeFromId(entity.id_string());
-  DCHECK_NE(model_type, syncer::UNSPECIFIED)
-      << "Invalid ID was given: " << entity.id_string();
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentTombstoneEntity(
-      entity.id_string(), entity.version(), model_type,
-      entity.client_defined_unique_tag()));
+  return CreateNewInternal(entity.id_string(), entity.version(),
+                           entity.client_defined_unique_tag());
 }
 
 // static
 std::unique_ptr<LoopbackServerEntity> PersistentTombstoneEntity::CreateNew(
     const std::string& id,
     const std::string& client_defined_unique_tag) {
-  const ModelType model_type = GetModelTypeFromId(id);
-  DCHECK_NE(model_type, syncer::UNSPECIFIED) << "Invalid ID was given: " << id;
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentTombstoneEntity(
-      id, 0, model_type, client_defined_unique_tag));
+  return CreateNewInternal(id, 0, client_defined_unique_tag);
+}
+
+// static
+std::unique_ptr<LoopbackServerEntity>
+PersistentTombstoneEntity::CreateNewInternal(
+    const std::string& id,
+    int64_t version,
+    const std::string& client_defined_unique_tag) {
+  const ModelType model_type = LoopbackServerEntity::GetModelTypeFromId(id);
+  if (model_type == syncer::UNSPECIFIED) {
+    DLOG(WARNING) << "Invalid ID was given: " << id;
+    return nullptr;
+  }
+
+  return base::WrapUnique(new PersistentTombstoneEntity(
+      id, version, model_type, client_defined_unique_tag));
 }
 
 PersistentTombstoneEntity::PersistentTombstoneEntity(
diff --git a/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.h b/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.h
index 0531666..985fc69 100644
--- a/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.h
+++ b/components/sync/engine_impl/loopback_server/persistent_tombstone_entity.h
@@ -36,6 +36,11 @@
       const override;
 
  private:
+  static std::unique_ptr<LoopbackServerEntity> CreateNewInternal(
+      const std::string& id,
+      int64_t version,
+      const std::string& client_defined_unique_tag);
+
   PersistentTombstoneEntity(const std::string& id,
                             int64_t version,
                             const syncer::ModelType& model_type,
diff --git a/components/sync/engine_impl/loopback_server/persistent_tombstone_entity_unittest.cc b/components/sync/engine_impl/loopback_server/persistent_tombstone_entity_unittest.cc
new file mode 100644
index 0000000..3617ac3
--- /dev/null
+++ b/components/sync/engine_impl/loopback_server/persistent_tombstone_entity_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/sync/engine_impl/loopback_server/persistent_tombstone_entity.h"
+
+#include "components/sync/protocol/sync.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+TEST(PersistentTombstoneEntityTest, CreateFromEntity) {
+  sync_pb::SyncEntity entity;
+  *entity.mutable_id_string() = "invalid_id";
+  ASSERT_FALSE(PersistentTombstoneEntity::CreateFromEntity(entity));
+  *entity.mutable_id_string() = "37702_id";
+  ASSERT_TRUE(PersistentTombstoneEntity::CreateFromEntity(entity));
+}
+
+TEST(PersistentTombstoneEntityTest, CreateNew) {
+  ASSERT_FALSE(PersistentTombstoneEntity::CreateNew(
+      "invalid_id", "client_defined_unique_tag"));
+  ASSERT_TRUE(PersistentTombstoneEntity::CreateNew(
+      "37702_id", "client_defined_unique_tag"));
+}
+
+}  // namespace
+
+}  // namespace syncer
diff --git a/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.cc b/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.cc
index 445f288b..eaca76a 100644
--- a/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.cc
+++ b/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.cc
@@ -5,6 +5,7 @@
 #include "components/sync/engine_impl/loopback_server/persistent_unique_client_entity.h"
 
 #include "base/guid.h"
+#include "base/memory/ptr_util.h"
 #include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "components/sync/base/hash_util.h"
@@ -38,10 +39,13 @@
 PersistentUniqueClientEntity::CreateFromEntity(
     const sync_pb::SyncEntity& client_entity) {
   ModelType model_type = GetModelTypeFromSpecifics(client_entity.specifics());
-  DCHECK_NE(client_entity.has_client_defined_unique_tag(),
-            syncer::CommitOnlyTypes().Has(model_type))
-      << "A UniqueClientEntity should have a client-defined unique tag iff it "
-         "is not a CommitOnly type.";
+  if (client_entity.has_client_defined_unique_tag() ==
+      syncer::CommitOnlyTypes().Has(model_type)) {
+    DLOG(WARNING) << "A UniqueClientEntity should have a client-defined unique "
+                     "tag iff it is not a CommitOnly type.";
+    return nullptr;
+  }
+
   // Without model type specific logic for each CommitOnly type, we cannot infer
   // a reasonable tag from the specifics. We need uniqueness for how the server
   // holds onto all objects, so simply make a new tag from a random  number.
@@ -49,7 +53,7 @@
                              ? client_entity.client_defined_unique_tag()
                              : base::Uint64ToString(base::RandUint64());
   string id = LoopbackServerEntity::CreateId(model_type, effective_tag);
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentUniqueClientEntity(
+  return base::WrapUnique(new PersistentUniqueClientEntity(
       id, model_type, client_entity.version(), client_entity.name(),
       client_entity.client_defined_unique_tag(), client_entity.specifics(),
       client_entity.ctime(), client_entity.mtime()));
@@ -59,14 +63,16 @@
 std::unique_ptr<LoopbackServerEntity>
 PersistentUniqueClientEntity::CreateFromEntitySpecifics(
     const string& name,
-    const sync_pb::EntitySpecifics& entity_specifics) {
+    const sync_pb::EntitySpecifics& entity_specifics,
+    int64_t creation_time,
+    int64_t last_modified_time) {
   ModelType model_type = GetModelTypeFromSpecifics(entity_specifics);
   string client_defined_unique_tag = GenerateSyncableHash(model_type, name);
   string id =
       LoopbackServerEntity::CreateId(model_type, client_defined_unique_tag);
-  return std::unique_ptr<LoopbackServerEntity>(new PersistentUniqueClientEntity(
+  return base::WrapUnique(new PersistentUniqueClientEntity(
       id, model_type, 0, name, client_defined_unique_tag, entity_specifics,
-      1337, 1337));
+      creation_time, last_modified_time));
 }
 
 bool PersistentUniqueClientEntity::RequiresParentId() const {
diff --git a/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.h b/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.h
index 746f0bd..7909356 100644
--- a/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.h
+++ b/components/sync/engine_impl/loopback_server/persistent_unique_client_entity.h
@@ -46,7 +46,9 @@
   // FakeServer injection API.
   static std::unique_ptr<LoopbackServerEntity> CreateFromEntitySpecifics(
       const std::string& name,
-      const sync_pb::EntitySpecifics& entity_specifics);
+      const sync_pb::EntitySpecifics& entity_specifics,
+      int64_t creation_time,
+      int64_t last_modified_time);
 
   // LoopbackServerEntity implementation.
   bool RequiresParentId() const override;
diff --git a/components/sync/engine_impl/loopback_server/persistent_unique_client_entity_unittest.cc b/components/sync/engine_impl/loopback_server/persistent_unique_client_entity_unittest.cc
new file mode 100644
index 0000000..6bedb58
--- /dev/null
+++ b/components/sync/engine_impl/loopback_server/persistent_unique_client_entity_unittest.cc
@@ -0,0 +1,41 @@
+// 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/sync/engine_impl/loopback_server/persistent_unique_client_entity.h"
+
+#include "components/sync/protocol/sync.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+TEST(PersistentUniqueClientEntityTest, CreateFromEntity) {
+  sync_pb::SyncEntity entity;
+  entity.mutable_specifics()->mutable_preference();
+  // Normal types need a client_defined_unique_tag.
+  ASSERT_FALSE(PersistentUniqueClientEntity::CreateFromEntity(entity));
+
+  *entity.mutable_client_defined_unique_tag() = "tag";
+  ASSERT_TRUE(PersistentUniqueClientEntity::CreateFromEntity(entity));
+
+  entity.clear_specifics();
+  entity.mutable_specifics()->mutable_user_event();
+  // CommitOnly type should never have a client_defined_unique_tag.
+  ASSERT_FALSE(PersistentUniqueClientEntity::CreateFromEntity(entity));
+
+  entity.clear_client_defined_unique_tag();
+  ASSERT_TRUE(PersistentUniqueClientEntity::CreateFromEntity(entity));
+}
+
+TEST(PersistentUniqueClientEntityTest, CreateFromEntitySpecifics) {
+  sync_pb::EntitySpecifics specifics;
+  specifics.mutable_preference();
+  ASSERT_TRUE(PersistentUniqueClientEntity::CreateFromEntitySpecifics(
+      "name", specifics, 0, 0));
+}
+
+}  // namespace
+
+}  // namespace syncer
diff --git a/components/sync/test/fake_server/android/fake_server_helper_android.cc b/components/sync/test/fake_server/android/fake_server_helper_android.cc
index 140c997c..5b6af3b 100644
--- a/components/sync/test/fake_server/android/fake_server_helper_android.cc
+++ b/components/sync/test/fake_server/android/fake_server_helper_android.cc
@@ -145,7 +145,8 @@
 
   fake_server_ptr->InjectEntity(
       syncer::PersistentUniqueClientEntity::CreateFromEntitySpecifics(
-          base::android::ConvertJavaStringToUTF8(env, name), entity_specifics));
+          base::android::ConvertJavaStringToUTF8(env, name), entity_specifics,
+          12345, 12345));
 }
 
 void FakeServerHelperAndroid::ModifyEntitySpecifics(
diff --git a/components/sync/test/fake_server/fake_server_entity.cc b/components/sync/test/fake_server/fake_server_entity.cc
deleted file mode 100644
index def68fcc..0000000
--- a/components/sync/test/fake_server/fake_server_entity.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2014 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/sync/test/fake_server/fake_server_entity.h"
-
-#include <limits>
-#include <memory>
-#include <vector>
-
-#include "base/guid.h"
-#include "base/logging.h"
-#include "base/memory/ref_counted.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
-#include "base/strings/stringprintf.h"
-#include "net/base/net_errors.h"
-#include "net/http/http_status_code.h"
-
-using std::string;
-using std::vector;
-using syncer::ModelType;
-
-// The separator used when formatting IDs.
-//
-// We chose the underscore character because it doesn't conflict with the
-// special characters used by base/base64.h's encoding, which is also used in
-// the construction of some IDs.
-const char kIdSeparator[] = "_";
-
-namespace fake_server {
-
-FakeServerEntity::~FakeServerEntity() {}
-
-int64_t FakeServerEntity::GetVersion() const {
-  return version_;
-}
-
-void FakeServerEntity::SetVersion(int64_t version) {
-  version_ = version;
-}
-
-const std::string& FakeServerEntity::GetName() const {
-  return name_;
-}
-
-void FakeServerEntity::SetName(const std::string& name) {
-  name_ = name;
-}
-
-void FakeServerEntity::SetSpecifics(
-    const sync_pb::EntitySpecifics& updated_specifics) {
-  specifics_ = updated_specifics;
-}
-
-bool FakeServerEntity::IsDeleted() const {
-  return false;
-}
-
-bool FakeServerEntity::IsFolder() const {
-  return false;
-}
-
-bool FakeServerEntity::IsPermanent() const {
-  return false;
-}
-
-// static
-string FakeServerEntity::CreateId(const ModelType& model_type,
-                                  const string& inner_id) {
-  int field_number = GetSpecificsFieldNumberFromModelType(model_type);
-  return base::StringPrintf("%d%s%s", field_number, kIdSeparator,
-                            inner_id.c_str());
-}
-
-// static
-std::string FakeServerEntity::GetTopLevelId(const ModelType& model_type) {
-  return FakeServerEntity::CreateId(model_type,
-                                    syncer::ModelTypeToRootTag(model_type));
-}
-
-// static
-ModelType FakeServerEntity::GetModelTypeFromId(const string& id) {
-  vector<base::StringPiece> tokens = base::SplitStringPiece(
-      id, kIdSeparator, base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-
-  int field_number;
-  if (tokens.size() != 2 || !base::StringToInt(tokens[0], &field_number)) {
-    return syncer::UNSPECIFIED;
-  }
-
-  return syncer::GetModelTypeFromSpecificsFieldNumber(field_number);
-}
-
-FakeServerEntity::FakeServerEntity(const string& id,
-                                   const string& client_defined_unique_tag,
-                                   const ModelType& model_type,
-                                   int64_t version,
-                                   const string& name)
-    : id_(id),
-      client_defined_unique_tag_(client_defined_unique_tag),
-      model_type_(model_type),
-      version_(version),
-      name_(name) {
-  // There shouldn't be a unique_tag if the type is bookmarks.
-  DCHECK(model_type != syncer::BOOKMARKS || client_defined_unique_tag.empty());
-}
-
-void FakeServerEntity::SerializeBaseProtoFields(
-    sync_pb::SyncEntity* sync_entity) const {
-  sync_pb::EntitySpecifics* specifics = sync_entity->mutable_specifics();
-  specifics->CopyFrom(specifics_);
-
-  // FakeServerEntity fields
-  sync_entity->set_id_string(id_);
-  if (!client_defined_unique_tag_.empty())
-    sync_entity->set_client_defined_unique_tag(client_defined_unique_tag_);
-  sync_entity->set_version(version_);
-  sync_entity->set_name(name_);
-
-  // Data via accessors
-  sync_entity->set_deleted(IsDeleted());
-  sync_entity->set_folder(IsFolder());
-
-  if (RequiresParentId())
-    sync_entity->set_parent_id_string(GetParentId());
-}
-
-}  // namespace fake_server
diff --git a/components/viz/service/display_embedder/display_provider.h b/components/viz/service/display_embedder/display_provider.h
index d490bf2..be728d87 100644
--- a/components/viz/service/display_embedder/display_provider.h
+++ b/components/viz/service/display_embedder/display_provider.h
@@ -11,10 +11,10 @@
 
 namespace viz {
 
-class BeginFrameSource;
 class Display;
 class FrameSinkId;
 class RendererSettings;
+class SyntheticBeginFrameSource;
 
 // Handles creating Display and related classes for FrameSinkManagerImpl.
 class DisplayProvider {
@@ -27,7 +27,7 @@
       const FrameSinkId& frame_sink_id,
       gpu::SurfaceHandle surface_handle,
       const RendererSettings& renderer_settings,
-      std::unique_ptr<BeginFrameSource>* begin_frame_source) = 0;
+      std::unique_ptr<SyntheticBeginFrameSource>* out_begin_frame_source) = 0;
 };
 
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/gpu_display_provider.cc b/components/viz/service/display_embedder/gpu_display_provider.cc
index 6954d86..8fd3cfea 100644
--- a/components/viz/service/display_embedder/gpu_display_provider.cc
+++ b/components/viz/service/display_embedder/gpu_display_provider.cc
@@ -61,7 +61,7 @@
     const FrameSinkId& frame_sink_id,
     gpu::SurfaceHandle surface_handle,
     const RendererSettings& renderer_settings,
-    std::unique_ptr<BeginFrameSource>* begin_frame_source) {
+    std::unique_ptr<SyntheticBeginFrameSource>* out_begin_frame_source) {
   auto synthetic_begin_frame_source =
       base::MakeUnique<DelayBasedBeginFrameSource>(
           base::MakeUnique<DelayBasedTimeSource>(task_runner_.get()),
@@ -102,7 +102,7 @@
       max_frames_pending);
 
   // The ownership of the BeginFrameSource is transfered to the caller.
-  *begin_frame_source = std::move(synthetic_begin_frame_source);
+  *out_begin_frame_source = std::move(synthetic_begin_frame_source);
 
   return base::MakeUnique<Display>(
       ServerSharedBitmapManager::current(), gpu_memory_buffer_manager_.get(),
diff --git a/components/viz/service/display_embedder/gpu_display_provider.h b/components/viz/service/display_embedder/gpu_display_provider.h
index ebc0382a..54a6bafc 100644
--- a/components/viz/service/display_embedder/gpu_display_provider.h
+++ b/components/viz/service/display_embedder/gpu_display_provider.h
@@ -39,7 +39,8 @@
       const FrameSinkId& frame_sink_id,
       gpu::SurfaceHandle surface_handle,
       const RendererSettings& renderer_settings,
-      std::unique_ptr<BeginFrameSource>* begin_frame_source) override;
+      std::unique_ptr<SyntheticBeginFrameSource>* out_begin_frame_source)
+      override;
 
  private:
   const uint32_t restart_id_;
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
index aed0c069..eb7ed9c 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -112,7 +112,7 @@
   DCHECK_EQ(0u, compositor_frame_sinks_.count(frame_sink_id));
   DCHECK(display_provider_);
 
-  std::unique_ptr<BeginFrameSource> begin_frame_source;
+  std::unique_ptr<SyntheticBeginFrameSource> begin_frame_source;
   auto display = display_provider_->CreateDisplay(
       frame_sink_id, surface_handle, renderer_settings, &begin_frame_source);
 
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
index a8a85be..7072e33af 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -16,7 +16,7 @@
     FrameSinkManagerImpl* frame_sink_manager,
     const FrameSinkId& frame_sink_id,
     std::unique_ptr<Display> display,
-    std::unique_ptr<BeginFrameSource> begin_frame_source,
+    std::unique_ptr<SyntheticBeginFrameSource> synthetic_begin_frame_source,
     mojom::CompositorFrameSinkAssociatedRequest request,
     mojom::CompositorFrameSinkClientPtr client,
     mojom::DisplayPrivateAssociatedRequest display_private_request)
@@ -29,23 +29,23 @@
           frame_sink_id,
           true /* is_root */,
           true /* needs_sync_points */)),
-      display_begin_frame_source_(std::move(begin_frame_source)),
+      synthetic_begin_frame_source_(std::move(synthetic_begin_frame_source)),
       display_(std::move(display)),
       hit_test_aggregator_(frame_sink_manager->hit_test_manager(), this) {
-  DCHECK(display_begin_frame_source_);
+  DCHECK(synthetic_begin_frame_source_);
   DCHECK(display_);
 
   compositor_frame_sink_binding_.set_connection_error_handler(
       base::Bind(&RootCompositorFrameSinkImpl::OnClientConnectionLost,
                  base::Unretained(this)));
   frame_sink_manager->RegisterBeginFrameSource(
-      display_begin_frame_source_.get(), frame_sink_id);
+      synthetic_begin_frame_source_.get(), frame_sink_id);
   display_->Initialize(this, frame_sink_manager->surface_manager());
 }
 
 RootCompositorFrameSinkImpl::~RootCompositorFrameSinkImpl() {
   support_->frame_sink_manager()->UnregisterBeginFrameSource(
-      display_begin_frame_source_.get());
+      synthetic_begin_frame_source_.get());
 }
 
 void RootCompositorFrameSinkImpl::SetDisplayVisible(bool visible) {
@@ -62,6 +62,12 @@
   display_->SetOutputIsSecure(secure);
 }
 
+void RootCompositorFrameSinkImpl::SetAuthoritativeVSyncInterval(
+    base::TimeDelta interval) {
+  if (synthetic_begin_frame_source_)
+    synthetic_begin_frame_source_->SetAuthoritativeVSyncInterval(interval);
+}
+
 void RootCompositorFrameSinkImpl::SetNeedsBeginFrame(bool needs_begin_frame) {
   support_->SetNeedsBeginFrame(needs_begin_frame);
 }
@@ -71,7 +77,7 @@
     CompositorFrame frame,
     mojom::HitTestRegionListPtr hit_test_region_list,
     uint64_t submit_time) {
-  // Update |display_| when size or local surface id changes.
+  // Update display when size or local surface id changes.
   if (support_->local_surface_id() != local_surface_id) {
     display_->Resize(frame.size_in_pixels());
     display_->SetLocalSurfaceId(local_surface_id, frame.device_scale_factor());
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
index a4c97797d..14e0bfe 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
@@ -19,9 +19,9 @@
 
 namespace viz {
 
-class BeginFrameSource;
 class Display;
 class FrameSinkManagerImpl;
+class SyntheticBeginFrameSource;
 
 // The viz portion of a root CompositorFrameSink. Holds the Binding/InterfacePtr
 // for the mojom::CompositorFrameSink interface and owns the Display.
@@ -34,18 +34,21 @@
       FrameSinkManagerImpl* frame_sink_manager,
       const FrameSinkId& frame_sink_id,
       std::unique_ptr<Display> display,
-      std::unique_ptr<BeginFrameSource> begin_frame_source,
+      std::unique_ptr<SyntheticBeginFrameSource> begin_frame_source,
       mojom::CompositorFrameSinkAssociatedRequest request,
       mojom::CompositorFrameSinkClientPtr client,
       mojom::DisplayPrivateAssociatedRequest display_private_request);
 
   ~RootCompositorFrameSinkImpl() override;
 
+  CompositorFrameSinkSupport* support() const { return support_.get(); }
+
   // mojom::DisplayPrivate:
   void SetDisplayVisible(bool visible) override;
   void SetDisplayColorSpace(const gfx::ColorSpace& blending_color_space,
                             const gfx::ColorSpace& device_color_space) override;
   void SetOutputIsSecure(bool secure) override;
+  void SetAuthoritativeVSyncInterval(base::TimeDelta interval) override;
 
   // mojom::CompositorFrameSink:
   void SetNeedsBeginFrame(bool needs_begin_frame) override;
@@ -55,8 +58,6 @@
                              uint64_t submit_time) override;
   void DidNotProduceFrame(const BeginFrameAck& begin_frame_ack) override;
 
-  CompositorFrameSinkSupport* support() const { return support_.get(); }
-
   // HitTestAggregatorDelegate:
   void OnAggregatedHitTestRegionListUpdated(
       mojo::ScopedSharedBufferHandle active_handle,
@@ -86,7 +87,7 @@
 
   // RootCompositorFrameSinkImpl holds a Display and its BeginFrameSource if
   // it was created with a non-null gpu::SurfaceHandle.
-  std::unique_ptr<BeginFrameSource> display_begin_frame_source_;
+  std::unique_ptr<SyntheticBeginFrameSource> synthetic_begin_frame_source_;
   std::unique_ptr<Display> display_;
 
   HitTestAggregator hit_test_aggregator_;
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index b8b30ca0..245bd83 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -1452,10 +1452,11 @@
   // so this cannot happen any earlier than now.
   InitializeMojo();
 
-#if defined(USE_AURA)
+#if BUILDFLAG(ENABLE_MUS)
   if (IsUsingMus()) {
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        switches::kIsRunningInMash);
+    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+        switches::kMus,
+        IsMusHostingViz() ? switches::kMusHostVizValue : std::string());
     base::CommandLine::ForCurrentProcess()->AppendSwitch(
         switches::kEnableSurfaceSynchronization);
   }
diff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc
index 6121053..0d9d89e7 100644
--- a/content/browser/compositor/viz_process_transport_factory.cc
+++ b/content/browser/compositor/viz_process_transport_factory.cc
@@ -213,8 +213,10 @@
 void VizProcessTransportFactory::SetAuthoritativeVSyncInterval(
     ui::Compositor* compositor,
     base::TimeDelta interval) {
-  // TODO(crbug.com/772524): Deal with vsync later.
-  NOTIMPLEMENTED();
+  auto iter = compositor_data_map_.find(compositor);
+  if (iter == compositor_data_map_.end() || !iter->second.display_private)
+    return;
+  iter->second.display_private->SetAuthoritativeVSyncInterval(interval);
 }
 
 void VizProcessTransportFactory::SetDisplayVSyncParameters(
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index e6d0d5c0..97870689 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -100,6 +100,7 @@
 #include "content/browser/media/midi_host.h"
 #include "content/browser/memory/memory_coordinator_impl.h"
 #include "content/browser/mime_registry_impl.h"
+#include "content/browser/mus_util.h"
 #include "content/browser/net/reporting_service_proxy.h"
 #include "content/browser/notifications/notification_message_filter.h"
 #include "content/browser/notifications/platform_notification_context_impl.h"
@@ -2609,7 +2610,6 @@
     switches::kIgnoreAutoplayRestrictionsForTests,
     switches::kIPCConnectionTimeout,
     switches::kIsolateOrigins,
-    switches::kIsRunningInMash,
     switches::kJavaScriptFlags,
     switches::kLoggingLevel,
     switches::kMainFrameResizesAreOrientationChanges,
@@ -2707,6 +2707,9 @@
     switches::kIpcDumpDirectory,
     switches::kIpcFuzzerTestcase,
 #endif
+#if BUILDFLAG(ENABLE_MUS)
+    switches::kMus,
+#endif
   };
   renderer_cmd->CopySwitchesFrom(browser_cmd, kSwitchNames,
                                  arraysize(kSwitchNames));
@@ -2747,8 +2750,7 @@
   // optimizes the common case to avoid wasted work.
   // Note: There is no ImageTransportFactory with Mash or Viz.
   // TODO(danakj): Get this info somewhere for viz mode.
-  if (!browser_cmd.HasSwitch(switches::kIsRunningInMash) &&
-      !browser_cmd.HasSwitch(switches::kEnableViz) &&
+  if (!IsUsingMus() && !browser_cmd.HasSwitch(switches::kEnableViz) &&
       ImageTransportFactory::GetInstance()->IsGpuCompositingDisabled())
     renderer_cmd->AppendSwitch(switches::kDisableGpuCompositing);
 #endif
diff --git a/content/ppapi_plugin/ppapi_thread.cc b/content/ppapi_plugin/ppapi_thread.cc
index ef93281..e2eea32083 100644
--- a/content/ppapi_plugin/ppapi_thread.cc
+++ b/content/ppapi_plugin/ppapi_thread.cc
@@ -92,9 +92,13 @@
 
 #endif
 
-static bool IsRunningInMash() {
+static bool IsRunningWithMus() {
+#if BUILDFLAG(ENABLE_MUS)
   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  return cmdline->HasSwitch(switches::kIsRunningInMash);
+  return cmdline->HasSwitch(switches::kMus);
+#else
+  return false;
+#endif
 }
 
 namespace content {
@@ -127,7 +131,7 @@
   // allocator.
   if (!command_line.HasSwitch(switches::kSingleProcess)) {
     discardable_memory::mojom::DiscardableSharedMemoryManagerPtr manager_ptr;
-    if (IsRunningInMash()) {
+    if (IsRunningWithMus()) {
 #if defined(USE_AURA)
       GetServiceManagerConnection()->GetConnector()->BindInterface(
           ui::mojom::kServiceName, &manager_ptr);
diff --git a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java
index 198ac82..ae671034 100644
--- a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java
@@ -159,7 +159,7 @@
      */
     public SelectionPopupController(
             Context context, WindowAndroid window, WebContents webContents, View view) {
-        this(context, window, webContents, view, /* initialNative = */ true);
+        this(context, window, webContents, view, /* initializeNative = */ true);
     }
 
     /**
@@ -173,7 +173,7 @@
     public static SelectionPopupController createForTesting(
             Context context, WindowAndroid window, WebContents webContents, View view) {
         return new SelectionPopupController(
-                context, window, webContents, view, /* initialNative = */ false);
+                context, window, webContents, view, /* initializeNative = */ false);
     }
 
     private SelectionPopupController(Context context, WindowAndroid window, WebContents webContents,
@@ -1265,7 +1265,7 @@
             if (!(result.startAdjust == 0 && result.endAdjust == 0)) {
                 // This call will cause showSelectionMenu again.
                 mWebContents.adjustSelectionByCharacterOffset(
-                        result.startAdjust, result.endAdjust, /* show_selection_menu = */ true);
+                        result.startAdjust, result.endAdjust, /* showSelectionMenu = */ true);
                 return;
             }
 
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java
index 879d64e6..462f0c6 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java
@@ -1744,4 +1744,27 @@
                                 + "  document.getElementById('div').firstChild, "
                                 + "  'composition', 1).endOffset"));
     }
+
+    @Test
+    @SmallTest
+    @Feature({"TextInput"})
+    public void testAutocorrectAttribute() throws Exception {
+        // Autocorrect should be on for a text field that doesn't have an autocorrect attribute.
+        mRule.focusElement("input_text");
+        Assert.assertEquals(EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT,
+                mRule.getConnectionFactory().getOutAttrs().inputType
+                        & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT);
+
+        // Autocorrect should be on for a text field that has autocorrect="on" set.
+        mRule.focusElement("autocorrect_on");
+        Assert.assertEquals(EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT,
+                mRule.getConnectionFactory().getOutAttrs().inputType
+                        & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT);
+
+        // Autocorrect should be off for a text field that has autocorrect="off" set.
+        mRule.focusElement("autocorrect_off");
+        Assert.assertEquals(0,
+                mRule.getConnectionFactory().getOutAttrs().inputType
+                        & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT);
+    }
 }
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index c3fd709f..cfa357b2 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -589,9 +589,6 @@
 //   --isolate-origins=https://www.foo.com,https://www.bar.com
 const char kIsolateOrigins[] = "isolate-origins";
 
-// Chrome is running in Mash.
-const char kIsRunningInMash[] = "is-running-in-mash";
-
 // Disable latest shipping ECMAScript 6 features.
 const char kDisableJavaScriptHarmonyShipping[] =
     "disable-javascript-harmony-shipping";
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index 5b2199e..5f27e8c 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -171,7 +171,6 @@
 CONTENT_EXPORT extern const char kInProcessGPU[];
 CONTENT_EXPORT extern const char kIPCConnectionTimeout[];
 CONTENT_EXPORT extern const char kIsolateOrigins[];
-CONTENT_EXPORT extern const char kIsRunningInMash[];
 CONTENT_EXPORT extern const char kJavaScriptFlags[];
 CONTENT_EXPORT extern const char kJavaScriptHarmony[];
 CONTENT_EXPORT extern const char kLogGpuControlListDecisions[];
diff --git a/content/renderer/browser_plugin/browser_plugin.cc b/content/renderer/browser_plugin/browser_plugin.cc
index 0365a284..f7ea97f0 100644
--- a/content/renderer/browser_plugin/browser_plugin.cc
+++ b/content/renderer/browser_plugin/browser_plugin.cc
@@ -144,7 +144,7 @@
     int browser_plugin_instance_id,
     const viz::SurfaceInfo& surface_info,
     const viz::SurfaceSequence& sequence) {
-  if (!attached() || IsRunningInMash())
+  if (!attached() || IsRunningWithMus())
     return;
 
   if (!enable_surface_synchronization_) {
@@ -296,7 +296,7 @@
     sent_resize_params_ = pending_resize_params_;
 
 #if defined(USE_AURA)
-  if (IsRunningInMash() && mus_embedded_frame_) {
+  if (IsRunningWithMus() && mus_embedded_frame_) {
     mus_embedded_frame_->SetWindowBounds(local_surface_id_,
                                          FrameRectInPixels());
   }
@@ -362,7 +362,7 @@
 void BrowserPlugin::OnSetMusEmbedToken(
     int instance_id,
     const base::UnguessableToken& embed_token) {
-  DCHECK(IsRunningInMash());
+  DCHECK(IsRunningWithMus());
   if (!attached_) {
     pending_embed_token_ = embed_token;
   } else {
diff --git a/content/renderer/mash_util.cc b/content/renderer/mash_util.cc
index 23c58773..4c3b525d 100644
--- a/content/renderer/mash_util.cc
+++ b/content/renderer/mash_util.cc
@@ -5,13 +5,17 @@
 #include "content/renderer/mash_util.h"
 
 #include "base/command_line.h"
-#include "content/public/common/content_switches.h"
+#include "ui/base/ui_base_switches.h"
 
 namespace content {
 
-bool IsRunningInMash() {
+bool IsRunningWithMus() {
+#if BUILDFLAG(ENABLE_MUS)
   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  return cmdline->HasSwitch(switches::kIsRunningInMash);
+  return cmdline->HasSwitch(switches::kMus);
+#else
+  return false;
+#endif
 }
 
 }  // namespace content
diff --git a/content/renderer/mash_util.h b/content/renderer/mash_util.h
index 9091cce..3933b73 100644
--- a/content/renderer/mash_util.h
+++ b/content/renderer/mash_util.h
@@ -7,7 +7,7 @@
 
 namespace content {
 
-bool IsRunningInMash();
+bool IsRunningWithMus();
 
 }  // namespace content
 
diff --git a/content/renderer/media/gpu/rtc_video_encoder.cc b/content/renderer/media/gpu/rtc_video_encoder.cc
index aebe2d24..8a2a2d3 100644
--- a/content/renderer/media/gpu/rtc_video_encoder.cc
+++ b/content/renderer/media/gpu/rtc_video_encoder.cc
@@ -22,7 +22,6 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "build/build_config.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/renderer/media/webrtc/webrtc_video_frame_adapter.h"
@@ -523,11 +522,7 @@
       }
       pending_timestamps_.pop_front();
     }
-#if !defined(OS_ANDROID)
-    // No capture timestamps available on Android at present. So generate rtp
-    // timestamps below.
     DCHECK(rtp_timestamp.has_value());
-#endif
   }
   if (!rtp_timestamp.has_value()) {
     failed_timestamp_match_ = true;
diff --git a/content/renderer/media/gpu/rtc_video_encoder_unittest.cc b/content/renderer/media/gpu/rtc_video_encoder_unittest.cc
index 336705c..31bccb0 100644
--- a/content/renderer/media/gpu/rtc_video_encoder_unittest.cc
+++ b/content/renderer/media/gpu/rtc_video_encoder_unittest.cc
@@ -292,15 +292,7 @@
             rtc_encoder_->Encode(rtc_frame, nullptr, &frame_types));
 }
 
-// We cannot run this test on Android because AndroidVideoEncodeAccelerator does
-// not preserve timestamps.
-#if defined(OS_ANDROID)
-#define MAYBE_PreserveTimestamps DISABLED_PreserveTimestamps
-#else
-#define MAYBE_PreserveTimestamps PreserveTimestamps
-#endif  // defined(OS_ANDROID)
-
-TEST_F(RTCVideoEncoderTest, MAYBE_PreserveTimestamps) {
+TEST_F(RTCVideoEncoderTest, PreserveTimestamps) {
   const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP8;
   CreateEncoder(codec_type);
   webrtc::VideoCodec codec = GetDefaultCodec();
diff --git a/content/renderer/mus/renderer_window_tree_client.cc b/content/renderer/mus/renderer_window_tree_client.cc
index 84353df..596740f 100644
--- a/content/renderer/mus/renderer_window_tree_client.cc
+++ b/content/renderer/mus/renderer_window_tree_client.cc
@@ -30,7 +30,7 @@
 
 // static
 void RendererWindowTreeClient::CreateIfNecessary(int routing_id) {
-  if (!IsRunningInMash() || Get(routing_id))
+  if (!IsRunningWithMus() || Get(routing_id))
     return;
   RendererWindowTreeClient* connection =
       new RendererWindowTreeClient(routing_id);
diff --git a/content/renderer/render_frame_proxy.cc b/content/renderer/render_frame_proxy.cc
index 132ce35..3d2f6dc 100644
--- a/content/renderer/render_frame_proxy.cc
+++ b/content/renderer/render_frame_proxy.cc
@@ -230,7 +230,7 @@
   pending_resize_params_.screen_info = render_widget_->screen_info();
 
 #if defined(USE_AURA)
-  if (IsRunningInMash()) {
+  if (IsRunningWithMus()) {
     RendererWindowTreeClient* renderer_window_tree_client =
         RendererWindowTreeClient::Get(render_widget_->routing_id());
     // It's possible a MusEmbeddedFrame has already been scheduled for creation
@@ -400,7 +400,7 @@
 
 void RenderFrameProxy::OnViewChanged(const viz::FrameSinkId& frame_sink_id) {
   // In mash the FrameSinkId comes from RendererWindowTreeClient.
-  if (!IsRunningInMash())
+  if (!IsRunningWithMus())
     frame_sink_id_ = frame_sink_id;
 
   // Resend the FrameRects and allocate a new viz::LocalSurfaceId when the view
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 4bfd1f3..b76a020 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -690,7 +690,7 @@
 
   gpu_ = ui::Gpu::Create(
       GetConnector(),
-      IsRunningInMash() ? ui::mojom::kServiceName : mojom::kBrowserServiceName,
+      IsRunningWithMus() ? ui::mojom::kServiceName : mojom::kBrowserServiceName,
       GetIOTaskRunner());
 
   viz::mojom::SharedBitmapAllocationNotifierPtr
@@ -799,7 +799,7 @@
 // Register exported services:
 
 #if defined(USE_AURA)
-  if (IsRunningInMash()) {
+  if (IsRunningWithMus()) {
     CreateRenderWidgetWindowTreeClientFactory(GetServiceManagerConnection());
   }
 #endif
@@ -952,7 +952,7 @@
   categorized_worker_pool_->Start(num_raster_threads);
 
   discardable_memory::mojom::DiscardableSharedMemoryManagerPtr manager_ptr;
-  if (IsRunningInMash()) {
+  if (IsRunningWithMus()) {
 #if defined(USE_AURA)
     GetServiceManagerConnection()->GetConnector()->BindInterface(
         ui::mojom::kServiceName, &manager_ptr);
@@ -2043,7 +2043,7 @@
   }
 
 #if defined(USE_AURA)
-  if (IsRunningInMash()) {
+  if (IsRunningWithMus()) {
     if (!RendererWindowTreeClient::Get(routing_id)) {
       callback.Run(nullptr);
       return;
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 6704d26b..d8bb604 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -403,7 +403,7 @@
   }
 #if defined(USE_AURA)
   RendererWindowTreeClient::CreateIfNecessary(routing_id_);
-  if (IsRunningInMash())
+  if (IsRunningWithMus())
     RendererWindowTreeClient::Get(routing_id_)->SetVisible(!is_hidden_);
 #endif
 }
@@ -2085,7 +2085,7 @@
   is_hidden_ = hidden;
 
 #if defined(USE_AURA)
-  if (IsRunningInMash())
+  if (IsRunningWithMus())
     RendererWindowTreeClient::Get(routing_id_)->SetVisible(!hidden);
 #endif
 
diff --git a/content/test/data/android/input/input_forms.html b/content/test/data/android/input/input_forms.html
index 97a049ce..3005f0e 100644
--- a/content/test/data/android/input/input_forms.html
+++ b/content/test/data/android/input/input_forms.html
@@ -23,6 +23,10 @@
       <input id="input_text1" type="text" size="10" size="10"><br>
       <input id="input_radio" type="radio" style="width:50px;height:50px"><br>
     </form>
+    <form>
+      <input id="autocorrect_off" type="input" autocorrect="off">
+      <input id="autocorrect_on" type="input" autocorrect="on">
+    </form>
 
     <!-- We may trigger different sets of events for CONTENTEDITABLE and INPUT / TEXTAREA -->
     <div id="contenteditable_event" contenteditable><b>ab</b>cd<i>ef<b>gh</b></i></div>
diff --git a/content/test/data/gpu/pixel_webgl_premultiplied_alpha_false.html b/content/test/data/gpu/pixel_webgl_premultiplied_alpha_false.html
new file mode 100644
index 0000000..b2c0ff9
--- /dev/null
+++ b/content/test/data/gpu/pixel_webgl_premultiplied_alpha_false.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<title>WebGL PremultipliedAlpha False Test</title>
+<style type="text/css">
+.nomargin {
+  margin: 0px auto;
+}
+</style>
+
+<script>
+function sendResult(status, detail) {
+  console.log(detail);
+  if (window.domAutomationController) {
+    window.domAutomationController.send(status);
+  } else {
+    console.log(status);
+  }
+}
+
+var numFramesBeforeEnd = 15;
+
+function main() {
+  var canvas = document.getElementById("c");
+  var gl = canvas.getContext(
+      'webgl', { antialias: false, premultipliedAlpha: false });
+  if (!gl) {
+    sendResult("FAILURE", "WebGL context not supported");
+    return;
+  }
+
+  // Clear the left half of the canvas to transparent red, assuming
+  // non-premultiplied alpha.
+  gl.scissor(0, 0, 150, 150);
+  gl.enable(gl.SCISSOR_TEST);
+  gl.clearColor(1.0, 0.0, 0.0, 0.4);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+  gl.disable(gl.SCISSOR_TEST);
+  window.requestAnimationFrame(waitForFinish);
+}
+
+function waitForFinish()
+{
+  if (--numFramesBeforeEnd == 0) {
+    sendResult("SUCCESS", "Test complete");
+  } else {
+    window.requestAnimationFrame(waitForFinish);
+  }
+}
+</script>
+</head>
+<body onload="main()">
+<canvas id="c" width="300" height="150" class="nomargin" style="position:absolute; top:0px; left:0px; background-color: #008000;"></canvas>
+</div>
+</body>
+</html>
diff --git a/content/test/gpu/generate_buildbot_json.py b/content/test/gpu/generate_buildbot_json.py
index 0a6cc28..195f1b8 100755
--- a/content/test/gpu/generate_buildbot_json.py
+++ b/content/test/gpu/generate_buildbot_json.py
@@ -227,6 +227,18 @@
       'swarming': True,
       'os_type': 'linux',
     },
+    'Android Release (Nexus 5X)': {
+      'swarming_dimensions': [
+        {
+          'device_type': 'bullhead',
+          'device_os': 'M',
+          'os': 'Android'
+        },
+      ],
+      'build_config': 'android-chromium',
+      'swarming': True,
+      'os_type': 'android',
+    },
   }
 }
 
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index 9af8ee1..057fd8b 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -150,6 +150,57 @@
       revision=7),
 
     PixelTestPage(
+      'pixel_webgl_premultiplied_alpha_false.html',
+      base_name + '_WebGL_PremultipliedAlpha_False',
+      test_rect=[0, 0, 150, 150],
+      revision=0, # This is not used.
+      expected_colors=[
+        # TODO(kbr): if this works, then factor it out so it applies
+        # to all pixel tests that use programmatic expectations.
+        {
+          "comment": "scale factor overrides",
+          "scale_factor_overrides": [
+            {
+              "device_type": "Nexus 5",
+              "scale_factor": 1.105
+            },
+            {
+              "device_type": "Nexus 5X",
+              "scale_factor": 1.105
+            },
+            {
+              "device_type": "Nexus 6",
+              "scale_factor": 1.47436
+            },
+            {
+              "device_type": "Nexus 6P",
+              "scale_factor": 1.472
+            },
+            {
+              "device_type": "Nexus 9",
+              "scale_factor": 1.566
+            },
+            {
+              "comment": "NVIDIA Shield",
+              "device_type": "sb_na_wf",
+              "scale_factor": 1.226
+            }
+          ]
+        },
+        {
+          'comment': 'brown',
+          'location': [1, 1],
+          'size': [148, 148],
+          # This is the color on an NVIDIA based MacBook Pro if the
+          # sRGB profile's applied correctly.
+          'color': [102, 77, 0],
+          # This is the color if it isn't.
+          # 'color': [101, 76, 12],
+          'tolerance': 3
+        },
+      ]),
+
+    PixelTestPage(
       'pixel_webgl2_blitframebuffer_result_displayed.html',
       base_name + '_WebGL2_BlitFramebuffer_Result_Displayed',
       test_rect=[0, 0, 200, 200],
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
index 721a2607..42803406 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
@@ -616,6 +616,9 @@
         bug=609883)
     self.Fail('conformance/extensions/webgl-compressed-texture-atc.html',
         ['android', ('qualcomm', 'Adreno (TM) 418')], bug=609883)
+    # This test is skipped because it is crashing the GPU process.
+    self.Skip('conformance/glsl/bugs/init-array-with-loop.html',
+        ['android', ('qualcomm', 'Adreno (TM) 418')], bug=784817)
     self.Fail('conformance/glsl/bugs/sampler-struct-function-arg.html',
         ['android', ('qualcomm', 'Adreno (TM) 418')], bug=609883)
     # This test is skipped because it is crashing the GPU process.
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
index b429206..d770d70b 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
+++ b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
@@ -1,3 +1,3 @@
 # AUTOGENERATED FILE - DO NOT EDIT
 # SEE roll_webgl_conformance.py
-Current webgl revision 12192b948daa2d83269ab46cbf03d65e9f2a48a3
+Current webgl revision e4919fa03c74bd561dcabf3e61668fa3c7e54353
diff --git a/device/bluetooth/bluetooth_adapter_win.cc b/device/bluetooth/bluetooth_adapter_win.cc
index c04b614..76e59305 100644
--- a/device/bluetooth/bluetooth_adapter_win.cc
+++ b/device/bluetooth/bluetooth_adapter_win.cc
@@ -282,8 +282,9 @@
       // (primary services of BLE device) are the same. However, in BLE tests,
       // we may simulate characteristic, descriptor and secondary GATT service
       // after device has been initialized.
-      if (force_update_device_for_test_)
+      if (force_update_device_for_test_) {
         device_win->Update(*device_state);
+      }
     }
   }
 }
diff --git a/device/bluetooth/bluetooth_device_unittest.cc b/device/bluetooth/bluetooth_device_unittest.cc
index 19fc5cd..b9cd97e 100644
--- a/device/bluetooth/bluetooth_device_unittest.cc
+++ b/device/bluetooth/bluetooth_device_unittest.cc
@@ -82,13 +82,7 @@
     service_uuids.push_back(duplicate_service_uuid_.canonical_value());
     SimulateGattServicesDiscovered(device_, service_uuids);
     base::RunLoop().RunUntilIdle();
-#if defined(OS_WIN)
-    // TODO(crbug.com/507419): Check connection once CreateGattConnection is
-    // implemented on Windows.
-    EXPECT_FALSE(device_->IsGattServicesDiscoveryComplete());
-#else
     EXPECT_TRUE(device_->IsGattServicesDiscoveryComplete());
-#endif  // defined(OS_WIN)
   }
 
  protected:
@@ -1388,11 +1382,39 @@
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1, observer.gatt_services_discovered_count());
-  EXPECT_EQ(2, observer.gatt_service_added_count());
 }
 #endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
 
-#if defined(OS_ANDROID)
+#if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
+TEST_F(BluetoothTest, GattServicesDiscovered_Success) {
+  if (!PlatformSupportsLowEnergy()) {
+    LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
+    return;
+  }
+  InitWithFakeAdapter();
+  StartLowEnergyDiscoverySession();
+  TestBluetoothAdapterObserver observer(adapter_);
+  BluetoothDevice* device = SimulateLowEnergyDevice(3);
+  device->CreateGattConnection(GetGattConnectionCallback(Call::EXPECTED),
+                               GetConnectErrorCallback(Call::NOT_EXPECTED));
+  ResetEventCounts();
+  SimulateGattConnection(device);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, gatt_discovery_attempts_);
+  EXPECT_EQ(0, observer.gatt_services_discovered_count());
+
+  SimulateGattServicesDiscovered(
+      device,
+      std::vector<std::string>({kTestUUIDGenericAccess, kTestUUIDHeartRate}));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(device->IsGattServicesDiscoveryComplete());
+  EXPECT_EQ(1, observer.gatt_services_discovered_count());
+  EXPECT_EQ(2u, device->GetGattServices().size());
+}
+#endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
+
+#if defined(OS_ANDROID) || defined(OS_WIN)
 // macOS: Not applicable: This can never happen because when
 // the device gets destroyed the CBPeripheralDelegate is also destroyed
 // and no more events are dispatched.
@@ -1421,9 +1443,9 @@
       std::vector<std::string>({kTestUUIDGenericAccess, kTestUUIDHeartRate}));
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_ANDROID)
+#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
-#if defined(OS_ANDROID)
+#if defined(OS_ANDROID) || defined(OS_WIN)
 // macOS: Not applicable: This can never happen because when
 // the device gets destroyed the CBPeripheralDelegate is also destroyed
 // and no more events are dispatched.
@@ -1450,11 +1472,12 @@
   SimulateGattServicesDiscoveryError(nullptr /* use remembered device */);
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_ANDROID)
+#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+// Windows does not support disconnection.
 TEST_F(BluetoothTest, GattServicesDiscovered_AfterDisconnection) {
-  // Tests that we don't crash there was an error discovering services after
+  // Tests that we don't crash if there was an error discovering services after
   // the device disconnects.
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
@@ -1484,6 +1507,7 @@
 #endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+// Windows does not support disconnecting.
 TEST_F(BluetoothTest, GattServicesDiscoveredError_AfterDisconnection) {
   // Tests that we don't crash if services are discovered after
   // the device disconnects.
@@ -1509,7 +1533,7 @@
   EXPECT_FALSE(device->IsGattServicesDiscoveryComplete());
   EXPECT_EQ(0u, device->GetGattServices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
+#endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
 TEST_F(BluetoothTest, GetGattServices_and_GetGattService) {
diff --git a/device/bluetooth/bluetooth_device_win.cc b/device/bluetooth/bluetooth_device_win.cc
index 35ceee7..7c1502d07 100644
--- a/device/bluetooth/bluetooth_device_win.cc
+++ b/device/bluetooth/bluetooth_device_win.cc
@@ -277,6 +277,21 @@
   UpdateServices(device_state);
 }
 
+void BluetoothDeviceWin::GattServiceDiscoveryComplete(
+    BluetoothRemoteGattServiceWin* service) {
+  DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(BluetoothDeviceWin::IsGattServiceDiscovered(
+      service->GetUUID(), service->GetAttributeHandle()));
+
+  discovery_completed_included_services_.insert(
+      {service->GetUUID(), service->GetAttributeHandle()});
+  if (discovery_completed_included_services_.size() != gatt_services_.size())
+    return;
+
+  SetGattServicesDiscoveryComplete(true);
+  adapter_->NotifyGattServicesDiscovered(this);
+}
+
 void BluetoothDeviceWin::CreateGattConnectionImpl() {
   // Windows implementation does not use the default CreateGattConnection
   // implementation.
@@ -310,7 +325,7 @@
     UpdateGattServices(device_state.service_record_states);
 }
 
-bool BluetoothDeviceWin::IsGattServiceDiscovered(BluetoothUUID& uuid,
+bool BluetoothDeviceWin::IsGattServiceDiscovered(const BluetoothUUID& uuid,
                                                  uint16_t attribute_handle) {
   for (const auto& gatt_service : gatt_services_) {
     uint16_t it_att_handle =
@@ -382,8 +397,6 @@
       adapter_->NotifyGattServiceAdded(primary_service);
     }
   }
-
-  adapter_->NotifyGattServicesDiscovered(this);
 }
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_device_win.h b/device/bluetooth/bluetooth_device_win.h
index ce853e8..6934d6e0 100644
--- a/device/bluetooth/bluetooth_device_win.h
+++ b/device/bluetooth/bluetooth_device_win.h
@@ -25,10 +25,13 @@
 namespace device {
 
 class BluetoothAdapterWin;
+class BluetoothRemoteGattServiceWin;
 class BluetoothServiceRecordWin;
 class BluetoothSocketThread;
 
-class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWin : public BluetoothDevice {
+class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWin
+    : public BluetoothDevice,
+      public BluetoothAdapter::Observer {
  public:
   explicit BluetoothDeviceWin(
       BluetoothAdapterWin* adapter,
@@ -100,6 +103,10 @@
   // |device_state|.
   void Update(const BluetoothTaskManagerWin::DeviceState& device_state);
 
+  // Notify |service| discovery complete, |service| is a remote GATT service of
+  // this device.
+  void GattServiceDiscoveryComplete(BluetoothRemoteGattServiceWin* service);
+
  protected:
   // BluetoothDevice override
   void CreateGattConnectionImpl() override;
@@ -117,7 +124,8 @@
 
   // Checks if GATT service with |uuid| and |attribute_handle| has already been
   // discovered.
-  bool IsGattServiceDiscovered(BluetoothUUID& uuid, uint16_t attribute_handle);
+  bool IsGattServiceDiscovered(const BluetoothUUID& uuid,
+                               uint16_t attribute_handle);
 
   // Checks if |service| still exist on device according to newly discovered
   // |service_state|.
@@ -162,6 +170,11 @@
   // The service records retrieved from SDP.
   std::vector<std::unique_ptr<BluetoothServiceRecordWin>> service_record_list_;
 
+  // The element of the set is the uuid / attribute handle pair of the
+  // BluetoothRemoteGattServiceWin instance.
+  std::set<std::pair<BluetoothUUID, uint16_t>>
+      discovery_completed_included_services_;
+
   DISALLOW_COPY_AND_ASSIGN(BluetoothDeviceWin);
 };
 
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
index a8e356a..44b90a52 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
@@ -2155,6 +2155,23 @@
 }
 #endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
+#if defined(OS_MACOSX) || defined(OS_WIN)
+// TODO(786473) Android should report that services are discovered when a
+// characteristic is added, but currently does not.
+TEST_F(BluetoothRemoteGattCharacteristicTest, GattCharacteristicAdded) {
+  if (!PlatformSupportsLowEnergy()) {
+    LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
+    return;
+  }
+  ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate());
+  TestBluetoothAdapterObserver observer(adapter_);
+
+  SimulateGattCharacteristic(service_, kTestUUIDDeviceName, 0);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, observer.gatt_services_discovered_count());
+}
+#endif  // defined(OS_MACOSX) || defined(OS_WIN)
+
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 // Tests Characteristic Value changes during a Notify Session.
 TEST_F(BluetoothRemoteGattCharacteristicTest, GattCharacteristicValueChanged) {
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc
index e451732..8453c17 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc
@@ -27,6 +27,7 @@
       characteristic_added_notified_(false),
       characteristic_value_read_or_write_in_progress_(false),
       gatt_event_handle_(nullptr),
+      discovery_pending_count_(0),
       weak_ptr_factory_(this) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(parent_service_);
@@ -205,6 +206,7 @@
 void BluetoothRemoteGattCharacteristicWin::Update() {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
 
+  ++discovery_pending_count_;
   task_manager_->PostGetGattIncludedDescriptors(
       parent_service_->GetServicePath(), characteristic_info_.get(),
       base::Bind(&BluetoothRemoteGattCharacteristicWin::
@@ -251,6 +253,10 @@
     characteristic_added_notified_ = true;
     parent_service_->GetWinAdapter()->NotifyGattCharacteristicAdded(this);
   }
+
+  // Report discovery complete.
+  if (--discovery_pending_count_ == 0)
+    parent_service_->GattCharacteristicDiscoveryComplete(this);
 }
 
 void BluetoothRemoteGattCharacteristicWin::UpdateIncludedDescriptors(
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h b/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h
index dd29121..8936119 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h
@@ -130,6 +130,10 @@
   // GATT event handle returned by GattEventRegistrationCallback.
   PVOID gatt_event_handle_;
 
+  // Counts the number of asynchronous operations that are discovering
+  // descriptors.
+  int discovery_pending_count_;
+
   base::WeakPtrFactory<BluetoothRemoteGattCharacteristicWin> weak_ptr_factory_;
   DISALLOW_COPY_AND_ASSIGN(BluetoothRemoteGattCharacteristicWin);
 };
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
index 4b4db43..c70448c 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
@@ -21,7 +21,7 @@
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 class BluetoothRemoteGattServiceTest : public BluetoothTest {};
-#endif
+#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 // Android is excluded because it fires a single discovery event per device.
 #if defined(OS_WIN) || defined(OS_MACOSX)
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_win.cc b/device/bluetooth/bluetooth_remote_gatt_service_win.cc
index 954c1e6..7f4e3381 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_win.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_win.cc
@@ -31,6 +31,7 @@
       parent_service_(parent_service),
       ui_task_runner_(ui_task_runner),
       discovery_complete_notified_(false),
+      discovery_pending_count_(0),
       weak_ptr_factory_(this) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(!service_path_.empty());
@@ -113,6 +114,7 @@
 void BluetoothRemoteGattServiceWin::Update() {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
 
+  ++discovery_pending_count_;
   task_manager_->PostGetGattIncludedCharacteristics(
       service_path_, service_uuid_, service_attribute_handle_,
       base::Bind(&BluetoothRemoteGattServiceWin::OnGetIncludedCharacteristics,
@@ -125,9 +127,14 @@
     HRESULT hr) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
 
-  UpdateIncludedCharacteristics(characteristics.get(), num);
+  if (--discovery_pending_count_ != 0)
+    return;
+
+  // Report discovery complete.
   SetDiscoveryComplete(true);
+  UpdateIncludedCharacteristics(characteristics.get(), num);
   NotifyGattDiscoveryCompleteForServiceIfNecessary();
+  device_->GattServiceDiscoveryComplete(this);
 }
 
 void BluetoothRemoteGattServiceWin::UpdateIncludedCharacteristics(
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_win.h b/device/bluetooth/bluetooth_remote_gatt_service_win.h
index 16983494..ac5cbae6 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_win.h
+++ b/device/bluetooth/bluetooth_remote_gatt_service_win.h
@@ -115,6 +115,10 @@
   // avoid duplicate notification.
   bool discovery_complete_notified_;
 
+  // Counts the number of asynchronous operations that are discovering
+  // characteristics.
+  int discovery_pending_count_;
+
   base::WeakPtrFactory<BluetoothRemoteGattServiceWin> weak_ptr_factory_;
   DISALLOW_COPY_AND_ASSIGN(BluetoothRemoteGattServiceWin);
 };
diff --git a/device/bluetooth/test/bluetooth_test_win.cc b/device/bluetooth/test/bluetooth_test_win.cc
index 9f80fc1..e38da91 100644
--- a/device/bluetooth/test/bluetooth_test_win.cc
+++ b/device/bluetooth/test/bluetooth_test_win.cc
@@ -196,8 +196,11 @@
 void BluetoothTestWin::SimulateGattServicesDiscovered(
     BluetoothDevice* device,
     const std::vector<std::string>& uuids) {
+  std::string address =
+      device ? device->GetAddress() : remembered_device_address_;
+
   win::BLEDevice* simulated_device =
-      fake_bt_le_wrapper_->GetSimulatedBLEDevice(device->GetAddress());
+      fake_bt_le_wrapper_->GetSimulatedBLEDevice(address);
   CHECK(simulated_device);
 
   for (auto uuid : uuids) {
@@ -206,6 +209,11 @@
   }
 
   FinishPendingTasks();
+
+  // We still need to discover characteristics.  Wait for the appropriate method
+  // to be posted and then finish the pending tasks.
+  base::RunLoop().RunUntilIdle();
+  FinishPendingTasks();
 }
 
 void BluetoothTestWin::SimulateGattServiceRemoved(
@@ -363,6 +371,11 @@
   FinishPendingTasks();
 }
 
+void BluetoothTestWin::RememberDeviceForSubsequentAction(
+    BluetoothDevice* device) {
+  remembered_device_address_ = device->GetAddress();
+}
+
 void BluetoothTestWin::DeleteDevice(BluetoothDevice* device) {
   CHECK(device);
   fake_bt_le_wrapper_->RemoveSimulatedBLEDevice(device->GetAddress());
@@ -506,6 +519,11 @@
 void BluetoothTestWin::ForceRefreshDevice() {
   adapter_win_->force_update_device_for_test_ = true;
   FinishPendingTasks();
+  adapter_win_->force_update_device_for_test_ = false;
+
+  // The characteristics still need to be discovered.
+  base::RunLoop().RunUntilIdle();
+  FinishPendingTasks();
 }
 
 void BluetoothTestWin::FinishPendingTasks() {
diff --git a/device/bluetooth/test/bluetooth_test_win.h b/device/bluetooth/test/bluetooth_test_win.h
index 9649d0f..c420229 100644
--- a/device/bluetooth/test/bluetooth_test_win.h
+++ b/device/bluetooth/test/bluetooth_test_win.h
@@ -56,6 +56,7 @@
   void SimulateGattCharacteristicWriteError(
       BluetoothRemoteGattCharacteristic* characteristic,
       BluetoothRemoteGattService::GattErrorCode error_code) override;
+  void RememberDeviceForSubsequentAction(BluetoothDevice* device) override;
   void DeleteDevice(BluetoothDevice* device) override;
   void SimulateGattDescriptor(BluetoothRemoteGattCharacteristic* characteristic,
                               const std::string& uuid) override;
@@ -83,6 +84,9 @@
   win::BluetoothClassicWrapperFake* fake_bt_classic_wrapper_;
   win::BluetoothLowEnergyWrapperFake* fake_bt_le_wrapper_;
 
+  // This is used for retaining access to a single deleted device.
+  std::string remembered_device_address_;
+
   void AdapterInitCallback();
   win::GattService* GetSimulatedService(win::BLEDevice* device,
                                         BluetoothRemoteGattService* service);
diff --git a/device/geolocation/BUILD.gn b/device/geolocation/BUILD.gn
index 3d70cc0..c3f47f0 100644
--- a/device/geolocation/BUILD.gn
+++ b/device/geolocation/BUILD.gn
@@ -74,8 +74,6 @@
     sources -= [
       "network_location_provider.cc",
       "network_location_provider.h",
-      "network_location_request.cc",
-      "network_location_request.h",
     ]
     deps += [ ":geolocation_jni_headers" ]
   }
diff --git a/device/geolocation/network_location_request.h b/device/geolocation/network_location_request.h
index 07fe536..10ae8e01 100644
--- a/device/geolocation/network_location_request.h
+++ b/device/geolocation/network_location_request.h
@@ -28,10 +28,11 @@
 
 // Takes wifi data and sends it to a server to get a position fix.
 // It performs formatting of the request and interpretation of the response.
-class NetworkLocationRequest : private net::URLFetcherDelegate {
+class DEVICE_GEOLOCATION_EXPORT NetworkLocationRequest
+    : private net::URLFetcherDelegate {
  public:
   // ID passed to URLFetcher::Create(). Used for testing.
-  DEVICE_GEOLOCATION_EXPORT static int url_fetcher_id_for_tests;
+  static int url_fetcher_id_for_tests;
 
   // Called when a new geo position is available. The second argument indicates
   // whether there was a server error or not. It is true when there was a
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 38663e3..7f61975 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -70,6 +70,7 @@
     if (enable_openvr) {
       deps += [
         "//device/gamepad",
+        "//device/gamepad/public/cpp:shared_with_blink",
         "//third_party/openvr:openvr",
       ]
       sources += [
diff --git a/device/vr/features/features.gni b/device/vr/features/features.gni
index 2e3c76a7..5698cd8 100644
--- a/device/vr/features/features.gni
+++ b/device/vr/features/features.gni
@@ -9,13 +9,12 @@
   # support arm and arm64.
   enable_gvr_services = is_android && !is_chromecast &&
                         (current_cpu == "arm" || current_cpu == "arm64")
-  enable_openvr = false
+  enable_openvr = is_win
 }
 
 declare_args() {
-  # Enable VR device support whenever VR device SDK(s) are supported and
-  # on Windows for testing.
-  enable_vr = enable_gvr_services || enable_openvr || is_win
+  # Enable VR device support whenever VR device SDK(s) are supported.
+  enable_vr = enable_gvr_services || enable_openvr
 
   # Whether to include VR extras like test APKs in non-VR-specific targets
   include_vr_data = false
diff --git a/device/vr/openvr/openvr_device_provider.cc b/device/vr/openvr/openvr_device_provider.cc
index baed53a..8ba4aee 100644
--- a/device/vr/openvr/openvr_device_provider.cc
+++ b/device/vr/openvr/openvr_device_provider.cc
@@ -14,14 +14,24 @@
 OpenVRDeviceProvider::OpenVRDeviceProvider()
     : initialized_(false), vr_system_(nullptr) {}
 
-OpenVRDeviceProvider::~OpenVRDeviceProvider() {}
+OpenVRDeviceProvider::~OpenVRDeviceProvider() {
+  device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory(
+      device::GAMEPAD_SOURCE_OPENVR);
+  device_ = nullptr;
+  vr::VR_Shutdown();
+}
 
 void OpenVRDeviceProvider::GetDevices(std::vector<VRDevice*>* devices) {
   if (initialized_) {
-    VRDevice* device = new OpenVRDevice(vr_system_);
-    devices->push_back(device);
-    GamepadDataFetcherManager::GetInstance()->AddFactory(
-        new OpenVRGamepadDataFetcher::Factory(device->GetId(), vr_system_));
+    if (!device_) {
+      device_ = std::make_unique<OpenVRDevice>(vr_system_);
+      GamepadDataFetcherManager::GetInstance()->AddFactory(
+          new OpenVRGamepadDataFetcher::Factory(device_->GetId(), vr_system_));
+    }
+
+    if (device_) {
+      devices->push_back(device_.get());
+    }
   }
 }
 
diff --git a/device/vr/openvr/openvr_device_provider.h b/device/vr/openvr/openvr_device_provider.h
index 7dc42db4..4e5fa3fd 100644
--- a/device/vr/openvr/openvr_device_provider.h
+++ b/device/vr/openvr/openvr_device_provider.h
@@ -18,6 +18,8 @@
 
 namespace device {
 
+class OpenVRDevice;
+
 class DEVICE_VR_EXPORT OpenVRDeviceProvider : public VRDeviceProvider {
  public:
   OpenVRDeviceProvider();
@@ -29,6 +31,7 @@
  private:
   bool initialized_;
   vr::IVRSystem* vr_system_;
+  std::unique_ptr<OpenVRDevice> device_;
 
   DISALLOW_COPY_AND_ASSIGN(OpenVRDeviceProvider);
 };
diff --git a/device/vr/openvr/openvr_render_loop.cc b/device/vr/openvr/openvr_render_loop.cc
index 1d0051a..788962d 100644
--- a/device/vr/openvr/openvr_render_loop.cc
+++ b/device/vr/openvr/openvr_render_loop.cc
@@ -20,7 +20,9 @@
   DCHECK(main_thread_task_runner_);
 }
 
-OpenVRRenderLoop::~OpenVRRenderLoop() {}
+OpenVRRenderLoop::~OpenVRRenderLoop() {
+  Stop();
+}
 
 void OpenVRRenderLoop::SubmitFrame(int16_t frame_index,
                                    const gpu::MailboxHolder& mailbox) {
@@ -78,6 +80,11 @@
 #endif
 }
 
+void OpenVRRenderLoop::CleanUp() {
+  submit_client_ = nullptr;
+  binding_.Close();
+}
+
 void OpenVRRenderLoop::UpdateLayerBounds(int16_t frame_id,
                                          const gfx::RectF& left_bounds,
                                          const gfx::RectF& right_bounds,
diff --git a/device/vr/openvr/openvr_render_loop.h b/device/vr/openvr/openvr_render_loop.h
index 676bc824..8719c53 100644
--- a/device/vr/openvr/openvr_render_loop.h
+++ b/device/vr/openvr/openvr_render_loop.h
@@ -45,6 +45,7 @@
  private:
   // base::Thread overrides:
   void Init() override;
+  void CleanUp() override;
 
   mojom::VRPosePtr GetPose();
 
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index a0537d0..92da9bf 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -377,6 +377,7 @@
     "//base",
     "//base/test:test_support",
     "//base/third_party/dynamic_annotations",
+    "//cc/paint",
     "//gpu/command_buffer/client:gles2_c_lib",
     "//gpu/command_buffer/client:gles2_implementation",
     "//gpu/command_buffer/common:gles2_utils",
diff --git a/gpu/GLES2/gl2chromium_autogen.h b/gpu/GLES2/gl2chromium_autogen.h
index 4d36aea..6b2d4bf 100644
--- a/gpu/GLES2/gl2chromium_autogen.h
+++ b/gpu/GLES2/gl2chromium_autogen.h
@@ -399,6 +399,12 @@
 #define glBeginRasterCHROMIUM GLES2_GET_FUN(BeginRasterCHROMIUM)
 #define glRasterCHROMIUM GLES2_GET_FUN(RasterCHROMIUM)
 #define glEndRasterCHROMIUM GLES2_GET_FUN(EndRasterCHROMIUM)
+#define glCreateTransferCacheEntryCHROMIUM \
+  GLES2_GET_FUN(CreateTransferCacheEntryCHROMIUM)
+#define glDeleteTransferCacheEntryCHROMIUM \
+  GLES2_GET_FUN(DeleteTransferCacheEntryCHROMIUM)
+#define glUnlockTransferCacheEntryCHROMIUM \
+  GLES2_GET_FUN(UnlockTransferCacheEntryCHROMIUM)
 #define glTexStorage2DImageCHROMIUM GLES2_GET_FUN(TexStorage2DImageCHROMIUM)
 #define glSetColorSpaceMetadataCHROMIUM \
   GLES2_GET_FUN(SetColorSpaceMetadataCHROMIUM)
diff --git a/gpu/command_buffer/build_gles2_cmd_buffer.py b/gpu/command_buffer/build_gles2_cmd_buffer.py
index ebfe95e..b3f0866 100755
--- a/gpu/command_buffer/build_gles2_cmd_buffer.py
+++ b/gpu/command_buffer/build_gles2_cmd_buffer.py
@@ -4635,6 +4635,30 @@
     'extension': 'CHROMIUM_raster_transport',
     'extension_flag': 'chromium_raster_transport',
   },
+  "CreateTransferCacheEntryCHROMIUM": {
+    'type': 'Custom',
+    'cmd_args': 'GLuint64 handle_id, GLuint handle_shm_id, '
+                'GLuint handle_shm_offset, GLuint type, '
+                'GLuint data_shm_id, GLuint data_shm_offset, '
+                'GLuint data_size',
+    'impl_func': False,
+    'client_test': False,
+    'extension': True,
+  },
+  "DeleteTransferCacheEntryCHROMIUM": {
+    'decoder_func': 'DoDeleteTransferCacheEntryCHROMIUM',
+    'cmd_args': 'GLuint64 handle_id',
+    'impl_func': True,
+    'client_test': False,
+    'extension': True,
+  },
+  "UnlockTransferCacheEntryCHROMIUM": {
+    'decoder_func': 'DoUnlockTransferCacheEntryCHROMIUM',
+    'cmd_args': 'GLuint64 handle_id',
+    'impl_func': True,
+    'client_test': False,
+    'extension': True,
+  },
   'TexStorage2DImageCHROMIUM': {
     'decoder_func': 'DoTexStorage2DImageCHROMIUM',
     'unit_test': False,
diff --git a/gpu/command_buffer/client/BUILD.gn b/gpu/command_buffer/client/BUILD.gn
index 50c937df..12beb1a 100644
--- a/gpu/command_buffer/client/BUILD.gn
+++ b/gpu/command_buffer/client/BUILD.gn
@@ -46,6 +46,8 @@
     "client_discardable_manager.h",
     "client_discardable_texture_manager.cc",
     "client_discardable_texture_manager.h",
+    "client_transfer_cache.cc",
+    "client_transfer_cache.h",
     "cmd_buffer_helper.cc",
     "cmd_buffer_helper.h",
     "fenced_allocator.cc",
@@ -70,11 +72,16 @@
     "//gpu/command_buffer/common:gles2_utils",
   ]
   deps = [
+    ":gles2_interface",
     "//gpu/command_buffer/common:common_sources",
     "//gpu/ipc/common:surface_handle_type",
     "//ui/gfx:memory_buffer",
     "//ui/gfx/geometry",
   ]
+
+  if (!is_nacl) {
+    deps += [ "//cc/paint" ]
+  }
 }
 
 source_set("gles2_cmd_helper_sources") {
diff --git a/gpu/command_buffer/client/client_transfer_cache.cc b/gpu/command_buffer/client/client_transfer_cache.cc
new file mode 100644
index 0000000..a929e87
--- /dev/null
+++ b/gpu/command_buffer/client/client_transfer_cache.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 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 "gpu/command_buffer/client/client_transfer_cache.h"
+
+namespace gpu {
+
+ClientTransferCache::ClientTransferCache() = default;
+ClientTransferCache::~ClientTransferCache() = default;
+
+TransferCacheEntryId ClientTransferCache::CreateCacheEntry(
+    gles2::GLES2Interface* gl,
+    CommandBuffer* command_buffer,
+    const cc::ClientTransferCacheEntry& entry) {
+  TransferCacheEntryId id = discardable_manager_.CreateHandle(command_buffer);
+  ClientDiscardableHandle handle = discardable_manager_.GetHandle(id);
+  gl->CreateTransferCacheEntryCHROMIUM(id.GetUnsafeValue(), handle.shm_id(),
+                                       handle.byte_offset(), entry);
+  return id;
+}
+
+bool ClientTransferCache::LockTransferCacheEntry(TransferCacheEntryId id) {
+  if (discardable_manager_.LockHandle(id))
+    return true;
+
+  // Could not lock. Entry is already deleted service side, just free the
+  // handle.
+  discardable_manager_.FreeHandle(id);
+  return false;
+}
+
+void ClientTransferCache::UnlockTransferCacheEntry(gles2::GLES2Interface* gl,
+                                                   TransferCacheEntryId id) {
+  gl->UnlockTransferCacheEntryCHROMIUM(id.GetUnsafeValue());
+}
+
+void ClientTransferCache::DeleteTransferCacheEntry(gles2::GLES2Interface* gl,
+                                                   TransferCacheEntryId id) {
+  discardable_manager_.FreeHandle(id);
+  gl->DeleteTransferCacheEntryCHROMIUM(id.GetUnsafeValue());
+}
+
+}  // namespace gpu
diff --git a/gpu/command_buffer/client/client_transfer_cache.h b/gpu/command_buffer/client/client_transfer_cache.h
new file mode 100644
index 0000000..d5c9c35
--- /dev/null
+++ b/gpu/command_buffer/client/client_transfer_cache.h
@@ -0,0 +1,57 @@
+// Copyright (c) 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 GPU_COMMAND_BUFFER_CLIENT_CLIENT_TRANSFER_CACHE_H_
+#define GPU_COMMAND_BUFFER_CLIENT_CLIENT_TRANSFER_CACHE_H_
+
+#include "cc/paint/transfer_cache_entry.h"
+#include "gpu/command_buffer/client/client_discardable_manager.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/common/transfer_cache_entry_id.h"
+
+namespace gpu {
+
+// ClientTransferCache allows for ClientTransferCacheEntries to be inserted
+// into the cache, which will send them to the ServiceTransferCache, making
+// them available for consumption in the GPU process. Typical usage is:
+//   1) Insert a new entry via CreateCacheEntry. It starts locked.
+//   2) Use the new entry's ID in one or more commands.
+//   3) Unlock the entry via UnlockTransferCacheEntry after dependent commands
+//      have been issued.
+//
+// If an entry is needed again:
+//   4) Attempt to lock the entry via LockTransferCacheEntry.
+//      4a) If this fails, DeleteTransferCacheEntry then go to (1)
+//      4b) If this succeeds, go to (2).
+//
+// If an entry is no longer needed:
+//   5) DeleteTransferCacheEntry
+//
+class GPU_EXPORT ClientTransferCache {
+ public:
+  ClientTransferCache();
+  ~ClientTransferCache();
+
+  TransferCacheEntryId CreateCacheEntry(
+      gles2::GLES2Interface* gl,
+      CommandBuffer* command_buffer,
+      const cc::ClientTransferCacheEntry& entry);
+  bool LockTransferCacheEntry(TransferCacheEntryId id);
+  void UnlockTransferCacheEntry(gles2::GLES2Interface* gl,
+                                TransferCacheEntryId id);
+  void DeleteTransferCacheEntry(gles2::GLES2Interface* gl,
+                                TransferCacheEntryId id);
+
+  // Test only functions
+  ClientDiscardableManager* DiscardableManagerForTesting() {
+    return &discardable_manager_;
+  }
+
+ private:
+  ClientDiscardableManager discardable_manager_;
+};
+
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_CLIENT_CLIENT_TRANSFER_CACHE_H_
diff --git a/gpu/command_buffer/client/gles2_c_lib_autogen.h b/gpu/command_buffer/client/gles2_c_lib_autogen.h
index f9ae81f4..e155246 100644
--- a/gpu/command_buffer/client/gles2_c_lib_autogen.h
+++ b/gpu/command_buffer/client/gles2_c_lib_autogen.h
@@ -1794,6 +1794,20 @@
 void GL_APIENTRY GLES2EndRasterCHROMIUM() {
   gles2::GetGLContext()->EndRasterCHROMIUM();
 }
+void GL_APIENTRY GLES2CreateTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id,
+    GLuint handle_shm_id,
+    GLuint handle_shm_offset,
+    const cc::ClientTransferCacheEntry& entry) {
+  gles2::GetGLContext()->CreateTransferCacheEntryCHROMIUM(
+      handle_id, handle_shm_id, handle_shm_offset, entry);
+}
+void GL_APIENTRY GLES2DeleteTransferCacheEntryCHROMIUM(GLuint64 handle_id) {
+  gles2::GetGLContext()->DeleteTransferCacheEntryCHROMIUM(handle_id);
+}
+void GL_APIENTRY GLES2UnlockTransferCacheEntryCHROMIUM(GLuint64 handle_id) {
+  gles2::GetGLContext()->UnlockTransferCacheEntryCHROMIUM(handle_id);
+}
 void GL_APIENTRY GLES2TexStorage2DImageCHROMIUM(GLenum target,
                                                 GLenum internalFormat,
                                                 GLenum bufferUsage,
@@ -3159,6 +3173,21 @@
         reinterpret_cast<GLES2FunctionPointer>(glEndRasterCHROMIUM),
     },
     {
+        "glCreateTransferCacheEntryCHROMIUM",
+        reinterpret_cast<GLES2FunctionPointer>(
+            glCreateTransferCacheEntryCHROMIUM),
+    },
+    {
+        "glDeleteTransferCacheEntryCHROMIUM",
+        reinterpret_cast<GLES2FunctionPointer>(
+            glDeleteTransferCacheEntryCHROMIUM),
+    },
+    {
+        "glUnlockTransferCacheEntryCHROMIUM",
+        reinterpret_cast<GLES2FunctionPointer>(
+            glUnlockTransferCacheEntryCHROMIUM),
+    },
+    {
         "glTexStorage2DImageCHROMIUM",
         reinterpret_cast<GLES2FunctionPointer>(glTexStorage2DImageCHROMIUM),
     },
diff --git a/gpu/command_buffer/client/gles2_cmd_helper_autogen.h b/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
index 5f7cab6..1423350 100644
--- a/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
+++ b/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
@@ -3298,6 +3298,37 @@
   }
 }
 
+void CreateTransferCacheEntryCHROMIUM(GLuint64 handle_id,
+                                      GLuint handle_shm_id,
+                                      GLuint handle_shm_offset,
+                                      GLuint type,
+                                      GLuint data_shm_id,
+                                      GLuint data_shm_offset,
+                                      GLuint data_size) {
+  gles2::cmds::CreateTransferCacheEntryCHROMIUM* c =
+      GetCmdSpace<gles2::cmds::CreateTransferCacheEntryCHROMIUM>();
+  if (c) {
+    c->Init(handle_id, handle_shm_id, handle_shm_offset, type, data_shm_id,
+            data_shm_offset, data_size);
+  }
+}
+
+void DeleteTransferCacheEntryCHROMIUM(GLuint64 handle_id) {
+  gles2::cmds::DeleteTransferCacheEntryCHROMIUM* c =
+      GetCmdSpace<gles2::cmds::DeleteTransferCacheEntryCHROMIUM>();
+  if (c) {
+    c->Init(handle_id);
+  }
+}
+
+void UnlockTransferCacheEntryCHROMIUM(GLuint64 handle_id) {
+  gles2::cmds::UnlockTransferCacheEntryCHROMIUM* c =
+      GetCmdSpace<gles2::cmds::UnlockTransferCacheEntryCHROMIUM>();
+  if (c) {
+    c->Init(handle_id);
+  }
+}
+
 void TexStorage2DImageCHROMIUM(GLenum target,
                                GLenum internalFormat,
                                GLsizei width,
diff --git a/gpu/command_buffer/client/gles2_implementation.cc b/gpu/command_buffer/client/gles2_implementation.cc
index b13547cf..6c4aeb7 100644
--- a/gpu/command_buffer/client/gles2_implementation.cc
+++ b/gpu/command_buffer/client/gles2_implementation.cc
@@ -52,6 +52,7 @@
 #if !defined(OS_NACL)
 #include "cc/paint/display_item_list.h"  // nogncheck
 #include "cc/paint/paint_op_buffer_serializer.h"
+#include "cc/paint/transfer_cache_entry.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/skia_util.h"
 #endif
@@ -7108,6 +7109,28 @@
   return true;
 }
 
+void GLES2Implementation::CreateTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id,
+    GLuint handle_shm_id,
+    GLuint handle_shm_offset,
+    const cc::ClientTransferCacheEntry& entry) {
+#if defined(OS_NACL)
+  NOTREACHED();
+#else
+  ScopedMappedMemoryPtr mapped_alloc(entry.SerializedSize(), helper_,
+                                     mapped_memory_.get());
+  DCHECK(mapped_alloc.valid());
+  bool succeeded = entry.Serialize(
+      mapped_alloc.size(), reinterpret_cast<uint8_t*>(mapped_alloc.address()));
+  DCHECK(succeeded);
+
+  helper_->CreateTransferCacheEntryCHROMIUM(
+      handle_id, handle_shm_id, handle_shm_offset,
+      static_cast<uint32_t>(entry.Type()), mapped_alloc.shm_id(),
+      mapped_alloc.offset(), mapped_alloc.size());
+#endif
+}
+
 void GLES2Implementation::UpdateCachedExtensionsIfNeeded() {
   if (cached_extension_string_) {
     return;
diff --git a/gpu/command_buffer/client/gles2_implementation_autogen.h b/gpu/command_buffer/client/gles2_implementation_autogen.h
index e5a124280..b5d99a0 100644
--- a/gpu/command_buffer/client/gles2_implementation_autogen.h
+++ b/gpu/command_buffer/client/gles2_implementation_autogen.h
@@ -1259,6 +1259,16 @@
 
 void EndRasterCHROMIUM() override;
 
+void CreateTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id,
+    GLuint handle_shm_id,
+    GLuint handle_shm_offset,
+    const cc::ClientTransferCacheEntry& entry) override;
+
+void DeleteTransferCacheEntryCHROMIUM(GLuint64 handle_id) override;
+
+void UnlockTransferCacheEntryCHROMIUM(GLuint64 handle_id) override;
+
 void TexStorage2DImageCHROMIUM(GLenum target,
                                GLenum internalFormat,
                                GLenum bufferUsage,
diff --git a/gpu/command_buffer/client/gles2_implementation_impl_autogen.h b/gpu/command_buffer/client/gles2_implementation_impl_autogen.h
index 6f7a6bd8..f7e26d4 100644
--- a/gpu/command_buffer/client/gles2_implementation_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_implementation_impl_autogen.h
@@ -3574,6 +3574,24 @@
   CheckGLError();
 }
 
+void GLES2Implementation::DeleteTransferCacheEntryCHROMIUM(GLuint64 handle_id) {
+  GPU_CLIENT_SINGLE_THREAD_CHECK();
+  GPU_CLIENT_LOG("[" << GetLogPrefix()
+                     << "] glDeleteTransferCacheEntryCHROMIUM(" << handle_id
+                     << ")");
+  helper_->DeleteTransferCacheEntryCHROMIUM(handle_id);
+  CheckGLError();
+}
+
+void GLES2Implementation::UnlockTransferCacheEntryCHROMIUM(GLuint64 handle_id) {
+  GPU_CLIENT_SINGLE_THREAD_CHECK();
+  GPU_CLIENT_LOG("[" << GetLogPrefix()
+                     << "] glUnlockTransferCacheEntryCHROMIUM(" << handle_id
+                     << ")");
+  helper_->UnlockTransferCacheEntryCHROMIUM(handle_id);
+  CheckGLError();
+}
+
 void GLES2Implementation::TexStorage2DImageCHROMIUM(GLenum target,
                                                     GLenum internalFormat,
                                                     GLenum bufferUsage,
diff --git a/gpu/command_buffer/client/gles2_interface.h b/gpu/command_buffer/client/gles2_interface.h
index 030ecdf..86a385f8 100644
--- a/gpu/command_buffer/client/gles2_interface.h
+++ b/gpu/command_buffer/client/gles2_interface.h
@@ -10,6 +10,7 @@
 #include "base/compiler_specific.h"
 
 namespace cc {
+class ClientTransferCacheEntry;
 class DisplayItemList;
 }
 
diff --git a/gpu/command_buffer/client/gles2_interface_autogen.h b/gpu/command_buffer/client/gles2_interface_autogen.h
index 4ea3d63..c332e63a 100644
--- a/gpu/command_buffer/client/gles2_interface_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_autogen.h
@@ -936,6 +936,13 @@
                             GLfloat post_translate_y,
                             GLfloat post_scale) = 0;
 virtual void EndRasterCHROMIUM() = 0;
+virtual void CreateTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id,
+    GLuint handle_shm_id,
+    GLuint handle_shm_offset,
+    const cc::ClientTransferCacheEntry& entry) = 0;
+virtual void DeleteTransferCacheEntryCHROMIUM(GLuint64 handle_id) = 0;
+virtual void UnlockTransferCacheEntryCHROMIUM(GLuint64 handle_id) = 0;
 virtual void TexStorage2DImageCHROMIUM(GLenum target,
                                        GLenum internalFormat,
                                        GLenum bufferUsage,
diff --git a/gpu/command_buffer/client/gles2_interface_stub_autogen.h b/gpu/command_buffer/client/gles2_interface_stub_autogen.h
index 9bb6c57..0d59364 100644
--- a/gpu/command_buffer/client/gles2_interface_stub_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_stub_autogen.h
@@ -909,6 +909,13 @@
                     GLfloat post_translate_y,
                     GLfloat post_scale) override;
 void EndRasterCHROMIUM() override;
+void CreateTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id,
+    GLuint handle_shm_id,
+    GLuint handle_shm_offset,
+    const cc::ClientTransferCacheEntry& entry) override;
+void DeleteTransferCacheEntryCHROMIUM(GLuint64 handle_id) override;
+void UnlockTransferCacheEntryCHROMIUM(GLuint64 handle_id) override;
 void TexStorage2DImageCHROMIUM(GLenum target,
                                GLenum internalFormat,
                                GLenum bufferUsage,
diff --git a/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h b/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
index bbcc356..1214bc2 100644
--- a/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
@@ -1220,6 +1220,15 @@
                                         GLfloat /* post_translate_y */,
                                         GLfloat /* post_scale */) {}
 void GLES2InterfaceStub::EndRasterCHROMIUM() {}
+void GLES2InterfaceStub::CreateTransferCacheEntryCHROMIUM(
+    GLuint64 /* handle_id */,
+    GLuint /* handle_shm_id */,
+    GLuint /* handle_shm_offset */,
+    const cc::ClientTransferCacheEntry& /* entry */) {}
+void GLES2InterfaceStub::DeleteTransferCacheEntryCHROMIUM(
+    GLuint64 /* handle_id */) {}
+void GLES2InterfaceStub::UnlockTransferCacheEntryCHROMIUM(
+    GLuint64 /* handle_id */) {}
 void GLES2InterfaceStub::TexStorage2DImageCHROMIUM(GLenum /* target */,
                                                    GLenum /* internalFormat */,
                                                    GLenum /* bufferUsage */,
diff --git a/gpu/command_buffer/client/gles2_trace_implementation_autogen.h b/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
index 5665f233..f0ef8bd 100644
--- a/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
+++ b/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
@@ -909,6 +909,13 @@
                     GLfloat post_translate_y,
                     GLfloat post_scale) override;
 void EndRasterCHROMIUM() override;
+void CreateTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id,
+    GLuint handle_shm_id,
+    GLuint handle_shm_offset,
+    const cc::ClientTransferCacheEntry& entry) override;
+void DeleteTransferCacheEntryCHROMIUM(GLuint64 handle_id) override;
+void UnlockTransferCacheEntryCHROMIUM(GLuint64 handle_id) override;
 void TexStorage2DImageCHROMIUM(GLenum target,
                                GLenum internalFormat,
                                GLenum bufferUsage,
diff --git a/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h b/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
index 338a0db8..32730225 100644
--- a/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
@@ -2603,6 +2603,31 @@
   gl_->EndRasterCHROMIUM();
 }
 
+void GLES2TraceImplementation::CreateTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id,
+    GLuint handle_shm_id,
+    GLuint handle_shm_offset,
+    const cc::ClientTransferCacheEntry& entry) {
+  TRACE_EVENT_BINARY_EFFICIENT0("gpu",
+                                "GLES2Trace::CreateTransferCacheEntryCHROMIUM");
+  gl_->CreateTransferCacheEntryCHROMIUM(handle_id, handle_shm_id,
+                                        handle_shm_offset, entry);
+}
+
+void GLES2TraceImplementation::DeleteTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id) {
+  TRACE_EVENT_BINARY_EFFICIENT0("gpu",
+                                "GLES2Trace::DeleteTransferCacheEntryCHROMIUM");
+  gl_->DeleteTransferCacheEntryCHROMIUM(handle_id);
+}
+
+void GLES2TraceImplementation::UnlockTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id) {
+  TRACE_EVENT_BINARY_EFFICIENT0("gpu",
+                                "GLES2Trace::UnlockTransferCacheEntryCHROMIUM");
+  gl_->UnlockTransferCacheEntryCHROMIUM(handle_id);
+}
+
 void GLES2TraceImplementation::TexStorage2DImageCHROMIUM(GLenum target,
                                                          GLenum internalFormat,
                                                          GLenum bufferUsage,
diff --git a/gpu/command_buffer/cmd_buffer_functions.txt b/gpu/command_buffer/cmd_buffer_functions.txt
index 4e65c80..255dad7c 100644
--- a/gpu/command_buffer/cmd_buffer_functions.txt
+++ b/gpu/command_buffer/cmd_buffer_functions.txt
@@ -381,6 +381,9 @@
 GL_APICALL void         GL_APIENTRY glBeginRasterCHROMIUM (GLuint texture_id, GLuint sk_color, GLuint msaa_sample_count, GLboolean can_use_lcd_text, GLboolean use_distance_field_text, GLint pixel_config);
 GL_APICALL void         GL_APIENTRY glRasterCHROMIUM (const cc::DisplayItemList* list, GLint translate_x, GLint translate_y, GLint clip_x, GLint clip_y, GLint clip_w, GLint clip_h, GLfloat post_translate_x, GLfloat post_translate_y, GLfloat post_scale);
 GL_APICALL void         GL_APIENTRY glEndRasterCHROMIUM (void);
+GL_APICALL void         GL_APIENTRY glCreateTransferCacheEntryCHROMIUM (GLuint64 handle_id, GLuint handle_shm_id, GLuint handle_shm_offset, const cc::ClientTransferCacheEntry& entry);
+GL_APICALL void         GL_APIENTRY glDeleteTransferCacheEntryCHROMIUM (GLuint64 handle_id);
+GL_APICALL void         GL_APIENTRY glUnlockTransferCacheEntryCHROMIUM (GLuint64 handle_id);
 
 // Extension CHROMIUM_texture_storage_image
 GL_APICALL void         GL_APIENTRY glTexStorage2DImageCHROMIUM (GLenumTextureBindTarget target, GLenumTextureInternalFormatStorage internalFormat, GLenumClientBufferUsage bufferUsage, GLsizei width, GLsizei height);
diff --git a/gpu/command_buffer/common/BUILD.gn b/gpu/command_buffer/common/BUILD.gn
index b7b3dc6..1f177b2b 100644
--- a/gpu/command_buffer/common/BUILD.gn
+++ b/gpu/command_buffer/common/BUILD.gn
@@ -62,6 +62,7 @@
     "texture_in_use_response.h",
     "thread_local.h",
     "time.h",
+    "transfer_cache_entry_id.h",
   ]
 
   configs += [ "//gpu:gpu_implementation" ]
diff --git a/gpu/command_buffer/common/discardable_handle.cc b/gpu/command_buffer/common/discardable_handle.cc
index 6b759c1..7401471 100644
--- a/gpu/command_buffer/common/discardable_handle.cc
+++ b/gpu/command_buffer/common/discardable_handle.cc
@@ -50,6 +50,10 @@
   return kHandleDeleted == base::subtle::NoBarrier_Load(AsAtomic());
 }
 
+scoped_refptr<Buffer> DiscardableHandleBase::BufferForTesting() const {
+  return buffer_;
+}
+
 volatile base::subtle::Atomic32* DiscardableHandleBase::AsAtomic() const {
   return reinterpret_cast<volatile base::subtle::Atomic32*>(
       buffer_->GetDataAddress(byte_offset_, sizeof(base::subtle::Atomic32)));
@@ -125,6 +129,11 @@
   // No barrier is needed as all GPU process access happens on a single thread,
   // and communication of dependent data between the GPU process and the
   // renderer process happens across the command buffer and includes barriers.
+
+  // This check notifies a non-malicious caller that they've issued unbalanced
+  // lock/unlock calls.
+  DLOG_IF(ERROR, kHandleLockedStart > base::subtle::NoBarrier_Load(AsAtomic()));
+
   base::subtle::NoBarrier_AtomicIncrement(AsAtomic(), -1);
 }
 
diff --git a/gpu/command_buffer/common/discardable_handle.h b/gpu/command_buffer/common/discardable_handle.h
index 1e9ea51..18cf0e8 100644
--- a/gpu/command_buffer/common/discardable_handle.h
+++ b/gpu/command_buffer/common/discardable_handle.h
@@ -43,7 +43,7 @@
   // Test only functions.
   bool IsLockedForTesting() const;
   bool IsDeletedForTesting() const;
-  scoped_refptr<Buffer> BufferForTesting() const { return buffer_; }
+  scoped_refptr<Buffer> BufferForTesting() const;
 
  protected:
   DiscardableHandleBase(scoped_refptr<Buffer> buffer,
diff --git a/gpu/command_buffer/common/gles2_cmd_format_autogen.h b/gpu/command_buffer/common/gles2_cmd_format_autogen.h
index c994825..46de934 100644
--- a/gpu/command_buffer/common/gles2_cmd_format_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_format_autogen.h
@@ -16185,6 +16185,183 @@
 static_assert(offsetof(EndRasterCHROMIUM, header) == 0,
               "offset of EndRasterCHROMIUM header should be 0");
 
+struct CreateTransferCacheEntryCHROMIUM {
+  typedef CreateTransferCacheEntryCHROMIUM ValueType;
+  static const CommandId kCmdId = kCreateTransferCacheEntryCHROMIUM;
+  static const cmd::ArgFlags kArgFlags = cmd::kFixed;
+  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3);
+
+  static uint32_t ComputeSize() {
+    return static_cast<uint32_t>(sizeof(ValueType));  // NOLINT
+  }
+
+  void SetHeader() { header.SetCmd<ValueType>(); }
+
+  void Init(GLuint64 _handle_id,
+            GLuint _handle_shm_id,
+            GLuint _handle_shm_offset,
+            GLuint _type,
+            GLuint _data_shm_id,
+            GLuint _data_shm_offset,
+            GLuint _data_size) {
+    SetHeader();
+    GLES2Util::MapUint64ToTwoUint32(static_cast<uint64_t>(_handle_id),
+                                    &handle_id_0, &handle_id_1);
+    handle_shm_id = _handle_shm_id;
+    handle_shm_offset = _handle_shm_offset;
+    type = _type;
+    data_shm_id = _data_shm_id;
+    data_shm_offset = _data_shm_offset;
+    data_size = _data_size;
+  }
+
+  void* Set(void* cmd,
+            GLuint64 _handle_id,
+            GLuint _handle_shm_id,
+            GLuint _handle_shm_offset,
+            GLuint _type,
+            GLuint _data_shm_id,
+            GLuint _data_shm_offset,
+            GLuint _data_size) {
+    static_cast<ValueType*>(cmd)->Init(_handle_id, _handle_shm_id,
+                                       _handle_shm_offset, _type, _data_shm_id,
+                                       _data_shm_offset, _data_size);
+    return NextCmdAddress<ValueType>(cmd);
+  }
+
+  GLuint64 handle_id() const volatile {
+    return static_cast<GLuint64>(
+        GLES2Util::MapTwoUint32ToUint64(handle_id_0, handle_id_1));
+  }
+
+  gpu::CommandHeader header;
+  uint32_t handle_id_0;
+  uint32_t handle_id_1;
+  uint32_t handle_shm_id;
+  uint32_t handle_shm_offset;
+  uint32_t type;
+  uint32_t data_shm_id;
+  uint32_t data_shm_offset;
+  uint32_t data_size;
+};
+
+static_assert(sizeof(CreateTransferCacheEntryCHROMIUM) == 36,
+              "size of CreateTransferCacheEntryCHROMIUM should be 36");
+static_assert(offsetof(CreateTransferCacheEntryCHROMIUM, header) == 0,
+              "offset of CreateTransferCacheEntryCHROMIUM header should be 0");
+static_assert(
+    offsetof(CreateTransferCacheEntryCHROMIUM, handle_id_0) == 4,
+    "offset of CreateTransferCacheEntryCHROMIUM handle_id_0 should be 4");
+static_assert(
+    offsetof(CreateTransferCacheEntryCHROMIUM, handle_id_1) == 8,
+    "offset of CreateTransferCacheEntryCHROMIUM handle_id_1 should be 8");
+static_assert(
+    offsetof(CreateTransferCacheEntryCHROMIUM, handle_shm_id) == 12,
+    "offset of CreateTransferCacheEntryCHROMIUM handle_shm_id should be 12");
+static_assert(offsetof(CreateTransferCacheEntryCHROMIUM, handle_shm_offset) ==
+                  16,
+              "offset of CreateTransferCacheEntryCHROMIUM handle_shm_offset "
+              "should be 16");
+static_assert(offsetof(CreateTransferCacheEntryCHROMIUM, type) == 20,
+              "offset of CreateTransferCacheEntryCHROMIUM type should be 20");
+static_assert(
+    offsetof(CreateTransferCacheEntryCHROMIUM, data_shm_id) == 24,
+    "offset of CreateTransferCacheEntryCHROMIUM data_shm_id should be 24");
+static_assert(
+    offsetof(CreateTransferCacheEntryCHROMIUM, data_shm_offset) == 28,
+    "offset of CreateTransferCacheEntryCHROMIUM data_shm_offset should be 28");
+static_assert(
+    offsetof(CreateTransferCacheEntryCHROMIUM, data_size) == 32,
+    "offset of CreateTransferCacheEntryCHROMIUM data_size should be 32");
+
+struct DeleteTransferCacheEntryCHROMIUM {
+  typedef DeleteTransferCacheEntryCHROMIUM ValueType;
+  static const CommandId kCmdId = kDeleteTransferCacheEntryCHROMIUM;
+  static const cmd::ArgFlags kArgFlags = cmd::kFixed;
+  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3);
+
+  static uint32_t ComputeSize() {
+    return static_cast<uint32_t>(sizeof(ValueType));  // NOLINT
+  }
+
+  void SetHeader() { header.SetCmd<ValueType>(); }
+
+  void Init(GLuint64 _handle_id) {
+    SetHeader();
+    GLES2Util::MapUint64ToTwoUint32(static_cast<uint64_t>(_handle_id),
+                                    &handle_id_0, &handle_id_1);
+  }
+
+  void* Set(void* cmd, GLuint64 _handle_id) {
+    static_cast<ValueType*>(cmd)->Init(_handle_id);
+    return NextCmdAddress<ValueType>(cmd);
+  }
+
+  GLuint64 handle_id() const volatile {
+    return static_cast<GLuint64>(
+        GLES2Util::MapTwoUint32ToUint64(handle_id_0, handle_id_1));
+  }
+
+  gpu::CommandHeader header;
+  uint32_t handle_id_0;
+  uint32_t handle_id_1;
+};
+
+static_assert(sizeof(DeleteTransferCacheEntryCHROMIUM) == 12,
+              "size of DeleteTransferCacheEntryCHROMIUM should be 12");
+static_assert(offsetof(DeleteTransferCacheEntryCHROMIUM, header) == 0,
+              "offset of DeleteTransferCacheEntryCHROMIUM header should be 0");
+static_assert(
+    offsetof(DeleteTransferCacheEntryCHROMIUM, handle_id_0) == 4,
+    "offset of DeleteTransferCacheEntryCHROMIUM handle_id_0 should be 4");
+static_assert(
+    offsetof(DeleteTransferCacheEntryCHROMIUM, handle_id_1) == 8,
+    "offset of DeleteTransferCacheEntryCHROMIUM handle_id_1 should be 8");
+
+struct UnlockTransferCacheEntryCHROMIUM {
+  typedef UnlockTransferCacheEntryCHROMIUM ValueType;
+  static const CommandId kCmdId = kUnlockTransferCacheEntryCHROMIUM;
+  static const cmd::ArgFlags kArgFlags = cmd::kFixed;
+  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3);
+
+  static uint32_t ComputeSize() {
+    return static_cast<uint32_t>(sizeof(ValueType));  // NOLINT
+  }
+
+  void SetHeader() { header.SetCmd<ValueType>(); }
+
+  void Init(GLuint64 _handle_id) {
+    SetHeader();
+    GLES2Util::MapUint64ToTwoUint32(static_cast<uint64_t>(_handle_id),
+                                    &handle_id_0, &handle_id_1);
+  }
+
+  void* Set(void* cmd, GLuint64 _handle_id) {
+    static_cast<ValueType*>(cmd)->Init(_handle_id);
+    return NextCmdAddress<ValueType>(cmd);
+  }
+
+  GLuint64 handle_id() const volatile {
+    return static_cast<GLuint64>(
+        GLES2Util::MapTwoUint32ToUint64(handle_id_0, handle_id_1));
+  }
+
+  gpu::CommandHeader header;
+  uint32_t handle_id_0;
+  uint32_t handle_id_1;
+};
+
+static_assert(sizeof(UnlockTransferCacheEntryCHROMIUM) == 12,
+              "size of UnlockTransferCacheEntryCHROMIUM should be 12");
+static_assert(offsetof(UnlockTransferCacheEntryCHROMIUM, header) == 0,
+              "offset of UnlockTransferCacheEntryCHROMIUM header should be 0");
+static_assert(
+    offsetof(UnlockTransferCacheEntryCHROMIUM, handle_id_0) == 4,
+    "offset of UnlockTransferCacheEntryCHROMIUM handle_id_0 should be 4");
+static_assert(
+    offsetof(UnlockTransferCacheEntryCHROMIUM, handle_id_1) == 8,
+    "offset of UnlockTransferCacheEntryCHROMIUM handle_id_1 should be 8");
+
 struct TexStorage2DImageCHROMIUM {
   typedef TexStorage2DImageCHROMIUM ValueType;
   static const CommandId kCmdId = kTexStorage2DImageCHROMIUM;
diff --git a/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h b/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
index 62b6b80..491e549d 100644
--- a/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
@@ -5390,6 +5390,51 @@
   CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
 }
 
+TEST_F(GLES2FormatTest, CreateTransferCacheEntryCHROMIUM) {
+  cmds::CreateTransferCacheEntryCHROMIUM& cmd =
+      *GetBufferAs<cmds::CreateTransferCacheEntryCHROMIUM>();
+  void* next_cmd = cmd.Set(&cmd, static_cast<GLuint64>(11),
+                           static_cast<GLuint>(12), static_cast<GLuint>(13),
+                           static_cast<GLuint>(14), static_cast<GLuint>(15),
+                           static_cast<GLuint>(16), static_cast<GLuint>(17));
+  EXPECT_EQ(
+      static_cast<uint32_t>(cmds::CreateTransferCacheEntryCHROMIUM::kCmdId),
+      cmd.header.command);
+  EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u);
+  EXPECT_EQ(static_cast<GLuint64>(11), cmd.handle_id());
+  EXPECT_EQ(static_cast<GLuint>(12), cmd.handle_shm_id);
+  EXPECT_EQ(static_cast<GLuint>(13), cmd.handle_shm_offset);
+  EXPECT_EQ(static_cast<GLuint>(14), cmd.type);
+  EXPECT_EQ(static_cast<GLuint>(15), cmd.data_shm_id);
+  EXPECT_EQ(static_cast<GLuint>(16), cmd.data_shm_offset);
+  EXPECT_EQ(static_cast<GLuint>(17), cmd.data_size);
+  CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
+}
+
+TEST_F(GLES2FormatTest, DeleteTransferCacheEntryCHROMIUM) {
+  cmds::DeleteTransferCacheEntryCHROMIUM& cmd =
+      *GetBufferAs<cmds::DeleteTransferCacheEntryCHROMIUM>();
+  void* next_cmd = cmd.Set(&cmd, static_cast<GLuint64>(11));
+  EXPECT_EQ(
+      static_cast<uint32_t>(cmds::DeleteTransferCacheEntryCHROMIUM::kCmdId),
+      cmd.header.command);
+  EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u);
+  EXPECT_EQ(static_cast<GLuint64>(11), cmd.handle_id());
+  CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
+}
+
+TEST_F(GLES2FormatTest, UnlockTransferCacheEntryCHROMIUM) {
+  cmds::UnlockTransferCacheEntryCHROMIUM& cmd =
+      *GetBufferAs<cmds::UnlockTransferCacheEntryCHROMIUM>();
+  void* next_cmd = cmd.Set(&cmd, static_cast<GLuint64>(11));
+  EXPECT_EQ(
+      static_cast<uint32_t>(cmds::UnlockTransferCacheEntryCHROMIUM::kCmdId),
+      cmd.header.command);
+  EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u);
+  EXPECT_EQ(static_cast<GLuint64>(11), cmd.handle_id());
+  CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
+}
+
 TEST_F(GLES2FormatTest, TexStorage2DImageCHROMIUM) {
   cmds::TexStorage2DImageCHROMIUM& cmd =
       *GetBufferAs<cmds::TexStorage2DImageCHROMIUM>();
diff --git a/gpu/command_buffer/common/gles2_cmd_ids_autogen.h b/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
index 524c5ef..c9cc3ac 100644
--- a/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
@@ -340,9 +340,12 @@
   OP(BeginRasterCHROMIUM)                                  /* 581 */ \
   OP(RasterCHROMIUM)                                       /* 582 */ \
   OP(EndRasterCHROMIUM)                                    /* 583 */ \
-  OP(TexStorage2DImageCHROMIUM)                            /* 584 */ \
-  OP(SetColorSpaceMetadataCHROMIUM)                        /* 585 */ \
-  OP(WindowRectanglesEXTImmediate)                         /* 586 */
+  OP(CreateTransferCacheEntryCHROMIUM)                     /* 584 */ \
+  OP(DeleteTransferCacheEntryCHROMIUM)                     /* 585 */ \
+  OP(UnlockTransferCacheEntryCHROMIUM)                     /* 586 */ \
+  OP(TexStorage2DImageCHROMIUM)                            /* 587 */ \
+  OP(SetColorSpaceMetadataCHROMIUM)                        /* 588 */ \
+  OP(WindowRectanglesEXTImmediate)                         /* 589 */
 
 enum CommandId {
   kOneBeforeStartPoint =
diff --git a/gpu/command_buffer/common/transfer_cache_entry_id.h b/gpu/command_buffer/common/transfer_cache_entry_id.h
new file mode 100644
index 0000000..d1f17ad
--- /dev/null
+++ b/gpu/command_buffer/common/transfer_cache_entry_id.h
@@ -0,0 +1,14 @@
+// Copyright (c) 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 GPU_COMMAND_BUFFER_COMMON_TRANSFER_CACHE_ENTRY_ID_H_
+#define GPU_COMMAND_BUFFER_COMMON_TRANSFER_CACHE_ENTRY_ID_H_
+
+#include "gpu/command_buffer/common/discardable_handle.h"
+
+namespace gpu {
+using TransferCacheEntryId = ClientDiscardableHandle::Id;
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_COMMON_TRANSFER_CACHE_ENTRY_ID_H_
diff --git a/gpu/command_buffer/service/BUILD.gn b/gpu/command_buffer/service/BUILD.gn
index 5c3ddb7..d5a0ee5d 100644
--- a/gpu/command_buffer/service/BUILD.gn
+++ b/gpu/command_buffer/service/BUILD.gn
@@ -126,6 +126,8 @@
     "sequence_id.h",
     "service_discardable_manager.cc",
     "service_discardable_manager.h",
+    "service_transfer_cache.cc",
+    "service_transfer_cache.h",
     "service_utils.cc",
     "service_utils.h",
     "shader_manager.cc",
diff --git a/gpu/command_buffer/service/context_group.cc b/gpu/command_buffer/service/context_group.cc
index 537205f..63bd30f 100644
--- a/gpu/command_buffer/service/context_group.cc
+++ b/gpu/command_buffer/service/context_group.cc
@@ -22,6 +22,7 @@
 #include "gpu/command_buffer/service/renderbuffer_manager.h"
 #include "gpu/command_buffer/service/sampler_manager.h"
 #include "gpu/command_buffer/service/service_discardable_manager.h"
+#include "gpu/command_buffer/service/service_transfer_cache.h"
 #include "gpu/command_buffer/service/shader_manager.h"
 #include "gpu/command_buffer/service/texture_manager.h"
 #include "gpu/command_buffer/service/transfer_buffer_manager.h"
@@ -115,7 +116,8 @@
       passthrough_resources_(new PassthroughResources),
       progress_reporter_(progress_reporter),
       gpu_feature_info_(gpu_feature_info),
-      discardable_manager_(discardable_manager) {
+      discardable_manager_(discardable_manager),
+      transfer_cache_(new ServiceTransferCache) {
   DCHECK(discardable_manager);
   DCHECK(feature_info_);
   DCHECK(mailbox_manager_);
diff --git a/gpu/command_buffer/service/context_group.h b/gpu/command_buffer/service/context_group.h
index d467f8b..74127fc 100644
--- a/gpu/command_buffer/service/context_group.h
+++ b/gpu/command_buffer/service/context_group.h
@@ -30,6 +30,7 @@
 struct GpuPreferences;
 class TransferBufferManager;
 class ServiceDiscardableManager;
+class ServiceTransferCache;
 
 namespace gles2 {
 
@@ -203,6 +204,8 @@
     return discardable_manager_;
   }
 
+  ServiceTransferCache* transfer_cache() const { return transfer_cache_.get(); }
+
   uint32_t GetMemRepresented() const;
 
   // Loses all the context associated with this group.
@@ -320,6 +323,14 @@
 
   ServiceDiscardableManager* discardable_manager_;
 
+  // ServiceTransferCache uses Ids based on transfer buffer shm_id+offset, which
+  // are guaranteed to be unique within the scope of the TransferBufferManager
+  // which generates them. Because of this, |transfer_cache_| must have the same
+  // scope as |transfer_buffer_manager_|. In the future, we could add a channel
+  // Id to allow a single ServiceTransferCache to be shared among multiple
+  // ContextGroups.
+  std::unique_ptr<ServiceTransferCache> transfer_cache_;
+
   DISALLOW_COPY_AND_ASSIGN(ContextGroup);
 };
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 381451c..ef324530 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -27,6 +27,7 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/transfer_cache_entry.h"
 #include "gpu/command_buffer/common/debug_marker_manager.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
@@ -62,6 +63,7 @@
 #include "gpu/command_buffer/service/renderbuffer_manager.h"
 #include "gpu/command_buffer/service/sampler_manager.h"
 #include "gpu/command_buffer/service/service_discardable_manager.h"
+#include "gpu/command_buffer/service/service_transfer_cache.h"
 #include "gpu/command_buffer/service/shader_manager.h"
 #include "gpu/command_buffer/service/shader_translator.h"
 #include "gpu/command_buffer/service/texture_manager.h"
@@ -1971,6 +1973,9 @@
                              GLint pixel_config);
   void DoEndRasterCHROMIUM();
 
+  void DoUnlockTransferCacheEntryCHROMIUM(GLuint64 id);
+  void DoDeleteTransferCacheEntryCHROMIUM(GLuint64 id);
+
   void DoWindowRectanglesEXT(GLenum mode, GLsizei n, const volatile GLint* box);
 
   // Returns false if textures were replaced.
@@ -20405,6 +20410,66 @@
   state_.UpdateWindowRectangles();
 }
 
+error::Error GLES2DecoderImpl::HandleCreateTransferCacheEntryCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::CreateTransferCacheEntryCHROMIUM& c =
+      *static_cast<
+          const volatile gles2::cmds::CreateTransferCacheEntryCHROMIUM*>(
+          cmd_data);
+  TransferCacheEntryId handle_id =
+      TransferCacheEntryId::FromUnsafeValue(c.handle_id());
+  GLuint handle_shm_id = c.handle_shm_id;
+  GLuint handle_shm_offset = c.handle_shm_offset;
+  GLuint data_shm_id = c.data_shm_id;
+  GLuint data_shm_offset = c.data_shm_offset;
+  GLuint data_size = c.data_size;
+
+  // Validate the type we are about to create.
+  cc::TransferCacheEntryType type;
+  if (!cc::ServiceTransferCacheEntry::SafeConvertToType(c.type, &type))
+    return error::kInvalidArguments;
+
+  uint8_t* data_memory =
+      GetSharedMemoryAs<uint8_t*>(data_shm_id, data_shm_offset, data_size);
+  if (!data_memory)
+    return error::kInvalidArguments;
+
+  scoped_refptr<gpu::Buffer> handle_buffer =
+      GetSharedMemoryBuffer(handle_shm_id);
+  if (!DiscardableHandleBase::ValidateParameters(handle_buffer.get(),
+                                                 handle_shm_offset))
+    return error::kInvalidArguments;
+  ServiceDiscardableHandle handle(std::move(handle_buffer), handle_shm_offset,
+                                  handle_shm_id);
+
+  if (!GetContextGroup()->transfer_cache()->CreateLockedEntry(
+          handle_id, handle, type, data_memory, data_size))
+    return error::kInvalidArguments;
+
+  return error::kNoError;
+}
+
+void GLES2DecoderImpl::DoUnlockTransferCacheEntryCHROMIUM(
+    GLuint64 raw_handle_id) {
+  TransferCacheEntryId handle_id =
+      TransferCacheEntryId::FromUnsafeValue(raw_handle_id);
+  if (!GetContextGroup()->transfer_cache()->UnlockEntry(handle_id)) {
+    LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glUnlockTransferCacheEntryCHROMIUM",
+                       "Attempt to unlock an invalid ID");
+  }
+}
+
+void GLES2DecoderImpl::DoDeleteTransferCacheEntryCHROMIUM(
+    GLuint64 raw_handle_id) {
+  TransferCacheEntryId handle_id =
+      TransferCacheEntryId::FromUnsafeValue(raw_handle_id);
+  if (!GetContextGroup()->transfer_cache()->DeleteEntry(handle_id)) {
+    LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glDeleteTransferCacheEntryCHROMIUM",
+                       "Attempt to delete an invalid ID");
+  }
+}
+
 // Include the auto-generated part of this file. We split this because it means
 // we can easily edit the non-auto generated parts right here in this file
 // instead of having to edit some template or the code generator.
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
index 6113fa0..a833ffb 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
@@ -5207,6 +5207,30 @@
   return error::kNoError;
 }
 
+error::Error GLES2DecoderImpl::HandleDeleteTransferCacheEntryCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::DeleteTransferCacheEntryCHROMIUM& c =
+      *static_cast<
+          const volatile gles2::cmds::DeleteTransferCacheEntryCHROMIUM*>(
+          cmd_data);
+  GLuint64 handle_id = c.handle_id();
+  DoDeleteTransferCacheEntryCHROMIUM(handle_id);
+  return error::kNoError;
+}
+
+error::Error GLES2DecoderImpl::HandleUnlockTransferCacheEntryCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::UnlockTransferCacheEntryCHROMIUM& c =
+      *static_cast<
+          const volatile gles2::cmds::UnlockTransferCacheEntryCHROMIUM*>(
+          cmd_data);
+  GLuint64 handle_id = c.handle_id();
+  DoUnlockTransferCacheEntryCHROMIUM(handle_id);
+  return error::kNoError;
+}
+
 error::Error GLES2DecoderImpl::HandleTexStorage2DImageCHROMIUM(
     uint32_t immediate_data_size,
     const volatile void* cmd_data) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
index 0d4b68c..fe776e1f 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
@@ -1012,6 +1012,8 @@
                                    GLboolean use_distance_field_text,
                                    GLint pixel_config);
 error::Error DoEndRasterCHROMIUM();
+error::Error DoUnlockTransferCacheEntryCHROMIUM(GLuint64 id);
+error::Error DoDeleteTransferCacheEntryCHROMIUM(GLuint64 id);
 error::Error DoWindowRectanglesEXT(GLenum mode,
                                    GLsizei n,
                                    const volatile GLint* box);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index b262e42..aea3e05b 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -4620,6 +4620,18 @@
   return error::kNoError;
 }
 
+error::Error GLES2DecoderPassthroughImpl::DoUnlockTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id) {
+  NOTIMPLEMENTED();
+  return error::kNoError;
+}
+
+error::Error GLES2DecoderPassthroughImpl::DoDeleteTransferCacheEntryCHROMIUM(
+    GLuint64 handle_id) {
+  NOTIMPLEMENTED();
+  return error::kNoError;
+}
+
 error::Error GLES2DecoderPassthroughImpl::DoWindowRectanglesEXT(
     GLenum mode,
     GLsizei n,
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc
index 02f20d9b8..6e2b183 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers.cc
@@ -2802,5 +2802,13 @@
   return error::kNoError;
 }
 
+error::Error
+GLES2DecoderPassthroughImpl::HandleCreateTransferCacheEntryCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  NOTIMPLEMENTED();
+  return error::kNoError;
+}
+
 }  // namespace gles2
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
index 2e9add33..8acaa4c 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
@@ -4592,6 +4592,38 @@
   return error::kNoError;
 }
 
+error::Error
+GLES2DecoderPassthroughImpl::HandleDeleteTransferCacheEntryCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::DeleteTransferCacheEntryCHROMIUM& c =
+      *static_cast<
+          const volatile gles2::cmds::DeleteTransferCacheEntryCHROMIUM*>(
+          cmd_data);
+  GLuint64 handle_id = c.handle_id();
+  error::Error error = DoDeleteTransferCacheEntryCHROMIUM(handle_id);
+  if (error != error::kNoError) {
+    return error;
+  }
+  return error::kNoError;
+}
+
+error::Error
+GLES2DecoderPassthroughImpl::HandleUnlockTransferCacheEntryCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::UnlockTransferCacheEntryCHROMIUM& c =
+      *static_cast<
+          const volatile gles2::cmds::UnlockTransferCacheEntryCHROMIUM*>(
+          cmd_data);
+  GLuint64 handle_id = c.handle_id();
+  error::Error error = DoUnlockTransferCacheEntryCHROMIUM(handle_id);
+  if (error != error::kNoError) {
+    return error;
+  }
+  return error::kNoError;
+}
+
 error::Error GLES2DecoderPassthroughImpl::HandleTexStorage2DImageCHROMIUM(
     uint32_t immediate_data_size,
     const volatile void* cmd_data) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_4_autogen.h b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_4_autogen.h
index 671309d..7ab35ba 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_4_autogen.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_4_autogen.h
@@ -42,4 +42,22 @@
   EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
   EXPECT_EQ(GL_NO_ERROR, GetGLError());
 }
+
+TEST_P(GLES2DecoderTest4, DeleteTransferCacheEntryCHROMIUMValidArgs) {
+  EXPECT_CALL(*gl_, DeleteTransferCacheEntryCHROMIUM(1));
+  SpecializedSetup<cmds::DeleteTransferCacheEntryCHROMIUM, 0>(true);
+  cmds::DeleteTransferCacheEntryCHROMIUM cmd;
+  cmd.Init(1);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
+
+TEST_P(GLES2DecoderTest4, UnlockTransferCacheEntryCHROMIUMValidArgs) {
+  EXPECT_CALL(*gl_, UnlockTransferCacheEntryCHROMIUM(1));
+  SpecializedSetup<cmds::UnlockTransferCacheEntryCHROMIUM, 0>(true);
+  cmds::UnlockTransferCacheEntryCHROMIUM cmd;
+  cmd.Init(1);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
 #endif  // GPU_COMMAND_BUFFER_SERVICE_GLES2_CMD_DECODER_UNITTEST_4_AUTOGEN_H_
diff --git a/gpu/command_buffer/service/service_transfer_cache.cc b/gpu/command_buffer/service/service_transfer_cache.cc
new file mode 100644
index 0000000..404a783
--- /dev/null
+++ b/gpu/command_buffer/service/service_transfer_cache.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 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 "gpu/command_buffer/service/service_transfer_cache.h"
+
+#include "base/sys_info.h"
+#include "cc/paint/raw_memory_transfer_cache_entry.h"
+
+namespace gpu {
+namespace {
+size_t CacheSizeLimit() {
+  if (base::SysInfo::IsLowEndDevice()) {
+    return 4 * 1024 * 1024;
+  } else {
+    return 128 * 1024 * 1024;
+  }
+}
+
+}  // namespace
+
+ServiceTransferCache::CacheEntryInternal::CacheEntryInternal(
+    ServiceDiscardableHandle handle,
+    std::unique_ptr<cc::ServiceTransferCacheEntry> entry)
+    : handle(handle), entry(std::move(entry)) {}
+
+ServiceTransferCache::CacheEntryInternal::~CacheEntryInternal() = default;
+
+ServiceTransferCache::CacheEntryInternal::CacheEntryInternal(
+    CacheEntryInternal&& other) = default;
+
+ServiceTransferCache::CacheEntryInternal&
+ServiceTransferCache::CacheEntryInternal::operator=(
+    CacheEntryInternal&& other) = default;
+
+ServiceTransferCache::ServiceTransferCache()
+    : entries_(EntryCache::NO_AUTO_EVICT),
+      cache_size_limit_(CacheSizeLimit()) {}
+
+ServiceTransferCache::~ServiceTransferCache() = default;
+
+bool ServiceTransferCache::CreateLockedEntry(TransferCacheEntryId id,
+                                             ServiceDiscardableHandle handle,
+                                             cc::TransferCacheEntryType type,
+                                             uint8_t* data_memory,
+                                             size_t data_size) {
+  auto found = entries_.Peek(id);
+  if (found != entries_.end()) {
+    return false;
+  }
+
+  std::unique_ptr<cc::ServiceTransferCacheEntry> entry =
+      cc::ServiceTransferCacheEntry::Create(type);
+  if (!entry)
+    return false;
+
+  entry->Deserialize(data_size, data_memory);
+  total_size_ += entry->Size();
+  entries_.Put(id, CacheEntryInternal(handle, std::move(entry)));
+  EnforceLimits();
+  return true;
+}
+
+bool ServiceTransferCache::UnlockEntry(TransferCacheEntryId id) {
+  auto found = entries_.Peek(id);
+  if (found == entries_.end())
+    return false;
+
+  found->second.handle.Unlock();
+  return true;
+}
+
+bool ServiceTransferCache::DeleteEntry(TransferCacheEntryId id) {
+  auto found = entries_.Peek(id);
+  if (found == entries_.end())
+    return false;
+
+  found->second.handle.ForceDelete();
+  total_size_ -= found->second.entry->Size();
+  entries_.Erase(found);
+  return true;
+}
+
+cc::ServiceTransferCacheEntry* ServiceTransferCache::GetEntry(
+    TransferCacheEntryId id) {
+  auto found = entries_.Get(id);
+  if (found == entries_.end())
+    return nullptr;
+  return found->second.entry.get();
+}
+
+void ServiceTransferCache::EnforceLimits() {
+  for (auto it = entries_.rbegin(); it != entries_.rend();) {
+    if (total_size_ <= cache_size_limit_) {
+      return;
+    }
+    if (!it->second.handle.Delete()) {
+      ++it;
+      continue;
+    }
+
+    total_size_ -= it->second.entry->Size();
+    it = entries_.Erase(it);
+  }
+}
+
+}  // namespace gpu
diff --git a/gpu/command_buffer/service/service_transfer_cache.h b/gpu/command_buffer/service/service_transfer_cache.h
new file mode 100644
index 0000000..00a1f92
--- /dev/null
+++ b/gpu/command_buffer/service/service_transfer_cache.h
@@ -0,0 +1,74 @@
+// Copyright (c) 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 GPU_COMMAND_BUFFER_SERVICE_SERVICE_TRANSFER_CACHE_H_
+#define GPU_COMMAND_BUFFER_SERVICE_SERVICE_TRANSFER_CACHE_H_
+
+#include <vector>
+
+#include "base/containers/mru_cache.h"
+#include "cc/paint/transfer_cache_entry.h"
+#include "gpu/command_buffer/common/discardable_handle.h"
+#include "gpu/command_buffer/common/transfer_cache_entry_id.h"
+#include "gpu/command_buffer/service/context_group.h"
+#include "gpu/gpu_export.h"
+
+namespace gpu {
+
+// ServiceTransferCache is a GPU process interface for retreiving cached entries
+// from the transfer cache. These entries are populated by client calls to the
+// ClientTransferCache.
+//
+// In addition to access, the ServiceTransferCache is also responsible for
+// unlocking and deleting entries when no longer needed, as well as enforcing
+// cache limits. If the cache exceeds its specified limits, unlocked transfer
+// cache entries may be deleted.
+class GPU_EXPORT ServiceTransferCache {
+ public:
+  ServiceTransferCache();
+  ~ServiceTransferCache();
+
+  bool CreateLockedEntry(TransferCacheEntryId id,
+                         ServiceDiscardableHandle handle,
+                         cc::TransferCacheEntryType type,
+                         uint8_t* data_memory,
+                         size_t data_size);
+  bool UnlockEntry(TransferCacheEntryId id);
+  bool DeleteEntry(TransferCacheEntryId id);
+  cc::ServiceTransferCacheEntry* GetEntry(TransferCacheEntryId id);
+
+  // Test-only functions:
+  void SetCacheSizeLimitForTesting(size_t cache_size_limit) {
+    cache_size_limit_ = cache_size_limit;
+    EnforceLimits();
+  }
+
+ private:
+  void EnforceLimits();
+
+  struct CacheEntryInternal {
+    CacheEntryInternal(ServiceDiscardableHandle handle,
+                       std::unique_ptr<cc::ServiceTransferCacheEntry> entry);
+    CacheEntryInternal(CacheEntryInternal&& other);
+    CacheEntryInternal& operator=(CacheEntryInternal&& other);
+    ~CacheEntryInternal();
+    ServiceDiscardableHandle handle;
+    std::unique_ptr<cc::ServiceTransferCacheEntry> entry;
+  };
+  using EntryCache = base::MRUCache<TransferCacheEntryId, CacheEntryInternal>;
+  EntryCache entries_;
+
+  // Total size of all |entries_|. The same as summing
+  // GpuDiscardableEntry::size for each entry.
+  size_t total_size_ = 0;
+
+  // The limit above which the cache will start evicting resources.
+  size_t cache_size_limit_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(ServiceTransferCache);
+};
+
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_SERVICE_SERVICE_TRANSFER_CACHE_H_
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 5473aa6..4835014 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -77,8 +77,13 @@
     {
       "id": 20,
       "description": "Disable EXT_draw_buffers on GeForce GT 650M on Mac OS X due to driver bugs",
+      "cr_bugs": [180397, 779991],
       "os": {
-        "type": "macosx"
+        "type": "macosx",
+        "version": {
+          "op": "<",
+          "value": "10.12"
+        }
       },
       "vendor_id": "0x10de",
       "device_id": ["0x0fd5"],
diff --git a/gpu/ipc/gl_in_process_context.cc b/gpu/ipc/gl_in_process_context.cc
index 39b4faa4..c162b3c 100644
--- a/gpu/ipc/gl_in_process_context.cc
+++ b/gpu/ipc/gl_in_process_context.cc
@@ -74,6 +74,7 @@
       const gpu::InProcessCommandBuffer::UpdateVSyncParametersCallback&
           callback) override;
   void SetLock(base::Lock* lock) override;
+  gpu::gles2::ContextGroup* ContextGroupForTesting() const override;
 
  private:
   void OnSignalSyncPoint(const base::Closure& callback);
@@ -133,6 +134,11 @@
   NOTREACHED();
 }
 
+gpu::gles2::ContextGroup* GLInProcessContextImpl::ContextGroupForTesting()
+    const {
+  return command_buffer_->ContextGroupForTesting();
+}
+
 gpu::ContextResult GLInProcessContextImpl::Initialize(
     scoped_refptr<gpu::InProcessCommandBuffer::Service> service,
     scoped_refptr<gl::GLSurface> surface,
diff --git a/gpu/ipc/gl_in_process_context.h b/gpu/ipc/gl_in_process_context.h
index 8de4def..7a30b85a 100644
--- a/gpu/ipc/gl_in_process_context.h
+++ b/gpu/ipc/gl_in_process_context.h
@@ -71,6 +71,9 @@
   virtual void SetUpdateVSyncParametersCallback(
       const gpu::InProcessCommandBuffer::UpdateVSyncParametersCallback&
           callback) = 0;
+
+  // Test only functions.
+  virtual gpu::gles2::ContextGroup* ContextGroupForTesting() const = 0;
 };
 
 }  // namespace gpu
diff --git a/gpu/ipc/in_process_command_buffer.h b/gpu/ipc/in_process_command_buffer.h
index 166e4804e..58c4738 100644
--- a/gpu/ipc/in_process_command_buffer.h
+++ b/gpu/ipc/in_process_command_buffer.h
@@ -32,6 +32,7 @@
 #include "gpu/command_buffer/service/gpu_preferences.h"
 #include "gpu/command_buffer/service/image_manager.h"
 #include "gpu/command_buffer/service/service_discardable_manager.h"
+#include "gpu/command_buffer/service/service_transfer_cache.h"
 #include "gpu/config/gpu_feature_info.h"
 #include "gpu/gpu_export.h"
 #include "gpu/ipc/service/image_transport_surface_delegate.h"
@@ -195,6 +196,10 @@
   static void InitializeDefaultServiceForTesting(
       const GpuFeatureInfo& gpu_feature_info);
 
+  gpu::gles2::ContextGroup* ContextGroupForTesting() const {
+    return context_group_.get();
+  }
+
   // The serializer interface to the GPU service (i.e. thread).
   class Service {
    public:
@@ -229,6 +234,7 @@
     ServiceDiscardableManager* discardable_manager() {
       return &discardable_manager_;
     }
+    ServiceTransferCache* transfer_cache() { return &transfer_cache_; }
     gles2::ShaderTranslatorCache* shader_translator_cache() {
       return &shader_translator_cache_;
     }
@@ -248,6 +254,7 @@
     GpuProcessActivityFlags activity_flags_;
     gles2::ImageManager image_manager_;
     ServiceDiscardableManager discardable_manager_;
+    ServiceTransferCache transfer_cache_;
     gles2::ShaderTranslatorCache shader_translator_cache_;
     gles2::FramebufferCompletenessCache framebuffer_completeness_cache_;
   };
diff --git a/ios/build/bots/chromium.mac/ios-simulator-eg.json b/ios/build/bots/chromium.mac/ios-simulator-eg.json
deleted file mode 100644
index 077ef21e..0000000
--- a/ios/build/bots/chromium.mac/ios-simulator-eg.json
+++ /dev/null
@@ -1,176 +0,0 @@
-{
-  "comments": [
-    "EarlGrey Tests for 64-bit iOS 11.0 simulators."
-  ],
-  "xcode version": "9.0",
-  "gn_args": [
-    "goma_dir=\"$(goma_dir)\"",
-    "is_component_build=false",
-    "is_debug=true",
-    "target_cpu=\"x64\"",
-    "target_os=\"ios\"",
-    "use_goma=true"
-  ],
-  "configuration": "Debug",
-  "sdk": "iphonesimulator11.0",
-  "tests": [
-    {
-      "app": "ios_chrome_integration_egtests",
-      "test args": [
-        "--enable-features=CredentialManager"
-      ],
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_payments_egtests",
-      "test args": [
-        "--enable-features=WebPayments"
-      ],
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_reading_list_egtests",
-      "test args": [
-        "--enable-reading-list"
-      ],
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_settings_egtests",
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_smoke_egtests",
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_ui_egtests",
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_bookmarks_egtests",
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_web_egtests",
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_showcase_egtests",
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_web_shell_egtests",
-      "device type": "iPhone 6s",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_integration_egtests",
-      "test args": [
-        "--enable-features=CredentialManager"
-      ],
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_payments_egtests",
-      "test args": [
-        "--enable-features=WebPayments"
-      ],
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_reading_list_egtests",
-      "test args": [
-        "--enable-reading-list"
-      ],
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_settings_egtests",
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_smoke_egtests",
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_ui_egtests",
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_bookmarks_egtests",
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_chrome_web_egtests",
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_showcase_egtests",
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    },
-    {
-      "app": "ios_web_shell_egtests",
-      "device type": "iPad Air 2",
-      "os": "11.0",
-      "xcode version": "9.0",
-      "xctest": true
-    }
-  ]
-}
diff --git a/ios/chrome/test/app/sync_test_util.mm b/ios/chrome/test/app/sync_test_util.mm
index bf9a2b2..69f2e21 100644
--- a/ios/chrome/test/app/sync_test_util.mm
+++ b/ios/chrome/test/app/sync_test_util.mm
@@ -178,7 +178,7 @@
 
   std::unique_ptr<syncer::LoopbackServerEntity> entity =
       syncer::PersistentUniqueClientEntity::CreateFromEntitySpecifics(
-          guid, entity_specifics);
+          guid, entity_specifics, 12345, 12345);
   gSyncFakeServer->InjectEntity(std::move(entity));
 }
 
@@ -268,7 +268,7 @@
 
   std::unique_ptr<syncer::LoopbackServerEntity> entity =
       syncer::PersistentUniqueClientEntity::CreateFromEntitySpecifics(
-          url, entitySpecifics);
+          url, entitySpecifics, 12345, 12345);
   gSyncFakeServer->InjectEntity(std::move(entity));
 }
 
diff --git a/media/gpu/android/android_video_encode_accelerator.cc b/media/gpu/android/android_video_encode_accelerator.cc
index a334169..684ffea 100644
--- a/media/gpu/android/android_video_encode_accelerator.cc
+++ b/media/gpu/android/android_video_encode_accelerator.cc
@@ -356,16 +356,30 @@
       frame->coded_size().height());
   RETURN_ON_FAILURE(converted, "Failed to I420ToNV12!", kPlatformFailureError);
 
-  input_timestamp_ += base::TimeDelta::FromMicroseconds(
+  // MediaCodec encoder assumes the presentation timestamps to be monotonically
+  // increasing at initialized framerate. But in Chromium, the video capture
+  // may be paused for a while or drop some frames, so the timestamp in input
+  // frames won't be continious. Here we cache the timestamps of input frames,
+  // mapping to the generated |presentation_timestamp_|, and will read them out
+  // after encoding. Then encoder can work happily always and we can preserve
+  // the timestamps in captured frames for other purpose.
+  presentation_timestamp_ += base::TimeDelta::FromMicroseconds(
       base::Time::kMicrosecondsPerSecond / INITIAL_FRAMERATE);
+  DCHECK(frame_timestamp_map_.find(presentation_timestamp_) ==
+         frame_timestamp_map_.end());
+  frame_timestamp_map_[presentation_timestamp_] = frame->timestamp();
+
   status = media_codec_->QueueInputBuffer(input_buf_index, nullptr, queued_size,
-                                          input_timestamp_);
+                                          presentation_timestamp_);
   UMA_HISTOGRAM_TIMES("Media.AVDA.InputQueueTime",
                       base::Time::Now() - std::get<2>(input));
   RETURN_ON_FAILURE(status == MEDIA_CODEC_OK,
                     "Failed to QueueInputBuffer: " << status,
                     kPlatformFailureError);
   ++num_buffers_at_codec_;
+  DCHECK(static_cast<int32_t>(frame_timestamp_map_.size()) ==
+         num_buffers_at_codec_);
+
   pending_frames_.pop();
 }
 
@@ -380,9 +394,10 @@
   size_t size = 0;
   bool key_frame = false;
 
-  MediaCodecStatus status =
-      media_codec_->DequeueOutputBuffer(NoWaitTimeOut(), &buf_index, &offset,
-                                        &size, nullptr, nullptr, &key_frame);
+  base::TimeDelta presentaion_timestamp;
+  MediaCodecStatus status = media_codec_->DequeueOutputBuffer(
+      NoWaitTimeOut(), &buf_index, &offset, &size, &presentaion_timestamp,
+      nullptr, &key_frame);
   switch (status) {
     case MEDIA_CODEC_TRY_AGAIN_LATER:
       return;
@@ -407,6 +422,11 @@
       break;
   }
 
+  const auto it = frame_timestamp_map_.find(presentaion_timestamp);
+  DCHECK(it != frame_timestamp_map_.end());
+  const base::TimeDelta frame_timestamp = it->second;
+  frame_timestamp_map_.erase(it);
+
   BitstreamBuffer bitstream_buffer = available_bitstream_buffers_.back();
   available_bitstream_buffers_.pop_back();
   std::unique_ptr<SharedMemoryRegion> shm(
@@ -427,7 +447,7 @@
       FROM_HERE,
       base::Bind(&VideoEncodeAccelerator::Client::BitstreamBufferReady,
                  client_ptr_factory_->GetWeakPtr(), bitstream_buffer.id(), size,
-                 key_frame, base::TimeDelta()));
+                 key_frame, frame_timestamp));
 }
 
 }  // namespace media
diff --git a/media/gpu/android/android_video_encode_accelerator.h b/media/gpu/android/android_video_encode_accelerator.h
index b9624217..91ad14b 100644
--- a/media/gpu/android/android_video_encode_accelerator.h
+++ b/media/gpu/android/android_video_encode_accelerator.h
@@ -9,6 +9,7 @@
 #include <stdint.h>
 
 #include <list>
+#include <map>
 #include <memory>
 #include <tuple>
 #include <vector>
@@ -96,7 +97,11 @@
   int32_t num_buffers_at_codec_;
 
   // A monotonically-growing value.
-  base::TimeDelta input_timestamp_;
+  base::TimeDelta presentation_timestamp_;
+
+  std::map<base::TimeDelta /* presentation_timestamp */,
+           base::TimeDelta /* frame_timestamp */>
+      frame_timestamp_map_;
 
   // Resolution of input stream. Set once in initialization and not allowed to
   // change after.
diff --git a/net/cert/internal/ocsp.cc b/net/cert/internal/ocsp.cc
index 606cb35b..49c34ddd 100644
--- a/net/cert/internal/ocsp.cc
+++ b/net/cert/internal/ocsp.cc
@@ -934,7 +934,7 @@
 //    the OCSPRequest}
 GURL CreateOCSPGetURL(const ParsedCertificate* cert,
                       const ParsedCertificate* issuer,
-                      const GURL& ocsp_responder_url) {
+                      base::StringPiece ocsp_responder_url) {
   std::vector<uint8_t> ocsp_request_der;
   if (!CreateOCSPRequest(cert, issuer, &ocsp_request_der)) {
     // Unexpected (means BoringSSL failed an operation).
@@ -956,24 +956,9 @@
   base::ReplaceSubstringsAfterOffset(&b64_encoded, 0, "/", "%2F");
   base::ReplaceSubstringsAfterOffset(&b64_encoded, 0, "=", "%3D");
 
-  // RFC 2560 and RFC 5019 are vague on what is intended for URL concatenation.
-  //
-  //   * If the path doesn't end in a slash, is one implicitly added?
-  //   * Is a straight up string concatenation expected, or only a concatenation
-  //     to the path?
-  //
-  // This code contenates the data to the path portion of the URL, and leaves
-  // the other URL components unmodified.
-  //
-  // TODO(eroman): Confirm whether OCSP responders use query parameters.
-  std::string path = ocsp_responder_url.path();
-  if (!base::StringPiece(path).ends_with("/"))
-    path += "/";
-  path += b64_encoded;
-
-  GURL::Replacements replacements;
-  replacements.SetPath(path.data(), url::Component(0, path.size()));
-  return ocsp_responder_url.ReplaceComponents(replacements);
+  // No attempt is made to collapse double slashes for URLs that end in slash,
+  // since the spec doesn't do that.
+  return GURL(std::string(ocsp_responder_url) + "/" + b64_encoded);
 }
 
 }  // namespace net
diff --git a/net/cert/internal/ocsp.h b/net/cert/internal/ocsp.h
index e4c7182..ffba90e5 100644
--- a/net/cert/internal/ocsp.h
+++ b/net/cert/internal/ocsp.h
@@ -314,7 +314,7 @@
 // Creates a URL to issue a GET request for OCSP information for |cert|.
 NET_EXPORT GURL CreateOCSPGetURL(const ParsedCertificate* cert,
                                  const ParsedCertificate* issuer,
-                                 const GURL& ocsp_responder_url);
+                                 base::StringPiece ocsp_responder_url);
 
 }  // namespace net
 
diff --git a/net/cert/internal/ocsp_unittest.cc b/net/cert/internal/ocsp_unittest.cc
index dff4ea1..436dd22 100644
--- a/net/cert/internal/ocsp_unittest.cc
+++ b/net/cert/internal/ocsp_unittest.cc
@@ -297,23 +297,15 @@
 #endif
 }
 
-struct GetURLTestParams {
-  const char* responder_url;
-  int index_start_data;
-  int index_end_data;
+base::StringPiece kGetURLTestParams[] = {
+    "http://www.example.com/", "http://www.example.com/path/",
+    "http://www.example.com/path",
+    "http://www.example.com/path?query"
+    "http://user:pass@www.example.com/path?query",
 };
 
-const GetURLTestParams kGetURLTestParams[] = {
-    {"http://www.example.com/", 23, -1},
-    {"http://www.example.com/path/", 28, -1},
-    {"http://www.example.com/path", 28, -1},
-    // The data will be appended to the path (before the ?query).
-    {"http://www.example.com/path?query", 28, -7},
-    {"http://user:pass@www.example.com/path?query", 38, -7},
-};
-
-class CreateOCSPGetURLTest : public ::testing::TestWithParam<GetURLTestParams> {
-};
+class CreateOCSPGetURLTest
+    : public ::testing::TestWithParam<base::StringPiece> {};
 
 INSTANTIATE_TEST_CASE_P(,
                         CreateOCSPGetURLTest,
@@ -340,20 +332,13 @@
   scoped_refptr<ParsedCertificate> issuer = ParseCertificate(ca_data);
   ASSERT_TRUE(issuer);
 
-  // Try using a URL that doesn't end with a slash.
-  GURL url = CreateOCSPGetURL(cert.get(), issuer.get(),
-                              GURL(GetParam().responder_url));
+  GURL url = CreateOCSPGetURL(cert.get(), issuer.get(), GetParam());
 
   // Try to extract the encoded data and compare against |request_data|.
   //
   // A known answer output test would be better as this just reverses the logic
   // from the implementaiton file.
-  int begin_index = GetParam().index_start_data;
-  int end_index = GetParam().index_end_data;
-  if (end_index < 0)
-    end_index += url.spec().size();
-
-  std::string b64 = url.spec().substr(begin_index, end_index - begin_index + 1);
+  std::string b64 = url.spec().substr(GetParam().size() + 1);
 
   // Hex un-escape the data.
   base::ReplaceSubstringsAfterOffset(&b64, 0, "%2B", "+");
diff --git a/net/cert/internal/revocation_checker.cc b/net/cert/internal/revocation_checker.cc
index 83c4ebf..a8333e2 100644
--- a/net/cert/internal/revocation_checker.cc
+++ b/net/cert/internal/revocation_checker.cc
@@ -76,9 +76,9 @@
     for (const auto& ocsp_uri : cert->ocsp_uris()) {
       // Only consider http:// URLs (https:// could create a circular
       // dependency).
-      GURL responder_url(ocsp_uri);
-      if (!responder_url.is_valid() ||
-          !responder_url.SchemeIs(url::kHttpScheme)) {
+      GURL parsed_ocsp_url(ocsp_uri);
+      if (!parsed_ocsp_url.is_valid() ||
+          !parsed_ocsp_url.SchemeIs(url::kHttpScheme)) {
         continue;
       }
 
@@ -94,9 +94,10 @@
 
       // TODO(eroman): Duplication of work if there are multiple URLs to try.
       // TODO(eroman): Are there cases where we would need to POST instead?
-      GURL get_url = CreateOCSPGetURL(cert, issuer_cert, responder_url);
+      GURL get_url = CreateOCSPGetURL(cert, issuer_cert, ocsp_uri);
       if (!get_url.is_valid()) {
-        // A failure here is unexpected, but could happen from BoringSSL.
+        // A failure here could mean an unexpected failure from BoringSSL, or a
+        // problem concatenating the URL.
         continue;
       }
 
diff --git a/services/device/BUILD.gn b/services/device/BUILD.gn
index 206197c..11cff3d 100644
--- a/services/device/BUILD.gn
+++ b/services/device/BUILD.gn
@@ -33,6 +33,7 @@
     "//device/sensors/public/interfaces",
     "//services/device/fingerprint",
     "//services/device/generic_sensor",
+    "//services/device/geolocation",
     "//services/device/power_monitor",
     "//services/device/public/cpp:device_features",
     "//services/device/public/interfaces",
@@ -81,6 +82,7 @@
     "generic_sensor/platform_sensor_fusion_unittest.cc",
     "generic_sensor/platform_sensor_provider_unittest_android.cc",
     "generic_sensor/relative_orientation_euler_angles_fusion_algorithm_using_accelerometer_unittest.cc",
+    "geolocation/public_ip_address_location_notifier_unittest.cc",
     "power_monitor/power_monitor_message_broadcaster_unittest.cc",
     "public/cpp/power_monitor/power_monitor_broadcast_source_unittest.cc",
     "vibration/vibration_manager_impl_unittest.cc",
@@ -92,8 +94,13 @@
     "//base",
     "//base/test:test_support",
     "//device/base/synchronization",
+    "//device/geolocation/public/cpp",
+    "//device/geolocation/public/interfaces",
     "//mojo/public/cpp/bindings",
+    "//net",
+    "//net:test_support",
     "//services/device/generic_sensor",
+    "//services/device/geolocation",
     "//services/device/power_monitor",
     "//services/device/public/cpp:device_features",
     "//services/device/public/cpp/power_monitor",
@@ -206,6 +213,8 @@
     "//base",
     "//base/test:test_support",
     "//mojo/public/cpp/bindings",
+    "//net",
+    "//net:test_support",
     "//services/device/public/interfaces:constants",
     "//services/service_manager/public/cpp",
     "//services/service_manager/public/cpp:service_test_support",
diff --git a/services/device/geolocation/BUILD.gn b/services/device/geolocation/BUILD.gn
new file mode 100644
index 0000000..55b04e6
--- /dev/null
+++ b/services/device/geolocation/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("//build/config/features.gni")
+
+source_set("geolocation") {
+  visibility = [ "//services/device:*" ]
+
+  sources = [
+    "public_ip_address_location_notifier.cc",
+    "public_ip_address_location_notifier.h",
+  ]
+
+  deps = [
+    "//base",
+    "//device/geolocation",
+    "//net",
+  ]
+}
diff --git a/services/device/geolocation/DEPS b/services/device/geolocation/DEPS
new file mode 100644
index 0000000..8fa9d48
--- /dev/null
+++ b/services/device/geolocation/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+net",
+]
diff --git a/services/device/geolocation/public_ip_address_location_notifier.cc b/services/device/geolocation/public_ip_address_location_notifier.cc
new file mode 100644
index 0000000..cf18163
--- /dev/null
+++ b/services/device/geolocation/public_ip_address_location_notifier.cc
@@ -0,0 +1,143 @@
+// 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 "services/device/geolocation/public_ip_address_location_notifier.h"
+
+#include "device/geolocation/wifi_data.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+namespace device {
+
+namespace {
+// Time to wait before issuing a network geolocation request in response to
+// network change notification. Network changes tend to occur in clusters.
+constexpr base::TimeDelta kNetworkChangeReactionDelay =
+    base::TimeDelta::FromMinutes(5);
+}  // namespace
+
+PublicIpAddressLocationNotifier::PublicIpAddressLocationNotifier(
+    GeolocationProvider::RequestContextProducer request_context_producer,
+    const std::string& api_key)
+    : network_changed_since_last_request_(true),
+      api_key_(api_key),
+      request_context_producer_(request_context_producer),
+      network_traffic_annotation_tag_(nullptr),
+      weak_ptr_factory_(this) {
+  // Subscribe to notifications of changes in network configuration.
+  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+}
+
+PublicIpAddressLocationNotifier::~PublicIpAddressLocationNotifier() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+}
+
+void PublicIpAddressLocationNotifier::QueryNextPosition(
+    base::Time time_of_prev_position,
+    const net::PartialNetworkTrafficAnnotationTag& tag,
+    QueryNextPositionCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  network_traffic_annotation_tag_.reset(
+      new net::PartialNetworkTrafficAnnotationTag(tag));
+  // If a network location request is in flight, wait.
+  if (network_location_request_) {
+    callbacks_.push_back(std::move(callback));
+    return;
+  }
+
+  // If a network change has occured since we last made a request, start a
+  // request and wait.
+  if (network_changed_since_last_request_) {
+    callbacks_.push_back(std::move(callback));
+    MakeNetworkLocationRequest();
+    return;
+  }
+
+  if (latest_geoposition_.has_value() &&
+      latest_geoposition_->timestamp > time_of_prev_position) {
+    std::move(callback).Run(*latest_geoposition_);
+    return;
+  }
+
+  // The cached geoposition is not new enough for this client, and
+  // there hasn't been a recent network change, so add the client
+  // to the list of clients waiting for a network change.
+  callbacks_.push_back(std::move(callback));
+}
+
+void PublicIpAddressLocationNotifier::OnNetworkChanged(
+    net::NetworkChangeNotifier::ConnectionType type) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Post a cancelable task to react to this network change after a reasonable
+  // delay, so that we only react once if multiple network changes occur in a
+  // short span of time.
+  react_to_network_change_closure_.Reset(
+      base::Bind(&PublicIpAddressLocationNotifier::ReactToNetworkChange,
+                 base::Unretained(this)));
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, react_to_network_change_closure_.callback(),
+      kNetworkChangeReactionDelay);
+}
+
+void PublicIpAddressLocationNotifier::ReactToNetworkChange() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  network_changed_since_last_request_ = true;
+
+  // Invalidate the cached recent position.
+  latest_geoposition_.reset();
+
+  // If any clients are waiting, start a request.
+  // (This will cancel any previous request, which is OK.)
+  if (!callbacks_.empty())
+    MakeNetworkLocationRequest();
+}
+
+void PublicIpAddressLocationNotifier::MakeNetworkLocationRequest() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  network_changed_since_last_request_ = false;
+  // Obtain URL request context using provided producer callback, then continue
+  // request in MakeNetworkLocationRequestWithRequestContext.
+  request_context_producer_.Run(base::BindOnce(
+      &PublicIpAddressLocationNotifier::MakeNetworkLocationRequestWithContext,
+      weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PublicIpAddressLocationNotifier::MakeNetworkLocationRequestWithContext(
+    scoped_refptr<net::URLRequestContextGetter> context_getter) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!context_getter)
+    return;
+
+  network_location_request_ = std::make_unique<NetworkLocationRequest>(
+      std::move(context_getter), api_key_,
+      base::BindRepeating(
+          &PublicIpAddressLocationNotifier::OnNetworkLocationResponse,
+          weak_ptr_factory_.GetWeakPtr()));
+
+  DCHECK(network_traffic_annotation_tag_);
+  network_location_request_->MakeRequest(WifiData(), base::Time::Now(),
+                                         *network_traffic_annotation_tag_);
+}
+
+void PublicIpAddressLocationNotifier::OnNetworkLocationResponse(
+    const mojom::Geoposition& position,
+    const bool server_error,
+    const WifiData& /* wifi_data */) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (server_error) {
+    network_changed_since_last_request_ = true;
+    DCHECK(!latest_geoposition_.has_value());
+  } else {
+    latest_geoposition_ = base::make_optional(position);
+  }
+  // Notify all clients.
+  for (QueryNextPositionCallback& callback : callbacks_)
+    std::move(callback).Run(position);
+  callbacks_.clear();
+  network_location_request_.reset();
+}
+
+}  // namespace device
diff --git a/services/device/geolocation/public_ip_address_location_notifier.h b/services/device/geolocation/public_ip_address_location_notifier.h
new file mode 100644
index 0000000..0f4e84b
--- /dev/null
+++ b/services/device/geolocation/public_ip_address_location_notifier.h
@@ -0,0 +1,123 @@
+// 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 SERVICES_DEVICE_GEOLOCATION_PUBLIC_IP_ADDRESS_LOCATION_NOTIFIER_H_
+#define SERVICES_DEVICE_GEOLOCATION_PUBLIC_IP_ADDRESS_LOCATION_NOTIFIER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/callback_list.h"
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "device/geolocation/geolocation_provider.h"
+#include "device/geolocation/network_location_request.h"
+#include "device/geolocation/public/interfaces/geoposition.mojom.h"
+#include "net/base/network_change_notifier.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+namespace device {
+
+class NetworkLocationRequest;
+struct WifiData;
+
+// Provides subscribers with updates of the device's approximate geographic
+// location inferred from its publicly-visible IP address.
+// Sequencing:
+// * Must be created, used, and destroyed on the same sequence.
+class PublicIpAddressLocationNotifier
+    : public net::NetworkChangeNotifier::NetworkChangeObserver {
+ public:
+  // Creates a notifier that uses the specified Google |api_key| and a URL
+  // request context produced by |request_context_producer| for network location
+  // requests.
+  PublicIpAddressLocationNotifier(
+      GeolocationProvider::RequestContextProducer request_context_producer,
+      const std::string& api_key);
+  ~PublicIpAddressLocationNotifier() override;
+
+  using QueryNextPositionCallback =
+      base::OnceCallback<void(const mojom::Geoposition&)>;
+
+  // Requests a callback with the next Geoposition obtained later than
+  // |time_of_prev_position|.
+  // Specifically:
+  // * If a position has been obtained subsequent to |time_of_prev_position|,
+  // returns it.
+  // * Otherwise, returns an updated position once a network change has
+  // occurred.
+  // * Note that it is possible for |callback| to never be called if no network
+  // change ever occurs after |time_of_prev_position|.
+  void QueryNextPosition(base::Time time_of_prev_position,
+                         const net::PartialNetworkTrafficAnnotationTag& tag,
+                         QueryNextPositionCallback callback);
+
+ private:
+  // Sequence checker for all methods.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // NetworkChangeNotifier::NetworkChangeObserver:
+  // Network change notifications tend to come in a cluster in a short time, so
+  // this just sets a task to run ReactToNetworkChange after a short time.
+  void OnNetworkChanged(
+      net::NetworkChangeNotifier::ConnectionType type) override;
+
+  // Actually react to a network change, starting a network geolocation request
+  // if any clients are waiting.
+  void ReactToNetworkChange();
+
+  // Begins a network location request, by first obtaining a
+  // URLRequestContextGetter, then continuing in
+  // MakeNetworkLocationRequestWithContext.
+  void MakeNetworkLocationRequest();
+
+  // Creates network_location_request_ and starts the network request, which
+  // will invoke OnNetworkLocationResponse when done.
+  void MakeNetworkLocationRequestWithContext(
+      scoped_refptr<net::URLRequestContextGetter> context_getter);
+
+  // Completion callback for network_location_request_.
+  void OnNetworkLocationResponse(const mojom::Geoposition& position,
+                                 bool server_error,
+                                 const WifiData& wifi_data);
+
+  // Cancelable closure to absorb overlapping delayed calls to
+  // ReactToNetworkChange.
+  base::CancelableClosure react_to_network_change_closure_;
+
+  // Whether we have been notified of a network change since the last network
+  // location request was sent.
+  bool network_changed_since_last_request_;
+
+  // The geoposition as of the latest network change, if it has been obtained.
+  base::Optional<mojom::Geoposition> latest_geoposition_;
+
+  // Google API key for network geolocation requests.
+  const std::string api_key_;
+
+  // Callback to produce a URL request context for network geolocation requests.
+  const GeolocationProvider::RequestContextProducer request_context_producer_;
+
+  // Used to make calls to the Maps geolocate API.
+  // Empty unless a call is currently in progress.
+  std::unique_ptr<NetworkLocationRequest> network_location_request_;
+
+  // Clients waiting for an updated geoposition.
+  std::vector<QueryNextPositionCallback> callbacks_;
+
+  // The most recent PartialNetworkTrafficAnnotationTag provided by a client.
+  std::unique_ptr<const net::PartialNetworkTrafficAnnotationTag>
+      network_traffic_annotation_tag_;
+
+  // Weak references to |this| for posted tasks.
+  base::WeakPtrFactory<PublicIpAddressLocationNotifier> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(PublicIpAddressLocationNotifier);
+};
+
+}  // namespace device
+
+#endif  // SERVICES_DEVICE_GEOLOCATION_PUBLIC_IP_ADDRESS_LOCATION_NOTIFIER_H_
diff --git a/services/device/geolocation/public_ip_address_location_notifier_unittest.cc b/services/device/geolocation/public_ip_address_location_notifier_unittest.cc
new file mode 100644
index 0000000..90a0dda
--- /dev/null
+++ b/services/device/geolocation/public_ip_address_location_notifier_unittest.cc
@@ -0,0 +1,312 @@
+// 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 "services/device/geolocation/public_ip_address_location_notifier.h"
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "device/geolocation/public/cpp/geoposition.h"
+#include "device/geolocation/public/interfaces/geoposition.mojom.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+// Simple request context producer that immediately produces a
+// TestURLRequestContextGetter.
+void TestRequestContextProducer(
+    const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner,
+    base::OnceCallback<void(scoped_refptr<net::URLRequestContextGetter>)>
+        response_callback) {
+  std::move(response_callback)
+      .Run(base::MakeRefCounted<net::TestURLRequestContextGetter>(
+          network_task_runner));
+}
+
+class PublicIpAddressLocationNotifierTest : public testing::Test {
+ protected:
+  // Helps test a single call to
+  // PublicIpAddressLocationNotifier::QueryNextPositionAfterTimestamp.
+  class TestPositionQuery {
+   public:
+    // Provides a callback suitable to pass to QueryNextPositionAfterTimestamp.
+    PublicIpAddressLocationNotifier::QueryNextPositionCallback MakeCallback() {
+      return base::BindOnce(&TestPositionQuery::OnQueryNextPositionResponse,
+                            base::Unretained(this));
+    }
+
+    // Optional. Wait until the callback from MakeCallback() is called.
+    void Wait() { loop_.Run(); }
+
+    const base::Optional<mojom::Geoposition>& position() const {
+      return position_;
+    }
+
+   private:
+    void OnQueryNextPositionResponse(const mojom::Geoposition& position) {
+      position_ = position;
+      loop_.Quit();
+    }
+
+    base::RunLoop loop_;
+    base::Optional<mojom::Geoposition> position_;
+  };
+
+  PublicIpAddressLocationNotifierTest()
+      : mock_time_task_runner_(
+            base::MakeRefCounted<base::TestMockTimeTaskRunner>(
+                base::TestMockTimeTaskRunner::Type::kBoundToThread)),
+        mock_time_scoped_context_(mock_time_task_runner_.get()),
+        network_change_notifier_(net::NetworkChangeNotifier::CreateMock()),
+        notifier_(
+            base::Bind(&TestRequestContextProducer, mock_time_task_runner_),
+            std::string() /* api_key */) {}
+
+  // Returns the current TestURLFetcher (if any) and advances the test fetcher
+  // id for disambiguation from subsequent tests.
+  net::TestURLFetcher* GetTestUrlFetcher() {
+    net::TestURLFetcher* const fetcher = url_fetcher_factory_.GetFetcherByID(
+        NetworkLocationRequest::url_fetcher_id_for_tests);
+    if (fetcher)
+      ++NetworkLocationRequest::url_fetcher_id_for_tests;
+    return fetcher;
+  }
+
+  // Gives a valid JSON reponse to the specified URLFetcher.
+  // For disambiguation purposes, the specified |latitude| is included in the
+  // response.
+  void RespondToFetchWithLatitude(net::TestURLFetcher* const fetcher,
+                                  const float latitude) {
+    // Issue a valid response including the specified latitude.
+    fetcher->set_url(fetcher->GetOriginalURL());
+    fetcher->set_status(net::URLRequestStatus());
+    fetcher->set_response_code(200);
+    const char kNetworkResponseFormatString[] =
+        R"({
+            "accuracy": 100.0,
+            "location": {
+              "lat": %f,
+              "lng": 90.0
+            }
+          })";
+    fetcher->SetResponseString(
+        base::StringPrintf(kNetworkResponseFormatString, latitude));
+    fetcher->delegate()->OnURLFetchComplete(fetcher);
+  }
+
+  void RespondToFetchWithServerError(net::TestURLFetcher* const fetcher) {
+    fetcher->set_url(fetcher->GetOriginalURL());
+    fetcher->set_status(net::URLRequestStatus());
+    fetcher->set_response_code(500);
+    fetcher->delegate()->OnURLFetchComplete(fetcher);
+  }
+
+  // Expects a non-empty and valid Geoposition, including the specified
+  // |latitude|.
+  void ExpectValidPosition(const base::Optional<mojom::Geoposition>& position,
+                           const float latitude) {
+    ASSERT_TRUE(position);
+    EXPECT_TRUE(ValidateGeoposition(*position));
+    EXPECT_FLOAT_EQ(position->latitude, latitude);
+  }
+
+  void ExpectError(const base::Optional<mojom::Geoposition>& position) {
+    ASSERT_TRUE(position);
+    EXPECT_THAT(position->error_code,
+                mojom::Geoposition::ErrorCode::POSITION_UNAVAILABLE);
+  }
+  // Use a TaskRunner on which we can fast-forward time.
+  const scoped_refptr<base::TestMockTimeTaskRunner> mock_time_task_runner_;
+  const base::TestMockTimeTaskRunner::ScopedContext mock_time_scoped_context_;
+
+  // notifier_ requires a NetworkChangeNotifier to exist.
+  std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_;
+
+  // Intercept URL fetchers.
+  net::TestURLFetcherFactory url_fetcher_factory_;
+
+  // The object under test.
+  PublicIpAddressLocationNotifier notifier_;
+};
+
+// Tests that a single initial query makes a URL fetch and returns a position.
+TEST_F(PublicIpAddressLocationNotifierTest, SingleQueryReturns) {
+  // Make query.
+  TestPositionQuery query;
+  notifier_.QueryNextPosition(base::Time::Now(),
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query.MakeCallback());
+  // Expect a URL fetch & send a valid response.
+  net::TestURLFetcher* const fetcher = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher, 1.0f);
+  // Expect the query to return.
+  ExpectValidPosition(query.position(), 1.0f);
+}
+
+// Tests that a second query asking for an older timestamp gets a cached result.
+TEST_F(PublicIpAddressLocationNotifierTest, OlderQueryReturnsCached) {
+  const auto time = base::Time::Now();
+
+  // Initial query.
+  TestPositionQuery query_1;
+  notifier_.QueryNextPosition(time, PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_1.MakeCallback());
+  net::TestURLFetcher* const fetcher = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher, 1.0f);
+  ExpectValidPosition(query_1.position(), 1.0f);
+
+  // Second query for an earlier time.
+  TestPositionQuery query_2;
+  notifier_.QueryNextPosition(time - base::TimeDelta::FromMinutes(5),
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_2.MakeCallback());
+  // Expect a cached result, so no new network request.
+  EXPECT_THAT(GetTestUrlFetcher(), testing::IsNull());
+  // Expect the same result as query_1.
+  ExpectValidPosition(query_2.position(), 1.0f);
+}
+
+// Tests that a subsequent query seeking a newer geoposition does not return,
+// until a network change occurs.
+TEST_F(PublicIpAddressLocationNotifierTest,
+       SubsequentQueryWaitsForNetworkChange) {
+  // Initial query.
+  TestPositionQuery query_1;
+  notifier_.QueryNextPosition(base::Time::Now(),
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_1.MakeCallback());
+  net::TestURLFetcher* const fetcher = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher, 1.0f);
+  ExpectValidPosition(query_1.position(), 1.0f);
+
+  // Second query seeking a position newer than the result of query_1.
+  TestPositionQuery query_2;
+  notifier_.QueryNextPosition(query_1.position()->timestamp,
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_2.MakeCallback());
+  // Expect no network request or callback.
+  EXPECT_THAT(GetTestUrlFetcher(), testing::IsNull());
+  EXPECT_FALSE(query_2.position().has_value());
+
+  // Fake a network change notification.
+  net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+      net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+  // Wait for the notifier to complete its delayed reaction.
+  mock_time_task_runner_->FastForwardUntilNoTasksRemain();
+
+  // Now expect a network request and query_2 to return.
+  net::TestURLFetcher* const fetcher_2 = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher_2, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher_2, 2.0f);
+  ExpectValidPosition(query_2.position(), 2.0f);
+}
+
+// Tests that multiple network changes in a short time result in only one
+// network request.
+TEST_F(PublicIpAddressLocationNotifierTest,
+       ConsecutiveNetworkChangesRequestsOnlyOnce) {
+  // Initial query.
+  TestPositionQuery query_1;
+  notifier_.QueryNextPosition(base::Time::Now(),
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_1.MakeCallback());
+  net::TestURLFetcher* const fetcher = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher, 1.0f);
+  ExpectValidPosition(query_1.position(), 1.0f);
+
+  // Second query seeking a position newer than the result of query_1.
+  TestPositionQuery query_2;
+  notifier_.QueryNextPosition(query_1.position()->timestamp,
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_2.MakeCallback());
+  // Expect no network request or callback since network has not changed.
+  EXPECT_THAT(GetTestUrlFetcher(), testing::IsNull());
+  EXPECT_FALSE(query_2.position().has_value());
+
+  // Fake several consecutive network changes notification.
+  for (int i = 0; i < 10; ++i) {
+    net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+        net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+    mock_time_task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(5));
+  }
+  // Expect still no network request or callback.
+  EXPECT_THAT(GetTestUrlFetcher(), testing::IsNull());
+  EXPECT_FALSE(query_2.position().has_value());
+
+  // Wait longer.
+  mock_time_task_runner_->FastForwardUntilNoTasksRemain();
+
+  // Now expect a network request & query_2 to return.
+  net::TestURLFetcher* const fetcher_2 = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher_2, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher_2, 2.0f);
+  ExpectValidPosition(query_2.position(), 2.0f);
+}
+
+// Tests multiple waiting queries.
+TEST_F(PublicIpAddressLocationNotifierTest, MutipleWaitingQueries) {
+  // Initial query.
+  TestPositionQuery query_1;
+  notifier_.QueryNextPosition(base::Time::Now(),
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_1.MakeCallback());
+  net::TestURLFetcher* const fetcher = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher, 1.0f);
+  ExpectValidPosition(query_1.position(), 1.0f);
+
+  // Multiple queries seeking positions newer than the result of query_1.
+  TestPositionQuery query_2;
+  notifier_.QueryNextPosition(query_1.position()->timestamp,
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_2.MakeCallback());
+  TestPositionQuery query_3;
+  notifier_.QueryNextPosition(query_1.position()->timestamp,
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query_3.MakeCallback());
+
+  // Expect no network requests or callback since network has not changed.
+  EXPECT_THAT(GetTestUrlFetcher(), testing::IsNull());
+  EXPECT_FALSE(query_2.position().has_value());
+  EXPECT_FALSE(query_3.position().has_value());
+
+  // Fake a network change notification.
+  net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+      net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+  // Wait for the notifier to complete its delayed reaction.
+  mock_time_task_runner_->FastForwardUntilNoTasksRemain();
+
+  // Now expect a network request & fake a valid response.
+  net::TestURLFetcher* const fetcher_2 = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher_2, testing::NotNull());
+  RespondToFetchWithLatitude(fetcher_2, 2.0f);
+  // Expect all queries to now return.
+  ExpectValidPosition(query_2.position(), 2.0f);
+  ExpectValidPosition(query_3.position(), 2.0f);
+}
+
+// Tests that server error is propogated to the client.
+TEST_F(PublicIpAddressLocationNotifierTest, ServerError) {
+  // Make query.
+  TestPositionQuery query;
+  notifier_.QueryNextPosition(base::Time::Now(),
+                              PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
+                              query.MakeCallback());
+  // Expect a URL fetch & send a valid response.
+  net::TestURLFetcher* const fetcher = GetTestUrlFetcher();
+  EXPECT_THAT(fetcher, testing::NotNull());
+  RespondToFetchWithServerError(fetcher);
+  // Expect the query to return.
+  ExpectError(query.position());
+}
+
+}  // namespace device
diff --git a/services/service_manager/sandbox/mac/renderer_v2.sb b/services/service_manager/sandbox/mac/renderer_v2.sb
index da0d278a..ed2bbbb 100644
--- a/services/service_manager/sandbox/mac/renderer_v2.sb
+++ b/services/service_manager/sandbox/mac/renderer_v2.sb
@@ -142,6 +142,8 @@
 (allow mach-lookup 
   (global-name "com.apple.distributed_notifications@Uv3")
   (global-name "com.apple.fonts")
+  ; crbug.com/756145, crbug.com/786615
+  (global-name "com.apple.FontObjectsServer")
   (global-name "com.apple.logd")
   (global-name "com.apple.lsd.mapdb")
   (global-name "com.apple.system.logger")
@@ -154,10 +156,6 @@
 (if (< os-version 1012)
   (allow mach-lookup (global-name "com.apple.FontServer")))
 
-; crbug.com/756145
-(if (= os-version 1011)
-  (allow mach-lookup (global-name "com.apple.FontObjectsServer")))
-
 ; sysctl
 (if (= os-version 1009)
   (allow sysctl-read)
diff --git a/services/viz/privileged/interfaces/compositing/display_private.mojom b/services/viz/privileged/interfaces/compositing/display_private.mojom
index 3bd6bbc..75dbde6 100644
--- a/services/viz/privileged/interfaces/compositing/display_private.mojom
+++ b/services/viz/privileged/interfaces/compositing/display_private.mojom
@@ -4,6 +4,7 @@
 
 module viz.mojom;
 
+import "mojo/common/time.mojom";
 import "ui/gfx/mojo/color_space.mojom";
 
 // See ui/compositor/compositor.h: ContextFactoryPrivate.
@@ -14,4 +15,9 @@
   SetDisplayColorSpace(gfx.mojom.ColorSpace blending_color_space,
                        gfx.mojom.ColorSpace device_color_space);
   SetOutputIsSecure(bool secure);
+
+  // Locks the BeginFrame vsync interval for this display to |interval| and
+  // ignores vsync interval information from other sources. This will do nothing
+  // if the display is using an external BeginFrame source.
+  SetAuthoritativeVSyncInterval(mojo.common.mojom.TimeDelta interval);
 };
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index 663e54e..a01eb11 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -1,6 +1,381 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "Android Release (Nexus 5X)": {
+    "gtest_tests": [
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "angle_unittests",
+        "use_xvfb": false
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gl_tests",
+        "use_xvfb": false
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gl_unittests",
+        "use_xvfb": false
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--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": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "depth_capture",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "depth_capture_tests",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "gpu_process",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "gpu_process_launch_tests",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "hardware_accelerated_feature_tests",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "info_collection",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--expected-vendor-id",
+          "0",
+          "--expected-device-id",
+          "0"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "info_collection_tests",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--os-type",
+          "android",
+          "--build-revision",
+          "${got_revision}",
+          "--test-machine-name",
+          "${buildername}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "maps_pixel_test",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--refimg-cloud-storage-bucket",
+          "chromium-gpu-archive/reference-images",
+          "--os-type",
+          "android",
+          "--build-revision",
+          "${got_revision}",
+          "--test-machine-name",
+          "${buildername}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "pixel_test",
+        "non_precommit_args": [
+          "--upload-refimg-to-cloud-storage"
+        ],
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "precommit_args": [
+          "--download-refimg-from-cloud-storage"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "screenshot_sync_tests",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "trace_test",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "trace_test",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "webgl_conformance_tests",
+        "override_compile_targets": [
+          "telemetry_gpu_integration_test"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "M",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "shards": 6
+        }
+      }
+    ]
+  },
   "GPU Linux Builder": {},
   "GPU Linux Builder (dbg)": {},
   "GPU Mac Builder": {},
diff --git a/testing/buildbot/filters/mojo.fyi.mash.browser_tests.filter b/testing/buildbot/filters/mojo.fyi.mash.browser_tests.filter
index d879992b..b9107278 100644
--- a/testing/buildbot/filters/mojo.fyi.mash.browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.mash.browser_tests.filter
@@ -1,234 +1,27 @@
 # These tests fail when running browser_tests --mash
 # http://crbug.com/678687
 
-# Uncategorized timeouts, crashes and failures.
+# Unknown failure.
 -BrowserTabRestoreTest.*
--GalleryBrowserTest.*
--GalleryBrowserTestInGuestMode.*
--GoodiesDisplayerBrowserTest.*
--HostedAppNonClientFrameViewAshTest.*
--IdentityGetProfileUserInfoFunctionTest.*
--IdentityOldProfilesGetAccountsFunctionTest.*
--ImageDecoderBrowserTest.*
--ImageLoaderJsTest.*
--ImageWriterPrivateApiTest.*
--ImmersiveModeBrowserViewTest.*
--ImmersiveModeControllerAshHostedAppBrowserTest.*
--IncognitoProfileMainNetworkContext/NetworkContextConfigurationBrowserTest.*
--InfoBarsTest.*
+
+# Extensive use of ash::WindowState.
+-AcceleratorCommandsBrowserTest.*
 -InitiallyMaximized/AcceleratorCommandsFullscreenBrowserTest.*
 -InitiallyMaximized/AcceleratorCommandsPlatformAppFullscreenBrowserTest.*
 -InitiallyRestored/AcceleratorCommandsFullscreenBrowserTest.*
 -InitiallyRestored/AcceleratorCommandsPlatformAppFullscreenBrowserTest.*
--InProcessAccessibilityBrowserTest.*
--InputMethodEngineBrowserTest/InputMethodEngineBrowserTest.*
--InputMethodEngineComponentExtensionBrowserTest/InputMethodEngineBrowserTest.*
--InputMethodEngineIncognitoBrowserTest/InputMethodEngineBrowserTest.*
--InspectUITest.*
--InstallableManagerBrowserTest.*
--InstantThemeTest.*
--InterstitialUITest.*
--InvalidationsWebUITest.*
--JavaScript/ExtensionBindingsApiTest.*
--JavaScriptBindings/ExternallyConnectableMessagingTest.*
--JavaScriptBindings/MessagingApiTest.*
--KeyboardEndToEndTest.*
--KeyboardOperations/FileManagerBrowserTest.*
--KeyboardOverlayUIBrowserTest.*
-# Works, but flaky timeouts. http://crbug.*
--KioskAppManagerTest.*
--KioskAppSettingsWebUITest.*
--KioskCrashRestoreTest.*
--KioskEnterpriseTest.*
--KioskHiddenWebUITest.*
--KioskTest.*
--KioskUpdateTest.*
--KioskVirtualKeyboardTest.*
--LauncherPlatformAppBrowserTest.*
--LazyBackgroundPageApiTest.*
--LoadImageBrowserTest.*
--LocalFileSystemExtensionApiTest.*
--LocalNTPDoodleTest.*
--LocalNTPOneGoogleBarSmokeTest.*
--LocalNTPTest.*
--LockScreenNoteTakingTest.*
--LoginFeedbackTest.*
--LoginPromptBrowserTest.*
--LoginScreenDefaultPolicyInSessionBrowsertest.*
--LoginScreenDefaultPolicyLoginScreenBrowsertest.*
--LoginScreenLocalePolicyTest.*
--LoginScreenPolicyTest.*
--LoginUIKeyboardTest.*
--LoginUIKeyboardTestWithUsersAndOwner.*
--LoginUtilsTest.*
--LoginWebDialogTest.*
--LogWebUIUrlTest.*
--MagnificationManagerTest.*
--ManagedWithoutPermission/ManagedWithoutPermissionPlatformKeysTest.*
--ManagedWithPermission/ManagedWithPermissionPlatformKeysTest.*
--MaterialBookmarksCommandManagerTest.*
--MaterialBookmarksDNDManagerTest.*
--MaterialBookmarksFolderNodeTest.*
--MaterialBookmarksPolicyTest.*
--MaterialHistoryListTest.*
--MaterialHistorySyncedTabsTest.*
--MaterialHistoryToolbarTest.*
--MaybeSetMetadata/SafeBrowsingServiceMetadataTest.*
--MaybeSetMetadata/V4SafeBrowsingServiceMetadataTest.*
--MdSettingsUITest.*
--MediaEngagementAutoplayBrowserTest.*
--MediaEngagementBrowserTest.*
--MediaFileValidatorTest.*
--MediaRouterElementsBrowserTest.*
--MediaStreamDevicesControllerBrowserTestInstance/MediaStreamDevicesControllerBrowserTest.*
--MediaStreamDevicesControllerTest.*
--MergeSessionTest.*
--MimeHandlerViewTests/MimeHandlerViewTest.*
--Minimal/MemlogBrowserTest.*
--Mojo/ECKEncryptedMediaTest.*
--MSE_ClearKey/EncryptedMediaTest.*
--MSE_ExternalClearKey_Mojo/EncryptedMediaTest.*
--MSE_ExternalClearKey/EncryptedMediaTest.*
--MultiAuthEnrollmentScreenTest.*
--MultiProfileFileManagerBrowserTest.*
--Native/ExtensionBindingsApiTest.*
--NativeBindings/ExternallyConnectableMessagingTest.*
--NativeBindings/MessagingApiTest.*
--NativeWindowTrackerTest.*
--NavigatingExtensionPopupBrowserTest.*
--NavigationConsumingTest.*
--NetInternalsTest.*
--NetworkConfigViewTest.*
--NetworkingConfigDelegateChromeosTest.*
--NetworkingConfigTest.*
--NetworkingPrivateApiTest.*
--NetworkingPrivateChromeOSApiTest.*
--NetworkPortalDetectorImplBrowserTest.*
--NoSessionRestoreTest.*
--NoStatePrefetchBrowserTest/NoStatePrefetchBrowserTest.*
--NtpExtensionBubbleViewBrowserTest.*
--NTPTilesTest.*
--OAuth2Test.*
--OnStartupSettingsBrowserTest.*
--OpenAudioFiles/FileManagerBrowserTest.*
--OpenFileDialog/FileManagerBrowserTest.*
--OutOfProcessPPAPIPrivateTest.*
--OutOfProcessPPAPITest.*
--PageLoadMetricsBrowserTest.*
--PatchTest.*
--PaymentManifestParserTest.*
--PaymentMethodViewControllerTest.*
--PaymentRequestCanMakePaymentMetricsTest.*
--PaymentRequestCanMakePaymentQueryTest.*
--PaymentRequestContactInfoEditorTest.*
--PaymentRequestCreditCardEditorTest.*
--PaymentRequestDebitTest.*
--PaymentRequestEmptyUpdateTest.*
--PaymentRequestIframeTest.*
--PaymentRequestJourneyLoggerNoSupportedPaymentMethodTest.*
--PaymentRequestNoUpdateWithTest.*
--PaymentRequestOrderSummaryViewControllerTest.*
--PaymentRequestSettingsLinkTest.*
--PaymentRequestShippingAddressEditorTest.*
--PaymentSheetViewControllerContactDetailsTest.*
--PaymentSheetViewControllerNoShippingTest.*
--PDFExtensionClipboardTest.*
--PDFExtensionLinkClickTest.*
--PDFExtensionTest.*
--PDFTestFiles/PDFExtensionTest.*
--Pepper/ECKEncryptedMediaTest.*
--PepperContentSettingsSpecialCasesTest.*
--PlatformAppBrowserTest.*
--PlatformAppNavigationRedirectorBrowserTest.*
--PluginPowerSaverBrowserTest.*
--PolicyDisplayRotationDefault/DisplayRotationBootTest.*
--PolicyDisplayRotationDefault/DisplayRotationDefaultTest.*
--PolicyPrefsTest.*
--PolicyProvidedTrustRootsPublicSessionTest.*
--PolicyTest.*
--PolicyToolUITest.*
--PolicyUITest.*
--PPAPIBrokerInfoBarTest.*
--PPAPIFileChooserTestWithSBService.*
--PPAPINaClGLibcTest.*
--PPAPINaClPNaClNonSfiTest.*
--PredictorBrowserTest.*
--PreferencesTest.*
--PrefHashBrowserTestChangedAtomicInstance/PrefHashBrowserTestChangedAtomic.*
--PrefHashBrowserTestClearedAtomicInstance/PrefHashBrowserTestClearedAtomic.*
--PrefHashBrowserTestUnchangedCustomInstance/PrefHashBrowserTestUnchangedCustom.*
--PrefHashBrowserTestUnchangedDefaultInstance/PrefHashBrowserTestUnchangedDefault.*
--PrefsTabHelperBrowserTest.*
--PreinstalledSigninExtensionsDeviceCloudPolicyBrowserTest.*
--PrerenderBrowserTest.*
--PrintBrowserTest.*
--PrintPreviewBrowserTest.*
--PrintPreviewDestinationSearchTest.*
--PrintPreviewDialogControllerBrowserTest.*
--PrintPreviewUIBrowserTest.*
--ProcessesApiTest.*
--ProcessManagementTest.*
--ProcessManagerBrowserTest.*
--ProcessMemoryMetricsEmitterTest.*
--ProfileMainNetworkContext/NetworkContextConfigurationBrowserTest.*
--ProfilingBrowserTest.*
--Providers/FileManagerBrowserTest.*
--ProvisionedEnrollmentScreenTest.*
--ProxySettingsApiTest.*
--PushMessagingBrowserTest.*
--RequestFileSystemDialogTest.*
--ResetFirstAfterBootTest.*
--SafeBrowsingBlockingPageBrowserTestWithThreatTypeAndIsolationSetting/SafeBrowsingBlockingPageBrowserTest.*
--SaveCardBubbleControllerImplTest.*
--SessionRestoreTest.*
--SessionRestoreTestChromeOS.*
--SettingsA11yManagePasswords.*
--SettingsAccessibilityTest.*
--ShelfAppBrowserTest.*
--ShelfAppBrowserTestNoDefaultBrowser.*
--ShutdownPolicyInSessionTest.*
--SigninExtensionsDeviceCloudPolicyBrowserTest.*
--SigninProfileAppsPolicyPerChannelTest.*
--SigninProfileAppsPolicyTest.*
--SimpleWebViewDialogTest.*
--SitePerProcess/TaskManagerOOPIFBrowserTest.*
--SmartSessionRestoreTest.*
--SortWindowsByZIndexBrowserTest.*
--StartupMetricsTest.*
--SupervisedUserCreationTest.*
--SystemTrayTrayCastMediaRouterChromeOSTest.*
--SystemUse24HourClockPolicyTest.*
--TabCaptureApiPixelTest.*
--TabManagerTest.*
--TabRestoreTest.*
--TaskManagerUtilityProcessBrowserTest.*
--TrayAccessibilityLoginScreenTest.*
--TrayAccessibilityTestInstance/TrayAccessibilityTest.*
--UserAddingScreenTest.*
--UserTypeInstantiation/AccessibilityManagerUserTypeTest.*
--VirtualKeyboardAppWindowTest.*
--VirtualKeyboardStateTest.*
--VirtualKeyboardWebContentTest.*
--VolumeControllerSoundsTest.*
--WallpaperManagerPolicyTest.*
--WebstoreInlineInstallerTest.*
--WebViewScrollBubbling/WebViewGuestScrollTouchTest.*
--WebViewTests/WebViewFocusTest.*
--WebViewTests/WebViewSizeTest.*
--WebViewTests/WebViewTest.*
-# Fails (assertion in JS side of code).
--WindowOpenApiTest.*
--WizardControllerKioskFlowTest.*
--WizardControllerTest.*
--ZoomBubbleBrowserTest.*
-
-# Extensive use of ash::WindowState.
--AcceleratorCommandsBrowserTest.*
 
 # ChromeVoxPanel directly calls into ash to set panel height.
 -AccessibilityFeatureaApiTestInstantiatePermission/AccessibilityFeaturesApiTest.*
 -AccessibilityManagerTest.*
 -ChromeOSInfoPrivateTest.*
+-PolicyPrefsTest.*
+-PolicyProvidedTrustRootsPublicSessionTest.*
+-PolicyTest.*
+-UserTypeInstantiation/AccessibilityManagerUserTypeTest.*
+-VolumeControllerSoundsTest.*
+-WizardControllerKioskFlowTest.*
+-WizardControllerTest.*
 
 # Crashes in net::URLRequestContext::CreateRequest.
 -AffiliationCheck/EnterpriseDeviceAttributesTest.*
@@ -237,6 +30,10 @@
 -ExistingUserControllerActiveDirectoryTest.*
 -ExistingUserControllerPublicSessionTest.*
 -ExistingUserControllerUntrustedTest.*
+-PreinstalledSigninExtensionsDeviceCloudPolicyBrowserTest.*
+-SigninExtensionsDeviceCloudPolicyBrowserTest.*
+-SigninProfileAppsPolicyPerChannelTest.*
+-SigninProfileAppsPolicyTest.*
 
 # Uses ash::Shell::GetRootWindowForNewWindows to choose a display for the app list.
 # More generally, app list needs to move into ash.
@@ -262,54 +59,65 @@
 # profile_destroyer.cc(57) Check failed: hosts.empty() || profile->IsOffTheRecord() || profile->HasOffTheRecordProfile() || content::RenderProcessHost::run_renderer_in_process(). Profile still has 1 hosts
 -BluetoothPairingUITest.*
 -DemoAppLauncherTest.*
+-LoginWebDialogTest.*
 
 # Direct access to ash window frames, tablet mode, overview mode, etc.
 -BrowserNonClientFrameViewAshBackButtonTest.*
 -BrowserNonClientFrameViewAshTest.*
+-HostedAppNonClientFrameViewAshTest.*
+-ImmersiveModeControllerAshHostedAppBrowserTest.*
 
 # Incorrect window bounds. Probably WindowSizerAsh problem.
 -BrowserTestTabbedOrApp/BrowserTestParam.*
+-TabRestoreTest.*
 
 # Timeout waiting for notification.
 -CaptivePortalAuthenticationIgnoresProxy/NetworkPortalDetectorImplBrowserTestIgnoreProxy.*
 
 # ash::Shell access from ChromeViewsDelegate::CreateDefaultNonClientFrameView()
-# from chromeos::CaptivePortalWindowProxy::Show().
+# e.g. from chromeos::CaptivePortalWindowProxy::Show().
 -CaptivePortalWindowCtorDtorTest.*
 -CaptivePortalWindowTest.*
+-SimpleWebViewDialogTest.*
 
 # Need pixel copy support. http://crbug.com/754864
 -CastStreamingApiTestWithPixelOutput.*
+-TabCaptureApiPixelTest.*
 
 # RefCounted check failed: CalledOnValidSequence() from SchedulerWorkerDelegate::OnMainExit
 -CheckSystemTokenAvailability/EnterprisePlatformKeysTest.*
 
 # aura::CrashInFlightChange::ChangeFailed() when searching PDF.
 -ChromeFindRequestManagerTest.*
+-PDFExtensionTest.*
 
 # Need screenshot support. http://crbug.com/754899
 -ChromeScreenshotGrabberBrowserTest.*
 
 # ChromeBrowserMainExtraPartsAsh: Check failed: views::MusClient::Exists().
 -ChromeMainTest.*
+-ProfilingBrowserTest.*
 
 # Null immersive_fullscreen_controller_.
 -ChromeNativeAppWindowViewsAuraAshBrowserTest.*
 
+# Flaky. SessionRestoreStatsCollector::Observe failure. crbug.com/785298
 # session_restore_stats_collector.cc(210) Check failed: 0u < loading_tab_count_ (0 vs. 0)
 -ContinueWhereILeftOffTest.*
 -DeviceIDTest.*
+-SessionRestoreTest.*
+-SessionRestoreTestChromeOS.*
+-SAMLPolicyTest.TransferCookiesAffiliated
+-SmartSessionRestoreTest.*
 
 # ash::Shell access in test for wallpaper images.
 -CustomizationWallpaperDownloaderBrowserTest.*
+-WallpaperManagerPolicyTest.*
 
 # Flaky: private_api_file_system.cc(811) Check failed: external_backend->CanHandleType(file_system_url.type()).
 -DefaultTaskDialog/FileManagerBrowserTest.*
 -QuickView/FileManagerBrowserTest.*
 
-# Need virtual keyboard support.
--DefaultKeyboardExtensionBrowserTest.*
-
 # ash::Shell access for LogoutConfirmationController
 -DeviceLocalAccountTest.*
 
@@ -318,7 +126,11 @@
 
 # WindowPortMus check failed in fullscreen test:
 # window_mus_type() == WindowMusType::TOP_LEVEL_IN_WM || window_mus_type() == WindowMusType::EMBED_IN_OWNER.
+# http://crbug.com/786544
 -ExtensionInstallUIBrowserTest.*
+-ImmersiveModeBrowserViewTest.*
+-WebstoreInlineInstallerTest.*
+-ZoomBubbleBrowserTest.*
 
 # ozone_platform.cc(61) Check failed: instance_. OzonePlatform is not initialized
 -ExtensionWebstoreGetWebGLStatusTest.*
@@ -332,13 +144,128 @@
 # ash::Shell::display_manager() to update displays.
 -ForceMaximizeOnFirstRunTest.*
 -ForceMaximizePolicyFalseTest.*
+-PolicyDisplayRotationDefault/DisplayRotationBootTest.*
+-PolicyDisplayRotationDefault/DisplayRotationDefaultTest.*
 
-# Flaky. SessionRestoreStatsCollector::Observe failure. crbug.com/785298
--SAMLPolicyTest.TransferCookiesAffiliated
+# Failures JS-side.
+-GalleryBrowserTest.*
+-GalleryBrowserTestInGuestMode.*
+
+# IME accesses ash::Shell for root window.
+-InputMethodEngineBrowserTest/InputMethodEngineBrowserTest.*
+-InputMethodEngineComponentExtensionBrowserTest/InputMethodEngineBrowserTest.*
+-InputMethodEngineIncognitoBrowserTest/InputMethodEngineBrowserTest.*
+
+# KeyboardOverlayUI uses ash::Shell.
+-KeyboardOverlayUIBrowserTest.*
+
+# Kiosk mode has a variety of failures:
+# termination_observer_->terminated() is false.
+# Value of: login_display_host == NULL || login_display_host->GetNativeWindow()->layer()->GetTargetOpacity() == 0.0f
+# Check failed: !browser_client || browser_client->IsShuttingDown() || did_respond() || ignore_all_did_respond_for_testing_do_not_use. app.window.create
+# chromeos::KioskExternalUpdateNotification::CreateAndShowNotificationView() uses ash::Shell.
+-KioskAppManagerTest.*
+-KioskAppSettingsWebUITest.*
+-KioskCrashRestoreTest.*
+-KioskEnterpriseTest.*
+-KioskHiddenWebUITest.*
+-KioskTest.*
+-KioskUpdateTest.*
+
+# Window state lookup failures for minimized, active, etc.
+-LauncherPlatformAppBrowserTest.*
+
+# JS failure: hasAccessToCurrentWindow: FAIL (no message)
+-LockScreenNoteTakingTest.*
+
+# desktop_window_tree_host_mus.cc(796) Check failed: !window->GetRootWindow() || this->window() == window->GetRootWindow().
+-LoginFeedbackTest.*
+
+# Missing magnification manager and ChromeVoxPanel crashes.
+-LoginScreenDefaultPolicyInSessionBrowsertest.*
+-LoginScreenDefaultPolicyLoginScreenBrowsertest.*
+
+# Test shutdown crashes.
+-LoginScreenLocalePolicyTest.*
+-LoginScreenPolicyTest.*
+
+# Crashes in pre-login phase, probably MagnificationManager not created.
+-MagnificationManagerTest.*
+
+# OutputProtection problems:
+# interface_endpoint_client.cc(32) Check failed: !is_valid. The callback passed to OutputProtection::QueryStatus() was never run.
+# binder_registry.h(89) Failed to locate a binder for interface: display::mojom::OutputProtection
+-Mojo/ECKEncryptedMediaTest.*
+-OutOfProcessPPAPITest.*
+-Pepper/ECKEncryptedMediaTest.*
+
+# ash::FocusRingController::SetVisible() from LoginDisplayHostWebUI.
+-MultiAuthEnrollmentScreenTest.*
+-ProvisionedEnrollmentScreenTest.*
+
+# VPN item not in system tray.
+-NetworkingConfigDelegateChromeosTest.*
+
+# Timeout device_event_log_impl.cc(156) Network: network_portal_detector_impl.cc:486 Portal detection timeout:  name= id=
+-NetworkingConfigTest.*
+-NetworkPortalDetectorImplBrowserTest.*
+
+# Crash in autofill::SaveCardBubbleViews::ShouldShowCloseButton().
+-SaveCardBubbleControllerImplTest.*
+
+# ash::Shell access in test.
+-ShelfAppBrowserTest.*
+-ShelfAppBrowserTestNoDefaultBrowser.*
+
+# ash::Shell access in test.
+-ShutdownPolicyInSessionTest.*
+
+# Function under test uses ash::Shell for window list.
+-SortWindowsByZIndexBrowserTest.*
+
+# Timeout.
+-StartupMetricsTest.*
+
+# ash::Shell access in test for StatusAreaWidget.
+-SupervisedUserCreationTest.*
+# Crash. Database is locked.
+-SyncAwareCounterTest.*
+
+# ash::Shell access in test.
+-SystemTrayTrayCastMediaRouterChromeOSTest.*
+
+# ash::Shell access in test.
+-SystemUse24HourClockPolicyTest.*
 
 # Flaky shutdown crashes in ~MusClient http://crbug.com/786234 and AtExit
 # crashes in ~WebContentsTaskProvider http://crbug.com/786230
 -AppBackgroundPageApiTest.*
 -DefaultIsolation/TaskManagerOOPIFBrowserTest.*
+-PrerenderBrowserTest.*
+-SitePerProcess/TaskManagerOOPIFBrowserTest.*
 -TaskManagerBrowserTest.*
 -TaskManagerMemoryCoordinatorBrowserTest.*
+-TaskManagerUtilityProcessBrowserTest.*
+
+# ash::Shell access in test.
+-TrayAccessibilityLoginScreenTest.*
+-TrayAccessibilityTestInstance/TrayAccessibilityTest.*
+
+# chromeos::UserAddingScreenImpl::Cancel() uses ash::Shell to enable system tray.
+-UserAddingScreenTest.*
+
+# Virtual keyboard not supported.
+-DefaultKeyboardExtensionBrowserTest.*
+-KeyboardEndToEndTest.*
+-KioskVirtualKeyboardTest.*
+-VirtualKeyboardAppWindowTest.*
+-VirtualKeyboardStateTest.*
+-VirtualKeyboardWebContentTest.*
+
+# Also fails in --mus. http://crbug.com/755318.
+-WebViewScrollBubbling/WebViewGuestScrollTouchTest.*
+
+# Also fails in --mus. http://crbug.com/755328
+-WebViewTests/WebViewFocusTest.*
+-WebViewTests/WebViewSizeTest.*
+-WebViewTests/WebViewTest.*
diff --git a/testing/libfuzzer/fuzzers/BUILD.gn b/testing/libfuzzer/fuzzers/BUILD.gn
index 93741c5..dab495a 100644
--- a/testing/libfuzzer/fuzzers/BUILD.gn
+++ b/testing/libfuzzer/fuzzers/BUILD.gn
@@ -197,7 +197,7 @@
   deps = [
     "//v8:parser_fuzzer",
   ]
-  dict = "dicts/generated/v8_script_parser_fuzzer.dict"
+  dict = "dicts/generated/javascript.dict"
   seed_corpus = "//v8/test/mjsunit/regress/"
   libfuzzer_options = [ "only_ascii=1" ]
 }
@@ -505,3 +505,17 @@
   ]
   include_dirs = [ "$root_build_dir/$target_gen_dir" ]
 }
+
+fuzzer_test("v8_fully_instrumented_fuzzer") {
+  sources = [
+    "v8_fuzzer.cc",
+  ]
+  deps = [
+    "//base",
+    "//v8:v8",
+    "//v8:v8_libplatform",
+  ]
+  dict = "dicts/generated/javascript.dict"
+  seed_corpus = "//v8/test/mjsunit/"
+  libfuzzer_options = [ "only_ascii=1" ]
+}
diff --git a/testing/libfuzzer/fuzzers/dicts/generated/v8_script_parser_fuzzer.dict b/testing/libfuzzer/fuzzers/dicts/generated/javascript.dict
similarity index 100%
rename from testing/libfuzzer/fuzzers/dicts/generated/v8_script_parser_fuzzer.dict
rename to testing/libfuzzer/fuzzers/dicts/generated/javascript.dict
diff --git a/testing/libfuzzer/fuzzers/v8_fuzzer.cc b/testing/libfuzzer/fuzzers/v8_fuzzer.cc
new file mode 100644
index 0000000..82995bbb
--- /dev/null
+++ b/testing/libfuzzer/fuzzers/v8_fuzzer.cc
@@ -0,0 +1,200 @@
+// 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 <chrono>
+#include <functional>
+#include <iostream>
+#include <mutex>
+#include <thread>
+
+#include "base/logging.h"
+#include "v8/include/libplatform/libplatform.h"
+#include "v8/include/v8.h"
+
+using v8::MaybeLocal;
+using std::ref;
+using std::chrono::time_point;
+using std::chrono::steady_clock;
+using std::chrono::seconds;
+using std::chrono::duration_cast;
+
+static const seconds kSleepSeconds(1);
+
+// Because of the sleep we do, the actual max will be:
+// kSleepSeconds + kMaxExecutionSeconds.
+// TODO(metzman): Determine if having such a short timeout causes too much
+// indeterminism.
+static const seconds kMaxExecutionSeconds(15);
+
+// Inspired by/copied from d8 code, this allocator will return nullptr when
+// an allocation request is made that puts currently_allocated_ over
+// kAllocationLimit (1 GB). Should handle the current allocations done by V8.
+class MockArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
+  std::unique_ptr<Allocator> allocator_ =
+      std::unique_ptr<Allocator>(NewDefaultAllocator());
+
+  const size_t kAllocationLimit = 1000 * 1024 * 1024;
+  // TODO(metzman): Determine if this approach where we keep track of state
+  // between runs is a good idea. Maybe we should simply prevent allocations
+  // over a certain size regardless of previous allocations.
+  size_t currently_allocated_;
+  std::mutex mtx_;
+
+ public:
+  MockArrayBufferAllocator()
+      : v8::ArrayBuffer::Allocator(), currently_allocated_(0) {}
+  void SetProtection(void* data,
+                     size_t length,
+                     Protection protection) override {
+    allocator_->SetProtection(data, length, protection);
+  }
+
+  void* Allocate(size_t length) override {
+    void* data = AllocateUninitialized(length);
+    return data == nullptr ? data : memset(data, 0, length);
+  }
+
+  void* AllocateUninitialized(size_t length) override {
+    mtx_.lock();
+    if (length + currently_allocated_ > kAllocationLimit) {
+      mtx_.unlock();
+      return nullptr;
+    }
+    currently_allocated_ += length;
+    mtx_.unlock();
+    return malloc(length);
+  }
+
+  void Free(void* ptr, size_t length) override {
+    mtx_.lock();
+    currently_allocated_ -= length;
+    // We need to free before we unlock, otherwise currently_allocated_ will
+    // be innacurate.
+    free(ptr);
+    mtx_.unlock();
+  }
+
+  void Free(void* data, size_t length, AllocationMode mode) override {
+    switch (mode) {
+      case AllocationMode::kNormal: {
+        mtx_.lock();
+        currently_allocated_ -= length;
+        Free(data, length);
+        mtx_.unlock();
+        return;
+      }
+      case AllocationMode::kReservation: {
+        mtx_.lock();
+        currently_allocated_ -= length;
+        allocator_->Free(data, length, mode);
+        mtx_.unlock();
+        return;
+      }
+      default:
+        NOTREACHED();
+    }
+  }
+
+  void* Reserve(size_t length) override {
+    mtx_.lock();
+    if (length + currently_allocated_ > kAllocationLimit) {
+      mtx_.unlock();
+      return nullptr;
+    }
+    currently_allocated_ += length;
+    mtx_.unlock();
+    return allocator_->Reserve(length);
+  }
+};
+
+void terminate_execution(v8::Isolate* isolate,
+                         std::mutex& mtx,
+                         bool& is_running,
+                         time_point<steady_clock>& start_time) {
+  while (true) {
+    std::this_thread::sleep_for(kSleepSeconds);
+    mtx.lock();
+    if (is_running) {
+      if (duration_cast<seconds>(steady_clock::now() - start_time) >
+          kMaxExecutionSeconds) {
+        isolate->TerminateExecution();
+        is_running = false;
+        std::cout << "Terminated" << std::endl;
+        fflush(0);
+      }
+    }
+    mtx.unlock();
+  }
+}
+
+struct Environment {
+  Environment() {
+    v8::Platform* platform = v8::platform::CreateDefaultPlatform(
+        0, v8::platform::IdleTaskSupport::kDisabled,
+        v8::platform::InProcessStackDumping::kDisabled, nullptr);
+
+    v8::V8::InitializePlatform(platform);
+    v8::V8::Initialize();
+    v8::Isolate::CreateParams create_params;
+
+    create_params.array_buffer_allocator = &mock_arraybuffer_allocator;
+    isolate = v8::Isolate::New(create_params);
+    terminator_thread = std::thread(terminate_execution, isolate, ref(mtx),
+                                    ref(is_running), ref(start_time));
+  }
+  MockArrayBufferAllocator mock_arraybuffer_allocator;
+  std::mutex mtx;
+  std::thread terminator_thread;
+  v8::Isolate* isolate;
+  time_point<steady_clock> start_time;
+  bool is_running;
+};
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+  v8::V8::InitializeICUDefaultLocation((*argv)[0]);
+  v8::V8::InitializeExternalStartupData((*argv)[0]);
+  v8::V8::SetFlagsFromCommandLine(argc, *argv, true);
+  return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size < 1)
+    return 0;
+
+  static Environment* env = new Environment();
+
+  v8::Isolate::Scope isolate_scope(env->isolate);
+  v8::HandleScope handle_scope(env->isolate);
+  v8::Local<v8::Context> context = v8::Context::New(env->isolate);
+  v8::Context::Scope context_scope(context);
+
+  std::string source_string =
+      std::string(reinterpret_cast<const char*>(data), size);
+
+  MaybeLocal<v8::String> source_v8_string = v8::String::NewFromUtf8(
+      env->isolate, source_string.c_str(), v8::NewStringType::kNormal);
+
+  if (source_v8_string.IsEmpty())
+    return 0;
+
+  v8::TryCatch try_catch(env->isolate);
+  MaybeLocal<v8::Script> script =
+      v8::Script::Compile(context, source_v8_string.ToLocalChecked());
+
+  if (script.IsEmpty())
+    return 0;
+
+  auto local_script = script.ToLocalChecked();
+  env->mtx.lock();
+  env->start_time = steady_clock::now();
+  env->is_running = true;
+  env->mtx.unlock();
+
+  ALLOW_UNUSED_LOCAL(local_script->Run(context));
+
+  env->mtx.lock();
+  env->is_running = false;
+  env->mtx.unlock();
+  return 0;
+}
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls b/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls
index 1c97cd0a..06c6fbb 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls
@@ -73,21 +73,7 @@
 crbug.com/417782 paint/invalidation/svg/absolute-sized-document-no-scrollbars.svg [ Failure ]
 crbug.com/417782 paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-1.html [ Failure ]
 crbug.com/417782 paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-2.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-background-image-fixed-centered.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-background-image-generated.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-background-image-non-fixed.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-frameset.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-media-query.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-no-layout-change1.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-no-layout-change2.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-percent-html.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-percent-width-height.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-positioned-bottom.html [ Failure ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-positioned-percent-top.html [ Failure ]
 crbug.com/417782 paint/invalidation/window-resize/window-resize-vertical-writing-mode.html [ Crash ]
-crbug.com/417782 paint/invalidation/window-resize/window-resize-viewport-percent.html [ Failure ]
 crbug.com/417782 plugins/webview-plugin-nested-iframe-scroll.html [ Failure ]
 crbug.com/417782 plugins/webview-plugin-scroll.html [ Failure ]
 crbug.com/417782 printing/quirks-percentage-height-body.html [ Failure ]
@@ -99,10 +85,6 @@
 crbug.com/417782 svg/custom/mask-with-default-value.svg [ Failure ]
 crbug.com/417782 [ Mac ] svg/custom/masking-clipping-hidpi.svg [ Failure ]
 crbug.com/417782 transforms/selection-bounds-in-transformed-view.html [ Failure ]
-crbug.com/417782 [ Linux ] virtual/android/fast/rootscroller/browser-controls-background-iframe.html [ Failure ]
-crbug.com/417782 [ Linux ] virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe-scroller.html [ Failure ]
-crbug.com/417782 [ Linux ] virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe.html [ Failure ]
-crbug.com/417782 [ Linux ] virtual/android/fast/rootscroller/browser-controls-gradient-background.html [ Failure ]
 crbug.com/417782 [ Linux ] virtual/android/fast/rootscroller/nested-rootscroller-browser-controls-bounds-hidden.html [ Failure ]
 crbug.com/417782 [ Linux ] virtual/android/fast/rootscroller/nested-rootscroller-browser-controls-bounds-shown.html [ Failure ]
 crbug.com/417782 [ Linux ] virtual/android/fullscreen/compositor-touch-hit-rects-fullscreen-video-controls.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/fast/harness/results.html b/third_party/WebKit/LayoutTests/fast/harness/results.html
index 92c6549..de7bc98 100644
--- a/third_party/WebKit/LayoutTests/fast/harness/results.html
+++ b/third_party/WebKit/LayoutTests/fast/harness/results.html
@@ -317,7 +317,8 @@
 </div>
 
 <div id="report_header" style="margin-top:8px">
-  <span class="fix-width">Tests shown</span><span id="report_title" style="font-weight:bold"></span>
+  <span class="fix-width">Tests shown</span><span id="report_count"></span>
+  <span id="report_title" style="font-weight:bold"></span>
   in format:
     <select id="report_format" onchange="Query.generateReport()">
       <option value="plain" selected>Plain text</option>
@@ -825,6 +826,7 @@
     };
     window.setTimeout( _ => {
       traversal.traverse(filter, report.print);
+      document.querySelector("#report_count").innerText = traversal.html.length;
       this.completeReportPromise(traversal);
       this.currentRAF = window.requestAnimationFrame(callback);
     }, 0);
@@ -1002,9 +1004,8 @@
 
   showNextExpectation: function(backward) {
     let nextExpectation;
-    let openDetails = document.querySelector(".details.open");
-    let openExpectation = openDetails && GUI.getExpectation(openDetails);
     let activeExpectation = GUI.activeExpectation();
+    let openExpectation = GUI.openExpectation();
     if (openExpectation)
       GUI.hideResults(openExpectation);
     if (openExpectation && openExpectation == activeExpectation) {
@@ -1024,12 +1025,13 @@
     }
   },
 
+  openExpectation: function() {
+    let openDetails = document.querySelector(".details.open");
+    return openDetails && GUI.getExpectation(openDetails);
+  },
+
   activeExpectation: function() {
-    let result = GUI.closest(document.activeElement, "expect");
-    if (result)
-      return result;
-    result = GUI.closest(document.activeElement, "result-frame");
-    return result ? result.previousElementSibling : null;
+    return GUI.getExpectation(document.activeElement) || GUI.openExpectation();
   },
 
   initEvents: function() {
@@ -1123,7 +1125,6 @@
   printSummary: function (fullResults) {
     if (fullResults.builder_name)
       document.querySelector("#builder_name").innerText = fullResults.builder_name;
-    document.querySelector("#summary_total").innerText = fullResults.num_passes + fullResults.num_regressions;
     document.querySelector("#summary_passed").innerText = fullResults.num_passes;
     document.querySelector("#summary_regressions").innerText = fullResults.num_regressions;
     let failures = fullResults["num_failures_by_type"];
@@ -1141,10 +1142,12 @@
       "count_unexpected_pass": 0,
       "count_unexpected_fail": 0,
       "count_testexpectations": 0,
-      "count_flaky": 0
+      "count_flaky": 0,
+      "count_all": 0,
     };
     var t = new Traversal(fullResults.tests);
     t.traverse( test => {
+      counts.count_all++;
       if (Filters.unexpectedPass(test))
         counts.count_unexpected_pass++;
       if (Filters.unexpectedFailure(test))
@@ -1156,11 +1159,16 @@
     });
     for (let p in counts)
       document.querySelector("#" + p).innerText = counts[p];
-    document.querySelector("#count_all").innerText = fullResults.num_passes + fullResults.num_regressions;
+
+    document.querySelector("#summary_total").innerText = counts.count_all;
   },
 
   getExpectation: function(el) {
-    return GUI.closest(el, "expect");
+    let result = GUI.closest(el, "expect");
+    if (result)
+      return result;
+    result = GUI.closest(document.activeElement, "result-frame");
+    return result ? result.previousElementSibling : null;
   },
 
   isFlag: function(el) {
@@ -1376,6 +1384,11 @@
       if (ev.target.tagName == "A") {
         this.selectAnchor(ev.target);
         ev.preventDefault();
+        if (this.animationIntervalId) {
+          // Restart animation to show the clicked view for one second.
+          this.setAnimation(false);
+          window.setTimeout(_ => this.setAnimation(true), 1000);
+        }
       }
     });
 
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt
new file mode 100644
index 0000000..be93e98
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt
@@ -0,0 +1,133 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 500],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 400, 600],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-expected.txt
new file mode 100644
index 0000000..e66b6e9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-expected.txt
@@ -0,0 +1,177 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 500],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 500],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 400, 600],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 400, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-generated-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-generated-expected.txt
new file mode 100644
index 0000000..e66b6e9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-generated-expected.txt
@@ -0,0 +1,177 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 500],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 500],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 400, 600],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 400, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "background"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-non-fixed-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-non-fixed-expected.txt
new file mode 100644
index 0000000..f3bc507e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-background-image-non-fixed-expected.txt
@@ -0,0 +1,177 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos-expected.txt
new file mode 100644
index 0000000..d949e6c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-centered-inline-under-fixed-pos-expected.txt
@@ -0,0 +1,311 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='container'",
+          "rect": [0, 0, 600, 500],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV class='parent'",
+          "rect": [0, 0, 6, 500],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+          "rect": [0, 250, 6, 30],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+          "rect": [0, 125, 6, 30],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='container'",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow DIV class='parent'",
+      "reason": "geometry"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='container'",
+          "rect": [0, 0, 600, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV class='parent'",
+          "rect": [0, 0, 6, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+          "rect": [0, 125, 6, 30],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='container'",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow DIV class='parent'",
+      "reason": "geometry"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='container'",
+          "rect": [0, 0, 400, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV class='parent'",
+          "rect": [0, 0, 6, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+          "rect": [0, 300, 6, 30],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+          "rect": [0, 125, 6, 30],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='container'",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow DIV class='parent'",
+      "reason": "geometry"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='container'",
+          "rect": [0, 0, 800, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV class='parent'",
+          "rect": [0, 0, 6, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+          "rect": [0, 300, 6, 30],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='container'",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow DIV class='parent'",
+      "reason": "geometry"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV class='child'",
+      "reason": "geometry"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-frameset-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-frameset-expected.txt
new file mode 100644
index 0000000..9fc2106
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-frameset-expected.txt
@@ -0,0 +1,501 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutFrameSet FRAMESET",
+          "rect": [0, 0, 600, 500],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [153, 0, 294, 500],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [153, 250, 294, 250],
+          "reason": "incremental"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [453, 0, 147, 500],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [0, 0, 147, 500],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [453, 250, 147, 250],
+          "reason": "incremental"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 147, 250],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutFrameSet FRAMESET",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutFrameSet FRAMESET",
+          "rect": [0, 0, 600, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [153, 0, 294, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [153, 0, 294, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [103, 0, 194, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [103, 0, 194, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [453, 0, 147, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [453, 0, 147, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [0, 0, 147, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [303, 0, 97, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [303, 0, 97, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [97, 0, 50, 250],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutFrameSet FRAMESET",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow HTML",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow BODY",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow HTML",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow BODY",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutFrameSet FRAMESET",
+          "rect": [0, 0, 400, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [103, 0, 194, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [103, 250, 194, 350],
+          "reason": "incremental"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [303, 0, 97, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [0, 0, 97, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [303, 250, 97, 350],
+          "reason": "incremental"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 97, 350],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutFrameSet FRAMESET",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutFrameSet FRAMESET",
+          "rect": [0, 0, 800, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [203, 0, 394, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [203, 0, 394, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [603, 0, 197, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [603, 0, 197, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [0, 0, 197, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [103, 0, 194, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [103, 0, 194, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [97, 0, 100, 600],
+          "reason": "incremental"
+        },
+        {
+          "object": "LayoutFrame FRAME",
+          "rect": [303, 0, 97, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutView #document",
+          "rect": [303, 0, 97, 600],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutFrameSet FRAMESET",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutFrame FRAME",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow HTML",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow BODY",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow HTML",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutBlockFlow BODY",
+      "reason": "geometry"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-media-query-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-media-query-expected.txt
new file mode 100644
index 0000000..eab3dc6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-media-query-expected.txt
@@ -0,0 +1,177 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-no-layout-change1-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-no-layout-change1-expected.txt
new file mode 100644
index 0000000..23e9f8d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-no-layout-change1-expected.txt
@@ -0,0 +1,133 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-no-layout-change2-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-no-layout-change2-expected.txt
new file mode 100644
index 0000000..1f2c5ba
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-no-layout-change2-expected.txt
@@ -0,0 +1,133 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2008],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2008],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2008],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [2008, 2008],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-percent-html-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-percent-html-expected.txt
new file mode 100644
index 0000000..94e61e08
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-percent-html-expected.txt
@@ -0,0 +1,213 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [0, 62, 300, 63],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [200, 0, 100, 63],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [0, 62, 200, 88],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [200, 0, 200, 150],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-percent-width-height-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-percent-width-height-expected.txt
new file mode 100644
index 0000000..3e23106
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-percent-width-height-expected.txt
@@ -0,0 +1,213 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 125, 300, 125],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [200, 0, 100, 125],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 125, 200, 175],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [200, 0, 200, 300],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-positioned-bottom-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-positioned-bottom-expected.txt
new file mode 100644
index 0000000..ccdff59
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-positioned-bottom-expected.txt
@@ -0,0 +1,205 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 460, 20, 20],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 210, 20, 20],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 560, 20, 20],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 210, 20, 20],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-positioned-percent-top-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-positioned-percent-top-expected.txt
new file mode 100644
index 0000000..a50abaa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-positioned-percent-top-expected.txt
@@ -0,0 +1,205 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 250, 20, 20],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 125, 20, 20],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 300, 20, 20],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV",
+          "rect": [0, 125, 20, 20],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-viewport-percent-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-viewport-percent-expected.txt
new file mode 100644
index 0000000..58eb3a2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/window-resize/window-resize-viewport-percent-expected.txt
@@ -0,0 +1,205 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [600, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [600, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 600, 250],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [0, 25, 50, 25],
+          "reason": "incremental"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [25, 0, 25, 50],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 200, 250],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 250],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 250],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [400, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 250, 400, 350],
+          "reason": "background on scrolling contents layer"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [0, 25, 60, 35],
+          "reason": "incremental"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [25, 0, 35, 60],
+          "reason": "incremental"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    },
+    {
+      "object": "LayoutBlockFlow DIV",
+      "reason": "incremental"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [400, 0, 400, 600],
+          "reason": "incremental"
+        }
+      ]
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "background on scrolling contents layer"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "Scrolling Contents Layer",
+      "reason": "background on scrolling contents layer"
+    },
+    {
+      "object": "LayoutView #document",
+      "reason": "incremental"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac-mac10.11/paint/invalidation/background/change-text-content-and-background-color-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac-mac10.11/paint/invalidation/background/change-text-content-and-background-color-expected.txt
new file mode 100644
index 0000000..45eb1d1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac-mac10.11/paint/invalidation/background/change-text-content-and-background-color-expected.txt
@@ -0,0 +1,60 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutTextControl (positioned) INPUT id='input'",
+          "rect": [8, 8, 244, 67],
+          "reason": "style change"
+        },
+        {
+          "object": "LayoutBlockFlow DIV",
+          "rect": [30, 30, 200, 23],
+          "reason": "geometry"
+        },
+        {
+          "object": "LayoutText #text",
+          "rect": [30, 30, 44, 23],
+          "reason": "full"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutTextControl (positioned) INPUT id='input'",
+      "reason": "style change"
+    },
+    {
+      "object": "LayoutBlockFlow DIV",
+      "reason": "geometry"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutText #text",
+      "reason": "full"
+    },
+    {
+      "object": "InlineTextBox 'NEW'",
+      "reason": "full"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/selection/japanese-rl-selection-clear-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/selection/japanese-rl-selection-clear-expected.txt
index e792836..08ce6de 100644
--- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/selection/japanese-rl-selection-clear-expected.txt
+++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/selection/japanese-rl-selection-clear-expected.txt
@@ -18,7 +18,7 @@
       "paintInvalidations": [
         {
           "object": "LayoutText #text",
-          "rect": [230, 123, 542, 394],
+          "rect": [230, 123, 499, 394],
           "reason": "geometry"
         }
       ]
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-expected.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-expected.html
deleted file mode 100644
index 1547d19..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-expected.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<style>
-  html {
-    background-color: grey;
-  }
-</style>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-expected.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-expected.html
deleted file mode 100644
index 1547d19..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-expected.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<style>
-  html {
-    background-color: grey;
-  }
-</style>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-scroller-expected.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-scroller-expected.html
deleted file mode 100644
index 1547d19..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-scroller-expected.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<style>
-  html {
-    background-color: grey;
-  }
-</style>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-scroller.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-scroller.html
deleted file mode 100644
index 5bbf20e..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe-scroller.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  // Set the browser control height but set them to be hidden so that the
-  // viewport is taller than the root scroller. This test passes if the root
-  // scroller's background is painted into the enitre height of the viewport.
-  // That is, the full viewport should be grey.
-  // NOTE: It is important that this test be run with the Android viewport
-  // flags turned on.
-  if (window.internals) {
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-
-  addEventListener("load", function() {
-    var iframe = document.getElementById("iframe");
-    document.rootScroller = iframe;
-    iframe.contentDocument.rootScroller = iframe.contentDocument.getElementById("scroller");
-  });
-</script>
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html, body {
-    height: 100%;
-    width: 100%;
-    margin: 0;
-  }
-  body {
-    background-color: red;
-  }
-  #iframe {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    border: 0;
-  }
-</style>
-
-<iframe id="iframe" srcdoc="
-    <style>
-      html,body {
-        height: 100vh;
-        width: 100%;
-        margin:0;
-        background-color: maroon;
-      }
-      #scroller {
-        position: absolute;
-        width: 100%;
-        height: 100%;
-        background-color: grey;
-        overflow: auto;
-      }
-    </style>
-    <div id='scroller'></div>"></iframe>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe.html
deleted file mode 100644
index 60fb3c9..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-iframe.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  // Set the browser control height but set them to be hidden so that the
-  // viewport is taller than the root scroller. This test passes if the root
-  // scroller's background is painted into the enitre height of the viewport.
-  // That is, the full viewport should be grey.
-  // NOTE: It is important that this test be run with the Android viewport
-  // flags turned on.
-  if (window.internals) {
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-
-  addEventListener("load", function() {
-    document.rootScroller = document.getElementById("scroller");
-  });
-</script>
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html, body {
-    height: 100%;
-    width: 100%;
-    margin: 0;
-  }
-  body {
-    background-color: red;
-  }
-  #scroller {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    border: 0;
-  }
-</style>
-
-<iframe id="scroller" srcdoc="<style>html {background-color: grey;}</style>"></iframe>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-modified-expected.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-modified-expected.html
deleted file mode 100644
index 1547d19..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-modified-expected.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<style>
-  html {
-    background-color: grey;
-  }
-</style>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-modified.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-modified.html
deleted file mode 100644
index f0e929e..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background-modified.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  // Set the browser control height but set them to be hidden so that the
-  // viewport is taller than the root scroller. This test passes if the root
-  // scroller's background is painted into the enitre height of the viewport.
-  // That is, the full viewport should be grey.
-  // NOTE: It is important that this test be run with the Android viewport
-  // flags turned on.
-  if (window.internals) {
-    testRunner.waitUntilDone();
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-
-  addEventListener("load", function() {
-    document.rootScroller = document.getElementById("scroller");
-    requestAnimationFrame(function() {
-      document.rootScroller.style.backgroundColor = "grey";
-      requestAnimationFrame(function() {
-        testRunner.notifyDone();
-      });
-    });
-  });
-</script>
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html, body {
-    height: 100%;
-    width: 100%;
-    margin: 0;
-  }
-  body {
-    background-color: red;
-  }
-  #scroller {
-    width: 100%;
-    height: 100%;
-    overflow: auto;
-  }
-  #content {
-    width: 10px;
-    height: 20px;
-  }
-</style>
-
-<div id="scroller">
-  <div id="content"></div>
-</div>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background.html
deleted file mode 100644
index 4d2d336..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-background.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  // Set the browser control height but set them to be hidden so that the
-  // viewport is taller than the root scroller. This test passes if the root
-  // scroller's background is painted into the enitre height of the viewport.
-  // That is, the full viewport should be grey.
-  // NOTE: It is important that this test be run with the Android viewport
-  // flags turned on.
-  if (window.internals) {
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-
-  addEventListener("load", function() {
-    document.rootScroller = document.getElementById("scroller");
-  });
-</script>
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html, body {
-    height: 100%;
-    width: 100%;
-    margin: 0;
-  }
-  body {
-    background-color: red;
-  }
-  #scroller {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    overflow: auto;
-    background-color: grey;
-  }
-  #content {
-    width: 10px;
-    height: 20px;
-  }
-</style>
-
-<div id="scroller">
-  <div id="content"></div>
-</div>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe-expected.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe-expected.html
deleted file mode 100644
index 14a1bcf..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe-expected.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  if (window.internals) {
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-</script>
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html {
-    height: 100%;
-    background: linear-gradient(green, blue);
-  }
-</style>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe-scroller.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe-scroller.html
deleted file mode 100644
index aabc873..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe-scroller.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  // Set the browser control height but set them to be hidden so that the
-  // viewport is taller than the root scroller. This test passes if the root
-  // scroller's background is painted into the enitre height of the viewport.
-  // That is, the full viewport should be a green-blue gradient that starts
-  // repeating 100px from the bottom.
-  // NOTE: It is important that this test be run with the Android viewport
-  // flags turned on.
-  if (window.internals) {
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-
-  addEventListener("load", function() {
-    var iframe = document.getElementById("iframe");
-    document.rootScroller = iframe;
-    iframe.contentDocument.rootScroller = iframe.contentDocument.getElementById("scroller");
-  });
-</script>
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html, body {
-    height: 100%;
-    width: 100%;
-    margin: 0;
-  }
-  body {
-    background-color: red;
-  }
-  #iframe {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    border: 0;
-  }
-</style>
-
-<iframe id="iframe" srcdoc="
-    <style>
-      html,body {
-        height: 100vh;
-        width: 100%;
-        margin:0;
-        background-color: maroon;
-      }
-      #scroller {
-        position: absolute;
-        width: 100%;
-        height: 100%;
-        background: linear-gradient(green, blue);
-        overflow: auto;
-      }
-    </style>
-    <div id='scroller'></div>"></iframe>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe.html
deleted file mode 100644
index c6dff42..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background-iframe.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  // Set the browser control height but set them to be hidden so that the
-  // viewport is taller than the root scroller. This test passes if the root
-  // scroller's background is painted into the enitre height of the viewport.
-  // That is, the full viewport should be a green-blue gradient that starts
-  // repeating 100px from the bottom.
-  // NOTE: It is important that this test be run with the Android viewport
-  // flags turned on.
-  if (window.internals) {
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-
-  addEventListener("load", function() {
-    document.rootScroller = document.getElementById("scroller");
-  });
-</script>
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html, body {
-    height: 100%;
-    width: 100%;
-    margin: 0;
-  }
-  body {
-    background-color: red;
-  }
-  #scroller {
-    width: 100%;
-    height: 100%;
-    border: 0;
-  }
-</style>
-
-<iframe id="scroller" srcdoc="<style>html {height: 100%; background: linear-gradient(green, blue);}</style>"></iframe>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background.html
deleted file mode 100644
index c8bbcd62..0000000
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/browser-controls-gradient-background.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE html>
-<meta name="viewport" content="width=device-width, user-scalable=no" />
-<script>
-  // Set the browser control height but set them to be hidden so that the
-  // viewport is taller than the root scroller. This test passes if the root
-  // scroller's background is painted into the enitre height of the viewport.
-  // That is, the full viewport should have a red-orange gradient that repeats
-  // near the bottom.
-  // NOTE: It is important that this test be run with the Android viewport
-  // flags turned on.
-  if (window.internals) {
-    window.internals.setBrowserControlsState(100, 0, false);
-  }
-
-  addEventListener("load", function() {
-    document.rootScroller = document.getElementById("scroller");
-  });
-</script>
-<style>
-
-<style>
-  ::-webkit-scrollbar {
-    width: 0px;
-    height: 0px;
-  }
-  html, body {
-    height: 100%;
-    width: 100%;
-    margin: 0;
-  }
-  body {
-    background-color: red;
-  }
-  #scroller {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    overflow: auto;
-    background: linear-gradient(green, blue);
-  }
-  #content {
-    width: 10px;
-    height: 20px;
-  }
-</style>
-
-<div id="scroller">
-  <div id="content"></div>
-</div>
diff --git a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/nested-rootscroller-browser-controls-bounds-shown-expected.html b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/nested-rootscroller-browser-controls-bounds-shown-expected.html
index 61139bd..a15d286 100644
--- a/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/nested-rootscroller-browser-controls-bounds-shown-expected.html
+++ b/third_party/WebKit/LayoutTests/virtual/android/fast/rootscroller/nested-rootscroller-browser-controls-bounds-shown-expected.html
@@ -7,7 +7,7 @@
     margin: 0;
   }
   body {
-    background-color: white;
+    background-color: red;
   }
   #bottom {
     position: fixed;
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index ccdd620..e60bc29 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -1959,12 +1959,6 @@
       !background_style->HasBackground())
     background_style = body_style;
 
-  // If the page set a rootScroller, we should use its background for painting
-  // the document background.
-  Node& root_scroller = GetRootScrollerController().EffectiveRootScroller();
-  if (this != &root_scroller)
-    background_style = ToElement(root_scroller).EnsureComputedStyle();
-
   Color background_color =
       background_style->VisitedDependentColor(CSSPropertyBackgroundColor);
   FillLayer background_layers = background_style->BackgroundLayers();
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
index a1119fa..82ae0c4 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
@@ -1322,12 +1322,13 @@
   }  // Reset m_layoutSchedulingEnabled to its previous value.
   CheckDoesNotNeedLayout();
 
-  Lifecycle().AdvanceTo(DocumentLifecycle::kLayoutClean);
+  DocumentLifecycle::Scope lifecycle_scope(Lifecycle(),
+                                           DocumentLifecycle::kLayoutClean);
 
   frame_timing_requests_dirty_ = true;
 
-  // FIXME: Could find the common ancestor layer of all dirty subtrees and
-  // mark from there. crbug.com/462719
+  // FIXME: Could find the common ancestor layer of all dirty subtrees and mark
+  // from there. crbug.com/462719
   GetLayoutViewItem().EnclosingLayer()->UpdateLayerPositionsAfterLayout();
 
   TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
diff --git a/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.cpp b/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.cpp
index f2efec94..a76c66c 100644
--- a/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.cpp
+++ b/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.cpp
@@ -85,7 +85,6 @@
 }
 
 void RootScrollerController::DidUpdateLayout() {
-  DCHECK(document_->Lifecycle().GetState() == DocumentLifecycle::kLayoutClean);
   RecomputeEffectiveRootScroller();
 }
 
@@ -134,14 +133,6 @@
   ApplyRootScrollerProperties(*old_effective_root_scroller);
   ApplyRootScrollerProperties(*effective_root_scroller_);
 
-  // Document (i.e. LayoutView) gets its background style from the rootScroller
-  // so we need to recalc its style. Ensure that we get back to a LayoutClean
-  // state after.
-  document_->SetNeedsStyleRecalc(kLocalStyleChange,
-                                 StyleChangeReasonForTracing::Create(
-                                     StyleChangeReason::kStyleInvalidator));
-  document_->UpdateStyleAndLayout();
-
   if (Page* page = document_->GetPage())
     page->GlobalRootScrollerController().DidChangeRootScroller();
 }
diff --git a/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.h b/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.h
index f998581..1989f67a 100644
--- a/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.h
+++ b/third_party/WebKit/Source/core/page/scrolling/RootScrollerController.h
@@ -66,7 +66,7 @@
 
   // This class needs to be informed of changes in layout so that it can
   // determine if the current root scroller is still valid or if it must be
-  // replaced by the default root scroller. Must be called from LayoutClean.
+  // replaced by the default root scroller.
   void DidUpdateLayout();
 
   // This class needs to be informed when the FrameView of its Document changes
diff --git a/third_party/WebKit/Source/core/page/scrolling/RootScrollerTest.cpp b/third_party/WebKit/Source/core/page/scrolling/RootScrollerTest.cpp
index 82db51f..564350a9 100644
--- a/third_party/WebKit/Source/core/page/scrolling/RootScrollerTest.cpp
+++ b/third_party/WebKit/Source/core/page/scrolling/RootScrollerTest.cpp
@@ -1170,49 +1170,6 @@
             &MainFrameView()->GetRootFrameViewport()->LayoutViewport());
 }
 
-// Ensure that background style is propagated to the layout view.
-TEST_P(RootScrollerTest, PropagateBackgroundToLayoutView) {
-  Initialize();
-
-  WebURL base_url = URLTestHelpers::ToKURL("http://www.test.com/");
-  FrameTestHelpers::LoadHTMLString(GetWebView()->MainFrameImpl(),
-                                   "<!DOCTYPE html>"
-                                   "<style>"
-                                   "  body, html {"
-                                   "    width: 100%;"
-                                   "    height: 100%;"
-                                   "    margin: 0px;"
-                                   "    background-color: #ff0000;"
-                                   "  }"
-                                   "  #container {"
-                                   "    width: 100%;"
-                                   "    height: 100%;"
-                                   "    overflow: auto;"
-                                   "    background-color: #0000ff;"
-                                   "  }"
-                                   "</style>"
-                                   "<div id='container'>"
-                                   "  <div style='height:1000px'>test</div>"
-                                   "</div>",
-                                   base_url);
-  MainFrameView()->UpdateAllLifecyclePhases();
-
-  Document* document = MainFrame()->GetDocument();
-  ASSERT_EQ(Color(255, 0, 0),
-            document->GetLayoutView()->Style()->VisitedDependentColor(
-                CSSPropertyBackgroundColor));
-
-  Element* container = MainFrame()->GetDocument()->getElementById("container");
-  document->setRootScroller(container, ASSERT_NO_EXCEPTION);
-
-  document->setRootScroller(container);
-  MainFrameView()->UpdateAllLifecyclePhases();
-
-  EXPECT_EQ(Color(0, 0, 255),
-            document->GetLayoutView()->Style()->VisitedDependentColor(
-                CSSPropertyBackgroundColor));
-}
-
 class RootScrollerHitTest : public RootScrollerTest {
  public:
   void CheckHitTestAtBottomOfScreen() {
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
index 1d963dd..aa85912 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
@@ -1408,6 +1408,13 @@
 bool DrawingBuffer::ShouldUseChromiumImage() {
   return RuntimeEnabledFeatures::WebGLImageChromiumEnabled() &&
          chromium_image_usage_ == kAllowChromiumImage &&
+#if defined(OS_MACOSX)
+         // Core Animation assumes that the incoming alpha channel is
+         // premultiplied into the color channels. Fall back to
+         // regular compositing if the user wants the alpha channel
+         // interpreted as separate.
+         premultiplied_alpha_ &&
+#endif
          Platform::Current()->GetGpuMemoryBufferManager();
 }
 
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
index 8eab17d..4def4f1b 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
@@ -431,7 +431,7 @@
             client,
             false /* discardFramebufferSupported */,
             true /* wantAlphaChannel */,
-            false /* premultipliedAlpha */,
+            true /* premultipliedAlpha */,
             preserve,
             kWebGL1,
             false /* wantDepth */,
diff --git a/third_party/instrumented_libraries/binaries/msan-chained-origins-trusty.tgz.sha1 b/third_party/instrumented_libraries/binaries/msan-chained-origins-trusty.tgz.sha1
index 22db5e09..d430b7a4 100644
--- a/third_party/instrumented_libraries/binaries/msan-chained-origins-trusty.tgz.sha1
+++ b/third_party/instrumented_libraries/binaries/msan-chained-origins-trusty.tgz.sha1
@@ -1 +1 @@
-0dcb36e70349be00195414cee70ff0d780e3b461
\ No newline at end of file
+ef790d476bc67356798ee0c2948a2eedf05dbfa1
\ No newline at end of file
diff --git a/third_party/instrumented_libraries/binaries/msan-no-origins-trusty.tgz.sha1 b/third_party/instrumented_libraries/binaries/msan-no-origins-trusty.tgz.sha1
index baca72e..06356e5 100644
--- a/third_party/instrumented_libraries/binaries/msan-no-origins-trusty.tgz.sha1
+++ b/third_party/instrumented_libraries/binaries/msan-no-origins-trusty.tgz.sha1
@@ -1 +1 @@
-66e243aeaafa32567781733747d569c9e1fe461d
\ No newline at end of file
+1ccd6518a2b15e53ba8d84d33e705639da9bc753
\ No newline at end of file
diff --git a/third_party/tcmalloc/README.chromium b/third_party/tcmalloc/README.chromium
index 1ff6d67..486b6d3 100644
--- a/third_party/tcmalloc/README.chromium
+++ b/third_party/tcmalloc/README.chromium
@@ -110,3 +110,4 @@
 - Changed kint64min to not depend on undefined behavior.
 - Fix potential missing nul character in symbol names produced by addr2line-pdb.
 - Remove superfluous size_t value >= 0 check.
+- Make kFooType in tcmalloc.cc truly const.
diff --git a/third_party/tcmalloc/chromium/src/tcmalloc.cc b/third_party/tcmalloc/chromium/src/tcmalloc.cc
index 4ea7cdc..bbd47e5 100644
--- a/third_party/tcmalloc/chromium/src/tcmalloc.cc
+++ b/third_party/tcmalloc/chromium/src/tcmalloc.cc
@@ -818,13 +818,13 @@
   }
 
   virtual void GetFreeListSizes(vector<MallocExtension::FreeListInfo>* v) {
-    static const char* kCentralCacheType = "tcmalloc.central";
-    static const char* kTransferCacheType = "tcmalloc.transfer";
-    static const char* kThreadCacheType = "tcmalloc.thread";
-    static const char* kPageHeapType = "tcmalloc.page";
-    static const char* kPageHeapUnmappedType = "tcmalloc.page_unmapped";
-    static const char* kLargeSpanType = "tcmalloc.large";
-    static const char* kLargeUnmappedSpanType = "tcmalloc.large_unmapped";
+    static const char kCentralCacheType[] = "tcmalloc.central";
+    static const char kTransferCacheType[] = "tcmalloc.transfer";
+    static const char kThreadCacheType[] = "tcmalloc.thread";
+    static const char kPageHeapType[] = "tcmalloc.page";
+    static const char kPageHeapUnmappedType[] = "tcmalloc.page_unmapped";
+    static const char kLargeSpanType[] = "tcmalloc.large";
+    static const char kLargeUnmappedSpanType[] = "tcmalloc.large_unmapped";
 
     v->clear();
 
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index bbb713d..bca4398 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -27,7 +27,7 @@
 # Do NOT CHANGE this if you don't know what you're doing -- see
 # https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang.md
 # Reverting problematic clang rolls is safe, though.
-CLANG_REVISION = '318369'
+CLANG_REVISION = '317263'
 
 use_head_revision = bool(os.environ.get('LLVM_FORCE_HEAD_REVISION', '0')
                          in ('1', 'YES'))
@@ -35,7 +35,7 @@
   CLANG_REVISION = 'HEAD'
 
 # This is incremented when pushing a new build of Clang at the same revision.
-CLANG_SUB_REVISION=1
+CLANG_SUB_REVISION=4
 
 PACKAGE_VERSION = "%s-%s" % (CLANG_REVISION, CLANG_SUB_REVISION)
 
diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids
index 2bf2d060..e95450a 100644
--- a/tools/gritsettings/resource_ids
+++ b/tools/gritsettings/resource_ids
@@ -103,28 +103,28 @@
     "structures": [12010],
   },
   "chrome/browser/resources/quota_internals_resources.grd": {
-    "includes": [12050],
+    "includes": [12110],
   },
   "chrome/browser/resources/settings/settings_resources_vulcanized.grd": {
-    "includes": [12070],
+    "includes": [12130],
   },
   "chrome/browser/resources/settings/settings_resources.grd": {
-    "structures": [12080],
+    "structures": [12140],
   },
   "chrome/browser/resources/sync_file_system_internals_resources.grd": {
-    "includes": [12580],
+    "includes": [12640],
   },
   "chrome/browser/resources/task_scheduler_internals/resources.grd": {
-    "includes": [12610],
+    "includes": [12670],
   },
   "chrome/browser/resources/translate_internals_resources.grd": {
-    "includes": [12620],
+    "includes": [12680],
   },
   "chrome/browser/resources/vr_shell_resources.grd": {
-    "includes": [12630],
+    "includes": [12690],
   },
   "chrome/browser/resources/webapks_ui_resources.grd": {
-    "includes": [12640],
+    "includes": [12700],
   },
   # END chrome/browser section.
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index c7f21ca..b644fa72 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -24249,7 +24249,6 @@
   <int value="-2062373123" label="WebPaymentsModifiers:enabled"/>
   <int value="-2059771509" label="NTPTilesLowerResolutionFavicons:disabled"/>
   <int value="-2058656447" label="ContextualSearchUrlActions:enabled"/>
-  <int value="-2054501930" label="NativeSamba:enabled"/>
   <int value="-2053860791" label="XGEOVisibleNetworks:enabled"/>
   <int value="-2048732429" label="enable-alternative-services"/>
   <int value="-2048679945" label="NTPOfflinePageDownloadSuggestions:disabled"/>
@@ -24297,7 +24296,6 @@
   <int value="-1960567385" label="KeepPrefetchedContentSuggestions:enabled"/>
   <int value="-1957328398" label="MacSystemShareMenu:disabled"/>
   <int value="-1956349722" label="disable-smooth-scrolling"/>
-  <int value="-1949100957" label="NativeSamba:disabled"/>
   <int value="-1948540128" label="disable-webrtc-hw-encoding (deprecated)"/>
   <int value="-1946595906" label="enable-push-api-background-mode"/>
   <int value="-1946522787" label="VrCustomTabBrowsing:disabled"/>
@@ -24555,6 +24553,7 @@
   <int value="-1302904242" label="enable-navigation-tracing"/>
   <int value="-1294050129" label="ContentFullscreen:disabled"/>
   <int value="-1289678848" label="SystemDownloadManager:enabled"/>
+  <int value="-1288130734" label="OpenVR:disabled"/>
   <int value="-1285021473" label="save-page-as-mhtml"/>
   <int value="-1284637134" label="pull-to-refresh"/>
   <int value="-1276912933" label="enable-quick-unlock-pin"/>
@@ -24565,6 +24564,7 @@
   <int value="-1262152606" label="disable-lock-screen-apps"/>
   <int value="-1261263046"
       label="RemoveUsageOfDeprecatedGaiaSigninEndpoint:disabled"/>
+  <int value="-1259901957" label="VrBrowserKeyboard:disabled"/>
   <int value="-1254070521" label="enable-slimming-paint-invalidation"/>
   <int value="-1251411236" label="disable-new-md-input-view"/>
   <int value="-1248478422" label="enable-zip-archiver-packer"/>
@@ -24708,6 +24708,7 @@
   <int value="-876148583" label="ArcBootCompletedBroadcast:disabled"/>
   <int value="-867087281" label="enable-virtual-keyboard"/>
   <int value="-866993841" label="OfflinePagesCTV2:disabled"/>
+  <int value="-865390600" label="NativeSmb:enabled"/>
   <int value="-864266073" label="cros-regions-mode"/>
   <int value="-864234985" label="UseDdljsonApi:enabled"/>
   <int value="-864205629" label="enable-offline-load-stale-cache"/>
@@ -24726,6 +24727,7 @@
   <int value="-835672415" label="PointerEventV1SpecCapturing:disabled"/>
   <int value="-834661509" label="ModalPermissionPrompts:disabled"/>
   <int value="-832561975" label="enable-picture-in-picture"/>
+  <int value="-828070439" label="NativeSmb:disabled"/>
   <int value="-825942229" label="tab-management-experiment-type-elderberry"/>
   <int value="-823165021" label="MaterialDesignUserMenu:enabled"/>
   <int value="-820041355" label="enable-transition-compositing"/>
@@ -25588,6 +25590,7 @@
   <int value="1612871297" label="WebPayments:disabled"/>
   <int value="1612974229" label="allow-insecure-localhost"/>
   <int value="1617187093" label="enable-improved-a2hs"/>
+  <int value="1621298798" label="VrBrowserKeyboard:enabled"/>
   <int value="1622131033" label="ozone-test-single-overlay-support"/>
   <int value="1626824478" label="ExperimentalAppBanners:disabled"/>
   <int value="1630988998" label="VrBrowsingExperimentalRendering:disabled"/>
@@ -25703,6 +25706,7 @@
   <int value="1928407249" label="NewPhotoPicker:enabled"/>
   <int value="1930901873" label="disable-sync-app-list"/>
   <int value="1931309368" label="fill-on-account-select:disabled"/>
+  <int value="1932732886" label="OpenVR:enabled"/>
   <int value="1936810062" label="WebVrVsyncAlign:enabled"/>
   <int value="1939413645" label="enable-invalid-cert-collection"/>
   <int value="1942911276" label="enable-grouped-history"/>
@@ -39339,6 +39343,15 @@
   <int value="1" label="The user clicked the promo to sign in"/>
 </enum>
 
+<enum name="SyncRequestType">
+  <summary>Maps to values in sync_pb::ClientToServerMessage_Contents.</summary>
+  <int value="1" label="COMMIT"/>
+  <int value="2" label="GET_UPDATES"/>
+  <int value="3" label="AUTHENTICATE"/>
+  <int value="4" label="DEPRECATED_4"/>
+  <int value="5" label="CLEAR_SERVER_DATA"/>
+</enum>
+
 <enum name="SyncSimpleConflictResolutions">
   <summary>
     Sync simple conflict resolutions. The codes are listed in
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index d1b352f..bb06ebee 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -35733,6 +35733,15 @@
   </summary>
 </histogram>
 
+<histogram name="MediaRouter.WiredDisplay.AvailableDevicesCount">
+  <owner>takumif@chromium.org</owner>
+  <summary>
+    The number of Media Sinks available for Casting a Presentation API URL to
+    local screens. Recorded at most once an hour, when the Wired Display Media
+    Route Provider reports an update on the sink count.
+  </summary>
+</histogram>
+
 <histogram name="MemCache.WriteResult" enum="MemCacheWriteResult">
   <owner>jkarlin@chromium.org</owner>
   <summary>The outcome of Entry::WriteData in the memory cache.</summary>
@@ -84869,6 +84878,13 @@
   <summary>Tracks the size of the local sync backend database file.</summary>
 </histogram>
 
+<histogram name="Sync.Local.RequestTypeOnError" enum="SyncRequestType">
+  <owner>skym@chromium.org</owner>
+  <summary>
+    Tracks the types of requests that caused errors inside of the local server.
+  </summary>
+</histogram>
+
 <histogram name="Sync.Local.RoamingProfileUnavailable" enum="BooleanError">
   <owner>pastarmovj@chromium.org</owner>
   <summary>
diff --git a/ui/base/ui_base_switches.cc b/ui/base/ui_base_switches.cc
index 54314e9e..ddecf81 100644
--- a/ui/base/ui_base_switches.cc
+++ b/ui/base/ui_base_switches.cc
@@ -129,8 +129,11 @@
 
 #if BUILDFLAG(ENABLE_MUS)
 // Used to enable the mus service (aka the UI service). This makes mus run in
-// process.
+// process. It is also used to notify the clients that the UI service is being
+// used. If the value of this flag is set to kMusHostVizValue, then that means
+// the UI service is hosting the viz service.
 const char kMus[] = "mus";
+const char kMusHostVizValue[] = "viz";
 #endif
 
 }  // namespace switches
diff --git a/ui/base/ui_base_switches.h b/ui/base/ui_base_switches.h
index d3d90c18..76bb09a 100644
--- a/ui/base/ui_base_switches.h
+++ b/ui/base/ui_base_switches.h
@@ -59,6 +59,7 @@
 
 #if BUILDFLAG(ENABLE_MUS)
 UI_BASE_EXPORT extern const char kMus[];
+UI_BASE_EXPORT extern const char kMusHostVizValue[];
 #endif
 
 }  // namespace switches