diff --git a/.gitignore b/.gitignore
index 719b6b94..12348f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,6 +68,7 @@
 /c
 /cdm
 /ceee/internal/
+/chrome/android/profiles/chrome-profile-*.prof
 /chrome/angle_unittests_run.xml
 /chrome/build/chrome.x64.orderfile
 /chrome/build/chrome.x86.orderfile
diff --git a/DEPS b/DEPS
index 458b731..13dcd7d6 100644
--- a/DEPS
+++ b/DEPS
@@ -78,7 +78,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'aebad197dcaf2f02d5c71e9cf9b3f383a36df538',
+  'skia_revision': 'da6d0720300a29a4deb5dd4c433a92a3ec41286e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -90,7 +90,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': 'd8724a947bd8663a796a3dc70057a17fc91ae516',
+  'angle_revision': '8b5e8fdb3c353d46c81243b31889856181ff66ae',
   # 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.
@@ -102,7 +102,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '05dcbc931eacb72f1a11835ae282fc8434b7a434',
+  'pdfium_revision': 'cbd4410908e2a4898fdd5e0d6d17591fc2c71f54',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -134,7 +134,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '7365f02611830723d60963f0619d5bab45060849',
+  'catapult_revision': '19e11600d3a9410651e6c12e801a03eeca7274cc',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -142,7 +142,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-node-modules
   # and whatever else without interference from each other.
-  'devtools_node_modules_revision': '2597f8672ac64e29173d3b9ec14a6fdfb73e2582',
+  'devtools_node_modules_revision': '5f7cd2497d7a643125c3b6eb910d99ba28be6899',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -640,7 +640,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd458ada06171a85af00367251a4ed55db7fe2018',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '731082ce7ea6e86a0d0d738460d7366f7df7c0c5', # commit position 20628
+    Var('webrtc_git') + '/src.git' + '@' + '35d2b7e9de9ddb40cac35a21d50977dc5ca231d8', # commit position 20628
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 7611210..0f57442 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -57,6 +57,7 @@
     "java/src/org/chromium/android_webview/AwResource.java",
     "java/src/org/chromium/android_webview/AwSettings.java",
     "java/src/org/chromium/android_webview/AwTokenBindingManager.java",
+    "java/src/org/chromium/android_webview/AwTracingController.java",
     "java/src/org/chromium/android_webview/AwWebContentsDelegate.java",
     "java/src/org/chromium/android_webview/AwWebResourceResponse.java",
     "java/src/org/chromium/android_webview/InputStreamUtil.java",
@@ -616,6 +617,8 @@
     "browser/surfaces_instance.cc",
     "browser/surfaces_instance.h",
     "browser/token_binding_manager_bridge.cc",
+    "browser/tracing/aw_tracing_controller.cc",
+    "browser/tracing/aw_tracing_controller.h",
     "browser/tracing/aw_tracing_delegate.cc",
     "browser/tracing/aw_tracing_delegate.h",
     "common/android_webview_message_generator.cc",
@@ -808,6 +811,7 @@
     "java/src/org/chromium/android_webview/AwSettings.java",
     "java/src/org/chromium/android_webview/AwSwitches.java",
     "java/src/org/chromium/android_webview/AwTokenBindingManager.java",
+    "java/src/org/chromium/android_webview/AwTracingController.java",
     "java/src/org/chromium/android_webview/AwViewMethods.java",
     "java/src/org/chromium/android_webview/AwViewAndroidDelegate.java",
     "java/src/org/chromium/android_webview/AwWebContentsDelegate.java",
diff --git a/android_webview/browser/tracing/aw_tracing_controller.cc b/android_webview/browser/tracing/aw_tracing_controller.cc
new file mode 100644
index 0000000..8184684
--- /dev/null
+++ b/android_webview/browser/tracing/aw_tracing_controller.cc
@@ -0,0 +1,134 @@
+// 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 "android_webview/browser/tracing/aw_tracing_controller.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/task_scheduler/post_task.h"
+
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/tracing_controller.h"
+
+#include "jni/AwTracingController_jni.h"
+
+using base::android::JavaParamRef;
+
+namespace {
+
+base::android::ScopedJavaLocalRef<jbyteArray> StringToJavaBytes(
+    JNIEnv* env,
+    const std::string& str) {
+  return base::android::ToJavaByteArray(
+      env, reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+
+class AwTraceDataEndpoint
+    : public content::TracingController::TraceDataEndpoint {
+ public:
+  typedef base::Callback<void(std::unique_ptr<std::string>)>
+      ReceivedChunkCallback;
+  typedef base::Callback<void(std::unique_ptr<const base::DictionaryValue>)>
+      CompletedCallback;
+
+  static scoped_refptr<content::TracingController::TraceDataEndpoint> Create(
+      ReceivedChunkCallback received_chunk_callback,
+      CompletedCallback completed_callback) {
+    return new AwTraceDataEndpoint(received_chunk_callback, completed_callback);
+  }
+
+  void ReceiveTraceFinalContents(
+      std::unique_ptr<const base::DictionaryValue> metadata) override {
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::Bind(completed_callback_, base::Passed(std::move(metadata))));
+  }
+
+  void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::Bind(received_chunk_callback_, base::Passed(std::move(chunk))));
+  }
+
+  explicit AwTraceDataEndpoint(ReceivedChunkCallback received_chunk_callback,
+                               CompletedCallback completed_callback)
+      : received_chunk_callback_(received_chunk_callback),
+        completed_callback_(completed_callback) {}
+
+ private:
+  ~AwTraceDataEndpoint() override {}
+
+  ReceivedChunkCallback received_chunk_callback_;
+  CompletedCallback completed_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(AwTraceDataEndpoint);
+};
+
+}  // namespace
+
+namespace android_webview {
+
+static jlong JNI_AwTracingController_Init(JNIEnv* env,
+                                          const JavaParamRef<jobject>& obj) {
+  AwTracingController* controller = new AwTracingController(env, obj);
+  return reinterpret_cast<intptr_t>(controller);
+}
+
+AwTracingController::AwTracingController(JNIEnv* env, jobject obj)
+    : weak_java_object_(env, obj), weak_factory_(this) {}
+
+AwTracingController::~AwTracingController() {}
+
+bool AwTracingController::Start(JNIEnv* env,
+                                const JavaParamRef<jobject>& obj,
+                                const JavaParamRef<jstring>& jcategories,
+                                jint jmode) {
+  std::string categories =
+      base::android::ConvertJavaStringToUTF8(env, jcategories);
+  base::trace_event::TraceConfig trace_config(
+      categories, static_cast<base::trace_event::TraceRecordMode>(jmode));
+  return content::TracingController::GetInstance()->StartTracing(
+      trace_config, content::TracingController::StartTracingDoneCallback());
+}
+
+bool AwTracingController::StopAndFlush(JNIEnv* env,
+                                       const JavaParamRef<jobject>& obj) {
+  return content::TracingController::GetInstance()->StopTracing(
+      AwTraceDataEndpoint::Create(
+          base::Bind(&AwTracingController::OnTraceDataReceived,
+                     weak_factory_.GetWeakPtr()),
+          base::Bind(&AwTracingController::OnTraceDataComplete,
+                     weak_factory_.GetWeakPtr())));
+}
+
+void AwTracingController::OnTraceDataComplete(
+    std::unique_ptr<const base::DictionaryValue> metadata) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  base::android::ScopedJavaLocalRef<jobject> obj = weak_java_object_.get(env);
+  if (obj.obj()) {
+    Java_AwTracingController_onTraceDataComplete(env, obj);
+  }
+}
+
+void AwTracingController::OnTraceDataReceived(
+    std::unique_ptr<std::string> chunk) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  base::android::ScopedJavaLocalRef<jobject> obj = weak_java_object_.get(env);
+  if (obj.obj()) {
+    base::android::ScopedJavaLocalRef<jbyteArray> java_trace_data =
+        StringToJavaBytes(env, *chunk);
+    Java_AwTracingController_onTraceDataChunkReceived(env, obj,
+                                                      java_trace_data);
+  }
+}
+
+bool AwTracingController::IsTracing(JNIEnv* env,
+                                    const JavaParamRef<jobject>& obj) {
+  return content::TracingController::GetInstance()->IsTracing();
+}
+
+}  // namespace android_webview
diff --git a/android_webview/browser/tracing/aw_tracing_controller.h b/android_webview/browser/tracing/aw_tracing_controller.h
new file mode 100644
index 0000000..509860f
--- /dev/null
+++ b/android_webview/browser/tracing/aw_tracing_controller.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ANDROID_WEBVIEW_BROWSER_TRACING_AW_TRACING_CONTROLLER_H_
+#define ANDROID_WEBVIEW_BROWSER_TRACING_AW_TRACING_CONTROLLER_H_
+
+#include "base/android/jni_weak_ref.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+
+#include "content/public/browser/tracing_controller.h"
+
+namespace android_webview {
+
+class AwTracingController {
+ public:
+  AwTracingController(JNIEnv* env, jobject obj);
+
+  bool Start(JNIEnv* env,
+             const base::android::JavaParamRef<jobject>& obj,
+             const base::android::JavaParamRef<jstring>& categories,
+             jint mode);
+  bool StopAndFlush(JNIEnv* env,
+                    const base::android::JavaParamRef<jobject>& obj);
+  bool IsTracing(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
+
+ private:
+  ~AwTracingController();
+
+  void OnTraceDataReceived(std::unique_ptr<std::string> chunk);
+  void OnTraceDataComplete(
+      std::unique_ptr<const base::DictionaryValue> metadata);
+
+  JavaObjectWeakGlobalRef weak_java_object_;
+  base::WeakPtrFactory<AwTracingController> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(AwTracingController);
+};
+
+}  // namespace android_webview
+
+#endif  // ANDROID_WEBVIEW_BROWSER_TRACING_AW_TRACING_CONTROLLER_H_
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
index 3d4504a..b6370a3e7 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserContext.java
@@ -24,6 +24,7 @@
     private AwGeolocationPermissions mGeolocationPermissions;
     private AwFormDatabase mFormDatabase;
     private AwServiceWorkerController mServiceWorkerController;
+    private AwTracingController mTracingController;
     private Context mApplicationContext;
 
     public AwBrowserContext(SharedPreferences sharedPreferences, Context applicationContext) {
@@ -54,6 +55,13 @@
         return mServiceWorkerController;
     }
 
+    public AwTracingController getTracingController() {
+        if (mTracingController == null) {
+            mTracingController = new AwTracingController();
+        }
+        return mTracingController;
+    }
+
     /**
      * @see android.webkit.WebView#pauseTimers()
      */
diff --git a/android_webview/java/src/org/chromium/android_webview/AwTracingController.java b/android_webview/java/src/org/chromium/android_webview/AwTracingController.java
new file mode 100644
index 0000000..8b2d6707
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/AwTracingController.java
@@ -0,0 +1,78 @@
+// 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.android_webview;
+
+import android.support.annotation.Nullable;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Manages tracing functionality in WebView.
+ */
+@JNINamespace("android_webview")
+public class AwTracingController {
+    private static final String TAG = "cr.AwTracingController";
+
+    private AwTracingOutputStream mOutputStream;
+
+    // TODO: consider caching a mIsTracing value for efficiency.
+    // boolean mIsTracing;
+
+    /**
+     * Interface for the callbacks to return tracing data.
+     */
+    public interface AwTracingOutputStream {
+        public void write(byte[] chunk);
+        public void complete();
+    }
+
+    public AwTracingController() {
+        mNativeAwTracingController = nativeInit();
+    }
+
+    // Start tracing.
+    public boolean start(String categoryFilter, int mode) {
+        if (isTracing()) {
+            return false;
+        }
+
+        boolean result = nativeStart(mNativeAwTracingController, categoryFilter, mode);
+        return result;
+    }
+
+    // Stop tracing and flush tracing data.
+    public boolean stopAndFlush(@Nullable AwTracingOutputStream outputStream) {
+        if (!isTracing()) return false;
+        mOutputStream = outputStream;
+        nativeStopAndFlush(mNativeAwTracingController);
+        return true;
+    }
+
+    public boolean isTracing() {
+        return nativeIsTracing(mNativeAwTracingController);
+    }
+
+    @CalledByNative
+    private void onTraceDataChunkReceived(byte[] data) {
+        if (mOutputStream != null) {
+            mOutputStream.write(data);
+        }
+    }
+
+    @CalledByNative
+    private void onTraceDataComplete() {
+        if (mOutputStream != null) {
+            mOutputStream.complete();
+        }
+    }
+
+    private long mNativeAwTracingController;
+    private native long nativeInit();
+    private native boolean nativeStart(
+            long nativeAwTracingController, String categories, int traceMode);
+    private native boolean nativeStopAndFlush(long nativeAwTracingController);
+    private native boolean nativeIsTracing(long nativeAwTracingController);
+}
diff --git a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java
index 1a192fd1..4203a74 100644
--- a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java
+++ b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellActivity.java
@@ -36,7 +36,6 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.TraceEvent;
-import org.chromium.content.app.ContentApplication;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.ContentUrlConstants;
@@ -66,7 +65,7 @@
 
         AwShellResourceProvider.registerResources(this);
 
-        ContentApplication.initCommandLine(this);
+        ((AwShellApplication) getApplication()).initCommandLine();
 
         ContextUtils.initApplicationContext(getApplicationContext());
         AwBrowserProcess.loadLibrary(null);
diff --git a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java
index d07ade4..cea82bc 100644
--- a/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java
+++ b/android_webview/test/shell/src/org/chromium/android_webview/shell/AwShellApplication.java
@@ -12,7 +12,6 @@
  */
 public class AwShellApplication extends ContentApplication {
 
-    @Override
     public void initCommandLine() {
         if (!CommandLine.isInitialized()) {
             CommandLine.initFromFile("/data/local/tmp/android-webview-command-line");
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 4b1c93e..513d365 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -108,6 +108,8 @@
     "display/display_error_observer_chromeos.h",
     "display/display_move_window_util.cc",
     "display/display_move_window_util.h",
+    "display/display_shutdown_observer.cc",
+    "display/display_shutdown_observer.h",
     "display/display_synchronizer.cc",
     "display/display_synchronizer.h",
     "display/display_util.cc",
@@ -139,8 +141,6 @@
     "display/screen_position_controller.h",
     "display/shared_display_edge_indicator.cc",
     "display/shared_display_edge_indicator.h",
-    "display/shutdown_observer_chromeos.cc",
-    "display/shutdown_observer_chromeos.h",
     "display/touch_calibrator_controller.cc",
     "display/touch_calibrator_controller.h",
     "display/touch_calibrator_view.cc",
diff --git a/ash/display/shutdown_observer_chromeos.cc b/ash/display/display_shutdown_observer.cc
similarity index 66%
rename from ash/display/shutdown_observer_chromeos.cc
rename to ash/display/display_shutdown_observer.cc
index ead47b4f..ee846a6 100644
--- a/ash/display/shutdown_observer_chromeos.cc
+++ b/ash/display/display_shutdown_observer.cc
@@ -2,22 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/display/shutdown_observer_chromeos.h"
+#include "ash/display/display_shutdown_observer.h"
 
 #include "ui/display/manager/chromeos/display_configurator.h"
 
 namespace ash {
 
-ShutdownObserver::ShutdownObserver(
+DisplayShutdownObserver::DisplayShutdownObserver(
     display::DisplayConfigurator* display_configurator)
     : display_configurator_(display_configurator),
       scoped_session_observer_(this) {}
 
-ShutdownObserver::~ShutdownObserver() = default;
+DisplayShutdownObserver::~DisplayShutdownObserver() = default;
 
-void ShutdownObserver::OnChromeTerminating() {
+void DisplayShutdownObserver::OnChromeTerminating() {
   // Stop handling display configuration events once the shutdown
-  // process starts. crbug.com/177014.
+  // process starts. http://crbug.com/177014.
   display_configurator_->PrepareForExit();
 }
 
diff --git a/ash/display/shutdown_observer_chromeos.h b/ash/display/display_shutdown_observer.h
similarity index 61%
rename from ash/display/shutdown_observer_chromeos.h
rename to ash/display/display_shutdown_observer.h
index fc773db..6f91770 100644
--- a/ash/display/shutdown_observer_chromeos.h
+++ b/ash/display/display_shutdown_observer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_DISPLAY_SHUTDOWN_OBSERVER_CHROMEOS_H_
-#define ASH_DISPLAY_SHUTDOWN_OBSERVER_CHROMEOS_H_
+#ifndef ASH_DISPLAY_DISPLAY_SHUTDOWN_OBSERVER_H_
+#define ASH_DISPLAY_DISPLAY_SHUTDOWN_OBSERVER_H_
 
 #include "ash/session/session_observer.h"
 #include "base/macros.h"
@@ -16,10 +16,11 @@
 
 // Adds self as SessionObserver and listens for OnChromeTerminating() on
 // behalf of |display_configurator_|.
-class ShutdownObserver : public SessionObserver {
+class DisplayShutdownObserver : public SessionObserver {
  public:
-  explicit ShutdownObserver(display::DisplayConfigurator* display_configurator);
-  ~ShutdownObserver() override;
+  explicit DisplayShutdownObserver(
+      display::DisplayConfigurator* display_configurator);
+  ~DisplayShutdownObserver() override;
 
  private:
   // SessionObserver:
@@ -28,9 +29,9 @@
   display::DisplayConfigurator* const display_configurator_;
   ScopedSessionObserver scoped_session_observer_;
 
-  DISALLOW_COPY_AND_ASSIGN(ShutdownObserver);
+  DISALLOW_COPY_AND_ASSIGN(DisplayShutdownObserver);
 };
 
 }  // namespace ash
 
-#endif  // ASH_DISPLAY_SHUTDOWN_OBSERVER_CHROMEOS_H_
+#endif  // ASH_DISPLAY_DISPLAY_SHUTDOWN_OBSERVER_H_
diff --git a/ash/display/projecting_observer_chromeos.cc b/ash/display/projecting_observer_chromeos.cc
index 727e923..352c7f0f 100644
--- a/ash/display/projecting_observer_chromeos.cc
+++ b/ash/display/projecting_observer_chromeos.cc
@@ -4,22 +4,29 @@
 
 #include "ash/display/projecting_observer_chromeos.h"
 
+#include "ash/shell.h"
 #include "base/logging.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/power_manager_client.h"
 #include "ui/display/types/display_snapshot.h"
 
 namespace ash {
 
 ProjectingObserver::ProjectingObserver(
-    chromeos::PowerManagerClient* power_manager_client)
-    : has_internal_output_(false),
-      output_count_(0),
-      casting_session_count_(0),
-      power_manager_client_(power_manager_client) {
-  DCHECK(power_manager_client);
+    display::DisplayConfigurator* display_configurator)
+    : display_configurator_(display_configurator) {
+  if (Shell::HasInstance())
+    Shell::Get()->AddShellObserver(this);
+  if (display_configurator_)
+    display_configurator_->AddObserver(this);
 }
 
-ProjectingObserver::~ProjectingObserver() = default;
+ProjectingObserver::~ProjectingObserver() {
+  if (Shell::HasInstance())
+    Shell::Get()->RemoveShellObserver(this);
+  if (display_configurator_)
+    display_configurator_->RemoveObserver(this);
+}
 
 void ProjectingObserver::OnDisplayModeChanged(
     const display::DisplayConfigurator::DisplayStateList& display_states) {
@@ -56,7 +63,12 @@
   bool projecting =
       has_internal_output_ && (output_count_ + casting_session_count_ > 1);
 
-  power_manager_client_->SetIsProjecting(projecting);
+  chromeos::PowerManagerClient* power_manager_client =
+      power_manager_client_for_test_
+          ? power_manager_client_for_test_
+          : chromeos::DBusThreadManager::Get()->GetPowerManagerClient();
+  if (power_manager_client)
+    power_manager_client->SetIsProjecting(projecting);
 }
 
 }  // namespace ash
diff --git a/ash/display/projecting_observer_chromeos.h b/ash/display/projecting_observer_chromeos.h
index 22f79c5..e64c7e0ae 100644
--- a/ash/display/projecting_observer_chromeos.h
+++ b/ash/display/projecting_observer_chromeos.h
@@ -20,9 +20,9 @@
     : public display::DisplayConfigurator::Observer,
       public ShellObserver {
  public:
-  // |power_manager_client| must outlive this object.
+  // |display_configurator| must outlive this instance. May be null in tests.
   explicit ProjectingObserver(
-      chromeos::PowerManagerClient* power_manager_client);
+      display::DisplayConfigurator* display_configurator);
   ~ProjectingObserver() override;
 
   // DisplayConfigurator::Observer implementation:
@@ -33,21 +33,30 @@
   void OnCastingSessionStartedOrStopped(bool started) override;
 
  private:
+  friend class ProjectingObserverTest;
+
+  void set_power_manager_client_for_test(
+      chromeos::PowerManagerClient* power_manager_client) {
+    power_manager_client_for_test_ = power_manager_client;
+  }
+
   // Sends the current projecting state to power manager.
   void SetIsProjecting();
 
+  display::DisplayConfigurator* display_configurator_;  // Unowned
+
   // True if at least one output is internal. This value is updated when
   // |OnDisplayModeChanged| is called.
-  bool has_internal_output_;
+  bool has_internal_output_ = false;
 
   // Keeps track of the number of connected outputs.
-  int output_count_;
+  int output_count_ = 0;
 
   // Number of outstanding casting sessions.
-  int casting_session_count_;
+  int casting_session_count_ = 0;
 
-  // Weak pointer to the DBusClient PowerManagerClient;
-  chromeos::PowerManagerClient* power_manager_client_;
+  // Weak pointer to the DBusClient PowerManagerClient for testing;
+  chromeos::PowerManagerClient* power_manager_client_for_test_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(ProjectingObserver);
 };
diff --git a/ash/display/projecting_observer_chromeos_unittest.cc b/ash/display/projecting_observer_chromeos_unittest.cc
index da2ef3db..55eff557 100644
--- a/ash/display/projecting_observer_chromeos_unittest.cc
+++ b/ash/display/projecting_observer_chromeos_unittest.cc
@@ -12,6 +12,7 @@
 #include "ui/display/manager/fake_display_snapshot.h"
 
 namespace ash {
+
 namespace {
 
 std::unique_ptr<display::DisplaySnapshot> CreateInternalSnapshot() {
@@ -30,20 +31,6 @@
       .Build();
 }
 
-class ProjectingObserverTest : public testing::Test {
- public:
-  ProjectingObserverTest() : observer_(&fake_power_client_) {}
-
-  ~ProjectingObserverTest() override = default;
-
- protected:
-  chromeos::FakePowerManagerClient fake_power_client_;
-  ProjectingObserver observer_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ProjectingObserverTest);
-};
-
 display::DisplayConfigurator::DisplayStateList GetPointers(
     const std::vector<std::unique_ptr<display::DisplaySnapshot>>& displays) {
   display::DisplayConfigurator::DisplayStateList result;
@@ -54,9 +41,26 @@
 
 }  // namespace
 
+class ProjectingObserverTest : public testing::Test {
+ public:
+  ProjectingObserverTest() {
+    observer_ = std::make_unique<ProjectingObserver>(nullptr);
+    observer_->set_power_manager_client_for_test(&fake_power_client_);
+  }
+
+  ~ProjectingObserverTest() override = default;
+
+ protected:
+  chromeos::FakePowerManagerClient fake_power_client_;
+  std::unique_ptr<ProjectingObserver> observer_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProjectingObserverTest);
+};
+
 TEST_F(ProjectingObserverTest, CheckNoDisplay) {
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
   EXPECT_EQ(1, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_FALSE(fake_power_client_.is_projecting());
@@ -65,7 +69,7 @@
 TEST_F(ProjectingObserverTest, CheckWithoutInternalDisplay) {
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateVGASnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
   EXPECT_EQ(1, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_FALSE(fake_power_client_.is_projecting());
@@ -74,7 +78,7 @@
 TEST_F(ProjectingObserverTest, CheckWithInternalDisplay) {
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateInternalSnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
   EXPECT_EQ(1, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_FALSE(fake_power_client_.is_projecting());
@@ -84,7 +88,7 @@
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateVGASnapshot());
   displays.push_back(CreateVGASnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
   EXPECT_EQ(1, fake_power_client_.num_set_is_projecting_calls());
   // We need at least 1 internal display to set projecting to on.
@@ -95,7 +99,7 @@
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateInternalSnapshot());
   displays.push_back(CreateVGASnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
   EXPECT_EQ(1, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_TRUE(fake_power_client_.is_projecting());
@@ -104,9 +108,9 @@
 TEST_F(ProjectingObserverTest, CheckWithVGADisplayAndOneCastingSession) {
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateVGASnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
-  observer_.OnCastingSessionStartedOrStopped(true);
+  observer_->OnCastingSessionStartedOrStopped(true);
 
   EXPECT_EQ(2, fake_power_client_.num_set_is_projecting_calls());
   // Need at least one internal display to set projecting state to |true|.
@@ -116,9 +120,9 @@
 TEST_F(ProjectingObserverTest, CheckWithInternalDisplayAndOneCastingSession) {
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateInternalSnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
-  observer_.OnCastingSessionStartedOrStopped(true);
+  observer_->OnCastingSessionStartedOrStopped(true);
 
   EXPECT_EQ(2, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_TRUE(fake_power_client_.is_projecting());
@@ -127,15 +131,15 @@
 TEST_F(ProjectingObserverTest, CheckProjectingAfterClosingACastingSession) {
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateInternalSnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
-  observer_.OnCastingSessionStartedOrStopped(true);
-  observer_.OnCastingSessionStartedOrStopped(true);
+  observer_->OnCastingSessionStartedOrStopped(true);
+  observer_->OnCastingSessionStartedOrStopped(true);
 
   EXPECT_EQ(3, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_TRUE(fake_power_client_.is_projecting());
 
-  observer_.OnCastingSessionStartedOrStopped(false);
+  observer_->OnCastingSessionStartedOrStopped(false);
 
   EXPECT_EQ(4, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_TRUE(fake_power_client_.is_projecting());
@@ -145,10 +149,10 @@
        CheckStopProjectingAfterClosingAllCastingSessions) {
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateInternalSnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
-  observer_.OnCastingSessionStartedOrStopped(true);
-  observer_.OnCastingSessionStartedOrStopped(false);
+  observer_->OnCastingSessionStartedOrStopped(true);
+  observer_->OnCastingSessionStartedOrStopped(false);
 
   EXPECT_EQ(3, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_FALSE(fake_power_client_.is_projecting());
@@ -159,11 +163,11 @@
   std::vector<std::unique_ptr<display::DisplaySnapshot>> displays;
   displays.push_back(CreateInternalSnapshot());
   displays.push_back(CreateVGASnapshot());
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
   // Remove VGA output.
   displays.erase(displays.begin() + 1);
-  observer_.OnDisplayModeChanged(GetPointers(displays));
+  observer_->OnDisplayModeChanged(GetPointers(displays));
 
   EXPECT_EQ(2, fake_power_client_.num_set_is_projecting_calls());
   EXPECT_FALSE(fake_power_client_.is_projecting());
diff --git a/ash/public/interfaces/wallpaper.mojom b/ash/public/interfaces/wallpaper.mojom
index e138386..f0f75789 100644
--- a/ash/public/interfaces/wallpaper.mojom
+++ b/ash/public/interfaces/wallpaper.mojom
@@ -63,7 +63,8 @@
 
 // Used by Chrome to set the wallpaper displayed by ash.
 interface WallpaperController {
-  // Sets the client interface and the paths of wallpaper directories. The paths
+  // Do the initialization: Sets the client interface, the paths of wallpaper
+  // directories and the device wallpaper policy enforcement flag. The paths
   // must be sent over IPC because chrome owns the concept of user data
   // directory.
   // |client|: The client interface.
@@ -72,10 +73,13 @@
   //                             reside.
   // |chromeos_custom_wallpapers_path|: Directory where custom wallpapers
   //                                    reside.
-  SetClientAndPaths(WallpaperControllerClient client,
-                    mojo.common.mojom.FilePath user_data_path,
-                    mojo.common.mojom.FilePath chromeos_wallpapers_path,
-                    mojo.common.mojom.FilePath chromeos_custom_wallpapers_path);
+  // |is_device_wallpaper_policy_enforced|: Whether the device wallpaper policy
+  //                                        is enforced on the device.
+  Init(WallpaperControllerClient client,
+       mojo.common.mojom.FilePath user_data_path,
+       mojo.common.mojom.FilePath chromeos_wallpapers_path,
+       mojo.common.mojom.FilePath chromeos_custom_wallpapers_path,
+       bool is_device_wallpaper_policy_enforced);
 
   // Sets wallpaper from policy or from a local file. Saves the custom wallpaper
   // to file, posts task to generate thumbnail and updates local state.
diff --git a/ash/shell.cc b/ash/shell.cc
index bf596d5..353008e 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -25,6 +25,7 @@
 #include "ash/display/display_color_manager_chromeos.h"
 #include "ash/display/display_configuration_controller.h"
 #include "ash/display/display_error_observer_chromeos.h"
+#include "ash/display/display_shutdown_observer.h"
 #include "ash/display/event_transformation_handler.h"
 #include "ash/display/mouse_cursor_event_filter.h"
 #include "ash/display/projecting_observer_chromeos.h"
@@ -32,7 +33,6 @@
 #include "ash/display/screen_ash.h"
 #include "ash/display/screen_orientation_controller_chromeos.h"
 #include "ash/display/screen_position_controller.h"
-#include "ash/display/shutdown_observer_chromeos.h"
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/drag_drop/drag_drop_controller.h"
 #include "ash/first_run/first_run_helper_impl.h"
@@ -141,7 +141,6 @@
 #include "base/threading/sequenced_worker_pool.h"
 #include "base/trace_event/trace_event.h"
 #include "chromeos/chromeos_switches.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/system/devicemode.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -786,22 +785,17 @@
   screen_position_controller_.reset();
 
   display_color_manager_.reset();
+  projecting_observer_.reset();
+
   if (display_change_observer_)
     display_configurator_->RemoveObserver(display_change_observer_.get());
   if (display_error_observer_)
     display_configurator_->RemoveObserver(display_error_observer_.get());
-  if (projecting_observer_) {
-    display_configurator_->RemoveObserver(projecting_observer_.get());
-    RemoveShellObserver(projecting_observer_.get());
-  }
   display_change_observer_.reset();
-  shutdown_observer_.reset();
+  display_shutdown_observer_.reset();
 
   PowerStatus::Shutdown();
 
-  // Ensure that DBusThreadManager outlives this Shell.
-  DCHECK(chromeos::DBusThreadManager::IsInitialized());
-
   // Needs to happen right before |instance_| is reset.
   shell_port_.reset();
   session_controller_->RemoveObserver(this);
@@ -881,65 +875,19 @@
         base::WrapUnique(native_cursor_manager_));
   }
 
+  // TODO(stevenjb): ChromeShellDelegate::PreInit currently handles
+  // DisplayPreference initialization, required for InitializeDisplayManager.
+  // Before we can move that code into ash/display where it belongs, we need to
+  // wait for |lcoal_state_| to be set in OnLocalStatePrefServiceInitialized
+  // before initializing DisplayPreferences (and therefore DisplayManager).
+  // http://crbug.com/678949.
   shell_delegate_->PreInit();
-  bool display_initialized = display_manager_->InitFromCommandLine();
-  if (!display_initialized && config != Config::CLASSIC) {
-    // Run display configuration off device in mus mode.
-    display_manager_->set_configure_displays(true);
-    display_configurator_->set_configure_display(true);
-  }
-  display_configuration_controller_ =
-      std::make_unique<DisplayConfigurationController>(
-          display_manager_.get(), window_tree_host_manager_.get());
-  display_configurator_->Init(shell_port_->CreateNativeDisplayDelegate(),
-                              false);
 
-  // The DBusThreadManager must outlive this Shell. See the DCHECK in ~Shell.
-  chromeos::DBusThreadManager* dbus_thread_manager =
-      chromeos::DBusThreadManager::Get();
-  projecting_observer_.reset(
-      new ProjectingObserver(dbus_thread_manager->GetPowerManagerClient()));
-  display_configurator_->AddObserver(projecting_observer_.get());
-  AddShellObserver(projecting_observer_.get());
-
-  if (!display_initialized) {
-    if (config != Config::CLASSIC && !chromeos::IsRunningAsSystemCompositor()) {
-      display::mojom::DevDisplayControllerPtr controller;
-      shell_delegate_->GetShellConnector()->BindInterface(
-          ui::mojom::kServiceName, &controller);
-      display_manager_->SetDevDisplayController(std::move(controller));
-    }
-
-    if (config != Config::CLASSIC || chromeos::IsRunningAsSystemCompositor()) {
-      display_change_observer_ =
-          std::make_unique<display::DisplayChangeObserver>(
-              display_configurator_.get(), display_manager_.get());
-
-      shutdown_observer_ =
-          std::make_unique<ShutdownObserver>(display_configurator_.get());
-
-      // Register |display_change_observer_| first so that the rest of
-      // observer gets invoked after the root windows are configured.
-      display_configurator_->AddObserver(display_change_observer_.get());
-      display_error_observer_.reset(new DisplayErrorObserver());
-      display_configurator_->AddObserver(display_error_observer_.get());
-      display_configurator_->set_state_controller(
-          display_change_observer_.get());
-      display_configurator_->set_mirroring_controller(display_manager_.get());
-      display_configurator_->ForceInitialConfigure();
-      display_initialized = true;
-    }
-  }
-
-  display_color_manager_ =
-      std::make_unique<DisplayColorManager>(display_configurator_.get());
-
-  if (!display_initialized)
-    display_manager_->InitDefaultDisplay();
+  InitializeDisplayManager();
 
   if (config == Config::CLASSIC) {
-    display_manager_->RefreshFontParams();
-
+    // This will initialize aura::Env which requires |display_manager_| to
+    // be initialized first.
     aura::Env::GetInstance()->set_context_factory(context_factory);
     aura::Env::GetInstance()->set_context_factory_private(
         context_factory_private);
@@ -1161,6 +1109,63 @@
   user_metrics_recorder_->OnShellInitialized();
 }
 
+void Shell::InitializeDisplayManager() {
+  const Config config = shell_port_->GetAshConfig();
+  bool display_initialized = display_manager_->InitFromCommandLine();
+
+  if (!display_initialized && config != Config::CLASSIC) {
+    // Run display configuration off device in mus mode.
+    display_manager_->set_configure_displays(true);
+    display_configurator_->set_configure_display(true);
+  }
+  display_configuration_controller_ =
+      std::make_unique<DisplayConfigurationController>(
+          display_manager_.get(), window_tree_host_manager_.get());
+  display_configurator_->Init(shell_port_->CreateNativeDisplayDelegate(),
+                              false);
+
+  projecting_observer_ =
+      std::make_unique<ProjectingObserver>(display_configurator_.get());
+
+  if (!display_initialized) {
+    if (config != Config::CLASSIC && !chromeos::IsRunningAsSystemCompositor()) {
+      display::mojom::DevDisplayControllerPtr controller;
+      shell_delegate_->GetShellConnector()->BindInterface(
+          ui::mojom::kServiceName, &controller);
+      display_manager_->SetDevDisplayController(std::move(controller));
+    }
+
+    if (config != Config::CLASSIC || chromeos::IsRunningAsSystemCompositor()) {
+      display_change_observer_ =
+          std::make_unique<display::DisplayChangeObserver>(
+              display_configurator_.get(), display_manager_.get());
+
+      display_shutdown_observer_ = std::make_unique<DisplayShutdownObserver>(
+          display_configurator_.get());
+
+      // Register |display_change_observer_| first so that the rest of
+      // observer gets invoked after the root windows are configured.
+      display_configurator_->AddObserver(display_change_observer_.get());
+      display_error_observer_.reset(new DisplayErrorObserver());
+      display_configurator_->AddObserver(display_error_observer_.get());
+      display_configurator_->set_state_controller(
+          display_change_observer_.get());
+      display_configurator_->set_mirroring_controller(display_manager_.get());
+      display_configurator_->ForceInitialConfigure();
+      display_initialized = true;
+    }
+  }
+
+  display_color_manager_ =
+      std::make_unique<DisplayColorManager>(display_configurator_.get());
+
+  if (!display_initialized)
+    display_manager_->InitDefaultDisplay();
+
+  if (config == Config::CLASSIC)
+    display_manager_->RefreshFontParams();
+}
+
 void Shell::InitRootWindow(aura::Window* root_window) {
   DCHECK(focus_controller_);
   DCHECK(visibility_controller_.get());
diff --git a/ash/shell.h b/ash/shell.h
index b620f74..890db6b 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -94,6 +94,7 @@
 class DisplayColorManager;
 class DisplayConfigurationController;
 class DisplayErrorObserver;
+class DisplayShutdownObserver;
 class DragDropController;
 class EventClientImpl;
 class EventTransformationHandler;
@@ -143,7 +144,6 @@
 struct ShellInitParams;
 class ShellObserver;
 class ShutdownController;
-class ShutdownObserver;
 class SmsObserver;
 class SplitViewController;
 class StickyKeysController;
@@ -588,6 +588,9 @@
   void Init(ui::ContextFactory* context_factory,
             ui::ContextFactoryPrivate* context_factory_private);
 
+  // Initializes the display manager and related components.
+  void InitializeDisplayManager();
+
   // Initializes the root window so that it can host browser windows.
   void InitRootWindow(aura::Window* root_window);
 
@@ -745,7 +748,7 @@
   std::unique_ptr<display::DisplayChangeObserver> display_change_observer_;
 
   // Listens for shutdown and updates DisplayConfigurator.
-  std::unique_ptr<ShutdownObserver> shutdown_observer_;
+  std::unique_ptr<DisplayShutdownObserver> display_shutdown_observer_;
 
   // Listens for new sms messages and shows notifications.
   std::unique_ptr<SmsObserver> sms_observer_;
diff --git a/ash/wallpaper/wallpaper_controller.cc b/ash/wallpaper/wallpaper_controller.cc
index ea90e6a..dc235d01 100644
--- a/ash/wallpaper/wallpaper_controller.cc
+++ b/ash/wallpaper/wallpaper_controller.cc
@@ -1075,15 +1075,18 @@
   return EmptyAccountId();
 }
 
-void WallpaperController::SetClientAndPaths(
+void WallpaperController::Init(
     mojom::WallpaperControllerClientPtr client,
     const base::FilePath& user_data_path,
     const base::FilePath& chromeos_wallpapers_path,
-    const base::FilePath& chromeos_custom_wallpapers_path) {
+    const base::FilePath& chromeos_custom_wallpapers_path,
+    bool is_device_wallpaper_policy_enforced) {
+  DCHECK(!wallpaper_controller_client_.get());
   wallpaper_controller_client_ = std::move(client);
   dir_user_data_path_ = user_data_path;
   dir_chrome_os_wallpapers_path_ = chromeos_wallpapers_path;
   dir_chrome_os_custom_wallpapers_path_ = chromeos_custom_wallpapers_path;
+  SetDeviceWallpaperPolicyEnforced(is_device_wallpaper_policy_enforced);
 }
 
 void WallpaperController::SetCustomWallpaper(
@@ -1263,10 +1266,12 @@
 }
 
 void WallpaperController::ShowSigninWallpaper() {
-  current_user_.reset();
-  // TODO(crbug.com/791654): Call |SetDeviceWallpaperIfApplicable| from here.
-  SetDefaultWallpaperImpl(EmptyAccountId(), user_manager::USER_TYPE_REGULAR,
-                          true /*show_wallpaper=*/);
+  if (ShouldSetDevicePolicyWallpaper()) {
+    SetDevicePolicyWallpaper();
+  } else {
+    SetDefaultWallpaperImpl(EmptyAccountId(), user_manager::USER_TYPE_REGULAR,
+                            true /*show_wallpaper=*/);
+  }
 }
 
 void WallpaperController::RemoveUserWallpaper(
@@ -1330,8 +1335,8 @@
 
 void WallpaperController::SetClientForTesting(
     mojom::WallpaperControllerClientPtr client) {
-  SetClientAndPaths(std::move(client), base::FilePath(), base::FilePath(),
-                    base::FilePath());
+  Init(std::move(client), base::FilePath(), base::FilePath(), base::FilePath(),
+       false /*is_device_wallpaper_policy_enforced=*/);
 }
 
 void WallpaperController::FlushForTesting() {
diff --git a/ash/wallpaper/wallpaper_controller.h b/ash/wallpaper/wallpaper_controller.h
index 325230c..7e5bc27 100644
--- a/ash/wallpaper/wallpaper_controller.h
+++ b/ash/wallpaper/wallpaper_controller.h
@@ -377,11 +377,11 @@
   AccountId GetCurrentUserAccountId();
 
   // mojom::WallpaperController overrides:
-  void SetClientAndPaths(
-      mojom::WallpaperControllerClientPtr client,
-      const base::FilePath& user_data_path,
-      const base::FilePath& chromeos_wallpapers_path,
-      const base::FilePath& chromeos_custom_wallpapers_path) override;
+  void Init(mojom::WallpaperControllerClientPtr client,
+            const base::FilePath& user_data_path,
+            const base::FilePath& chromeos_wallpapers_path,
+            const base::FilePath& chromeos_custom_wallpapers_path,
+            bool is_device_wallpaper_policy_enforced) override;
   void SetCustomWallpaper(mojom::WallpaperUserInfoPtr user_info,
                           const std::string& wallpaper_files_id,
                           const std::string& file_name,
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 27697fc..0544de5 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2860,6 +2860,7 @@
       "android/library_loader/library_loader_hooks.h",
       "memory/memory_pressure_listener.h",
       "metrics/histogram_base.h",
+      "trace_event/trace_config.h",
     ]
   }
 
diff --git a/base/android/java/src/org/chromium/base/BaseChromiumApplication.java b/base/android/java/src/org/chromium/base/BaseChromiumApplication.java
index 9a1d3c2..649090b1e 100644
--- a/base/android/java/src/org/chromium/base/BaseChromiumApplication.java
+++ b/base/android/java/src/org/chromium/base/BaseChromiumApplication.java
@@ -25,18 +25,6 @@
         }
     }
 
-    /** Initializes the {@link CommandLine}. */
-    public void initCommandLine() {}
-
-    /**
-     * This must only be called for contexts whose application is a subclass of
-     * {@link BaseChromiumApplication}.
-     */
-    @VisibleForTesting
-    public static void initCommandLine(Context context) {
-        ((BaseChromiumApplication) context.getApplicationContext()).initCommandLine();
-    }
-
     /** Ensure this application object is not out-of-date. */
     private void checkAppBeingReplaced() {
         // During app update the old apk can still be triggered by broadcasts and spin up an
diff --git a/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java b/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java
index 8aeede1..34b06d5 100644
--- a/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java
+++ b/base/android/javatests/src/org/chromium/base/CommandLineInitUtilTest.java
@@ -13,6 +13,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 
 /**
@@ -24,7 +25,7 @@
     public void setUp() throws Exception {
         CommandLineInitUtil.initCommandLine(
                 InstrumentationRegistry.getInstrumentation().getTargetContext(),
-                "content-shell-command-line");
+                CommandLineFlags.getTestCmdLineFile());
     }
 
     /**
diff --git a/base/message_loop/incoming_task_queue.cc b/base/message_loop/incoming_task_queue.cc
index 941cbd8..f84b326 100644
--- a/base/message_loop/incoming_task_queue.cc
+++ b/base/message_loop/incoming_task_queue.cc
@@ -85,11 +85,6 @@
   return PostPendingTask(&pending_task);
 }
 
-bool IncomingTaskQueue::IsIdleForTesting() {
-  AutoLock lock(incoming_queue_lock_);
-  return incoming_queue_.empty();
-}
-
 void IncomingTaskQueue::WillDestroyCurrentMessageLoop() {
   {
     AutoLock auto_lock(incoming_queue_lock_);
@@ -181,6 +176,7 @@
 }
 
 void IncomingTaskQueue::TriageQueue::ReloadFromIncomingQueueIfEmpty() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_);
   if (queue_.empty()) {
     // TODO(robliao): Since these high resolution tasks aren't yet in the
     // delayed queue, they technically shouldn't trigger high resolution timers
@@ -351,6 +347,8 @@
 }
 
 int IncomingTaskQueue::ReloadWorkQueue(TaskQueue* work_queue) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   // Make sure no tasks are lost.
   DCHECK(work_queue->empty());
 
diff --git a/base/message_loop/incoming_task_queue.h b/base/message_loop/incoming_task_queue.h
index 861f0fc..0a476365 100644
--- a/base/message_loop/incoming_task_queue.h
+++ b/base/message_loop/incoming_task_queue.h
@@ -77,9 +77,6 @@
                           TimeDelta delay,
                           Nestable nestable);
 
-  // Returns true if the message loop is "idle". Provided for testing.
-  bool IsIdleForTesting();
-
   // Disconnects |this| from the parent message loop.
   void WillDestroyCurrentMessageLoop();
 
@@ -109,8 +106,9 @@
   // maintaining three queue queues to process tasks:
   //
   // TriageQueue
-  // The first queue to receive all tasks for the processing sequence. Tasks are
-  // generally either dispatched immediately or sent to the queues below.
+  // The first queue to receive all tasks for the processing sequence (when
+  // reloading from the thread-safe |incoming_queue_|). Tasks are generally
+  // either dispatched immediately or sent to the queues below.
   //
   // DelayedQueue
   // The queue for holding tasks that should be run later and sorted by expected
@@ -242,7 +240,7 @@
 
   // An incoming queue of tasks that are acquired under a mutex for processing
   // on this instance's thread. These tasks have not yet been been pushed to
-  // |message_loop_|.
+  // |triage_tasks_|.
   TaskQueue incoming_queue_;
 
   // True if new tasks should be accepted.
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
index 41c58bf..455a7d0 100644
--- a/base/message_loop/message_loop.cc
+++ b/base/message_loop/message_loop.cc
@@ -258,9 +258,12 @@
 }
 
 bool MessageLoop::IsIdleForTesting() {
-  // We only check the incoming queue, since we don't want to lock the work
-  // queue.
-  return incoming_task_queue_->IsIdleForTesting();
+  return !incoming_task_queue_->triage_tasks().HasTasks() &&
+         (!incoming_task_queue_->deferred_tasks().HasTasks() ||
+          RunLoop::IsNestedOnCurrentThread()) &&
+         (!incoming_task_queue_->delayed_tasks().HasTasks() ||
+          incoming_task_queue_->delayed_tasks().Peek().delayed_run_time >
+              TimeTicks::Now());
 }
 
 //------------------------------------------------------------------------------
diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h
index 46c7ab9..90715a0 100644
--- a/base/message_loop/message_loop.h
+++ b/base/message_loop/message_loop.h
@@ -266,7 +266,10 @@
   void AddTaskObserver(TaskObserver* task_observer);
   void RemoveTaskObserver(TaskObserver* task_observer);
 
-  // Returns true if the message loop is "idle". Provided for testing.
+  // Returns true if the message loop is idle (same condition which triggers
+  // RunLoop::RunUntilIdle() to return: i.e. out of tasks which can be processed
+  // at the current run-level -- there might be deferred non-nestable tasks
+  // remaining if currently in a nested run level). Provided for testing.
   bool IsIdleForTesting();
 
   // Runs the specified PendingTask.
diff --git a/base/metrics/statistics_recorder.cc b/base/metrics/statistics_recorder.cc
index 1215474..82d7a93 100644
--- a/base/metrics/statistics_recorder.cc
+++ b/base/metrics/statistics_recorder.cc
@@ -20,9 +20,12 @@
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
 
-namespace base {
 namespace {
 
+// Initialize histogram statistics gathering system.
+base::LazyInstance<base::StatisticsRecorder>::Leaky g_statistics_recorder_ =
+    LAZY_INSTANCE_INITIALIZER;
+
 bool HistogramNameLesser(const base::HistogramBase* a,
                          const base::HistogramBase* b) {
   return strcmp(a->histogram_name(), b->histogram_name()) < 0;
@@ -30,14 +33,7 @@
 
 }  // namespace
 
-// static
-LazyInstance<Lock>::Leaky StatisticsRecorder::lock_;
-
-// static
-StatisticsRecorder* StatisticsRecorder::top_ = nullptr;
-
-// static
-bool StatisticsRecorder::is_vlog_initialized_ = false;
+namespace base {
 
 size_t StatisticsRecorder::BucketRangesHash::operator()(
     const BucketRanges* const a) const {
@@ -51,55 +47,67 @@
 }
 
 StatisticsRecorder::~StatisticsRecorder() {
-  const AutoLock auto_lock(lock_.Get());
-  DCHECK_EQ(this, top_);
-  top_ = previous_;
+  DCHECK(histograms_);
+  DCHECK(ranges_);
+
+  // Clean out what this object created and then restore what existed before.
+  Reset();
+  base::AutoLock auto_lock(lock_.Get());
+  histograms_ = existing_histograms_.release();
+  callbacks_ = existing_callbacks_.release();
+  ranges_ = existing_ranges_.release();
+  providers_ = existing_providers_.release();
+  record_checker_ = existing_record_checker_.release();
 }
 
 // static
 void StatisticsRecorder::Initialize() {
-  const AutoLock auto_lock(lock_.Get());
-  if (top_)
+  // Tests sometimes create local StatisticsRecorders in order to provide a
+  // contained environment of histograms that can be later discarded. If a
+  // true global instance gets created in this environment then it will
+  // eventually get disconnected when the local instance destructs and
+  // restores the previous state, resulting in no StatisticsRecorder at all.
+  // The global lazy instance, however, will remain valid thus ensuring that
+  // another never gets installed via this method. If a |histograms_| map
+  // exists then assume the StatisticsRecorder is already "initialized".
+  if (histograms_)
     return;
 
-  const StatisticsRecorder* const p = new StatisticsRecorder;
-  // The global recorder is never deleted.
-  ANNOTATE_LEAKING_OBJECT_PTR(p);
-  DCHECK_EQ(p, top_);
+  // Ensure that an instance of the StatisticsRecorder object is created.
+  g_statistics_recorder_.Get();
 }
 
 // static
 bool StatisticsRecorder::IsActive() {
-  const AutoLock auto_lock(lock_.Get());
-  return top_ != nullptr;
+  base::AutoLock auto_lock(lock_.Get());
+  return histograms_ != nullptr;
 }
 
 // static
 void StatisticsRecorder::RegisterHistogramProvider(
     const WeakPtr<HistogramProvider>& provider) {
-  const AutoLock auto_lock(lock_.Get());
-  top_->providers_.push_back(provider);
+  providers_->push_back(provider);
 }
 
 // static
 HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
     HistogramBase* histogram) {
-  // Declared before |auto_lock| to ensure correct destruction order.
+  // Declared before auto_lock to ensure correct destruction order.
   std::unique_ptr<HistogramBase> histogram_deleter;
-  const AutoLock auto_lock(lock_.Get());
+  base::AutoLock auto_lock(lock_.Get());
 
-  if (!top_) {
+  if (!histograms_) {
     // As per crbug.com/79322 the histograms are intentionally leaked, so we
     // need to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used
     // only once for an object, the duplicates should not be annotated.
     // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr)
-    // twice |if (!top_)|.
+    // twice |if (!histograms_)|.
     ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
     return histogram;
   }
 
   const char* const name = histogram->histogram_name();
-  HistogramBase*& registered = top_->histograms_[name];
+  HistogramBase*& registered = (*histograms_)[name];
 
   if (!registered) {
     // |name| is guaranteed to never change or be deallocated so long
@@ -108,8 +116,8 @@
     ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
     // If there are callbacks for this histogram, we set the kCallbackExists
     // flag.
-    const auto callback_iterator = top_->callbacks_.find(name);
-    if (callback_iterator != top_->callbacks_.end()) {
+    const auto callback_iterator = callbacks_->find(name);
+    if (callback_iterator != callbacks_->end()) {
       if (!callback_iterator->second.is_null())
         histogram->SetFlags(HistogramBase::kCallbackExists);
       else
@@ -133,16 +141,16 @@
     const BucketRanges* ranges) {
   DCHECK(ranges->HasValidChecksum());
 
-  // Declared before |auto_lock| to ensure correct destruction order.
+  // Declared before auto_lock to ensure correct destruction order.
   std::unique_ptr<const BucketRanges> ranges_deleter;
-  const AutoLock auto_lock(lock_.Get());
+  base::AutoLock auto_lock(lock_.Get());
 
-  if (!top_) {
+  if (!ranges_) {
     ANNOTATE_LEAKING_OBJECT_PTR(ranges);
     return ranges;
   }
 
-  const BucketRanges* const registered = *top_->ranges_.insert(ranges).first;
+  const BucketRanges* const registered = *ranges_->insert(ranges).first;
   if (registered == ranges) {
     ANNOTATE_LEAKING_OBJECT_PTR(ranges);
   } else {
@@ -155,6 +163,9 @@
 // static
 void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
                                         std::string* output) {
+  if (!IsActive())
+    return;
+
   Histograms snapshot;
   GetSnapshot(query, &snapshot);
   std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
@@ -167,6 +178,8 @@
 // static
 void StatisticsRecorder::WriteGraph(const std::string& query,
                                     std::string* output) {
+  if (!IsActive())
+    return;
   if (query.length())
     StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
   else
@@ -183,6 +196,9 @@
 
 // static
 std::string StatisticsRecorder::ToJSON(JSONVerbosityLevel verbosity_level) {
+  if (!IsActive())
+    return std::string();
+
   Histograms snapshot;
   GetSnapshot(std::string(), &snapshot);
 
@@ -201,11 +217,11 @@
 
 // static
 void StatisticsRecorder::GetHistograms(Histograms* output) {
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  base::AutoLock auto_lock(lock_.Get());
+  if (!histograms_)
     return;
 
-  for (const auto& entry : top_->histograms_) {
+  for (const auto& entry : *histograms_) {
     output->push_back(entry.second);
   }
 }
@@ -213,11 +229,11 @@
 // static
 void StatisticsRecorder::GetBucketRanges(
     std::vector<const BucketRanges*>* output) {
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  base::AutoLock auto_lock(lock_.Get());
+  if (!ranges_)
     return;
 
-  for (const BucketRanges* const p : top_->ranges_) {
+  for (const BucketRanges* const p : *ranges_) {
     output->push_back(p);
   }
 }
@@ -229,28 +245,21 @@
   // will acquire the lock at that time.
   ImportGlobalPersistentHistograms();
 
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  base::AutoLock auto_lock(lock_.Get());
+  if (!histograms_)
     return nullptr;
 
-  const HistogramMap::const_iterator it = top_->histograms_.find(name);
-  return it != top_->histograms_.end() ? it->second : nullptr;
-}
-
-// static
-StatisticsRecorder::HistogramProviders
-StatisticsRecorder::GetHistogramProviders() {
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
-    return {};
-
-  return top_->providers_;
+  const HistogramMap::const_iterator it = histograms_->find(name);
+  return it != histograms_->end() ? it->second : nullptr;
 }
 
 // static
 void StatisticsRecorder::ImportProvidedHistograms() {
+  if (!providers_)
+    return;
+
   // Merge histogram data from each provider in turn.
-  for (const WeakPtr<HistogramProvider>& provider : GetHistogramProviders()) {
+  for (const WeakPtr<HistogramProvider>& provider : *providers_) {
     // Weak-pointer may be invalid if the provider was destructed, though they
     // generally never are.
     if (provider)
@@ -274,8 +283,11 @@
 
 // static
 void StatisticsRecorder::InitLogOnShutdown() {
-  const AutoLock auto_lock(lock_.Get());
-  InitLogOnShutdownWhileLocked();
+  if (!histograms_)
+    return;
+
+  base::AutoLock auto_lock(lock_.Get());
+  g_statistics_recorder_.Get().InitLogOnShutdownWithoutLock();
 }
 
 // static
@@ -286,14 +298,14 @@
   // will acquire the lock at that time.
   ImportGlobalPersistentHistograms();
 
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  base::AutoLock auto_lock(lock_.Get());
+  if (!histograms_)
     return;
 
   // Need a c-string query for comparisons against c-string histogram name.
   const char* query_string = query.c_str();
 
-  for (const auto& entry : top_->histograms_) {
+  for (const auto& entry : *histograms_) {
     if (strstr(entry.second->histogram_name(), query_string) != nullptr)
       snapshot->push_back(entry.second);
   }
@@ -304,15 +316,15 @@
     const std::string& name,
     const StatisticsRecorder::OnSampleCallback& cb) {
   DCHECK(!cb.is_null());
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  base::AutoLock auto_lock(lock_.Get());
+  if (!histograms_)
     return false;
 
-  if (!top_->callbacks_.insert({name, cb}).second)
+  if (!callbacks_->insert({name, cb}).second)
     return false;
 
-  const HistogramMap::const_iterator it = top_->histograms_.find(name);
-  if (it != top_->histograms_.end())
+  const HistogramMap::const_iterator it = histograms_->find(name);
+  if (it != histograms_->end())
     it->second->SetFlags(HistogramBase::kCallbackExists);
 
   return true;
@@ -320,43 +332,42 @@
 
 // static
 void StatisticsRecorder::ClearCallback(const std::string& name) {
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  base::AutoLock auto_lock(lock_.Get());
+  if (!histograms_)
     return;
 
-  top_->callbacks_.erase(name);
+  callbacks_->erase(name);
 
   // We also clear the flag from the histogram (if it exists).
-  const HistogramMap::const_iterator it = top_->histograms_.find(name);
-  if (it != top_->histograms_.end())
+  const HistogramMap::const_iterator it = histograms_->find(name);
+  if (it != histograms_->end())
     it->second->ClearFlags(HistogramBase::kCallbackExists);
 }
 
 // static
 StatisticsRecorder::OnSampleCallback StatisticsRecorder::FindCallback(
     const std::string& name) {
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  base::AutoLock auto_lock(lock_.Get());
+  if (!histograms_)
     return OnSampleCallback();
 
-  const auto it = top_->callbacks_.find(name);
-  return it != top_->callbacks_.end() ? it->second : OnSampleCallback();
+  const auto it = callbacks_->find(name);
+  return it != callbacks_->end() ? it->second : OnSampleCallback();
 }
 
 // static
 size_t StatisticsRecorder::GetHistogramCount() {
-  const AutoLock auto_lock(lock_.Get());
-  return top_ ? top_->histograms_.size() : 0;
+  base::AutoLock auto_lock(lock_.Get());
+  return histograms_ ? histograms_->size() : 0;
 }
 
 // static
 void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_)
+  if (!histograms_)
     return;
 
-  const HistogramMap::iterator found = top_->histograms_.find(name);
-  if (found == top_->histograms_.end())
+  const HistogramMap::iterator found = histograms_->find(name);
+  if (found == histograms_->end())
     return;
 
   HistogramBase* const base = found->second;
@@ -368,40 +379,53 @@
     static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
   }
 
-  top_->histograms_.erase(found);
+  histograms_->erase(found);
 }
 
 // static
 std::unique_ptr<StatisticsRecorder>
 StatisticsRecorder::CreateTemporaryForTesting() {
-  const AutoLock auto_lock(lock_.Get());
   return WrapUnique(new StatisticsRecorder());
 }
 
 // static
+void StatisticsRecorder::UninitializeForTesting() {
+  // Stop now if it's never been initialized.
+  if (!histograms_)
+    return;
+
+  // Get the global instance and destruct it. It's held in static memory so
+  // can't "delete" it; call the destructor explicitly.
+  DCHECK(g_statistics_recorder_.private_instance_);
+  g_statistics_recorder_.Get().~StatisticsRecorder();
+
+  // Now the ugly part. There's no official way to release a LazyInstance once
+  // created so it's necessary to clear out an internal variable which
+  // shouldn't be publicly visible but is for initialization reasons.
+  g_statistics_recorder_.private_instance_ = 0;
+}
+
+// static
 void StatisticsRecorder::SetRecordChecker(
     std::unique_ptr<RecordHistogramChecker> record_checker) {
-  const AutoLock auto_lock(lock_.Get());
-  top_->record_checker_ = std::move(record_checker);
+  record_checker_ = record_checker.release();
 }
 
 // static
 bool StatisticsRecorder::ShouldRecordHistogram(uint64_t histogram_hash) {
-  const AutoLock auto_lock(lock_.Get());
-  return !top_ || !top_->record_checker_ ||
-         top_->record_checker_->ShouldRecord(histogram_hash);
+  return !record_checker_ || record_checker_->ShouldRecord(histogram_hash);
 }
 
 // static
 StatisticsRecorder::Histograms StatisticsRecorder::GetKnownHistograms(
     bool include_persistent) {
   Histograms known;
-  const AutoLock auto_lock(lock_.Get());
-  if (!top_ || top_->histograms_.empty())
+  base::AutoLock auto_lock(lock_.Get());
+  if (!histograms_ || histograms_->empty())
     return known;
 
-  known.reserve(top_->histograms_.size());
-  for (const auto& h : top_->histograms_) {
+  known.reserve(histograms_->size());
+  for (const auto& h : *histograms_) {
     if (include_persistent ||
         (h.second->flags() & HistogramBase::kIsPersistent) == 0)
       known.push_back(h.second);
@@ -412,6 +436,9 @@
 
 // static
 void StatisticsRecorder::ImportGlobalPersistentHistograms() {
+  if (!histograms_)
+    return;
+
   // Import histograms from known persistent storage. Histograms could have been
   // added by other processes and they must be fetched and recognized locally.
   // If the persistent memory segment is not shared between processes, this call
@@ -424,24 +451,73 @@
 // of main(), and hence it is not thread safe. It initializes globals to provide
 // support for all future calls.
 StatisticsRecorder::StatisticsRecorder() {
-  lock_.Get().AssertAcquired();
-  previous_ = top_;
-  top_ = this;
-  InitLogOnShutdownWhileLocked();
+  base::AutoLock auto_lock(lock_.Get());
+
+  existing_histograms_.reset(histograms_);
+  existing_callbacks_.reset(callbacks_);
+  existing_ranges_.reset(ranges_);
+  existing_providers_.reset(providers_);
+  existing_record_checker_.reset(record_checker_);
+
+  histograms_ = new HistogramMap;
+  callbacks_ = new CallbackMap;
+  ranges_ = new RangesMap;
+  providers_ = new HistogramProviders;
+  record_checker_ = nullptr;
+
+  InitLogOnShutdownWithoutLock();
+}
+
+void StatisticsRecorder::InitLogOnShutdownWithoutLock() {
+  if (!vlog_initialized_ && VLOG_IS_ON(1)) {
+    vlog_initialized_ = true;
+    AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this);
+  }
 }
 
 // static
-void StatisticsRecorder::InitLogOnShutdownWhileLocked() {
-  lock_.Get().AssertAcquired();
-  if (!is_vlog_initialized_ && VLOG_IS_ON(1)) {
-    is_vlog_initialized_ = true;
-    const auto dump_to_vlog = [](void*) {
-      std::string output;
-      WriteGraph("", &output);
-      VLOG(1) << output;
-    };
-    AtExitManager::RegisterCallback(dump_to_vlog, nullptr);
-  }
+void StatisticsRecorder::Reset() {
+  // Declared before auto_lock to ensure correct destruction order.
+  std::unique_ptr<HistogramMap> histograms_deleter;
+  std::unique_ptr<CallbackMap> callbacks_deleter;
+  std::unique_ptr<RangesMap> ranges_deleter;
+  std::unique_ptr<HistogramProviders> providers_deleter;
+  std::unique_ptr<RecordHistogramChecker> record_checker_deleter;
+  base::AutoLock auto_lock(lock_.Get());
+  histograms_deleter.reset(histograms_);
+  callbacks_deleter.reset(callbacks_);
+  ranges_deleter.reset(ranges_);
+  providers_deleter.reset(providers_);
+  record_checker_deleter.reset(record_checker_);
+  histograms_ = nullptr;
+  callbacks_ = nullptr;
+  ranges_ = nullptr;
+  providers_ = nullptr;
+  record_checker_ = nullptr;
+
+  // We are going to leak the histograms and the ranges.
 }
 
+// static
+void StatisticsRecorder::DumpHistogramsToVlog(void* instance) {
+  std::string output;
+  StatisticsRecorder::WriteGraph(std::string(), &output);
+  VLOG(1) << output;
+}
+
+// static
+StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = nullptr;
+// static
+StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = nullptr;
+// static
+StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = nullptr;
+// static
+StatisticsRecorder::HistogramProviders* StatisticsRecorder::providers_ =
+    nullptr;
+// static
+RecordHistogramChecker* StatisticsRecorder::record_checker_ = nullptr;
+// static
+base::LazyInstance<base::Lock>::Leaky StatisticsRecorder::lock_ =
+    LAZY_INSTANCE_INITIALIZER;
+
 }  // namespace base
diff --git a/base/metrics/statistics_recorder.h b/base/metrics/statistics_recorder.h
index 4af25812..25427fd 100644
--- a/base/metrics/statistics_recorder.h
+++ b/base/metrics/statistics_recorder.h
@@ -34,18 +34,6 @@
 class BucketRanges;
 class HistogramSnapshotManager;
 
-// In-memory recorder of usage statistics (aka metrics, aka histograms).
-//
-// Most of the methods are static and act on a global recorder. This global
-// recorder must first be initialized using Initialize() before being used. This
-// global recorder is internally synchronized and all the static methods are
-// thread safe.
-//
-// StatisticsRecorder doesn't have any public constructor. For testing purpose,
-// you can create a temporary recorder using the factory method
-// CreateTemporaryForTesting(). This temporary recorder becomes the global one
-// until deleted. When this temporary recorder is deleted, it restores the
-// previous global one.
 class BASE_EXPORT StatisticsRecorder {
  public:
   // An interface class that allows the StatisticsRecorder to forcibly merge
@@ -58,83 +46,58 @@
 
   typedef std::vector<HistogramBase*> Histograms;
 
-  // Restores the previous global recorder.
-  //
-  // When several temporary recorders are created using
-  // CreateTemporaryForTesting(), these recorders must be deleted in reverse
-  // order of creation.
-  //
-  // This method is thread safe.
-  //
-  // Precondition: The recorder being deleted is the current global recorder.
   ~StatisticsRecorder();
 
-  // Initializes the global recorder. Safe to call multiple times.
-  //
-  // This method is thread safe.
+  // Initializes the StatisticsRecorder system. Safe to call multiple times.
   static void Initialize();
 
-  // Finds out if histograms can now be registered into our list.
-  //
-  // This method is thread safe.
+  // Find out if histograms can now be registered into our list.
   static bool IsActive();
 
-  // Registers a provider of histograms that can be called to merge those into
-  // the global recorder. Calls to ImportProvidedHistograms() will fetch from
-  // registered providers.
-  //
-  // This method is thread safe.
+  // Register a provider of histograms that can be called to merge those into
+  // the global StatisticsRecorder. Calls to ImportProvidedHistograms() will
+  // fetch from registered providers.
   static void RegisterHistogramProvider(
       const WeakPtr<HistogramProvider>& provider);
 
-  // Registers or adds a new histogram to the collection of statistics. If an
+  // Register, or add a new histogram to the collection of statistics. If an
   // identically named histogram is already registered, then the argument
-  // |histogram| will be deleted. The returned value is always the registered
+  // |histogram| will deleted.  The returned value is always the registered
   // histogram (either the argument, or the pre-existing registered histogram).
-  //
-  // This method is thread safe.
   static HistogramBase* RegisterOrDeleteDuplicate(HistogramBase* histogram);
 
-  // Registers or adds a new BucketRanges. If an equivalent BucketRanges is
-  // already registered, then the argument |ranges| will be deleted. The
-  // returned value is always the registered BucketRanges (either the argument,
-  // or the pre-existing one).
-  //
-  // This method is thread safe.
+  // Register, or add a new BucketRanges. If an identically BucketRanges is
+  // already registered, then the argument |ranges| will deleted. The returned
+  // value is always the registered BucketRanges (either the argument, or the
+  // pre-existing one).
   static const BucketRanges* RegisterOrDeleteDuplicateRanges(
       const BucketRanges* ranges);
 
   // Methods for appending histogram data to a string.  Only histograms which
   // have |query| as a substring are written to |output| (an empty string will
   // process all registered histograms).
-  //
-  // These methods are thread safe.
   static void WriteHTMLGraph(const std::string& query, std::string* output);
   static void WriteGraph(const std::string& query, std::string* output);
 
   // Returns the histograms with |verbosity_level| as the serialization
   // verbosity.
-  //
-  // This method is thread safe.
   static std::string ToJSON(JSONVerbosityLevel verbosity_level);
 
-  // Extracts histograms which were marked for use by UMA.
+  // Method for extracting histograms which were marked for use by UMA.
   //
   // This method is thread safe.
   static void GetHistograms(Histograms* output);
 
-  // Extracts BucketRanges used by all histograms registered.
+  // Method for extracting BucketRanges used by all histograms registered.
   static void GetBucketRanges(std::vector<const BucketRanges*>* output);
 
-  // Finds a histogram by name. Matches the exact name. Returns a null pointer
-  // if a matching histogram is not found.
+  // Find a histogram by name. It matches the exact name.
+  // It returns NULL if a matching histogram is not found.
   //
   // This method is thread safe.
   static HistogramBase* FindHistogram(base::StringPiece name);
 
-  // Imports histograms from providers.
-  //
-  // This method must be called on the UI thread.
+  // Imports histograms from providers. This must be called on the UI thread.
   static void ImportProvidedHistograms();
 
   // Snapshots all histograms via |snapshot_manager|. |flags_to_set| is used to
@@ -147,81 +110,71 @@
                             HistogramBase::Flags required_flags,
                             HistogramSnapshotManager* snapshot_manager);
 
-  // Extracts registered histograms. Only histograms which have |query| as a
-  // substring are extracted. An empty query will extract all registered
-  // histograms.
+  // GetSnapshot copies some of the pointers to registered histograms into the
+  // caller supplied vector (Histograms). Only histograms which have |query| as
+  // a substring are copied (an empty string will process all registered
+  // histograms).
   //
   // This method is thread safe.
   static void GetSnapshot(const std::string& query, Histograms* snapshot);
 
   typedef base::Callback<void(HistogramBase::Sample)> OnSampleCallback;
 
-  // Sets the callback to notify when a new sample is recorded on the histogram
-  // referred to by |histogram_name|. Can be called before or after the
-  // histogram is created. Returns whether the callback was successfully set.
-  //
-  // This method is thread safe.
+  // SetCallback sets the callback to notify when a new sample is recorded on
+  // the histogram referred to by |histogram_name|. The call to this method can
+  // be be done before or after the histogram is created. This method is thread
+  // safe. The return value is whether or not the callback was successfully set.
   static bool SetCallback(const std::string& histogram_name,
                           const OnSampleCallback& callback);
 
-  // Clears any callback set on the histogram referred to by |histogram_name|.
-  //
-  // This method is thread safe.
+  // ClearCallback clears any callback set on the histogram referred to by
+  // |histogram_name|. This method is thread safe.
   static void ClearCallback(const std::string& histogram_name);
 
-  // Retrieves the callback for the histogram referred to by |histogram_name|,
-  // or a null callback if no callback exists for this histogram.
-  //
-  // This method is thread safe.
+  // FindCallback retrieves the callback for the histogram referred to by
+  // |histogram_name|, or a null callback if no callback exists for this
+  // histogram. This method is thread safe.
   static OnSampleCallback FindCallback(const std::string& histogram_name);
 
   // Returns the number of known histograms.
-  //
-  // This method is thread safe.
   static size_t GetHistogramCount();
 
   // Initializes logging histograms with --v=1. Safe to call multiple times.
   // Is called from ctor but for browser it seems that it is more useful to
   // start logging after statistics recorder, so we need to init log-on-shutdown
   // later.
-  //
-  // This method is thread safe.
   static void InitLogOnShutdown();
 
   // Removes a histogram from the internal set of known ones. This can be
   // necessary during testing persistent histograms where the underlying
   // memory is being released.
-  //
-  // This method is thread safe.
   static void ForgetHistogramForTesting(base::StringPiece name);
 
-  // Creates a temporary StatisticsRecorder object for testing purposes. All new
-  // histograms will be registered in it until it is destructed or pushed aside
-  // for the lifetime of yet another StatisticsRecorder object. The destruction
-  // of the returned object will re-activate the previous one.
-  // StatisticsRecorder objects must be deleted in the opposite order to which
-  // they're created.
-  //
-  // This method is thread safe.
+  // Creates a local StatisticsRecorder object for testing purposes. All new
+  // histograms will be registered in it until it is destructed or pushed
+  // aside for the lifetime of yet another SR object. The destruction of the
+  // returned object will re-activate the previous one. Always release SR
+  // objects in the opposite order to which they're created.
   static std::unique_ptr<StatisticsRecorder> CreateTemporaryForTesting()
       WARN_UNUSED_RESULT;
 
+  // Resets any global instance of the statistics-recorder that was created
+  // by a call to Initialize().
+  static void UninitializeForTesting();
+
   // Sets the record checker for determining if a histogram should be recorded.
   // Record checker doesn't affect any already recorded histograms, so this
   // method must be called very early, before any threads have started.
   // Record checker methods can be called on any thread, so they shouldn't
   // mutate any state.
-  //
   // TODO(iburak): This is not yet hooked up to histogram recording
   // infrastructure.
   static void SetRecordChecker(
       std::unique_ptr<RecordHistogramChecker> record_checker);
 
-  // Checks if the given histogram should be recorded based on the
-  // ShouldRecord() method of the record checker. If the record checker is not
-  // set, returns true.
-  //
-  // This method is thread safe.
+  // Returns true iff the given histogram should be recorded based on
+  // the ShouldRecord() method of the record checker.
+  // If the record checker is not set, returns true.
   static bool ShouldRecordHistogram(uint64_t histogram_hash);
 
  private:
@@ -246,57 +199,52 @@
       unordered_set<const BucketRanges*, BucketRangesHash, BucketRangesEqual>
           RangesMap;
 
+  friend struct LazyInstanceTraitsBase<StatisticsRecorder>;
   friend class StatisticsRecorderTest;
   FRIEND_TEST_ALL_PREFIXES(StatisticsRecorderTest, IterationTest);
 
-  // Fetches set of existing histograms. Ownership of the individual histograms
+  // Fetch set of existing histograms. Ownership of the individual histograms
   // remains with the StatisticsRecorder.
-  //
-  // This method is thread safe.
   static Histograms GetKnownHistograms(bool include_persistent);
 
-  // Gets histogram providers.
-  //
-  // This method is thread safe.
-  static HistogramProviders GetHistogramProviders();
-
-  // Imports histograms from global persistent memory.
-  //
-  // Precondition: The global lock must not be held during this call.
+  // Imports histograms from global persistent memory. The global lock must
+  // not be held during this call.
   static void ImportGlobalPersistentHistograms();
 
-  // Constructs a new StatisticsRecorder and sets it as the current global
-  // recorder.
-  //
-  // Precondition: The global lock is already acquired.
+  // The constructor just initializes static members. Usually client code should
+  // use Initialize to do this. But in test code, you can friend this class and
+  // call the constructor to get a clean StatisticsRecorder.
   StatisticsRecorder();
 
   // Initialize implementation but without lock. Caller should guard
   // StatisticsRecorder by itself if needed (it isn't in unit tests).
-  //
-  // Precondition: The global lock is already acquired.
-  static void InitLogOnShutdownWhileLocked();
+  void InitLogOnShutdownWithoutLock();
 
-  HistogramMap histograms_;
-  CallbackMap callbacks_;
-  RangesMap ranges_;
-  HistogramProviders providers_;
-  std::unique_ptr<RecordHistogramChecker> record_checker_;
+  // These are copies of everything that existed when the (test) Statistics-
+  // Recorder was created. The global ones have to be moved aside to create a
+  // clean environment.
+  std::unique_ptr<HistogramMap> existing_histograms_;
+  std::unique_ptr<CallbackMap> existing_callbacks_;
+  std::unique_ptr<RangesMap> existing_ranges_;
+  std::unique_ptr<HistogramProviders> existing_providers_;
+  std::unique_ptr<RecordHistogramChecker> existing_record_checker_;
 
-  // Previous global recorder that existed when this one was created.
-  StatisticsRecorder* previous_ = nullptr;
+  bool vlog_initialized_ = false;
 
-  // Global lock for internal synchronization.
-  static LazyInstance<Lock>::Leaky lock_;
+  static void Reset();
+  static void DumpHistogramsToVlog(void* instance);
 
-  // Current global recorder. This recorder is used by static methods. When a
-  // new global recorder is created by CreateTemporaryForTesting(), then the
-  // previous global recorder is referenced by top_->previous_.
-  static StatisticsRecorder* top_;
+  static HistogramMap* histograms_;
+  static CallbackMap* callbacks_;
+  static RangesMap* ranges_;
+  static HistogramProviders* providers_;
+  static RecordHistogramChecker* record_checker_;
 
-  // Tracks whether InitLogOnShutdownWhileLocked() has registered a logging
-  // function that will be called when the program finishes.
-  static bool is_vlog_initialized_;
+  // Lock protects access to above maps. This is a LazyInstance to avoid races
+  // when the above methods are used before Initialize(). Previously each method
+  // would do |if (!lock_) return;| which would race with
+  // |lock_ = new Lock;| in StatisticsRecorder(). http://crbug.com/672852.
+  static base::LazyInstance<base::Lock>::Leaky lock_;
 
   DISALLOW_COPY_AND_ASSIGN(StatisticsRecorder);
 };
diff --git a/base/metrics/statistics_recorder_unittest.cc b/base/metrics/statistics_recorder_unittest.cc
index ada0523..92d1bba6 100644
--- a/base/metrics/statistics_recorder_unittest.cc
+++ b/base/metrics/statistics_recorder_unittest.cc
@@ -30,7 +30,9 @@
  public:
   LogStateSaver() : old_min_log_level_(logging::GetMinLogLevel()) {}
 
-  ~LogStateSaver() { logging::SetMinLogLevel(old_min_log_level_); }
+  ~LogStateSaver() {
+    logging::SetMinLogLevel(old_min_log_level_);
+  }
 
  private:
   int old_min_log_level_;
@@ -68,8 +70,8 @@
 
     // Use persistent memory for histograms if so indicated by test parameter.
     if (use_persistent_histogram_allocator_) {
-      GlobalHistogramAllocator::CreateWithLocalMemory(kAllocatorMemorySize, 0,
-                                                      "StatisticsRecorderTest");
+      GlobalHistogramAllocator::CreateWithLocalMemory(
+          kAllocatorMemorySize, 0, "StatisticsRecorderTest");
     }
   }
 
@@ -80,10 +82,14 @@
 
   void InitializeStatisticsRecorder() {
     DCHECK(!statistics_recorder_);
+    StatisticsRecorder::UninitializeForTesting();
     statistics_recorder_ = StatisticsRecorder::CreateTemporaryForTesting();
   }
 
-  void UninitializeStatisticsRecorder() { statistics_recorder_.reset(); }
+  void UninitializeStatisticsRecorder() {
+    statistics_recorder_.reset();
+    StatisticsRecorder::UninitializeForTesting();
+  }
 
   Histogram* CreateHistogram(const char* name,
                              HistogramBase::Sample min,
@@ -96,15 +102,18 @@
     return new Histogram(name, min, max, registered_ranges);
   }
 
-  void DeleteHistogram(HistogramBase* histogram) { delete histogram; }
+  void DeleteHistogram(HistogramBase* histogram) {
+    delete histogram;
+  }
 
-  void InitLogOnShutdown() { StatisticsRecorder::InitLogOnShutdown(); }
+  void InitLogOnShutdown() {
+    DCHECK(statistics_recorder_);
+    statistics_recorder_->InitLogOnShutdownWithoutLock();
+  }
 
-  bool IsVLogInitialized() { return StatisticsRecorder::is_vlog_initialized_; }
-
-  void ResetVLogInitialized() {
-    UninitializeStatisticsRecorder();
-    StatisticsRecorder::is_vlog_initialized_ = false;
+  bool VLogInitialized() {
+    DCHECK(statistics_recorder_);
+    return statistics_recorder_->vlog_initialized_;
   }
 
   const bool use_persistent_histogram_allocator_;
@@ -268,8 +277,8 @@
   ASSERT_EQ(0u, registered_histograms.size());
 
   // Create a histogram.
-  HistogramBase* histogram = Histogram::FactoryGet("TestHistogram", 1, 1000, 10,
-                                                   HistogramBase::kNoFlags);
+  HistogramBase* histogram = Histogram::FactoryGet(
+      "TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
   registered_histograms.clear();
   StatisticsRecorder::GetHistograms(&registered_histograms);
   EXPECT_EQ(1u, registered_histograms.size());
@@ -283,15 +292,15 @@
   EXPECT_EQ(histogram, histogram2);
 
   // Create a LinearHistogram.
-  histogram = LinearHistogram::FactoryGet("TestLinearHistogram", 1, 1000, 10,
-                                          HistogramBase::kNoFlags);
+  histogram = LinearHistogram::FactoryGet(
+      "TestLinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
   registered_histograms.clear();
   StatisticsRecorder::GetHistograms(&registered_histograms);
   EXPECT_EQ(2u, registered_histograms.size());
 
   // Create a BooleanHistogram.
-  histogram = BooleanHistogram::FactoryGet("TestBooleanHistogram",
-                                           HistogramBase::kNoFlags);
+  histogram = BooleanHistogram::FactoryGet(
+      "TestBooleanHistogram", HistogramBase::kNoFlags);
   registered_histograms.clear();
   StatisticsRecorder::GetHistograms(&registered_histograms);
   EXPECT_EQ(3u, registered_histograms.size());
@@ -300,8 +309,8 @@
   std::vector<int> custom_ranges;
   custom_ranges.push_back(1);
   custom_ranges.push_back(5);
-  histogram = CustomHistogram::FactoryGet("TestCustomHistogram", custom_ranges,
-                                          HistogramBase::kNoFlags);
+  histogram = CustomHistogram::FactoryGet(
+      "TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags);
   registered_histograms.clear();
   StatisticsRecorder::GetHistograms(&registered_histograms);
   EXPECT_EQ(4u, registered_histograms.size());
@@ -615,33 +624,33 @@
 }
 
 TEST_P(StatisticsRecorderTest, LogOnShutdownNotInitialized) {
-  ResetVLogInitialized();
+  UninitializeStatisticsRecorder();
   logging::SetMinLogLevel(logging::LOG_WARNING);
   InitializeStatisticsRecorder();
   EXPECT_FALSE(VLOG_IS_ON(1));
-  EXPECT_FALSE(IsVLogInitialized());
+  EXPECT_FALSE(VLogInitialized());
   InitLogOnShutdown();
-  EXPECT_FALSE(IsVLogInitialized());
+  EXPECT_FALSE(VLogInitialized());
 }
 
 TEST_P(StatisticsRecorderTest, LogOnShutdownInitializedExplicitly) {
-  ResetVLogInitialized();
+  UninitializeStatisticsRecorder();
   logging::SetMinLogLevel(logging::LOG_WARNING);
   InitializeStatisticsRecorder();
   EXPECT_FALSE(VLOG_IS_ON(1));
-  EXPECT_FALSE(IsVLogInitialized());
+  EXPECT_FALSE(VLogInitialized());
   logging::SetMinLogLevel(logging::LOG_VERBOSE);
   EXPECT_TRUE(VLOG_IS_ON(1));
   InitLogOnShutdown();
-  EXPECT_TRUE(IsVLogInitialized());
+  EXPECT_TRUE(VLogInitialized());
 }
 
 TEST_P(StatisticsRecorderTest, LogOnShutdownInitialized) {
-  ResetVLogInitialized();
+  UninitializeStatisticsRecorder();
   logging::SetMinLogLevel(logging::LOG_VERBOSE);
   InitializeStatisticsRecorder();
   EXPECT_TRUE(VLOG_IS_ON(1));
-  EXPECT_TRUE(IsVLogInitialized());
+  EXPECT_TRUE(VLogInitialized());
 }
 
 class TestHistogramProvider : public StatisticsRecorder::HistogramProvider {
diff --git a/base/optional.h b/base/optional.h
index fbc62893..7e039c6f 100644
--- a/base/optional.h
+++ b/base/optional.h
@@ -134,7 +134,7 @@
   template <class... Args>
   void Init(Args&&... args) {
     DCHECK(storage_.is_null_);
-    new (&storage_.value_) T(std::forward<Args>(args)...);
+    ::new (&storage_.value_) T(std::forward<Args>(args)...);
     storage_.is_null_ = false;
   }
 
diff --git a/base/optional_unittest.cc b/base/optional_unittest.cc
index 479424d6..60347d5e 100644
--- a/base/optional_unittest.cc
+++ b/base/optional_unittest.cc
@@ -104,6 +104,25 @@
   ~NonTriviallyDestructible() {}
 };
 
+class DeletedDefaultConstructor {
+ public:
+  DeletedDefaultConstructor() = delete;
+  DeletedDefaultConstructor(int foo) : foo_(foo) {}
+
+  int foo() const { return foo_; }
+
+ private:
+  int foo_;
+};
+
+class DeleteNewOperators {
+ public:
+  void* operator new(size_t) = delete;
+  void* operator new(size_t, void*) = delete;
+  void* operator new[](size_t) = delete;
+  void* operator new[](size_t, void*) = delete;
+};
+
 }  // anonymous namespace
 
 static_assert(std::is_trivially_destructible<Optional<int>>::value,
@@ -1535,4 +1554,21 @@
   EXPECT_EQ(1, a->move_ctors_count());
 }
 
+TEST(OptionalTest, DontCallDefaultCtor) {
+  Optional<DeletedDefaultConstructor> a;
+  EXPECT_FALSE(a.has_value());
+
+  a = base::make_optional<DeletedDefaultConstructor>(42);
+  EXPECT_TRUE(a.has_value());
+  EXPECT_EQ(42, a->foo());
+}
+
+TEST(OptionalTest, DontCallNewMemberFunction) {
+  Optional<DeleteNewOperators> a;
+  EXPECT_FALSE(a.has_value());
+
+  a = DeleteNewOperators();
+  EXPECT_TRUE(a.has_value());
+}
+
 }  // namespace base
diff --git a/base/strings/string_number_conversions.cc b/base/strings/string_number_conversions.cc
index 5b133d4..46c229a 100644
--- a/base/strings/string_number_conversions.cc
+++ b/base/strings/string_number_conversions.cc
@@ -284,23 +284,6 @@
 typedef BaseHexIteratorRangeToUInt64Traits<StringPiece::const_iterator>
     HexIteratorRangeToUInt64Traits;
 
-template <typename STR>
-bool HexStringToBytesT(const STR& input, std::vector<uint8_t>* output) {
-  DCHECK_EQ(output->size(), 0u);
-  size_t count = input.size();
-  if (count == 0 || (count % 2) != 0)
-    return false;
-  for (uintptr_t i = 0; i < count / 2; ++i) {
-    uint8_t msb = 0;  // most significant 4 bits
-    uint8_t lsb = 0;  // least significant 4 bits
-    if (!CharToDigit<16>(input[i * 2], &msb) ||
-        !CharToDigit<16>(input[i * 2 + 1], &lsb))
-      return false;
-    output->push_back((msb << 4) | lsb);
-  }
-  return true;
-}
-
 template <typename VALUE, int BASE>
 class StringPieceToNumberTraits
     : public BaseIteratorRangeToNumberTraits<StringPiece::const_iterator,
@@ -498,8 +481,21 @@
       input.begin(), input.end(), output);
 }
 
-bool HexStringToBytes(const std::string& input, std::vector<uint8_t>* output) {
-  return HexStringToBytesT(input, output);
+bool HexStringToBytes(const StringPiece& input, std::vector<uint8_t>* output) {
+  DCHECK_EQ(output->size(), 0u);
+  size_t count = input.size();
+  if (count == 0 || (count % 2) != 0)
+    return false;
+  for (uintptr_t i = 0; i < count / 2; ++i) {
+    uint8_t msb = 0;  // most significant 4 bits
+    uint8_t lsb = 0;  // least significant 4 bits
+    if (!CharToDigit<16>(input[i * 2], &msb) ||
+        !CharToDigit<16>(input[i * 2 + 1], &lsb)) {
+      return false;
+    }
+    output->push_back((msb << 4) | lsb);
+  }
+  return true;
 }
 
 }  // namespace base
diff --git a/base/strings/string_number_conversions.h b/base/strings/string_number_conversions.h
index f334b308..798ce2f 100644
--- a/base/strings/string_number_conversions.h
+++ b/base/strings/string_number_conversions.h
@@ -158,7 +158,7 @@
 // |*output| will contain as many bytes as were successfully parsed prior to the
 // error.  There is no overflow, but input.size() must be evenly divisible by 2.
 // Leading 0x or +/- are not allowed.
-BASE_EXPORT bool HexStringToBytes(const std::string& input,
+BASE_EXPORT bool HexStringToBytes(const StringPiece& input,
                                   std::vector<uint8_t>* output);
 
 }  // namespace base
diff --git a/base/task_scheduler/task_tracker.cc b/base/task_scheduler/task_tracker.cc
index ca03f7f..2069f4d 100644
--- a/base/task_scheduler/task_tracker.cc
+++ b/base/task_scheduler/task_tracker.cc
@@ -477,8 +477,8 @@
 }
 #endif
 
-int TaskTracker::GetNumIncompleteUndelayedTasksForTesting() const {
-  return subtle::NoBarrier_Load(&num_incomplete_undelayed_tasks_);
+bool TaskTracker::HasIncompleteUndelayedTasksForTesting() const {
+  return subtle::Acquire_Load(&num_incomplete_undelayed_tasks_) != 0;
 }
 
 bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) {
diff --git a/base/task_scheduler/task_tracker.h b/base/task_scheduler/task_tracker.h
index f1ef25c..d425027 100644
--- a/base/task_scheduler/task_tracker.h
+++ b/base/task_scheduler/task_tracker.h
@@ -164,9 +164,10 @@
   virtual bool IsPostingBlockShutdownTaskAfterShutdownAllowed();
 #endif
 
-  // Returns the number of undelayed tasks that haven't completed their
-  // execution (still queued or in progress).
-  int GetNumIncompleteUndelayedTasksForTesting() const;
+  // Returns true if there are undelayed tasks that haven't completed their
+  // execution (still queued or in progress). If it returns false: the side-
+  // effects of all completed tasks are guaranteed to be visible to the caller.
+  bool HasIncompleteUndelayedTasksForTesting() const;
 
  private:
   class State;
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
index 353f72b..edbcbbbf 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
@@ -10,8 +10,8 @@
 import org.junit.Assert;
 import org.junit.Rule;
 
-import org.chromium.base.BaseChromiumApplication;
 import org.chromium.base.CommandLine;
+import org.chromium.base.CommandLineInitUtil;
 import org.chromium.base.test.BaseTestResult.PreTestHook;
 
 import java.lang.annotation.ElementType;
@@ -91,7 +91,7 @@
     public static void setUp(Context targetContext, AnnotatedElement element) {
         Assert.assertNotNull("Unable to get a non-null target context.", targetContext);
         CommandLine.reset();
-        BaseChromiumApplication.initCommandLine(targetContext);
+        CommandLineInitUtil.initCommandLine(targetContext, getTestCmdLineFile());
         Set<String> enableFeatures = new HashSet<String>();
         Set<String> disableFeatures = new HashSet<String>();
         Set<String> flags = getFlags(element);
@@ -189,4 +189,8 @@
 
         };
     }
+
+    public static String getTestCmdLineFile() {
+        return "test-cmdline-file";
+    }
 }
diff --git a/base/test/scoped_task_environment.cc b/base/test/scoped_task_environment.cc
index a9fe5ac..e93ad5b 100644
--- a/base/test/scoped_task_environment.cc
+++ b/base/test/scoped_task_environment.cc
@@ -209,10 +209,10 @@
     // the above logic as it'd then be possible for a TaskScheduler task to be
     // running during the DisallowRunTasks() test, causing it to fail, but then
     // post to the main thread and complete before the loop's condition is
-    // verified which could result in GetNumIncompleteUndelayedTasksForTesting()
-    // returning 0 and the loop erroneously exiting with a pending task on the
-    // main thread.
-    if (task_tracker_->GetNumIncompleteUndelayedTasksForTesting() == 0)
+    // verified which could result in HasIncompleteUndelayedTasksForTesting()
+    // returning false and the loop erroneously exiting with a pending task on
+    // the main thread.
+    if (!task_tracker_->HasIncompleteUndelayedTasksForTesting())
       break;
   }
 
diff --git a/base/trace_event/trace_config.h b/base/trace_event/trace_config.h
index 2e7f6c0..5460489 100644
--- a/base/trace_event/trace_config.h
+++ b/base/trace_event/trace_config.h
@@ -26,6 +26,8 @@
 class ConvertableToTraceFormat;
 
 // Options determines how the trace buffer stores data.
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base
 enum TraceRecordMode {
   // Record until the trace buffer is full.
   RECORD_UNTIL_FULL,
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index f079468c..f2ff5e27 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -241,15 +241,10 @@
       @trace_event.traced
       def create_flag_changer(dev):
         if self._test_instance.flags:
-          if not self._test_instance.package_info:
-            logging.error("Couldn't set flags: no package info")
-          elif not self._test_instance.package_info.cmdline_file:
-            logging.error("Couldn't set flags: no cmdline_file")
-          else:
-            self._CreateFlagChangerIfNeeded(dev)
-            logging.debug('Attempting to set flags: %r',
-                          self._test_instance.flags)
-            self._flag_changers[str(dev)].AddFlags(self._test_instance.flags)
+          self._CreateFlagChangerIfNeeded(dev)
+          logging.debug('Attempting to set flags: %r',
+                        self._test_instance.flags)
+          self._flag_changers[str(dev)].AddFlags(self._test_instance.flags)
 
         valgrind_tools.SetChromeTimeoutScale(
             dev, self._test_instance.timeout_scale)
@@ -328,7 +323,7 @@
   def _CreateFlagChangerIfNeeded(self, device):
     if not str(device) in self._flag_changers:
       self._flag_changers[str(device)] = flag_changer.FlagChanger(
-        device, self._test_instance.package_info.cmdline_file)
+        device, "test-cmdline-file")
 
   #override
   def _CreateShards(self, tests):
diff --git a/build/android/pylib/results/presentation/test_results_presentation.py b/build/android/pylib/results/presentation/test_results_presentation.py
index cb3f1ae..21137fe 100755
--- a/build/android/pylib/results/presentation/test_results_presentation.py
+++ b/build/android/pylib/results/presentation/test_results_presentation.py
@@ -386,6 +386,10 @@
            '(Output of the swarming.py collect '
            '--task-summary-json=XXX command.)')
   parser.add_argument(
+      '--task-output-dir',
+      help='(Swarming Merge Script API) '
+           'Directory containing all swarming task results.')
+  parser.add_argument(
       'positional', nargs='*',
       help='output.json from shards.')
 
diff --git a/build/android/pylib/symbols/stack_symbolizer.py b/build/android/pylib/symbols/stack_symbolizer.py
index c1c9afbb..05e40657 100644
--- a/build/android/pylib/symbols/stack_symbolizer.py
+++ b/build/android/pylib/symbols/stack_symbolizer.py
@@ -7,6 +7,7 @@
 import re
 import shutil
 import tempfile
+import time
 import zipfile
 
 from devil.utils import cmd_helper
@@ -38,6 +39,7 @@
     self._libs_dir = None
     self._apk_libs = []
     self._has_unzipped = False
+    self._time_spent_symbolizing = 0
 
 
   def __del__(self):
@@ -52,6 +54,9 @@
     if self._libs_dir:
       shutil.rmtree(self._libs_dir)
       self._libs_dir = None
+    if self._time_spent_symbolizing > 0:
+      logging.info(
+          'Total time spent symbolizing: %.2fs', self._time_spent_symbolizing)
 
 
   def UnzipAPKIfNecessary(self):
@@ -97,7 +102,11 @@
     with tempfile.NamedTemporaryFile() as f:
       f.write('\n'.join(data_to_symbolize))
       f.flush()
-      _, output = cmd_helper.GetCmdStatusAndOutput(cmd + [f.name], env=env)
+      start = time.time()
+      try:
+        _, output = cmd_helper.GetCmdStatusAndOutput(cmd + [f.name], env=env)
+      finally:
+        self._time_spent_symbolizing += time.time() - start
     for line in output.splitlines():
       if not include_stack and 'Stack Data:' in line:
         break
diff --git a/build/cipd/android/android.ensure b/build/cipd/android/android.ensure
index 9a2c705c..cee2bf1 100644
--- a/build/cipd/android/android.ensure
+++ b/build/cipd/android/android.ensure
@@ -135,3 +135,10 @@
 @Subdir third_party/xstream
 chromium/third_party/xstream version:1.4.8-cr0
 
+# Three unchanging lines
+# avoid the horror that is
+# endless merge conflicts
+
+@Subdir chrome/android/profiles
+chromium/afdo/profiles/android version:3309
+
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index b8d515426..853c849 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -525,6 +525,9 @@
   "//build/config/compiler:default_optimization",
   "//build/config/compiler:default_stack_frames",
   "//build/config/compiler:default_symbols",
+
+  # TODO(crbug.com/795158): Remove once libwidevinecdmadapter.so is fixed.
+  "//build/config/compiler:default_fatal_linker_warnings",
   "//build/config/compiler:no_exceptions",
   "//build/config/compiler:no_rtti",
   "//build/config/compiler:runtime_library",
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index a894a39..5154ead 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -265,16 +265,6 @@
         cflags += [ "-fstack-protector" ]
       }
     }
-
-    # Linker warnings.
-    if (fatal_linker_warnings && !(is_chromeos && current_cpu == "arm") &&
-        !(is_android && use_order_profiling) && !is_mac && !is_ios &&
-        current_os != "aix") {
-      # TODO(jochen): Enable this on chromeos on arm. http://crbug.com/356580
-      # TODO(lizeb,pasko): Fix link errors when linking with order_profiling=1
-      # crbug.com/485542
-      ldflags += [ "-Wl,--fatal-warnings" ]
-    }
   }
 
   # Eliminate build metadata (__DATE__, __TIME__ and __TIMESTAMP__) for
@@ -2050,3 +2040,19 @@
     cflags_objcc = common_flags
   }
 }
+
+# Default linker warnings.
+config("default_fatal_linker_warnings") {
+  ldflags = []
+
+  # Linker warnings.
+  if (!is_win && fatal_linker_warnings &&
+      !(is_chromeos && current_cpu == "arm") &&
+      !(is_android && use_order_profiling) && !is_mac && !is_ios &&
+      current_os != "aix") {
+    # TODO(jochen): Enable this on chromeos on arm. http://crbug.com/356580
+    # TODO(lizeb,pasko): Fix link errors when linking with order_profiling=1
+    # crbug.com/485542
+    ldflags += [ "-Wl,--fatal-warnings" ]
+  }
+}
diff --git a/build/fuchsia/runner_common.py b/build/fuchsia/runner_common.py
index 7c7f2c6..bb8eaad7 100755
--- a/build/fuchsia/runner_common.py
+++ b/build/fuchsia/runner_common.py
@@ -146,8 +146,9 @@
     symbols_mapping[os.path.basename(target)] = source
     symbols_mapping[target] = source
 
-    if dry_run:
-      print 'Symbols:', binary_name, '->', symbols_mapping[target]
+  if dry_run:
+    for target, path in symbols_mapping.iteritems():
+      print 'Symbols:', target, '->', path
 
   return symbols_mapping
 
diff --git a/build/toolchain/toolchain.gni b/build/toolchain/toolchain.gni
index ebb98fe..949aa09f 100644
--- a/build/toolchain/toolchain.gni
+++ b/build/toolchain/toolchain.gni
@@ -48,7 +48,12 @@
 declare_args() {
   if (is_clang) {
     # Clang compiler version. Clang files are placed at version-dependent paths.
-    clang_version = "6.0.0"
+    if (llvm_force_head_revision) {
+      clang_version = "7.0.0"
+    } else {
+      # TODO(hans): Trunk was updated; remove after the next roll.
+      clang_version = "6.0.0"
+    }
   }
 }
 
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index f7e50d1..300b84b 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -241,6 +241,8 @@
     "tiles/raster_tile_priority_queue_required.h",
     "tiles/software_image_decode_cache.cc",
     "tiles/software_image_decode_cache.h",
+    "tiles/software_image_decode_cache_utils.cc",
+    "tiles/software_image_decode_cache_utils.h",
     "tiles/tile.cc",
     "tiles/tile.h",
     "tiles/tile_draw_info.cc",
diff --git a/cc/resources/video_resource_updater.cc b/cc/resources/video_resource_updater.cc
index dac835d..5036233 100644
--- a/cc/resources/video_resource_updater.cc
+++ b/cc/resources/video_resource_updater.cc
@@ -486,21 +486,20 @@
 
       // The |resource_size_pixels| is the size of the resource we want to
       // upload to.
-      gfx::Size resource_size_pixels = plane_resource.resource_size();
+      const gfx::Size resource_size_pixels = plane_resource.resource_size();
       // The |video_stride_bytes| is the width of the video frame we are
       // uploading (including non-frame data to fill in the stride).
-      int video_stride_bytes = video_frame->stride(i);
+      const int video_stride_bytes = video_frame->stride(i);
 
-      size_t bytes_per_row = ResourceUtil::CheckedWidthInBytes<size_t>(
+      const size_t bytes_per_row = ResourceUtil::CheckedWidthInBytes<size_t>(
           resource_size_pixels.width(), plane_resource.resource_format());
       // Use 4-byte row alignment (OpenGL default) for upload performance.
       // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
-      size_t upload_image_stride =
+      const size_t upload_image_stride =
           MathUtil::CheckedRoundUp<size_t>(bytes_per_row, 4u);
 
       // R16_EXT can represent 16-bit int, so we don't need a conversion step.
       bool needs_conversion = false;
-      int shift = 0;
 
       // viz::LUMINANCE_F16 uses half-floats, so we always need a conversion
       // step.
@@ -511,7 +510,6 @@
         // If bits_per_channel > 8 and we can't use viz::LUMINANCE_F16 or
         // R16_EXT we need to shift the data down and create an 8-bit texture.
         needs_conversion = true;
-        shift = bits_per_channel - 8;
       }
       const uint8_t* pixels;
       if (static_cast<int>(upload_image_stride) == video_stride_bytes &&
@@ -519,35 +517,35 @@
         pixels = video_frame->data(i);
       } else {
         // Avoid malloc for each frame/plane if possible.
-        size_t needed_size =
+        const size_t needed_size =
             upload_image_stride * resource_size_pixels.height();
         if (upload_pixels_.size() < needed_size)
           upload_pixels_.resize(needed_size);
 
-        for (int row = 0; row < resource_size_pixels.height(); ++row) {
-          if (plane_resource.resource_format() == viz::LUMINANCE_F16) {
+        if (plane_resource.resource_format() == viz::LUMINANCE_F16) {
+          for (int row = 0; row < resource_size_pixels.height(); ++row) {
             uint16_t* dst = reinterpret_cast<uint16_t*>(
                 &upload_pixels_[upload_image_stride * row]);
             const uint16_t* src = reinterpret_cast<uint16_t*>(
                 video_frame->data(i) + (video_stride_bytes * row));
             half_float_maker->MakeHalfFloats(src, bytes_per_row / 2, dst);
-          } else if (shift != 0) {
-            // We have more-than-8-bit input which we need to shift
-            // down to fit it into an 8-bit texture.
-            uint8_t* dst = &upload_pixels_[upload_image_stride * row];
-            const uint16_t* src = reinterpret_cast<uint16_t*>(
-                video_frame->data(i) + (video_stride_bytes * row));
-            for (size_t i = 0; i < bytes_per_row; i++)
-              dst[i] = src[i] >> shift;
-          } else {
-            // Input and output are the same size and format, but
-            // differ in stride, copy one row at a time.
-            uint8_t* dst = &upload_pixels_[upload_image_stride * row];
-            const uint8_t* src =
-                video_frame->data(i) + (video_stride_bytes * row);
-            memcpy(dst, src, bytes_per_row);
           }
+        } else if (bits_per_channel > 8) {
+          const int scale = 0x10000 >> (bits_per_channel - 8);
+          libyuv::Convert16To8Plane(
+              reinterpret_cast<uint16_t*>(video_frame->data(i)),
+              video_stride_bytes / 2, upload_pixels_.data(),
+              upload_image_stride, scale, bytes_per_row,
+              resource_size_pixels.height());
+        } else {
+          // Make a copy because input and output are the same size and
+          // format, but differ in stride.
+          libyuv::CopyPlane(video_frame->data(i), video_stride_bytes,
+                            upload_pixels_.data(), upload_image_stride,
+                            resource_size_pixels.width(),
+                            resource_size_pixels.height());
         }
+
         pixels = &upload_pixels_[0];
       }
 
diff --git a/cc/test/layer_tree_pixel_test.cc b/cc/test/layer_tree_pixel_test.cc
index eb7e641..e246ede7 100644
--- a/cc/test/layer_tree_pixel_test.cc
+++ b/cc/test/layer_tree_pixel_test.cc
@@ -54,12 +54,16 @@
   bool synchronous_composite =
       !HasImplThread() &&
       !layer_tree_host()->GetSettings().single_thread_proxy_scheduler;
+  viz::RendererSettings test_settings = renderer_settings;
+  // Keep texture sizes exactly matching the bounds of the RenderPass to avoid
+  // floating point badness in texcoords.
+  test_settings.dont_round_texture_sizes_for_pixel_tests = true;
   auto delegating_output_surface =
       std::make_unique<viz::TestLayerTreeFrameSink>(
           compositor_context_provider, worker_context_provider,
-          shared_bitmap_manager(), gpu_memory_buffer_manager(),
-          renderer_settings, ImplThreadTaskRunner(), synchronous_composite,
-          disable_display_vsync, refresh_rate);
+          shared_bitmap_manager(), gpu_memory_buffer_manager(), test_settings,
+          ImplThreadTaskRunner(), synchronous_composite, disable_display_vsync,
+          refresh_rate);
   delegating_output_surface->SetEnlargePassTextureAmount(
       enlarge_texture_amount_);
   return delegating_output_surface;
diff --git a/cc/test/pixel_test.cc b/cc/test/pixel_test.cc
index 3c974b7..8d3e23e4 100644
--- a/cc/test/pixel_test.cc
+++ b/cc/test/pixel_test.cc
@@ -36,7 +36,11 @@
 PixelTest::PixelTest()
     : device_viewport_size_(gfx::Size(200, 200)),
       disable_picture_quad_image_filtering_(false),
-      output_surface_client_(new FakeOutputSurfaceClient) {}
+      output_surface_client_(new FakeOutputSurfaceClient) {
+  // Keep texture sizes exactly matching the bounds of the RenderPass to avoid
+  // floating point badness in texcoords.
+  renderer_settings_.dont_round_texture_sizes_for_pixel_tests = true;
+}
 
 PixelTest::~PixelTest() = default;
 
diff --git a/cc/tiles/decoded_image_tracker_unittest.cc b/cc/tiles/decoded_image_tracker_unittest.cc
index 6f409f9e..f5bb5a37 100644
--- a/cc/tiles/decoded_image_tracker_unittest.cc
+++ b/cc/tiles/decoded_image_tracker_unittest.cc
@@ -21,8 +21,10 @@
   void UnlockImageDecode(ImageDecodeRequestId id) override {
     auto it = std::find_if(
         locked_ids_.begin(), locked_ids_.end(),
-        [id](const std::pair<const ImageDecodeRequestId, ImageDecodeCacheKey>&
-                 item) { return item.first == id; });
+        [id](const std::pair<const ImageDecodeRequestId,
+                             SoftwareImageDecodeCache::CacheKey>& item) {
+          return item.first == id;
+        });
     ASSERT_FALSE(it == locked_ids_.end());
     locked_ids_.erase(it);
   }
@@ -31,27 +33,32 @@
       const DrawImage& image,
       const ImageDecodedCallback& callback) override {
     auto id = next_id_++;
-    locked_ids_.insert(std::make_pair(
-        id, ImageDecodeCacheKey::FromDrawImage(image, kRGBA_8888_SkColorType)));
+    locked_ids_.insert(
+        std::make_pair(id, SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+                               image, kRGBA_8888_SkColorType)));
     callback.Run(id, ImageDecodeResult::SUCCESS);
     return id;
   }
 
   bool IsDrawImageLocked(const DrawImage& image) {
-    ImageDecodeCacheKey key =
-        ImageDecodeCacheKey::FromDrawImage(image, kRGBA_8888_SkColorType);
-    return std::find_if(locked_ids_.begin(), locked_ids_.end(),
-                        [&key](const std::pair<const ImageDecodeRequestId,
-                                               ImageDecodeCacheKey>& item) {
-                          return item.second == key;
-                        }) != locked_ids_.end();
+    SoftwareImageDecodeCache::CacheKey key =
+        SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+            image, kRGBA_8888_SkColorType);
+    return std::find_if(
+               locked_ids_.begin(), locked_ids_.end(),
+               [&key](
+                   const std::pair<const ImageDecodeRequestId,
+                                   SoftwareImageDecodeCache::CacheKey>& item) {
+                 return item.second == key;
+               }) != locked_ids_.end();
   }
 
   size_t num_locked_images() { return locked_ids_.size(); }
 
  private:
   ImageDecodeRequestId next_id_ = 1;
-  std::unordered_map<ImageDecodeRequestId, ImageDecodeCacheKey> locked_ids_;
+  std::unordered_map<ImageDecodeRequestId, SoftwareImageDecodeCache::CacheKey>
+      locked_ids_;
 };
 
 class DecodedImageTrackerTest : public testing::Test {
diff --git a/cc/tiles/software_image_decode_cache.cc b/cc/tiles/software_image_decode_cache.cc
index 6c5df401..765d94a 100644
--- a/cc/tiles/software_image_decode_cache.cc
+++ b/cc/tiles/software_image_decode_cache.cc
@@ -4,28 +4,18 @@
 
 #include "cc/tiles/software_image_decode_cache.h"
 
-#include <inttypes.h>
 #include <stdint.h>
 
-#include <algorithm>
-#include <functional>
-
 #include "base/format_macros.h"
 #include "base/macros.h"
-#include "base/memory/discardable_memory.h"
 #include "base/memory/memory_coordinator_client_registry.h"
-#include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/stringprintf.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/memory_dump_manager.h"
 #include "cc/base/devtools_instrumentation.h"
 #include "cc/base/histograms.h"
 #include "cc/raster/tile_task.h"
 #include "cc/tiles/mipmap_util.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkImage.h"
-#include "third_party/skia/include/core/SkPixmap.h"
 #include "ui/gfx/skia_util.h"
 
 using base::trace_event::MemoryAllocatorDump;
@@ -42,42 +32,28 @@
 const size_t kThrottledMaxItemsInCacheForSoftware = 100;
 const size_t kSuspendedMaxItemsInCacheForSoftware = 0;
 
-// If the size of the original sized image breaches kMemoryRatioToSubrect but we
-// don't need to scale the image, consider caching only the needed subrect.
-// The second part that much be true is that we cache only the needed subrect if
-// the total size needed for the subrect is at most kMemoryRatioToSubrect *
-// (size needed for the full original image).
-// Note that at least one of the dimensions has to be at least
-// kMinDimensionToSubrect before an image can breach the threshold.
-const size_t kMemoryThresholdToSubrect = 64 * 1024 * 1024;
-const int kMinDimensionToSubrect = 4 * 1024;
-const float kMemoryRatioToSubrect = 0.5f;
-
-// Tracing ID sequence for use in CacheEntry.
-base::AtomicSequenceNumber g_next_tracing_id_;
-
 class AutoRemoveKeyFromTaskMap {
  public:
   AutoRemoveKeyFromTaskMap(
-      std::unordered_map<SoftwareImageDecodeCache::ImageKey,
+      std::unordered_map<SoftwareImageDecodeCache::CacheKey,
                          scoped_refptr<TileTask>,
-                         SoftwareImageDecodeCache::ImageKeyHash>* task_map,
-      const SoftwareImageDecodeCache::ImageKey& key)
+                         SoftwareImageDecodeCache::CacheKeyHash>* task_map,
+      const SoftwareImageDecodeCache::CacheKey& key)
       : task_map_(task_map), key_(key) {}
   ~AutoRemoveKeyFromTaskMap() { task_map_->erase(key_); }
 
  private:
-  std::unordered_map<SoftwareImageDecodeCache::ImageKey,
+  std::unordered_map<SoftwareImageDecodeCache::CacheKey,
                      scoped_refptr<TileTask>,
-                     SoftwareImageDecodeCache::ImageKeyHash>* task_map_;
-  const SoftwareImageDecodeCache::ImageKey& key_;
+                     SoftwareImageDecodeCache::CacheKeyHash>* task_map_;
+  const SoftwareImageDecodeCache::CacheKey& key_;
 };
 
 class SoftwareImageDecodeTaskImpl : public TileTask {
  public:
   SoftwareImageDecodeTaskImpl(
       SoftwareImageDecodeCache* cache,
-      const SoftwareImageDecodeCache::ImageKey& image_key,
+      const SoftwareImageDecodeCache::CacheKey& image_key,
       const PaintImage& paint_image,
       SoftwareImageDecodeCache::DecodeTaskType task_type,
       const ImageDecodeCache::TracingInfo& tracing_info)
@@ -110,7 +86,7 @@
 
  private:
   SoftwareImageDecodeCache* cache_;
-  SoftwareImageDecodeCache::ImageKey image_key_;
+  SoftwareImageDecodeCache::CacheKey image_key_;
   PaintImage paint_image_;
   SoftwareImageDecodeCache::DecodeTaskType task_type_;
   const ImageDecodeCache::TracingInfo tracing_info_;
@@ -118,10 +94,10 @@
   DISALLOW_COPY_AND_ASSIGN(SoftwareImageDecodeTaskImpl);
 };
 
-SkSize GetScaleAdjustment(const ImageDecodeCacheKey& key) {
+SkSize GetScaleAdjustment(const SoftwareImageDecodeCache::CacheKey& key) {
   // If the requested filter quality did not require scale, then the adjustment
   // is identity.
-  if (key.type() != ImageDecodeCacheKey::kSubrectAndScale) {
+  if (key.type() != SoftwareImageDecodeCache::CacheKey::kSubrectAndScale) {
     return SkSize::Make(1.f, 1.f);
   } else {
     return MipMapUtil::GetScaleAdjustmentForSize(key.src_rect().size(),
@@ -136,16 +112,12 @@
 // to do a bilinear interpolation. The exception to this is if the developer
 // specified a pixelated effect, which results in a None filter quality (nearest
 // neighbor).
-SkFilterQuality GetDecodedFilterQuality(const ImageDecodeCacheKey& key) {
+SkFilterQuality GetDecodedFilterQuality(
+    const SoftwareImageDecodeCache::CacheKey& key) {
   return key.is_nearest_neighbor() ? kNone_SkFilterQuality
                                    : kLow_SkFilterQuality;
 }
 
-SkImageInfo CreateImageInfo(const SkISize& size, SkColorType color_type) {
-  return SkImageInfo::Make(size.width(), size.height(), color_type,
-                           kPremul_SkAlphaType);
-}
-
 void RecordLockExistingCachedImageHistogram(TilePriority::PriorityBin bin,
                                             bool success) {
   switch (bin) {
@@ -161,24 +133,6 @@
   }
 }
 
-gfx::Rect GetSrcRect(const DrawImage& image) {
-  const SkIRect& src_rect = image.src_rect();
-  int x = std::max(0, src_rect.x());
-  int y = std::max(0, src_rect.y());
-  int right = std::min(image.paint_image().width(), src_rect.right());
-  int bottom = std::min(image.paint_image().height(), src_rect.bottom());
-  if (x >= right || y >= bottom)
-    return gfx::Rect();
-  return gfx::Rect(x, y, right - x, bottom - y);
-}
-
-std::unique_ptr<base::DiscardableMemory> AllocateDiscardable(
-    const SkImageInfo& info) {
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"), "AllocateDiscardable");
-  return base::DiscardableMemoryAllocator::GetInstance()
-      ->AllocateLockedDiscardableMemory(info.minRowBytes() * info.height());
-}
-
 }  // namespace
 
 SoftwareImageDecodeCache::SoftwareImageDecodeCache(
@@ -239,7 +193,7 @@
     const DrawImage& image,
     const TracingInfo& tracing_info,
     DecodeTaskType task_type) {
-  ImageKey key = ImageKey::FromDrawImage(image, color_type_);
+  CacheKey key = CacheKey::FromDrawImage(image, color_type_);
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "SoftwareImageDecodeCache::GetTaskForImageAndRefInternal", "key",
                key.ToString());
@@ -301,7 +255,7 @@
   return TaskResult(task);
 }
 
-void SoftwareImageDecodeCache::AddBudgetForImage(const ImageKey& key,
+void SoftwareImageDecodeCache::AddBudgetForImage(const CacheKey& key,
                                                  CacheEntry* entry) {
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "SoftwareImageDecodeCache::AddBudgetForImage", "key",
@@ -314,7 +268,7 @@
   entry->is_budgeted = true;
 }
 
-void SoftwareImageDecodeCache::RemoveBudgetForImage(const ImageKey& key,
+void SoftwareImageDecodeCache::RemoveBudgetForImage(const CacheKey& key,
                                                     CacheEntry* entry) {
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "SoftwareImageDecodeCache::RemoveBudgetForImage", "key",
@@ -327,7 +281,7 @@
 }
 
 void SoftwareImageDecodeCache::UnrefImage(const DrawImage& image) {
-  const ImageKey& key = ImageKey::FromDrawImage(image, color_type_);
+  const CacheKey& key = CacheKey::FromDrawImage(image, color_type_);
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "SoftwareImageDecodeCache::UnrefImage", "key", key.ToString());
 
@@ -335,7 +289,7 @@
   UnrefImage(key);
 }
 
-void SoftwareImageDecodeCache::UnrefImage(const ImageKey& key) {
+void SoftwareImageDecodeCache::UnrefImage(const CacheKey& key) {
   lock_.AssertAcquired();
   auto decoded_image_it = decoded_images_.Peek(key);
   DCHECK(decoded_image_it != decoded_images_.end());
@@ -349,7 +303,7 @@
   }
 }
 
-void SoftwareImageDecodeCache::DecodeImageInTask(const ImageKey& key,
+void SoftwareImageDecodeCache::DecodeImageInTask(const CacheKey& key,
                                                  const PaintImage& paint_image,
                                                  DecodeTaskType task_type) {
   TRACE_EVENT1("cc", "SoftwareImageDecodeCache::DecodeImageInTask", "key",
@@ -372,7 +326,7 @@
 }
 
 void SoftwareImageDecodeCache::DecodeImageIfNecessary(
-    const ImageKey& key,
+    const CacheKey& key,
     const PaintImage& paint_image,
     CacheEntry* entry) {
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
@@ -401,13 +355,13 @@
 
   std::unique_ptr<CacheEntry> local_cache_entry;
   // If we can use the original decode, we'll definitely need a decode.
-  if (key.type() == ImageDecodeCacheKey::kOriginal) {
+  if (key.type() == CacheKey::kOriginal) {
     base::AutoUnlock release(lock_);
-    local_cache_entry = DoDecodeImage(key, paint_image);
+    local_cache_entry = Utils::DoDecodeImage(key, paint_image, color_type_);
   } else {
     // Use the full image decode to generate a scaled/subrected decode.
     // TODO(vmpstr): This part needs to handle decode to scale.
-    base::Optional<ImageKey> candidate_key;
+    base::Optional<CacheKey> candidate_key;
     auto image_keys_it = frame_key_to_image_keys_.find(key.frame_key());
     // We know that we must have at least our own |entry| in this list, so it
     // won't be empty.
@@ -415,7 +369,7 @@
 
     auto& available_keys = image_keys_it->second;
     std::sort(available_keys.begin(), available_keys.end(),
-              [](const ImageKey& one, const ImageKey& two) {
+              [](const CacheKey& one, const CacheKey& two) {
                 // Return true if |one| scale is less than |two| scale.
                 return one.target_size().width() < two.target_size().width() &&
                        one.target_size().height() < two.target_size().height();
@@ -458,14 +412,14 @@
       // This way the GenerateCacheEntryFromCandidate() function will simply
       // extract the subset and be done with it.
       auto src_rect =
-          key.type() == ImageDecodeCacheKey::kSubrectOriginal
+          key.type() == CacheKey::kSubrectOriginal
               ? SkIRect::MakeWH(paint_image.width(), paint_image.height())
               : gfx::RectToSkIRect(key.src_rect());
       DrawImage candidate_draw_image(
           paint_image, src_rect, kNone_SkFilterQuality, SkMatrix::I(),
           key.frame_key().frame_index(), key.target_color_space());
       candidate_key.emplace(
-          ImageKey::FromDrawImage(candidate_draw_image, color_type_));
+          CacheKey::FromDrawImage(candidate_draw_image, color_type_));
     }
     CHECK(*candidate_key != key) << key.ToString();
 
@@ -479,9 +433,9 @@
       // If the candidate could have used the original decode, that means we
       // need to extractSubset from it. In all other cases, this would have
       // already been done to generate the candidate.
-      local_cache_entry = GenerateCacheEntryFromCandidate(
-          key, decoded_draw_image,
-          candidate_key->type() == ImageDecodeCacheKey::kOriginal);
+      local_cache_entry = Utils::GenerateCacheEntryFromCandidate(
+          key, decoded_draw_image, candidate_key->type() == CacheKey::kOriginal,
+          color_type_);
     }
 
     // Unref to balance the GetDecodedImageForDrawInternal() call.
@@ -510,100 +464,16 @@
   }
 }
 
-std::unique_ptr<SoftwareImageDecodeCache::CacheEntry>
-SoftwareImageDecodeCache::DoDecodeImage(const ImageKey& key,
-                                        const PaintImage& paint_image) {
-  SkISize target_size =
-      SkISize::Make(key.target_size().width(), key.target_size().height());
-  DCHECK(target_size == paint_image.GetSupportedDecodeSize(target_size));
-
-  SkImageInfo target_info = CreateImageInfo(target_size, color_type_);
-  std::unique_ptr<base::DiscardableMemory> target_pixels =
-      AllocateDiscardable(target_info);
-  DCHECK(target_pixels);
-
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
-               "SoftwareImageDecodeCache::DoDecodeImage - "
-               "decode");
-  DCHECK_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
-  bool result = paint_image.Decode(target_pixels->data(), &target_info,
-                                   key.target_color_space().ToSkColorSpace(),
-                                   key.frame_key().frame_index());
-  if (!result) {
-    target_pixels->Unlock();
-    return nullptr;
-  }
-  return std::make_unique<CacheEntry>(target_info, std::move(target_pixels),
-                                      SkSize::Make(0, 0));
-}
-
-std::unique_ptr<SoftwareImageDecodeCache::CacheEntry>
-SoftwareImageDecodeCache::GenerateCacheEntryFromCandidate(
-    const ImageKey& key,
-    const DecodedDrawImage& candidate_image,
-    bool needs_extract_subset) {
-  SkISize target_size =
-      SkISize::Make(key.target_size().width(), key.target_size().height());
-  SkImageInfo target_info = CreateImageInfo(target_size, color_type_);
-  std::unique_ptr<base::DiscardableMemory> target_pixels =
-      AllocateDiscardable(target_info);
-  DCHECK(target_pixels);
-
-  if (key.type() == ImageDecodeCacheKey::kSubrectOriginal) {
-    DCHECK(needs_extract_subset);
-    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
-                 "SoftwareImageDecodeCache::GenerateCacheEntryFromCandidate - "
-                 "subrect");
-    bool result = candidate_image.image()->readPixels(
-        target_info, target_pixels->data(), target_info.minRowBytes(),
-        key.src_rect().x(), key.src_rect().y(), SkImage::kDisallow_CachingHint);
-    // We have a decoded image, and we're reading into already allocated memory.
-    // This should never fail.
-    DCHECK(result) << key.ToString();
-    return std::make_unique<CacheEntry>(
-        target_info.makeColorSpace(candidate_image.image()->refColorSpace()),
-        std::move(target_pixels),
-        SkSize::Make(-key.src_rect().x(), -key.src_rect().y()));
-  }
-
-  DCHECK_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
-  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
-               "SoftwareImageDecodeCache::GenerateCacheEntryFromCandidate - "
-               "scale");
-  SkPixmap decoded_pixmap;
-  // We don't need to subrect this image, since all candidates passed in would
-  // already have a src_rect applied to them.
-  bool result = candidate_image.image()->peekPixels(&decoded_pixmap);
-  DCHECK(result) << key.ToString();
-  if (needs_extract_subset) {
-    result = decoded_pixmap.extractSubset(&decoded_pixmap,
-                                          gfx::RectToSkIRect(key.src_rect()));
-    DCHECK(result) << key.ToString();
-  }
-
-  // Nearest neighbor would only be set in the unscaled case.
-  DCHECK(!key.is_nearest_neighbor());
-  SkPixmap target_pixmap(target_info, target_pixels->data(),
-                         target_info.minRowBytes());
-  // Always use medium quality for scaling.
-  result = decoded_pixmap.scalePixels(target_pixmap, kMedium_SkFilterQuality);
-  DCHECK(result) << key.ToString();
-  return std::make_unique<CacheEntry>(
-      target_info.makeColorSpace(candidate_image.image()->refColorSpace()),
-      std::move(target_pixels),
-      SkSize::Make(-key.src_rect().x(), -key.src_rect().y()));
-}
-
 DecodedDrawImage SoftwareImageDecodeCache::GetDecodedImageForDraw(
     const DrawImage& draw_image) {
   base::AutoLock hold(lock_);
   return GetDecodedImageForDrawInternal(
-      ImageKey::FromDrawImage(draw_image, color_type_),
+      CacheKey::FromDrawImage(draw_image, color_type_),
       draw_image.paint_image());
 }
 
 DecodedDrawImage SoftwareImageDecodeCache::GetDecodedImageForDrawInternal(
-    const ImageKey& key,
+    const CacheKey& key,
     const PaintImage& paint_image) {
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "SoftwareImageDecodeCache::GetDecodedImageForDrawInternal",
@@ -633,7 +503,7 @@
     const DecodedDrawImage& decoded_image) {
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "SoftwareImageDecodeCache::DrawWithImageFinished", "key",
-               ImageKey::FromDrawImage(image, color_type_).ToString());
+               CacheKey::FromDrawImage(image, color_type_).ToString());
   UnrefImage(image);
 }
 
@@ -649,7 +519,7 @@
       continue;
     }
 
-    const ImageKey& key = it->first;
+    const CacheKey& key = it->first;
     auto vector_it = frame_key_to_image_keys_.find(key.frame_key());
     auto item_it =
         std::find(vector_it->second.begin(), vector_it->second.end(), key);
@@ -685,7 +555,7 @@
     return;
 
   for (auto key_it = it->second.begin(); key_it != it->second.end();) {
-    // This iterates over the ImageKey vector for the given skimage_id,
+    // This iterates over the CacheKey vector for the given skimage_id,
     // and deletes all entries from decoded_images_ corresponding to the
     // skimage_id.
     auto image_it = decoded_images_.Peek(*key_it);
@@ -702,7 +572,7 @@
 }
 
 void SoftwareImageDecodeCache::OnImageDecodeTaskCompleted(
-    const ImageKey& key,
+    const CacheKey& key,
     DecodeTaskType task_type) {
   base::AutoLock hold(lock_);
 
@@ -786,7 +656,7 @@
 }
 
 SoftwareImageDecodeCache::CacheEntry* SoftwareImageDecodeCache::AddCacheEntry(
-    const ImageKey& key) {
+    const CacheKey& key) {
   lock_.AssertAcquired();
   frame_key_to_image_keys_[key.frame_key()].push_back(key);
   auto it = decoded_images_.Put(key, std::make_unique<CacheEntry>());
@@ -794,270 +664,6 @@
   return it->second.get();
 }
 
-// SoftwareImageDecodeCacheKey -------------------------------------------------
-// static
-ImageDecodeCacheKey ImageDecodeCacheKey::FromDrawImage(const DrawImage& image,
-                                                       SkColorType color_type) {
-  const PaintImage::FrameKey frame_key = image.frame_key();
-
-  const SkSize& scale = image.scale();
-  // If the src_rect falls outside of the image, we need to clip it since
-  // otherwise we might end up with uninitialized memory in the decode process.
-  // Note that the scale is still unchanged and the target size is now a
-  // function of the new src_rect.
-  const gfx::Rect& src_rect = GetSrcRect(image);
-
-  // Start with the exact target size. However, this will be adjusted below to
-  // be either a mip level, the original size, or a subrect size. This is done
-  // to keep memory accounting correct.
-  gfx::Size target_size(
-      SkScalarRoundToInt(std::abs(src_rect.width() * scale.width())),
-      SkScalarRoundToInt(std::abs(src_rect.height() * scale.height())));
-
-  // If the target size is empty, then we'll be skipping the decode anyway, so
-  // the filter quality doesn't matter. Early out instead.
-  if (target_size.IsEmpty()) {
-    return ImageDecodeCacheKey(frame_key, kOriginal, false, src_rect,
-                               target_size, image.target_color_space());
-  }
-
-  ProcessingType type = kOriginal;
-  bool is_nearest_neighbor = image.filter_quality() == kNone_SkFilterQuality;
-  int mip_level = MipMapUtil::GetLevelForSize(src_rect.size(), target_size);
-  // If any of the following conditions hold, then use at most low filter
-  // quality and adjust the target size to match the original image:
-  // - Quality is none: We need a pixelated image, so we can't upgrade it.
-  // - Format is 4444: Skia doesn't support scaling these, so use low
-  //   filter quality.
-  // - Mip level is 0: The required mip is the original image, so just use low
-  //   filter quality.
-  // - Matrix is not decomposable: There's perspective on this image and we
-  //   can't determine the size, so use the original.
-  if (is_nearest_neighbor || color_type == kARGB_4444_SkColorType ||
-      mip_level == 0 || !image.matrix_is_decomposable()) {
-    type = kOriginal;
-    // Update the size to be the original image size.
-    target_size =
-        gfx::Size(image.paint_image().width(), image.paint_image().height());
-  } else {
-    type = kSubrectAndScale;
-    // Update the target size to be a mip level size.
-    // TODO(vmpstr): MipMapUtil and JPEG decoders disagree on what to do with
-    // odd sizes. If width = 2k + 1, and the mip level is 1, then this will
-    // return width = k; JPEG decoder, however, will support decoding to width =
-    // k + 1. We need to figure out what to do in this case.
-    SkSize mip_scale_adjustment =
-        MipMapUtil::GetScaleAdjustmentForLevel(src_rect.size(), mip_level);
-    target_size.set_width(src_rect.width() * mip_scale_adjustment.width());
-    target_size.set_height(src_rect.height() * mip_scale_adjustment.height());
-  }
-
-  // If the original image is large, we might want to do a subrect instead if
-  // the subrect would be kMemoryRatioToSubrect times smaller.
-  if (type == kOriginal &&
-      (image.paint_image().width() >= kMinDimensionToSubrect ||
-       image.paint_image().height() >= kMinDimensionToSubrect)) {
-    base::CheckedNumeric<size_t> checked_original_size = 4u;
-    checked_original_size *= image.paint_image().width();
-    checked_original_size *= image.paint_image().height();
-    size_t original_size = checked_original_size.ValueOrDefault(
-        std::numeric_limits<size_t>::max());
-
-    base::CheckedNumeric<size_t> checked_src_rect_size = 4u;
-    checked_src_rect_size *= src_rect.width();
-    checked_src_rect_size *= src_rect.height();
-    size_t src_rect_size = checked_src_rect_size.ValueOrDefault(
-        std::numeric_limits<size_t>::max());
-
-    // If the sizes are such that we get good savings by subrecting, then do
-    // that. Also update the target size to be the src rect size since that's
-    // the rect we want to use.
-    if (original_size > kMemoryThresholdToSubrect &&
-        src_rect_size <= original_size * kMemoryRatioToSubrect) {
-      type = kSubrectOriginal;
-      target_size = src_rect.size();
-    }
-  }
-
-  return ImageDecodeCacheKey(frame_key, type, is_nearest_neighbor, src_rect,
-                             target_size, image.target_color_space());
-}
-
-ImageDecodeCacheKey::ImageDecodeCacheKey(
-    PaintImage::FrameKey frame_key,
-    ProcessingType type,
-    bool is_nearest_neighbor,
-    const gfx::Rect& src_rect,
-    const gfx::Size& target_size,
-    const gfx::ColorSpace& target_color_space)
-    : frame_key_(frame_key),
-      type_(type),
-      is_nearest_neighbor_(is_nearest_neighbor),
-      src_rect_(src_rect),
-      target_size_(target_size),
-      target_color_space_(target_color_space) {
-  if (type == kOriginal) {
-    hash_ = frame_key_.hash();
-  } else {
-    // TODO(vmpstr): This is a mess. Maybe it's faster to just search the vector
-    // always (forwards or backwards to account for LRU).
-    uint64_t src_rect_hash = base::HashInts(
-        static_cast<uint64_t>(base::HashInts(src_rect_.x(), src_rect_.y())),
-        static_cast<uint64_t>(
-            base::HashInts(src_rect_.width(), src_rect_.height())));
-
-    uint64_t target_size_hash =
-        base::HashInts(target_size_.width(), target_size_.height());
-
-    hash_ = base::HashInts(base::HashInts(src_rect_hash, target_size_hash),
-                           frame_key_.hash());
-  }
-  // Include the target color space in the hash regardless of scaling.
-  hash_ = base::HashInts(hash_, target_color_space.GetHash());
-}
-
-ImageDecodeCacheKey::ImageDecodeCacheKey(const ImageDecodeCacheKey& other) =
-    default;
-
-std::string ImageDecodeCacheKey::ToString() const {
-  std::ostringstream str;
-  str << "frame_key[" << frame_key_.ToString() << "]\ntype[";
-  switch (type_) {
-    case kOriginal:
-      str << "Original";
-      break;
-    case kSubrectOriginal:
-      str << "SubrectOriginal";
-      break;
-    case kSubrectAndScale:
-      str << "SubrectAndScale";
-      break;
-  }
-  str << "]\nis_nearest_neightbor[" << is_nearest_neighbor_ << "]\nsrc_rect["
-      << src_rect_.ToString() << "]\ntarget_size[" << target_size_.ToString()
-      << "]\ntarget_color_space[" << target_color_space_.ToString()
-      << "]\nhash[" << hash_ << "]";
-  return str.str();
-}
-
-// CacheEntry ------------------------------------------------------------------
-SoftwareImageDecodeCache::CacheEntry::CacheEntry()
-    : tracing_id_(g_next_tracing_id_.GetNext()) {}
-SoftwareImageDecodeCache::CacheEntry::CacheEntry(
-    const SkImageInfo& info,
-    std::unique_ptr<base::DiscardableMemory> in_memory,
-    const SkSize& src_rect_offset)
-    : is_locked(true),
-      memory(std::move(in_memory)),
-      image_info_(info),
-      src_rect_offset_(src_rect_offset),
-      tracing_id_(g_next_tracing_id_.GetNext()) {
-  DCHECK(memory);
-  SkPixmap pixmap(image_info_, memory->data(), image_info_.minRowBytes());
-  image_ = SkImage::MakeFromRaster(
-      pixmap, [](const void* pixels, void* context) {}, nullptr);
-}
-
-SoftwareImageDecodeCache::CacheEntry::~CacheEntry() {
-  DCHECK(!is_locked);
-
-  // We create temporary CacheEntries as a part of decoding. However, we move
-  // the memory to cache entries that actually live in the cache. Destroying the
-  // temporaries should not cause any of the stats to be recorded. Specifically,
-  // if allowed to report, they would report every single temporary entry as
-  // wasted, which is misleading. As a fix, don't report on a cache entry that
-  // has never been in the cache.
-  if (!cached_)
-    return;
-
-  // lock_count | used  | last lock failed | result state
-  // ===========+=======+==================+==================
-  //  1         | false | false            | WASTED
-  //  1         | false | true             | WASTED
-  //  1         | true  | false            | USED
-  //  1         | true  | true             | USED_RELOCK_FAILED
-  //  >1        | false | false            | WASTED_RELOCKED
-  //  >1        | false | true             | WASTED_RELOCKED
-  //  >1        | true  | false            | USED_RELOCKED
-  //  >1        | true  | true             | USED_RELOCKED
-  // Note that it's important not to reorder the following enums, since the
-  // numerical values are used in the histogram code.
-  enum State : int {
-    DECODED_IMAGE_STATE_WASTED,
-    DECODED_IMAGE_STATE_USED,
-    DECODED_IMAGE_STATE_USED_RELOCK_FAILED,
-    DECODED_IMAGE_STATE_WASTED_RELOCKED,
-    DECODED_IMAGE_STATE_USED_RELOCKED,
-    DECODED_IMAGE_STATE_COUNT
-  } state = DECODED_IMAGE_STATE_WASTED;
-
-  if (usage_stats_.lock_count == 1) {
-    if (!usage_stats_.used)
-      state = DECODED_IMAGE_STATE_WASTED;
-    else if (usage_stats_.last_lock_failed)
-      state = DECODED_IMAGE_STATE_USED_RELOCK_FAILED;
-    else
-      state = DECODED_IMAGE_STATE_USED;
-  } else {
-    if (usage_stats_.used)
-      state = DECODED_IMAGE_STATE_USED_RELOCKED;
-    else
-      state = DECODED_IMAGE_STATE_WASTED_RELOCKED;
-  }
-
-  UMA_HISTOGRAM_ENUMERATION("Renderer4.SoftwareImageDecodeState", state,
-                            DECODED_IMAGE_STATE_COUNT);
-  UMA_HISTOGRAM_BOOLEAN("Renderer4.SoftwareImageDecodeState.FirstLockWasted",
-                        usage_stats_.first_lock_wasted);
-  if (usage_stats_.first_lock_out_of_raster)
-    UMA_HISTOGRAM_BOOLEAN(
-        "Renderer4.SoftwareImageDecodeState.FirstLockWasted.OutOfRaster",
-        usage_stats_.first_lock_wasted);
-}
-
-void SoftwareImageDecodeCache::CacheEntry::MoveImageMemoryTo(
-    CacheEntry* entry) {
-  DCHECK(!is_budgeted);
-  DCHECK_EQ(ref_count, 0);
-
-  // Copy/move most things except budgeted and ref counts.
-  entry->decode_failed = decode_failed;
-  entry->is_locked = is_locked;
-  is_locked = false;
-
-  entry->memory = std::move(memory);
-  entry->image_info_ = std::move(image_info_);
-  entry->src_rect_offset_ = std::move(src_rect_offset_);
-  entry->image_ = std::move(image_);
-}
-
-bool SoftwareImageDecodeCache::CacheEntry::Lock() {
-  if (!memory)
-    return false;
-
-  DCHECK(!is_locked);
-  bool success = memory->Lock();
-  if (!success) {
-    memory = nullptr;
-    usage_stats_.last_lock_failed = true;
-    return false;
-  }
-  is_locked = true;
-  ++usage_stats_.lock_count;
-  return true;
-}
-
-void SoftwareImageDecodeCache::CacheEntry::Unlock() {
-  if (!memory)
-    return;
-
-  DCHECK(is_locked);
-  memory->Unlock();
-  is_locked = false;
-  if (usage_stats_.lock_count == 1)
-    usage_stats_.first_lock_wasted = !usage_stats_.used;
-}
-
 // MemoryBudget ----------------------------------------------------------------
 SoftwareImageDecodeCache::MemoryBudget::MemoryBudget(size_t limit_bytes)
     : limit_bytes_(limit_bytes), current_usage_bytes_(0u) {}
diff --git a/cc/tiles/software_image_decode_cache.h b/cc/tiles/software_image_decode_cache.h
index 951b1d5..98d1832 100644
--- a/cc/tiles/software_image_decode_cache.h
+++ b/cc/tiles/software_image_decode_cache.h
@@ -9,117 +9,27 @@
 
 #include <memory>
 #include <unordered_map>
-#include <unordered_set>
 
-#include "base/atomic_sequence_num.h"
 #include "base/containers/mru_cache.h"
-#include "base/hash.h"
-#include "base/memory/discardable_memory_allocator.h"
 #include "base/memory/memory_coordinator_client.h"
 #include "base/memory/ref_counted.h"
 #include "base/numerics/safe_math.h"
-#include "base/threading/thread_checker.h"
 #include "base/trace_event/memory_dump_provider.h"
 #include "cc/cc_export.h"
 #include "cc/paint/draw_image.h"
 #include "cc/tiles/image_decode_cache.h"
-#include "third_party/skia/include/core/SkRefCnt.h"
-#include "ui/gfx/geometry/rect.h"
+#include "cc/tiles/software_image_decode_cache_utils.h"
 
 namespace cc {
 
-// ImageDecodeCacheKey is a class that gets a cache key out of a given draw
-// image. That is, this key uniquely identifies an image in the cache. Note that
-// it's insufficient to use SkImage's unique id, since the same image can appear
-// in the cache multiple times at different scales and filter qualities.
-class CC_EXPORT ImageDecodeCacheKey {
- public:
-  // Enum indicating the type of processing to do for this key:
-  // kOriginal - use the original decode without any subrecting or scaling.
-  // kSubrectOriginal - extract a subrect from the original decode but do not
-  //                    scale it.
-  // kSubrectAndScale - extract a subrect (if needed) from the original decode
-  //                    and scale it.
-  enum ProcessingType { kOriginal, kSubrectOriginal, kSubrectAndScale };
-
-  static ImageDecodeCacheKey FromDrawImage(const DrawImage& image,
-                                           SkColorType color_type);
-
-  ImageDecodeCacheKey(const ImageDecodeCacheKey& other);
-
-  bool operator==(const ImageDecodeCacheKey& other) const {
-    // The frame_key always has to be the same. However, after that all original
-    // decodes are the same, so if we can use the original decode, return true.
-    // If not, then we have to compare every field.
-    // Note we don't compare |nearest_neighbor_| because we would only use
-    // kOriginal type in that case (dchecked below), which implies no scale. The
-    // returned scale to Skia would respect the nearest neighbor value of the
-    // requested image.
-    DCHECK(!is_nearest_neighbor_ || type_ == kOriginal);
-    return frame_key_ == other.frame_key_ && type_ == other.type_ &&
-           target_color_space_ == other.target_color_space_ &&
-           (type_ == kOriginal || (src_rect_ == other.src_rect_ &&
-                                   target_size_ == other.target_size_));
-  }
-
-  bool operator!=(const ImageDecodeCacheKey& other) const {
-    return !(*this == other);
-  }
-
-  const PaintImage::FrameKey& frame_key() const { return frame_key_; }
-  ProcessingType type() const { return type_; }
-  bool is_nearest_neighbor() const { return is_nearest_neighbor_; }
-  gfx::Rect src_rect() const { return src_rect_; }
-  gfx::Size target_size() const { return target_size_; }
-  const gfx::ColorSpace& target_color_space() const {
-    return target_color_space_;
-  }
-
-  size_t get_hash() const { return hash_; }
-
-  // Helper to figure out how much memory the locked image represented by this
-  // key would take.
-  size_t locked_bytes() const {
-    // TODO(vmpstr): Handle formats other than RGBA.
-    base::CheckedNumeric<size_t> result = 4;
-    result *= target_size_.width();
-    result *= target_size_.height();
-    return result.ValueOrDefault(std::numeric_limits<size_t>::max());
-  }
-
-  std::string ToString() const;
-
- private:
-  ImageDecodeCacheKey(PaintImage::FrameKey frame_key,
-                      ProcessingType type,
-                      bool is_nearest_neighbor,
-                      const gfx::Rect& src_rect,
-                      const gfx::Size& size,
-                      const gfx::ColorSpace& target_color_space);
-
-  PaintImage::FrameKey frame_key_;
-  ProcessingType type_;
-  bool is_nearest_neighbor_;
-  gfx::Rect src_rect_;
-  gfx::Size target_size_;
-  gfx::ColorSpace target_color_space_;
-  size_t hash_;
-};
-
-// Hash function for the above ImageDecodeCacheKey.
-struct ImageDecodeCacheKeyHash {
-  size_t operator()(const ImageDecodeCacheKey& key) const {
-    return key.get_hash();
-  }
-};
-
 class CC_EXPORT SoftwareImageDecodeCache
     : public ImageDecodeCache,
       public base::trace_event::MemoryDumpProvider,
       public base::MemoryCoordinatorClient {
  public:
-  using ImageKey = ImageDecodeCacheKey;
-  using ImageKeyHash = ImageDecodeCacheKeyHash;
+  using Utils = SoftwareImageDecodeCacheUtils;
+  using CacheKey = Utils::CacheKey;
+  using CacheKeyHash = Utils::CacheKeyHash;
 
   enum class DecodeTaskType { USE_IN_RASTER_TASKS, USE_OUT_OF_RASTER_TASKS };
 
@@ -146,11 +56,11 @@
 
   // Decode the given image and store it in the cache. This is only called by an
   // image decode task from a worker thread.
-  void DecodeImageInTask(const ImageKey& key,
+  void DecodeImageInTask(const CacheKey& key,
                          const PaintImage& paint_image,
                          DecodeTaskType task_type);
 
-  void OnImageDecodeTaskCompleted(const ImageKey& key,
+  void OnImageDecodeTaskCompleted(const CacheKey& key,
                                   DecodeTaskType task_type);
 
   // MemoryDumpProvider overrides.
@@ -160,73 +70,7 @@
   size_t GetNumCacheEntriesForTesting() const { return decoded_images_.size(); }
 
  private:
-  // CacheEntry is a convenience storage for discardable memory. It can also
-  // construct an image out of SkImageInfo and stored discardable memory.
-  class CacheEntry {
-   public:
-    CacheEntry();
-    CacheEntry(const SkImageInfo& info,
-               std::unique_ptr<base::DiscardableMemory> memory,
-               const SkSize& src_rect_offset);
-    ~CacheEntry();
-
-    void MoveImageMemoryTo(CacheEntry* entry);
-
-    sk_sp<SkImage> image() const {
-      if (!memory)
-        return nullptr;
-      DCHECK(is_locked);
-      return image_;
-    }
-    const SkSize& src_rect_offset() const { return src_rect_offset_; }
-
-    bool Lock();
-    void Unlock();
-
-    // An ID which uniquely identifies this CacheEntry within the image decode
-    // cache. Used in memory tracing.
-    uint64_t tracing_id() const { return tracing_id_; }
-    // Mark this image as being used in either a draw or as a source for a
-    // scaled image. Either case represents this decode as being valuable and
-    // not wasted.
-    void mark_used() { usage_stats_.used = true; }
-    void mark_cached() { cached_ = true; }
-    void mark_out_of_raster() { usage_stats_.first_lock_out_of_raster = true; }
-
-    // Since this is an inner class, we expose these variables publicly for
-    // simplicity.
-    // TODO(vmpstr): A good simple clean-up would be to rethink this class
-    // and its interactions to instead expose a few functions which would also
-    // facilitate easier DCHECKs.
-    int ref_count = 0;
-    bool decode_failed = false;
-    bool is_locked = false;
-    bool is_budgeted = false;
-
-    scoped_refptr<TileTask> in_raster_task;
-    scoped_refptr<TileTask> out_of_raster_task;
-
-    std::unique_ptr<base::DiscardableMemory> memory;
-
-   private:
-    struct UsageStats {
-      // We can only create a decoded image in a locked state, so the initial
-      // lock count is 1.
-      int lock_count = 1;
-      bool used = false;
-      bool last_lock_failed = false;
-      bool first_lock_wasted = false;
-      bool first_lock_out_of_raster = false;
-    };
-
-    SkImageInfo image_info_;
-    sk_sp<SkImage> image_;
-    SkSize src_rect_offset_;
-    uint64_t tracing_id_;
-    UsageStats usage_stats_;
-    // Indicates whether this entry was ever in the cache.
-    bool cached_ = false;
-  };
+  using CacheEntry = Utils::CacheEntry;
 
   // MemoryBudget is a convenience class for memory bookkeeping and ensuring
   // that we don't go over the limit when pre-decoding.
@@ -247,12 +91,12 @@
   };
 
   using ImageMRUCache = base::
-      HashingMRUCache<ImageKey, std::unique_ptr<CacheEntry>, ImageKeyHash>;
+      HashingMRUCache<CacheKey, std::unique_ptr<CacheEntry>, CacheKeyHash>;
 
   // Actually decode the image. Note that this function can (and should) be
   // called with no lock acquired, since it can do a lot of work. Note that it
   // can also return nullptr to indicate the decode failed.
-  std::unique_ptr<CacheEntry> DecodeImageInternal(const ImageKey& key,
+  std::unique_ptr<CacheEntry> DecodeImageInternal(const CacheKey& key,
                                                   const DrawImage& draw_image);
 
   // Get the decoded draw image for the given key and paint_image. Note that
@@ -261,7 +105,7 @@
   // when used internally, we still require that DrawWithImageFinished() is
   // called afterwards.
   DecodedDrawImage GetDecodedImageForDrawInternal(
-      const ImageKey& key,
+      const CacheKey& key,
       const PaintImage& paint_image);
 
   // Removes unlocked decoded images until the number of decoded images is
@@ -278,26 +122,19 @@
                                            const TracingInfo& tracing_info,
                                            DecodeTaskType type);
 
-  CacheEntry* AddCacheEntry(const ImageKey& key);
+  CacheEntry* AddCacheEntry(const CacheKey& key);
 
-  void DecodeImageIfNecessary(const ImageKey& key,
+  void DecodeImageIfNecessary(const CacheKey& key,
                               const PaintImage& paint_image,
                               CacheEntry* cache_entry);
-  void AddBudgetForImage(const ImageKey& key, CacheEntry* entry);
-  void RemoveBudgetForImage(const ImageKey& key, CacheEntry* entry);
+  void AddBudgetForImage(const CacheKey& key, CacheEntry* entry);
+  void RemoveBudgetForImage(const CacheKey& key, CacheEntry* entry);
 
-  std::unique_ptr<CacheEntry> DoDecodeImage(const ImageKey& key,
-                                            const PaintImage& image);
-  std::unique_ptr<CacheEntry> GenerateCacheEntryFromCandidate(
-      const ImageKey& key,
-      const DecodedDrawImage& candidate,
-      bool needs_extract_subset);
+  void UnrefImage(const CacheKey& key);
 
-  void UnrefImage(const ImageKey& key);
-
-  std::unordered_map<ImageKey, scoped_refptr<TileTask>, ImageKeyHash>
+  std::unordered_map<CacheKey, scoped_refptr<TileTask>, CacheKeyHash>
       pending_in_raster_image_tasks_;
-  std::unordered_map<ImageKey, scoped_refptr<TileTask>, ImageKeyHash>
+  std::unordered_map<CacheKey, scoped_refptr<TileTask>, CacheKeyHash>
       pending_out_of_raster_image_tasks_;
 
   // The members below this comment can only be accessed if the lock is held to
@@ -312,7 +149,7 @@
   // A map of PaintImage::FrameKey to the ImageKeys for cached decodes of this
   // PaintImage.
   std::unordered_map<PaintImage::FrameKey,
-                     std::vector<ImageKey>,
+                     std::vector<CacheKey>,
                      PaintImage::FrameKeyHash>
       frame_key_to_image_keys_;
 
diff --git a/cc/tiles/software_image_decode_cache_perftest.cc b/cc/tiles/software_image_decode_cache_perftest.cc
index 24bae70..b3c9787a 100644
--- a/cc/tiles/software_image_decode_cache_perftest.cc
+++ b/cc/tiles/software_image_decode_cache_perftest.cc
@@ -70,8 +70,10 @@
 
     timer_.Reset();
     do {
-      for (auto& image : images)
-        ImageDecodeCacheKey::FromDrawImage(image, kN32_SkColorType);
+      for (auto& image : images) {
+        SoftwareImageDecodeCache::CacheKey::FromDrawImage(image,
+                                                          kN32_SkColorType);
+      }
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
diff --git a/cc/tiles/software_image_decode_cache_unittest.cc b/cc/tiles/software_image_decode_cache_unittest.cc
index 5508946b..2dbd35d 100644
--- a/cc/tiles/software_image_decode_cache_unittest.cc
+++ b/cc/tiles/software_image_decode_cache_unittest.cc
@@ -54,12 +54,13 @@
       CreateMatrix(SkSize::Make(0.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_TRUE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   // Since the original decode will be used, the locked_bytes is that of the
   // original image.
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
@@ -75,9 +76,10 @@
       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(50, key.target_size().width());
   EXPECT_EQ(50, key.target_size().height());
   EXPECT_EQ(50u * 50u * 4u, key.locked_bytes());
@@ -92,12 +94,13 @@
       CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -110,13 +113,13 @@
       CreateMatrix(SkSize::Make(0.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key =
-      ImageDecodeCacheKey::FromDrawImage(draw_image, kARGB_4444_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kARGB_4444_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -129,13 +132,13 @@
       CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key =
-      ImageDecodeCacheKey::FromDrawImage(draw_image, kARGB_4444_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kARGB_4444_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -148,12 +151,13 @@
       CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -167,9 +171,10 @@
       quality, CreateMatrix(SkSize::Make(0.5f, 0.4f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(50, key.target_size().width());
   EXPECT_EQ(50, key.target_size().height());
   EXPECT_EQ(50u * 50u * 4u, key.locked_bytes());
@@ -185,12 +190,13 @@
       quality, CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -204,12 +210,13 @@
       quality, CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -224,12 +231,13 @@
       quality, CreateMatrix(SkSize::Make(1.001f, 1.001f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -244,12 +252,13 @@
       quality, CreateMatrix(SkSize::Make(0.999f, 0.999f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -264,13 +273,14 @@
       quality, CreateMatrix(SkSize::Make(0.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
 
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -284,12 +294,13 @@
       quality, CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(500, key.target_size().width());
   EXPECT_EQ(200, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(500u * 200u * 4u, key.locked_bytes());
 }
 
@@ -303,12 +314,13 @@
       quality, CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(500, key.target_size().width());
   EXPECT_EQ(200, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(500u * 200u * 4u, key.locked_bytes());
 }
 
@@ -322,12 +334,13 @@
       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(500, key.target_size().width());
   EXPECT_EQ(200, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(500u * 200u * 4u, key.locked_bytes());
 }
 
@@ -341,9 +354,10 @@
       quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(250, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
   EXPECT_EQ(250u * 100u * 4u, key.locked_bytes());
@@ -359,9 +373,10 @@
       quality, CreateMatrix(SkSize::Make(0.49f, 0.49f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(250, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
   EXPECT_EQ(250u * 100u * 4u, key.locked_bytes());
@@ -377,9 +392,10 @@
       quality, CreateMatrix(SkSize::Make(0.1f, 0.1f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(62, key.target_size().width());
   EXPECT_EQ(25, key.target_size().height());
   EXPECT_EQ(62u * 25u * 4u, key.locked_bytes());
@@ -395,9 +411,10 @@
       quality, CreateMatrix(SkSize::Make(0.01f, 0.01f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(7, key.target_size().width());
   EXPECT_EQ(3, key.target_size().height());
   EXPECT_EQ(7u * 3u * 4u, key.locked_bytes());
@@ -414,9 +431,10 @@
       quality, CreateMatrix(SkSize::Make(0.5f, 0.2f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(50, key.target_size().width());
   EXPECT_EQ(50, key.target_size().height());
   EXPECT_EQ(50u * 50u * 4u, key.locked_bytes());
@@ -432,12 +450,13 @@
       quality, CreateMatrix(SkSize::Make(2.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -454,9 +473,10 @@
       quality, CreateMatrix(SkSize::Make(0.45f, 0.45f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(2277, key.target_size().width());
   EXPECT_EQ(1024, key.target_size().height());
   EXPECT_EQ(2277u * 1024u * 4u, key.locked_bytes());
@@ -473,12 +493,13 @@
       quality, CreateMatrix(SkSize::Make(0.5f, 1.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -492,12 +513,13 @@
       quality, CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -512,12 +534,13 @@
       quality, CreateMatrix(SkSize::Make(1.001f, 1.001f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -532,12 +555,13 @@
       quality, CreateMatrix(SkSize::Make(0.999f, 0.999f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 }
 
@@ -551,12 +575,13 @@
       quality, CreateMatrix(SkSize::Make(0.5f, 0.5), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_TRUE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
   EXPECT_EQ(100, key.target_size().height());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, key.locked_bytes());
 
   DrawImage another_draw_image(
@@ -564,13 +589,13 @@
       quality, CreateMatrix(SkSize::Make(1.5f, 1.5), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto another_key =
-      ImageDecodeCacheKey::FromDrawImage(another_draw_image, kN32_SkColorType);
+  auto another_key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      another_draw_image, kN32_SkColorType);
   EXPECT_EQ(another_draw_image.frame_key(), another_key.frame_key());
   EXPECT_TRUE(another_key.is_nearest_neighbor());
   EXPECT_EQ(100, another_key.target_size().width());
   EXPECT_EQ(100, another_key.target_size().height());
-  EXPECT_EQ(another_key.type(), ImageDecodeCacheKey::kOriginal);
+  EXPECT_EQ(another_key.type(), SoftwareImageDecodeCache::CacheKey::kOriginal);
   EXPECT_EQ(100u * 100u * 4u, another_key.locked_bytes());
 
   EXPECT_TRUE(key == another_key);
@@ -587,7 +612,8 @@
       quality, CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
   EXPECT_FALSE(key.is_nearest_neighbor());
   EXPECT_EQ(100, key.target_size().width());
@@ -607,9 +633,10 @@
       quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
-  auto key = ImageDecodeCacheKey::FromDrawImage(draw_image, kN32_SkColorType);
+  auto key = SoftwareImageDecodeCache::CacheKey::FromDrawImage(
+      draw_image, kN32_SkColorType);
   EXPECT_EQ(draw_image.frame_key(), key.frame_key());
-  EXPECT_EQ(key.type(), ImageDecodeCacheKey::kSubrectAndScale);
+  EXPECT_EQ(key.type(), SoftwareImageDecodeCache::CacheKey::kSubrectAndScale);
   EXPECT_EQ(40, key.target_size().width());
   EXPECT_EQ(35, key.target_size().height());
   EXPECT_EQ(gfx::Rect(20, 30, 80, 70), key.src_rect());
diff --git a/cc/tiles/software_image_decode_cache_utils.cc b/cc/tiles/software_image_decode_cache_utils.cc
new file mode 100644
index 0000000..1272668
--- /dev/null
+++ b/cc/tiles/software_image_decode_cache_utils.cc
@@ -0,0 +1,411 @@
+// Copyright 2018 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/tiles/software_image_decode_cache_utils.h"
+
+#include "base/atomic_sequence_num.h"
+#include "base/hash.h"
+#include "base/memory/discardable_memory_allocator.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/trace_event/trace_event.h"
+#include "cc/tiles/mipmap_util.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+namespace {
+// If the size of the original sized image breaches kMemoryRatioToSubrect but we
+// don't need to scale the image, consider caching only the needed subrect.
+// The second part that much be true is that we cache only the needed subrect if
+// the total size needed for the subrect is at most kMemoryRatioToSubrect *
+// (size needed for the full original image).
+// Note that at least one of the dimensions has to be at least
+// kMinDimensionToSubrect before an image can breach the threshold.
+const size_t kMemoryThresholdToSubrect = 64 * 1024 * 1024;
+const int kMinDimensionToSubrect = 4 * 1024;
+const float kMemoryRatioToSubrect = 0.5f;
+
+// Tracing ID sequence for use in CacheEntry.
+base::AtomicSequenceNumber g_next_tracing_id_;
+
+gfx::Rect GetSrcRect(const DrawImage& image) {
+  const SkIRect& src_rect = image.src_rect();
+  int x = std::max(0, src_rect.x());
+  int y = std::max(0, src_rect.y());
+  int right = std::min(image.paint_image().width(), src_rect.right());
+  int bottom = std::min(image.paint_image().height(), src_rect.bottom());
+  if (x >= right || y >= bottom)
+    return gfx::Rect();
+  return gfx::Rect(x, y, right - x, bottom - y);
+}
+
+SkImageInfo CreateImageInfo(const SkISize& size, SkColorType color_type) {
+  return SkImageInfo::Make(size.width(), size.height(), color_type,
+                           kPremul_SkAlphaType);
+}
+
+std::unique_ptr<base::DiscardableMemory> AllocateDiscardable(
+    const SkImageInfo& info) {
+  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"), "AllocateDiscardable");
+  return base::DiscardableMemoryAllocator::GetInstance()
+      ->AllocateLockedDiscardableMemory(info.minRowBytes() * info.height());
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<SoftwareImageDecodeCacheUtils::CacheEntry>
+SoftwareImageDecodeCacheUtils::DoDecodeImage(const CacheKey& key,
+                                             const PaintImage& paint_image,
+                                             SkColorType color_type) {
+  SkISize target_size =
+      SkISize::Make(key.target_size().width(), key.target_size().height());
+  DCHECK(target_size == paint_image.GetSupportedDecodeSize(target_size));
+
+  SkImageInfo target_info = CreateImageInfo(target_size, color_type);
+  std::unique_ptr<base::DiscardableMemory> target_pixels =
+      AllocateDiscardable(target_info);
+  DCHECK(target_pixels);
+
+  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
+               "SoftwareImageDecodeCacheUtils::DoDecodeImage - "
+               "decode");
+  DCHECK_EQ(key.type(), CacheKey::kOriginal);
+  bool result = paint_image.Decode(target_pixels->data(), &target_info,
+                                   key.target_color_space().ToSkColorSpace(),
+                                   key.frame_key().frame_index());
+  if (!result) {
+    target_pixels->Unlock();
+    return nullptr;
+  }
+  return std::make_unique<CacheEntry>(target_info, std::move(target_pixels),
+                                      SkSize::Make(0, 0));
+}
+
+// static
+std::unique_ptr<SoftwareImageDecodeCacheUtils::CacheEntry>
+SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate(
+    const CacheKey& key,
+    const DecodedDrawImage& candidate_image,
+    bool needs_extract_subset,
+    SkColorType color_type) {
+  SkISize target_size =
+      SkISize::Make(key.target_size().width(), key.target_size().height());
+  SkImageInfo target_info = CreateImageInfo(target_size, color_type);
+  std::unique_ptr<base::DiscardableMemory> target_pixels =
+      AllocateDiscardable(target_info);
+  DCHECK(target_pixels);
+
+  if (key.type() == CacheKey::kSubrectOriginal) {
+    DCHECK(needs_extract_subset);
+    TRACE_EVENT0(
+        TRACE_DISABLED_BY_DEFAULT("cc.debug"),
+        "SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate - "
+        "subrect");
+    bool result = candidate_image.image()->readPixels(
+        target_info, target_pixels->data(), target_info.minRowBytes(),
+        key.src_rect().x(), key.src_rect().y(), SkImage::kDisallow_CachingHint);
+    // We have a decoded image, and we're reading into already allocated memory.
+    // This should never fail.
+    DCHECK(result) << key.ToString();
+    return std::make_unique<CacheEntry>(
+        target_info.makeColorSpace(candidate_image.image()->refColorSpace()),
+        std::move(target_pixels),
+        SkSize::Make(-key.src_rect().x(), -key.src_rect().y()));
+  }
+
+  DCHECK_EQ(key.type(), CacheKey::kSubrectAndScale);
+  TRACE_EVENT0(
+      TRACE_DISABLED_BY_DEFAULT("cc.debug"),
+      "SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate - "
+      "scale");
+  SkPixmap decoded_pixmap;
+  // We don't need to subrect this image, since all candidates passed in would
+  // already have a src_rect applied to them.
+  bool result = candidate_image.image()->peekPixels(&decoded_pixmap);
+  DCHECK(result) << key.ToString();
+  if (needs_extract_subset) {
+    result = decoded_pixmap.extractSubset(&decoded_pixmap,
+                                          gfx::RectToSkIRect(key.src_rect()));
+    DCHECK(result) << key.ToString();
+  }
+
+  // Nearest neighbor would only be set in the unscaled case.
+  DCHECK(!key.is_nearest_neighbor());
+  SkPixmap target_pixmap(target_info, target_pixels->data(),
+                         target_info.minRowBytes());
+  // Always use medium quality for scaling.
+  result = decoded_pixmap.scalePixels(target_pixmap, kMedium_SkFilterQuality);
+  DCHECK(result) << key.ToString();
+  return std::make_unique<CacheEntry>(
+      target_info.makeColorSpace(candidate_image.image()->refColorSpace()),
+      std::move(target_pixels),
+      SkSize::Make(-key.src_rect().x(), -key.src_rect().y()));
+}
+
+// CacheKey --------------------------------------------------------------------
+// static
+SoftwareImageDecodeCacheUtils::CacheKey
+SoftwareImageDecodeCacheUtils::CacheKey::FromDrawImage(const DrawImage& image,
+                                                       SkColorType color_type) {
+  const PaintImage::FrameKey frame_key = image.frame_key();
+
+  const SkSize& scale = image.scale();
+  // If the src_rect falls outside of the image, we need to clip it since
+  // otherwise we might end up with uninitialized memory in the decode process.
+  // Note that the scale is still unchanged and the target size is now a
+  // function of the new src_rect.
+  const gfx::Rect& src_rect = GetSrcRect(image);
+
+  // Start with the exact target size. However, this will be adjusted below to
+  // be either a mip level, the original size, or a subrect size. This is done
+  // to keep memory accounting correct.
+  gfx::Size target_size(
+      SkScalarRoundToInt(std::abs(src_rect.width() * scale.width())),
+      SkScalarRoundToInt(std::abs(src_rect.height() * scale.height())));
+
+  // If the target size is empty, then we'll be skipping the decode anyway, so
+  // the filter quality doesn't matter. Early out instead.
+  if (target_size.IsEmpty()) {
+    return CacheKey(frame_key, kOriginal, false, src_rect, target_size,
+                    image.target_color_space());
+  }
+
+  ProcessingType type = kOriginal;
+  bool is_nearest_neighbor = image.filter_quality() == kNone_SkFilterQuality;
+  int mip_level = MipMapUtil::GetLevelForSize(src_rect.size(), target_size);
+  // If any of the following conditions hold, then use at most low filter
+  // quality and adjust the target size to match the original image:
+  // - Quality is none: We need a pixelated image, so we can't upgrade it.
+  // - Format is 4444: Skia doesn't support scaling these, so use low
+  //   filter quality.
+  // - Mip level is 0: The required mip is the original image, so just use low
+  //   filter quality.
+  // - Matrix is not decomposable: There's perspective on this image and we
+  //   can't determine the size, so use the original.
+  if (is_nearest_neighbor || color_type == kARGB_4444_SkColorType ||
+      mip_level == 0 || !image.matrix_is_decomposable()) {
+    type = kOriginal;
+    // Update the size to be the original image size.
+    target_size =
+        gfx::Size(image.paint_image().width(), image.paint_image().height());
+  } else {
+    type = kSubrectAndScale;
+    // Update the target size to be a mip level size.
+    // TODO(vmpstr): MipMapUtil and JPEG decoders disagree on what to do with
+    // odd sizes. If width = 2k + 1, and the mip level is 1, then this will
+    // return width = k; JPEG decoder, however, will support decoding to width =
+    // k + 1. We need to figure out what to do in this case.
+    SkSize mip_scale_adjustment =
+        MipMapUtil::GetScaleAdjustmentForLevel(src_rect.size(), mip_level);
+    target_size.set_width(src_rect.width() * mip_scale_adjustment.width());
+    target_size.set_height(src_rect.height() * mip_scale_adjustment.height());
+  }
+
+  // If the original image is large, we might want to do a subrect instead if
+  // the subrect would be kMemoryRatioToSubrect times smaller.
+  if (type == kOriginal &&
+      (image.paint_image().width() >= kMinDimensionToSubrect ||
+       image.paint_image().height() >= kMinDimensionToSubrect)) {
+    base::CheckedNumeric<size_t> checked_original_size = 4u;
+    checked_original_size *= image.paint_image().width();
+    checked_original_size *= image.paint_image().height();
+    size_t original_size = checked_original_size.ValueOrDefault(
+        std::numeric_limits<size_t>::max());
+
+    base::CheckedNumeric<size_t> checked_src_rect_size = 4u;
+    checked_src_rect_size *= src_rect.width();
+    checked_src_rect_size *= src_rect.height();
+    size_t src_rect_size = checked_src_rect_size.ValueOrDefault(
+        std::numeric_limits<size_t>::max());
+
+    // If the sizes are such that we get good savings by subrecting, then do
+    // that. Also update the target size to be the src rect size since that's
+    // the rect we want to use.
+    if (original_size > kMemoryThresholdToSubrect &&
+        src_rect_size <= original_size * kMemoryRatioToSubrect) {
+      type = kSubrectOriginal;
+      target_size = src_rect.size();
+    }
+  }
+
+  return CacheKey(frame_key, type, is_nearest_neighbor, src_rect, target_size,
+                  image.target_color_space());
+}
+
+SoftwareImageDecodeCacheUtils::CacheKey::CacheKey(
+    PaintImage::FrameKey frame_key,
+    ProcessingType type,
+    bool is_nearest_neighbor,
+    const gfx::Rect& src_rect,
+    const gfx::Size& target_size,
+    const gfx::ColorSpace& target_color_space)
+    : frame_key_(frame_key),
+      type_(type),
+      is_nearest_neighbor_(is_nearest_neighbor),
+      src_rect_(src_rect),
+      target_size_(target_size),
+      target_color_space_(target_color_space) {
+  if (type == kOriginal) {
+    hash_ = frame_key_.hash();
+  } else {
+    // TODO(vmpstr): This is a mess. Maybe it's faster to just search the vector
+    // always (forwards or backwards to account for LRU).
+    uint64_t src_rect_hash = base::HashInts(
+        static_cast<uint64_t>(base::HashInts(src_rect_.x(), src_rect_.y())),
+        static_cast<uint64_t>(
+            base::HashInts(src_rect_.width(), src_rect_.height())));
+
+    uint64_t target_size_hash =
+        base::HashInts(target_size_.width(), target_size_.height());
+
+    hash_ = base::HashInts(base::HashInts(src_rect_hash, target_size_hash),
+                           frame_key_.hash());
+  }
+  // Include the target color space in the hash regardless of scaling.
+  hash_ = base::HashInts(hash_, target_color_space.GetHash());
+}
+
+SoftwareImageDecodeCacheUtils::CacheKey::CacheKey(const CacheKey& other) =
+    default;
+
+std::string SoftwareImageDecodeCacheUtils::CacheKey::ToString() const {
+  std::ostringstream str;
+  str << "frame_key[" << frame_key_.ToString() << "]\ntype[";
+  switch (type_) {
+    case kOriginal:
+      str << "Original";
+      break;
+    case kSubrectOriginal:
+      str << "SubrectOriginal";
+      break;
+    case kSubrectAndScale:
+      str << "SubrectAndScale";
+      break;
+  }
+  str << "]\nis_nearest_neightbor[" << is_nearest_neighbor_ << "]\nsrc_rect["
+      << src_rect_.ToString() << "]\ntarget_size[" << target_size_.ToString()
+      << "]\ntarget_color_space[" << target_color_space_.ToString()
+      << "]\nhash[" << hash_ << "]";
+  return str.str();
+}
+
+// CacheEntry ------------------------------------------------------------------
+SoftwareImageDecodeCacheUtils::CacheEntry::CacheEntry()
+    : tracing_id_(g_next_tracing_id_.GetNext()) {}
+SoftwareImageDecodeCacheUtils::CacheEntry::CacheEntry(
+    const SkImageInfo& info,
+    std::unique_ptr<base::DiscardableMemory> in_memory,
+    const SkSize& src_rect_offset)
+    : is_locked(true),
+      memory(std::move(in_memory)),
+      image_info_(info),
+      src_rect_offset_(src_rect_offset),
+      tracing_id_(g_next_tracing_id_.GetNext()) {
+  DCHECK(memory);
+  SkPixmap pixmap(image_info_, memory->data(), image_info_.minRowBytes());
+  image_ = SkImage::MakeFromRaster(
+      pixmap, [](const void* pixels, void* context) {}, nullptr);
+}
+
+SoftwareImageDecodeCacheUtils::CacheEntry::~CacheEntry() {
+  DCHECK(!is_locked);
+
+  // We create temporary CacheEntries as a part of decoding. However, we move
+  // the memory to cache entries that actually live in the cache. Destroying the
+  // temporaries should not cause any of the stats to be recorded. Specifically,
+  // if allowed to report, they would report every single temporary entry as
+  // wasted, which is misleading. As a fix, don't report on a cache entry that
+  // has never been in the cache.
+  if (!cached_)
+    return;
+
+  // lock_count | used  | last lock failed | result state
+  // ===========+=======+==================+==================
+  //  1         | false | false            | WASTED
+  //  1         | false | true             | WASTED
+  //  1         | true  | false            | USED
+  //  1         | true  | true             | USED_RELOCK_FAILED
+  //  >1        | false | false            | WASTED_RELOCKED
+  //  >1        | false | true             | WASTED_RELOCKED
+  //  >1        | true  | false            | USED_RELOCKED
+  //  >1        | true  | true             | USED_RELOCKED
+  // Note that it's important not to reorder the following enums, since the
+  // numerical values are used in the histogram code.
+  enum State : int {
+    DECODED_IMAGE_STATE_WASTED,
+    DECODED_IMAGE_STATE_USED,
+    DECODED_IMAGE_STATE_USED_RELOCK_FAILED,
+    DECODED_IMAGE_STATE_WASTED_RELOCKED,
+    DECODED_IMAGE_STATE_USED_RELOCKED,
+    DECODED_IMAGE_STATE_COUNT
+  } state = DECODED_IMAGE_STATE_WASTED;
+
+  if (usage_stats_.lock_count == 1) {
+    if (!usage_stats_.used)
+      state = DECODED_IMAGE_STATE_WASTED;
+    else if (usage_stats_.last_lock_failed)
+      state = DECODED_IMAGE_STATE_USED_RELOCK_FAILED;
+    else
+      state = DECODED_IMAGE_STATE_USED;
+  } else {
+    if (usage_stats_.used)
+      state = DECODED_IMAGE_STATE_USED_RELOCKED;
+    else
+      state = DECODED_IMAGE_STATE_WASTED_RELOCKED;
+  }
+
+  UMA_HISTOGRAM_ENUMERATION("Renderer4.SoftwareImageDecodeState", state,
+                            DECODED_IMAGE_STATE_COUNT);
+  UMA_HISTOGRAM_BOOLEAN("Renderer4.SoftwareImageDecodeState.FirstLockWasted",
+                        usage_stats_.first_lock_wasted);
+  if (usage_stats_.first_lock_out_of_raster)
+    UMA_HISTOGRAM_BOOLEAN(
+        "Renderer4.SoftwareImageDecodeState.FirstLockWasted.OutOfRaster",
+        usage_stats_.first_lock_wasted);
+}
+
+void SoftwareImageDecodeCacheUtils::CacheEntry::MoveImageMemoryTo(
+    CacheEntry* entry) {
+  DCHECK(!is_budgeted);
+  DCHECK_EQ(ref_count, 0);
+
+  // Copy/move most things except budgeted and ref counts.
+  entry->decode_failed = decode_failed;
+  entry->is_locked = is_locked;
+  is_locked = false;
+
+  entry->memory = std::move(memory);
+  entry->image_info_ = std::move(image_info_);
+  entry->src_rect_offset_ = std::move(src_rect_offset_);
+  entry->image_ = std::move(image_);
+}
+
+bool SoftwareImageDecodeCacheUtils::CacheEntry::Lock() {
+  if (!memory)
+    return false;
+
+  DCHECK(!is_locked);
+  bool success = memory->Lock();
+  if (!success) {
+    memory = nullptr;
+    usage_stats_.last_lock_failed = true;
+    return false;
+  }
+  is_locked = true;
+  ++usage_stats_.lock_count;
+  return true;
+}
+
+void SoftwareImageDecodeCacheUtils::CacheEntry::Unlock() {
+  if (!memory)
+    return;
+
+  DCHECK(is_locked);
+  memory->Unlock();
+  is_locked = false;
+  if (usage_stats_.lock_count == 1)
+    usage_stats_.first_lock_wasted = !usage_stats_.used;
+}
+
+}  // namespace cc
diff --git a/cc/tiles/software_image_decode_cache_utils.h b/cc/tiles/software_image_decode_cache_utils.h
new file mode 100644
index 0000000..875a889
--- /dev/null
+++ b/cc/tiles/software_image_decode_cache_utils.h
@@ -0,0 +1,190 @@
+// Copyright 2018 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_TILES_SOFTWARE_IMAGE_DECODE_CACHE_UTILS_H_
+#define CC_TILES_SOFTWARE_IMAGE_DECODE_CACHE_UTILS_H_
+
+#include "base/memory/discardable_memory.h"
+#include "base/memory/scoped_refptr.h"
+#include "cc/cc_export.h"
+#include "cc/paint/decoded_draw_image.h"
+#include "cc/paint/draw_image.h"
+#include "cc/paint/paint_image.h"
+#include "cc/raster/tile_task.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "third_party/skia/include/core/SkSize.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace cc {
+
+class SoftwareImageDecodeCacheUtils {
+ private:
+  // The following should only be accessed by the software image cache.
+  friend class SoftwareImageDecodeCache;
+
+  // CacheKey is a class that gets a cache key out of a given draw
+  // image. That is, this key uniquely identifies an image in the cache. Note
+  // that it's insufficient to use SkImage's unique id, since the same image can
+  // appear in the cache multiple times at different scales and filter
+  // qualities.
+  class CC_EXPORT CacheKey {
+   public:
+    // Enum indicating the type of processing to do for this key:
+    // kOriginal - use the original decode without any subrecting or scaling.
+    // kSubrectOriginal - extract a subrect from the original decode but do not
+    //                    scale it.
+    // kSubrectAndScale - extract a subrect (if needed) from the original decode
+    //                    and scale it.
+    enum ProcessingType { kOriginal, kSubrectOriginal, kSubrectAndScale };
+
+    static CacheKey FromDrawImage(const DrawImage& image,
+                                  SkColorType color_type);
+
+    CacheKey(const CacheKey& other);
+
+    bool operator==(const CacheKey& other) const {
+      // The frame_key always has to be the same. However, after that all
+      // original decodes are the same, so if we can use the original decode,
+      // return true. If not, then we have to compare every field. Note we don't
+      // compare |nearest_neighbor_| because we would only use kOriginal type in
+      // that case (dchecked below), which implies no scale. The returned scale
+      // to Skia would respect the nearest neighbor value of the requested
+      // image.
+      DCHECK(!is_nearest_neighbor_ || type_ == kOriginal);
+      return frame_key_ == other.frame_key_ && type_ == other.type_ &&
+             target_color_space_ == other.target_color_space_ &&
+             (type_ == kOriginal || (src_rect_ == other.src_rect_ &&
+                                     target_size_ == other.target_size_));
+    }
+
+    bool operator!=(const CacheKey& other) const { return !(*this == other); }
+
+    const PaintImage::FrameKey& frame_key() const { return frame_key_; }
+    ProcessingType type() const { return type_; }
+    bool is_nearest_neighbor() const { return is_nearest_neighbor_; }
+    gfx::Rect src_rect() const { return src_rect_; }
+    gfx::Size target_size() const { return target_size_; }
+    const gfx::ColorSpace& target_color_space() const {
+      return target_color_space_;
+    }
+
+    size_t get_hash() const { return hash_; }
+
+    // Helper to figure out how much memory the locked image represented by this
+    // key would take.
+    size_t locked_bytes() const {
+      // TODO(vmpstr): Handle formats other than RGBA.
+      base::CheckedNumeric<size_t> result = 4;
+      result *= target_size_.width();
+      result *= target_size_.height();
+      return result.ValueOrDefault(std::numeric_limits<size_t>::max());
+    }
+
+    std::string ToString() const;
+
+   private:
+    CacheKey(PaintImage::FrameKey frame_key,
+             ProcessingType type,
+             bool is_nearest_neighbor,
+             const gfx::Rect& src_rect,
+             const gfx::Size& size,
+             const gfx::ColorSpace& target_color_space);
+
+    PaintImage::FrameKey frame_key_;
+    ProcessingType type_;
+    bool is_nearest_neighbor_;
+    gfx::Rect src_rect_;
+    gfx::Size target_size_;
+    gfx::ColorSpace target_color_space_;
+    size_t hash_;
+  };
+
+  struct CacheKeyHash {
+    size_t operator()(const CacheKey& key) const { return key.get_hash(); }
+  };
+
+  // CacheEntry is a convenience storage for discardable memory. It can also
+  // construct an image out of SkImageInfo and stored discardable memory.
+  class CC_EXPORT CacheEntry {
+   public:
+    CacheEntry();
+    CacheEntry(const SkImageInfo& info,
+               std::unique_ptr<base::DiscardableMemory> memory,
+               const SkSize& src_rect_offset);
+    ~CacheEntry();
+
+    void MoveImageMemoryTo(CacheEntry* entry);
+
+    sk_sp<SkImage> image() const {
+      if (!memory)
+        return nullptr;
+      DCHECK(is_locked);
+      return image_;
+    }
+    const SkSize& src_rect_offset() const { return src_rect_offset_; }
+
+    bool Lock();
+    void Unlock();
+
+    // An ID which uniquely identifies this CacheEntry within the image decode
+    // cache. Used in memory tracing.
+    uint64_t tracing_id() const { return tracing_id_; }
+    // Mark this image as being used in either a draw or as a source for a
+    // scaled image. Either case represents this decode as being valuable and
+    // not wasted.
+    void mark_used() { usage_stats_.used = true; }
+    void mark_cached() { cached_ = true; }
+    void mark_out_of_raster() { usage_stats_.first_lock_out_of_raster = true; }
+
+    // Since this is an inner class, we expose these variables publicly for
+    // simplicity.
+    // TODO(vmpstr): A good simple clean-up would be to rethink this class
+    // and its interactions to instead expose a few functions which would also
+    // facilitate easier DCHECKs.
+    int ref_count = 0;
+    bool decode_failed = false;
+    bool is_locked = false;
+    bool is_budgeted = false;
+
+    scoped_refptr<TileTask> in_raster_task;
+    scoped_refptr<TileTask> out_of_raster_task;
+
+    std::unique_ptr<base::DiscardableMemory> memory;
+
+   private:
+    struct UsageStats {
+      // We can only create a decoded image in a locked state, so the initial
+      // lock count is 1.
+      int lock_count = 1;
+      bool used = false;
+      bool last_lock_failed = false;
+      bool first_lock_wasted = false;
+      bool first_lock_out_of_raster = false;
+    };
+
+    SkImageInfo image_info_;
+    sk_sp<SkImage> image_;
+    SkSize src_rect_offset_;
+    uint64_t tracing_id_;
+    UsageStats usage_stats_;
+    // Indicates whether this entry was ever in the cache.
+    bool cached_ = false;
+  };
+
+  static std::unique_ptr<CacheEntry> DoDecodeImage(const CacheKey& key,
+                                                   const PaintImage& image,
+                                                   SkColorType color_type);
+  static std::unique_ptr<CacheEntry> GenerateCacheEntryFromCandidate(
+      const CacheKey& key,
+      const DecodedDrawImage& candidate,
+      bool needs_extract_subset,
+      SkColorType color_type);
+};
+
+}  // namespace cc
+
+#endif  // CC_TILES_SOFTWARE_IMAGE_DECODE_CACHE_UTILS_H_
diff --git a/chrome/android/java/res/layout/main.xml b/chrome/android/java/res/layout/main.xml
index 055165e..c7602447 100644
--- a/chrome/android/java/res/layout/main.xml
+++ b/chrome/android/java/res/layout/main.xml
@@ -54,6 +54,19 @@
                 android:inflatedId="@+id/bottombar"
                 android:layout="@layout/custom_tabs_bottombar" />
 
+            <ViewStub
+                android:id="@+id/tab_modal_dialog_container_stub"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:inflatedId="@+id/tab_modal_dialog_container" />
+
+            <!-- Please do not add anything in between tab_modal_dialog_container_stub and
+                 tab_modal_dialog_container_sibling_view. -->
+            <ViewStub
+                android:id="@+id/tab_modal_dialog_container_sibling_view"
+                android:layout_width="0dp"
+                android:layout_height="0dp" />
+
             <org.chromium.chrome.browser.widget.FadingBackgroundView
                 android:id="@+id/fading_focus_target"
                 android:layout_width="match_parent"
diff --git a/chrome/android/java/res/layout/modal_dialog_container.xml b/chrome/android/java/res/layout/modal_dialog_container.xml
new file mode 100644
index 0000000..e7a9623
--- /dev/null
+++ b/chrome/android/java/res/layout/modal_dialog_container.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/scrim"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/modal_dialog_scrim_color" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/modal_dialog_view.xml b/chrome/android/java/res/layout/modal_dialog_view.xml
new file mode 100644
index 0000000..fe5cf07a
--- /dev/null
+++ b/chrome/android/java/res/layout/modal_dialog_view.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<!-- TODO(huayinz): rename menu_bg or change the dialog background to the desired one. -->
+<org.chromium.chrome.browser.widget.BoundedLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:chrome="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:background="@drawable/menu_bg"
+    chrome:maxWidth="@dimen/dialog_max_width">
+
+    <android.support.v7.widget.DialogTitle
+        android:id="@+id/title"
+        android:textAppearance="@style/BlackHeadline2"
+        style="@style/AlertDialogContent" />
+
+    <ScrollView
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        style="@style/AlertDialogContent">
+
+        <org.chromium.ui.widget.TextViewWithLeading
+            android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/BlackBody"
+            chrome:leading="20sp" />
+
+    </ScrollView>
+
+    <FrameLayout
+        android:id="@+id/custom"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <android.support.v7.widget.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?attr/buttonBarStyle" >
+
+        <Button
+            android:id="@+id/negative_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="?attr/buttonBarNegativeButtonStyle" />
+
+        <Button
+            android:id="@+id/positive_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="?attr/buttonBarPositiveButtonStyle" />
+
+    </android.support.v7.widget.ButtonBarLayout>
+
+</org.chromium.chrome.browser.widget.BoundedLinearLayout>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/promo_dialog_layout.xml b/chrome/android/java/res/layout/promo_dialog_layout.xml
index a9653c7..ce46e3a 100644
--- a/chrome/android/java/res/layout/promo_dialog_layout.xml
+++ b/chrome/android/java/res/layout/promo_dialog_layout.xml
@@ -19,7 +19,7 @@
             android:layout_gravity="center"
             android:orientation="vertical"
             android:background="@drawable/menu_bg"
-            chrome:maxWidth="@dimen/promo_dialog_max_width" >
+            chrome:maxWidth="@dimen/dialog_max_width" >
 
         <org.chromium.chrome.browser.widget.FadingEdgeScrollView
                 android:id="@+id/promo_container"
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index 78901f8..b9de644e 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -124,6 +124,29 @@
         <item name="chrometint">@color/dark_mode_tint</item>
     </style>
 
+    <style name="ModalDialogTheme" parent="AlertDialogTheme">
+        <item name="android:windowFrame">@null</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowMinWidthMajor">100%</item>
+        <item name="android:windowMinWidthMinor">100%</item>
+        <item name="buttonBarStyle">@style/ModalDialogButtonBarStyle</item>
+        <item name="buttonBarButtonStyle">@style/ModalDialogButtonStyle</item>
+    </style>
+
+    <style name="ModalDialogButtonBarStyle" parent="Widget.AppCompat.ButtonBar.AlertDialog">
+        <item name="android:orientation">horizontal</item>
+        <item name="android:gravity">bottom|end</item>
+        <item name="android:paddingStart">@dimen/modal_dialog_control_padding_horizontal</item>
+        <item name="android:paddingEnd">@dimen/modal_dialog_control_padding_horizontal</item>
+        <item name="android:paddingTop">@dimen/modal_dialog_control_padding_vertical</item>
+        <item name="android:paddingBottom">@dimen/modal_dialog_control_padding_vertical</item>
+    </style>
+
+    <style name="ModalDialogButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
+        <item name="android:textColor">@color/light_active_color</item>
+    </style>
+
     <style name="SimpleDialog" parent="AlertDialogTheme">
         <item name="windowNoTitle">true</item>
         <item name="windowActionBar">false</item>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 46f34d4..9558983f 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -139,7 +139,6 @@
     <dimen name="promo_dialog_illustration_margin">24dp</dimen>
     <dimen name="promo_dialog_illustration_width">150dp</dimen>
     <dimen name="promo_dialog_padding">16dp</dimen>
-    <dimen name="promo_dialog_max_width">600dp</dimen>
     <dimen name="promo_dialog_max_content_width">320dp</dimen>
     <dimen name="promo_dialog_min_scrollable_height">100dp</dimen>
     <dimen name="promo_dialog_title_text_size">23sp</dimen>
@@ -190,6 +189,12 @@
     <!-- Alert dialog -->
     <dimen name="dialog_padding_top">@dimen/abc_dialog_padding_top_material</dimen>
     <dimen name="dialog_padding_sides">@dimen/abc_dialog_padding_material</dimen>
+    <dimen name="dialog_max_width">600dp</dimen>
+
+    <!-- ModalDialogView dimensions -->
+    <dimen name="modal_dialog_control_padding_vertical">4dp</dimen>
+    <dimen name="modal_dialog_control_padding_horizontal">12dp</dimen>
+    <dimen name="tab_modal_scrim_vertical_margin">16dp</dimen>
 
     <!-- Tab Strip Dimensions -->
     <dimen name="tab_strip_height">0dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ApplicationInitialization.java b/chrome/android/java/src/org/chromium/chrome/browser/ApplicationInitialization.java
index 9f15f0e..74dd974e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ApplicationInitialization.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ApplicationInitialization.java
@@ -10,7 +10,6 @@
 
 import org.chromium.base.CommandLine;
 import org.chromium.chrome.R;
-import org.chromium.content.app.ContentApplication;
 import org.chromium.content.common.ContentSwitches;
 
 
@@ -29,7 +28,7 @@
      */
     public static void enableFullscreenFlags(
             Resources resources, Context context, int resControlContainerHeight) {
-        ContentApplication.initCommandLine(context);
+        ((ChromeApplication) context.getApplicationContext()).initCommandLine();
 
         CommandLine commandLine = CommandLine.getInstance();
         if (commandLine.hasSwitch(ChromeSwitches.DISABLE_FULLSCREEN)) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 87f328ff..4801a7c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -93,6 +93,8 @@
 import org.chromium.chrome.browser.metrics.StartupMetrics;
 import org.chromium.chrome.browser.metrics.UmaSessionStats;
 import org.chromium.chrome.browser.metrics.WebApkUma;
+import org.chromium.chrome.browser.modaldialog.AppModalPresenter;
+import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
 import org.chromium.chrome.browser.nfc.BeamController;
@@ -246,6 +248,7 @@
     private ContextualSearchManager mContextualSearchManager;
     protected ReaderModeManager mReaderModeManager;
     private SnackbarManager mSnackbarManager;
+    private ModalDialogManager mModalDialogManager;
     private DataUseSnackbarController mDataUseSnackbarController;
     private DataReductionPromoSnackbarController mDataReductionPromoSnackbarController;
     private AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
@@ -390,6 +393,8 @@
             mBottomSheetContentController.init(mBottomSheet, mTabModelSelector, this);
         }
         ((BottomContainer) findViewById(R.id.bottom_container)).initialize(mFullscreenManager);
+
+        mModalDialogManager = createModalDialogManager();
     }
 
     @Override
@@ -1180,6 +1185,21 @@
         return mSnackbarManager;
     }
 
+    /**
+     * @return The {@link ModalDialogManager} created for this class.
+     */
+    protected ModalDialogManager createModalDialogManager() {
+        return new ModalDialogManager(new AppModalPresenter(this), ModalDialogManager.APP_MODAL);
+    }
+
+    /**
+     * @return The {@link ModalDialogManager} that manages the display of modal dialogs (e.g.
+     *         JavaScript dialogs).
+     */
+    public ModalDialogManager getModalDialogManager() {
+        return mModalDialogManager;
+    }
+
     protected Drawable getBackgroundDrawable() {
         return new ColorDrawable(
                 ApiCompatibilityUtils.getColor(getResources(), R.color.light_background_color));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
index 689b2689..53eb1f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
@@ -98,7 +98,6 @@
         InvalidStartupDialog.show(activity, e.getErrorCode());
     }
 
-    @Override
     public void initCommandLine() {
         CommandLineInitUtil.initCommandLine(this, COMMAND_LINE_FILE);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java
index da1c84a2..a21a4963 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java
@@ -184,6 +184,10 @@
     /** Switch for enabling the Chrome Home Survey. */
     public static final String CHROME_HOME_FORCE_ENABLE_SURVEY = "force-enable-chrome-home-survey";
 
+    /** Switch to enable incognito tabs to be seen in Android Recents. */
+    public static final String ENABLE_INCOGNITO_SNAPSHOTS_IN_ANDROID_RECENTS =
+            "enable-incognito-snapshots-in-android-recents";
+
     /**
      * Don't crash on undispatched VIEW intents sent to .Main.
      * See ChromeTabbedActivity.maybeDispatchExplicitMainViewIntent() for more info.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index d2b60139..869cf8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -78,6 +78,7 @@
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.fullscreen.ComposedBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.incognito.IncognitoNotificationManager;
+import org.chromium.chrome.browser.incognito.IncognitoTabSnapshotController;
 import org.chromium.chrome.browser.infobar.DataReductionPromoInfoBar;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.metrics.ActivityStopMetrics;
@@ -85,6 +86,8 @@
 import org.chromium.chrome.browser.metrics.MainIntentBehaviorMetrics;
 import org.chromium.chrome.browser.metrics.StartupMetrics;
 import org.chromium.chrome.browser.metrics.UmaUtils;
+import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
+import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceChromeTabbedActivity;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
@@ -261,6 +264,8 @@
 
     private ScreenshotMonitor mScreenshotMonitor;
 
+    private TabModalLifetimeHandler mTabModalHandler;
+
     private boolean mUIInitialized;
 
     private Boolean mMergeTabsOnResume;
@@ -893,6 +898,12 @@
 
             mScreenshotMonitor = ScreenshotMonitor.create(ChromeTabbedActivity.this);
 
+            if (!CommandLine.getInstance().hasSwitch(
+                        ChromeSwitches.ENABLE_INCOGNITO_SNAPSHOTS_IN_ANDROID_RECENTS)) {
+                IncognitoTabSnapshotController.createIncognitoTabSnapshotController(
+                        getWindow(), mLayoutManager, mTabModelSelectorImpl);
+            }
+
             mUIInitialized = true;
         } finally {
             TraceEvent.end("ChromeTabbedActivity.initializeUI");
@@ -1901,6 +1912,8 @@
         super.onOmniboxFocusChanged(hasFocus);
 
         mMainIntentMetrics.onOmniboxFocused();
+
+        mTabModalHandler.onOmniboxFocusChanged(hasFocus);
     }
 
     private void recordBackPressedUma(String logMessage, @BackPressedResult int action) {
@@ -1942,6 +1955,8 @@
 
         if (getBottomSheet() != null && getBottomSheet().handleBackPress()) return true;
 
+        if (mTabModalHandler.handleBackPress()) return true;
+
         if (currentTab == null) {
             recordBackPressedUma("currentTab is null", BACK_PRESSED_TAB_IS_NULL);
             moveTaskToBack(true);
@@ -2158,6 +2173,11 @@
             mUndoBarPopupController = null;
         }
 
+        if (mTabModalHandler != null) {
+            mTabModalHandler.destroy();
+            mTabModalHandler = null;
+        }
+
         super.onDestroyInternal();
 
         FeatureUtilities.finalizePendingFeatures();
@@ -2212,6 +2232,13 @@
         return getLayoutManager().getOverviewListLayout();
     }
 
+    @Override
+    protected ModalDialogManager createModalDialogManager() {
+        ModalDialogManager manager = super.createModalDialogManager();
+        mTabModalHandler = new TabModalLifetimeHandler(this, manager);
+        return manager;
+    }
+
     // App Menu related code -----------------------------------------------------------------------
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index c76f8d38..22eeaeb8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -29,7 +29,6 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import org.chromium.base.BaseChromiumApplication;
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
@@ -219,7 +218,7 @@
         super();
         mContext = ContextUtils.getApplicationContext();
         // Command line switch values are used below.
-        BaseChromiumApplication.initCommandLine(mContext);
+        ((ChromeApplication) mContext).initCommandLine();
         mClientManager = new ClientManager(mContext);
         mLogRequests = CommandLine.getInstance().hasSwitch(LOG_SERVICE_REQUESTS);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotController.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotController.java
new file mode 100644
index 0000000..ec4049c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotController.java
@@ -0,0 +1,110 @@
+// 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.incognito;
+
+import android.view.Window;
+import android.view.WindowManager;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+
+/**
+ * This is the controller that prevents incognito tabs from being visible in Android Recents.
+ */
+public class IncognitoTabSnapshotController
+        extends EmptyTabModelSelectorObserver implements OverviewModeObserver {
+    private final Window mWindow;
+    private final TabModelSelector mTabModelSelector;
+    private boolean mInOverviewMode;
+
+    /**
+     * Creates and registers a new {@link IncognitoTabSnapshotController}.
+     * @param window The {@link Window} containing the flags to which the secure flag will be added
+     *               and cleared.
+     * @param layoutManager The {@link LayoutManagerChrome} where this controller will be added.
+     * @param tabModelSelector The {@link TabModelSelector} from where tab information will be
+     *                         extracted.
+     */
+    public static void createIncognitoTabSnapshotController(
+            Window window, LayoutManagerChrome layoutManager, TabModelSelector tabModelSelector) {
+        new IncognitoTabSnapshotController(window, layoutManager, tabModelSelector);
+    }
+
+    @VisibleForTesting
+    IncognitoTabSnapshotController(
+            Window window, LayoutManagerChrome layoutManager, TabModelSelector tabModelSelector) {
+        mWindow = window;
+        mTabModelSelector = tabModelSelector;
+
+        layoutManager.addOverviewModeObserver(this);
+        tabModelSelector.addObserver(this);
+    }
+
+    @Override
+    public void onOverviewModeStartedShowing(boolean showToolbar) {
+        mInOverviewMode = true;
+        updateIncognitoState();
+    }
+
+    @Override
+    public void onOverviewModeFinishedShowing() {}
+
+    @Override
+    public void onOverviewModeStartedHiding(boolean showToolbar, boolean delayAnimation) {
+        mInOverviewMode = false;
+    }
+
+    @Override
+    public void onOverviewModeFinishedHiding() {}
+
+    @Override
+    public void onChange() {
+        updateIncognitoState();
+    }
+
+    /**
+     * Sets the attributes flags to secure if there is an incognito tab visible.
+     */
+    @VisibleForTesting
+    void updateIncognitoState() {
+        WindowManager.LayoutParams attributes = mWindow.getAttributes();
+        boolean currentSecureState = (attributes.flags & WindowManager.LayoutParams.FLAG_SECURE)
+                == WindowManager.LayoutParams.FLAG_SECURE;
+        boolean expectedSecureState = isShowingIncognito();
+        if (currentSecureState == expectedSecureState) return;
+
+        if (expectedSecureState) {
+            mWindow.addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        } else {
+            mWindow.clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        }
+    }
+
+    /**
+     * @return Whether an incognito tab is visible.
+     */
+    @VisibleForTesting
+    boolean isShowingIncognito() {
+        boolean isInIncognitoModel = mTabModelSelector.getCurrentModel().isIncognito();
+        // Chrome Home is in overview mode when creating new tabs.
+        return isInIncognitoModel || (mInOverviewMode && getIncognitoTabCount() > 0);
+    }
+
+    // Set in overview mode for testing.
+    @VisibleForTesting
+    public void setInOverViewMode(boolean overviewMode) {
+        mInOverviewMode = overviewMode;
+    }
+
+    /**
+     * @return The number of incognito tabs.
+     */
+    private int getIncognitoTabCount() {
+        return mTabModelSelector.getModel(true).getCount();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
index e22e90f..0f6b678 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
@@ -43,7 +43,6 @@
 import org.chromium.chrome.browser.webapps.ActivityAssigner;
 import org.chromium.chrome.browser.webapps.ChromeWebApkHost;
 import org.chromium.components.crash.browser.CrashDumpManager;
-import org.chromium.content.app.ContentApplication;
 import org.chromium.content.browser.BrowserStartupController;
 import org.chromium.content.browser.DeviceUtils;
 import org.chromium.content.browser.SpeechRecognition;
@@ -211,7 +210,7 @@
         // Ensure critical files are available, so they aren't blocked on the file-system
         // behind long-running accesses in next phase.
         // Don't do any large file access here!
-        ContentApplication.initCommandLine(mApplication);
+        mApplication.initCommandLine();
         ChromeStrictMode.configureStrictMode();
         ChromeWebApkHost.init();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/jsdialog/JavascriptModalDialogView.java b/chrome/android/java/src/org/chromium/chrome/browser/jsdialog/JavascriptModalDialogView.java
new file mode 100644
index 0000000..41f3f76
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/jsdialog/JavascriptModalDialogView.java
@@ -0,0 +1,122 @@
+// 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.jsdialog;
+
+import android.support.annotation.StringRes;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.modaldialog.ModalDialogView;
+
+/**
+ * The JavaScript dialog that is either app modal or tab modal.
+ */
+public class JavascriptModalDialogView extends ModalDialogView {
+    private final TextView mMessageView;
+    private final EditText mPromptEditText;
+    private final CheckBox mSuppressCheckBox;
+    private final View mScrollView;
+
+    /**
+     * Create a {@link JavascriptModalDialogView} with the specified properties.
+     * @param controller The controller for the dialog view.
+     * @param title The title of the dialog view.
+     * @param message The message of the dialog view.
+     * @param promptText The promptText of the dialog view. If null,
+     *                   prompt edit text will not be shown.
+     * @param shouldShowSuppressCheckBox Whether the suppress check box should be shown.
+     * @param positiveButtonTextId The string resource id of the positive button.
+     * @param negativeButtonTextId The string resource id of the negative button.
+     * @return A {@link JavascriptModalDialogView} with the specified properties.
+     */
+    public static JavascriptModalDialogView create(Controller controller, String title,
+            String message, String promptText, boolean shouldShowSuppressCheckBox,
+            @StringRes int positiveButtonTextId, @StringRes int negativeButtonTextId) {
+        Params params = new Params();
+        params.title = title;
+        params.positiveButtonTextId = positiveButtonTextId;
+        params.negativeButtonTextId = negativeButtonTextId;
+        return new JavascriptModalDialogView(
+                controller, params, message, promptText, shouldShowSuppressCheckBox);
+    }
+
+    private JavascriptModalDialogView(Controller controller, Params params, String message,
+            String promptText, boolean shouldShowSuppressCheckBox) {
+        super(controller, params);
+
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        View customLayout = inflater.inflate(R.layout.js_modal_dialog, null);
+        params.customView = customLayout;
+
+        mScrollView = params.customView.findViewById(R.id.js_modal_dialog_scroll_view);
+        mMessageView = customLayout.findViewById(R.id.js_modal_dialog_message);
+        mPromptEditText = customLayout.findViewById(R.id.js_modal_dialog_prompt);
+        mSuppressCheckBox = customLayout.findViewById(R.id.suppress_js_modal_dialogs);
+
+        mMessageView.setText(message);
+        setPromptText(promptText);
+        setSuppressCheckBoxVisibility(shouldShowSuppressCheckBox);
+    }
+
+    @Override
+    protected void prepareBeforeShow() {
+        super.prepareBeforeShow();
+        // If the message is null or empty do not display the message text view.
+        // Hide parent scroll view instead of text view in order to prevent ui discrepancies.
+        if (mMessageView.getText().length() == 0) {
+            mScrollView.setVisibility(View.GONE);
+        } else {
+            // TODO(huayinz): See if View#canScrollVertictically() can be used for checking if
+            // scrollView is scrollable.
+            mScrollView.addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        boolean isScrollable =
+                                v.getMeasuredHeight() - v.getPaddingTop() - v.getPaddingBottom()
+                                < ((ViewGroup) v).getChildAt(0).getMeasuredHeight();
+                        v.setFocusable(isScrollable);
+                    });
+        }
+    }
+
+    /**
+     * @param promptText Prompt text for prompt dialog. If null, prompt text is not visible.
+     */
+    private void setPromptText(String promptText) {
+        if (promptText == null) return;
+        mPromptEditText.setVisibility(View.VISIBLE);
+
+        if (promptText.length() > 0) {
+            mPromptEditText.setText(promptText);
+            mPromptEditText.selectAll();
+        }
+    }
+
+    /**
+     * @return The prompt text edited by user.
+     */
+    public String getPromptText() {
+        return mPromptEditText.getText().toString();
+    }
+
+    /**
+     * @param visible Whether the suppress check box should be visible. The check box should only
+     *                be set visible if applicable for app modal JavaScript dialogs.
+     */
+    private void setSuppressCheckBoxVisibility(boolean visible) {
+        mSuppressCheckBox.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * @return Whether the suppress check box is checked by user.
+     */
+    public boolean isSuppressCheckBoxChecked() {
+        return mSuppressCheckBox.isChecked();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java
new file mode 100644
index 0000000..d678a37b
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java
@@ -0,0 +1,47 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.modaldialog;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.widget.AlwaysDismissedDialog;
+
+/** The presenter that shows a {@link ModalDialogView} in an Android dialog. */
+public class AppModalPresenter extends ModalDialogManager.Presenter {
+    private final Activity mActivity;
+    private Dialog mDialog;
+
+    public AppModalPresenter(Activity activity) {
+        mActivity = activity;
+    }
+
+    @Override
+    protected void addDialogView(View dialogView) {
+        mDialog = new AlwaysDismissedDialog(mActivity, R.style.ModalDialogTheme);
+        mDialog.setOnCancelListener(dialogInterface -> cancelCurrentDialog());
+        ViewGroup container = (ViewGroup) LayoutInflater.from(mActivity).inflate(
+                R.layout.modal_dialog_container, null);
+        mDialog.setContentView(container);
+        FrameLayout.LayoutParams params =
+                new FrameLayout.LayoutParams(ViewGroup.MarginLayoutParams.MATCH_PARENT,
+                        ViewGroup.MarginLayoutParams.WRAP_CONTENT, Gravity.CENTER);
+        container.addView(dialogView, params);
+        mDialog.show();
+    }
+
+    @Override
+    protected void removeDialogView(View dialogView) {
+        // Dismiss the currently showing dialog.
+        if (mDialog != null) mDialog.dismiss();
+        mDialog = null;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
new file mode 100644
index 0000000..865c2b5f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
@@ -0,0 +1,215 @@
+// 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.modaldialog;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.View;
+
+import org.chromium.base.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manager for managing the display of a queue of {@link ModalDialogView}s.
+ */
+public class ModalDialogManager {
+    /**
+     * Present a {@link ModalDialogView} in a container.
+     */
+    public static abstract class Presenter {
+        private Runnable mCancelCallback;
+        private ModalDialogView mModalDialog;
+        private View mCurrentView;
+
+        /**
+         * @param dialog The dialog that's currently showing in this presenter. If null, no dialog
+         *               is currently showing.
+         */
+        private void setModalDialog(
+                @Nullable ModalDialogView dialog, @Nullable Runnable cancelCallback) {
+            if (dialog == null) {
+                removeDialogView(mCurrentView);
+                mModalDialog = null;
+                mCancelCallback = null;
+            } else {
+                assert mModalDialog
+                        == null : "Should call setModalDialog(null) before setting a modal dialog.";
+                mModalDialog = dialog;
+                mCurrentView = dialog.getView();
+                mCancelCallback = cancelCallback;
+                addDialogView(mCurrentView);
+            }
+        }
+
+        /**
+         * Run the cached cancel callback and reset the cached callback.
+         */
+        protected final void cancelCurrentDialog() {
+            if (mCancelCallback == null) return;
+
+            // Set #mCancelCallback to null before calling the callback to avoid it being
+            // updated during the callback.
+            Runnable callback = mCancelCallback;
+            mCancelCallback = null;
+            callback.run();
+        }
+
+        /**
+         * @return The modal dialog that this presenter is showing.
+         */
+        protected final ModalDialogView getModalDialog() {
+            return mModalDialog;
+        }
+
+        /**
+         * Add the specified {@link ModalDialogView} in a container.
+         * @param dialogView The {@link ModalDialogView} that needs to be shown.
+         */
+        protected abstract void addDialogView(View dialogView);
+
+        /**
+         * Remove the specified {@link ModalDialogView} from a container.
+         * @param dialogView The {@link ModalDialogView} that needs to be removed.
+         */
+        protected abstract void removeDialogView(View dialogView);
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({APP_MODAL, TAB_MODAL})
+    public @interface ModalDialogType {}
+    public static final int APP_MODAL = 0;
+    public static final int TAB_MODAL = 1;
+
+    /** Mapping of the {@link Presenter}s and the type of dialogs they are showing. */
+    private final SparseArray<Presenter> mPresenters = new SparseArray<>();
+
+    /** The list of pending dialogs */
+    private final List<Pair<ModalDialogView, Integer>> mPendingDialogs = new ArrayList<>();
+
+    /** The default presenter to be used if a specified type is not supported. */
+    private final Presenter mDefaultPresenter;
+
+    /** The presenter of the type of the dialog that is currently showing. */
+    private Presenter mCurrentPresenter;
+
+    /**
+     * Constructor for initializing default {@link Presenter}.
+     * @param defaultPresenter The default presenter to be used when no presenter specified.
+     * @param defaultType The dialog type of the default presenter.
+     */
+    public ModalDialogManager(
+            @NonNull Presenter defaultPresenter, @ModalDialogType int defaultType) {
+        mDefaultPresenter = defaultPresenter;
+        registerPresenter(defaultPresenter, defaultType);
+    }
+
+    /**
+     * Register a {@link Presenter} that shows a specific type of dialog. Note that only one
+     * presenter of each type can be registered.
+     * @param presenter The {@link Presenter} to be registered.
+     * @param dialogType The type of the dialog shown by the specified presenter.
+     */
+    public void registerPresenter(Presenter presenter, @ModalDialogType int dialogType) {
+        assert mPresenters.get(dialogType)
+                == null : "Only one presenter can be registered for each type.";
+        mPresenters.put(dialogType, presenter);
+    }
+
+    /**
+     * @return Whether a dialog is currently showing.
+     */
+    public boolean isShowing() {
+        return mCurrentPresenter != null;
+    }
+
+    /**
+     * Show the specified dialog. If another dialog is currently showing, the specified dialog will
+     * be added to the pending dialog list.
+     * @param dialog The dialog to be shown or added to pending list.
+     * @param dialogType The type of the dialog to be shown.
+     */
+    public void showDialog(ModalDialogView dialog, @ModalDialogType int dialogType) {
+        if (isShowing()) {
+            mPendingDialogs.add(Pair.create(dialog, dialogType));
+            return;
+        }
+
+        dialog.prepareBeforeShow();
+        mCurrentPresenter = mPresenters.get(dialogType, mDefaultPresenter);
+        mCurrentPresenter.setModalDialog(dialog, () -> cancelDialog(dialog));
+    }
+
+    /**
+     * Dismiss the specified dialog. If the dialog is not currently showing, it will be removed from
+     * the pending dialog list.
+     * @param dialog The dialog to be dismissed or removed from pending list.
+     */
+    public void dismissDialog(ModalDialogView dialog) {
+        if (dialog != mCurrentPresenter.getModalDialog()) {
+            for (int i = 0; i < mPendingDialogs.size(); ++i) {
+                if (mPendingDialogs.get(i).first == dialog) {
+                    mPendingDialogs.remove(i);
+                    break;
+                }
+            }
+            return;
+        }
+
+        if (!isShowing()) return;
+        assert dialog == mCurrentPresenter.getModalDialog();
+
+        mCurrentPresenter.setModalDialog(null, null);
+        mCurrentPresenter = null;
+
+        if (!mPendingDialogs.isEmpty()) {
+            Pair<ModalDialogView, Integer> nextDialog = mPendingDialogs.remove(0);
+            showDialog(nextDialog.first, nextDialog.second);
+        }
+    }
+
+    /**
+     * Cancel showing the specified dialog. This is essentially the same as
+     * {@link #dismissDialog(ModalDialogView)} but will also call the onCancelled callback from the
+     * modal dialog.
+     * @param dialog The dialog to be cancelled.
+     */
+    public void cancelDialog(ModalDialogView dialog) {
+        dismissDialog(dialog);
+        dialog.getController().onCancel();
+    }
+
+    /**
+     * Dismiss the dialog currently shown and remove all pending dialogs and call the onCancelled
+     * callbacks from the modal dialogs.
+     */
+    protected void cancelAllDialogs() {
+        while (!mPendingDialogs.isEmpty()) {
+            mPendingDialogs.remove(0).first.getController().onCancel();
+        }
+        if (isShowing()) cancelDialog(mCurrentPresenter.getModalDialog());
+    }
+
+    @VisibleForTesting
+    List getPendingDialogsForTest() {
+        return mPendingDialogs;
+    }
+
+    @VisibleForTesting
+    Presenter getPresenterForTest(@ModalDialogType int dialogType) {
+        return mPresenters.get(dialogType);
+    }
+
+    @VisibleForTesting
+    Presenter getCurrentPresenterForTest() {
+        return mCurrentPresenter;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogView.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogView.java
new file mode 100644
index 0000000..53d9a5d0
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogView.java
@@ -0,0 +1,172 @@
+// 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.modaldialog;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.text.TextUtils;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Generic builder for app modal or tab modal alert dialogs.
+ */
+public class ModalDialogView implements View.OnClickListener {
+    /**
+     * Interface that controls the actions on the modal dialog.
+     */
+    public interface Controller {
+        /**
+         * Handle click event of the buttons on the dialog.
+         * @param buttonType The type of the button.
+         */
+        void onClick(@ButtonType int buttonType);
+
+        /**
+         * Handle dismiss event when the dialog is not dismissed by actions on the dialog such as
+         * back press, and on tab modal dialog, tab switcher button click.
+         */
+        void onCancel();
+    }
+
+    /** Parameters that can be used to create a new ModalDialogView. */
+    public static class Params {
+        /** Optional: The String to show as the dialog title. */
+        public String title;
+
+        /** Optional: The String to show as descriptive text. */
+        public String message;
+
+        /**
+         * Optional: The customized View to show in the dialog. Note that the message and the
+         * custom view cannot be set together.
+         */
+        public View customView;
+
+        /** Optional: Resource ID of the String to show on the positive button. */
+        public @StringRes int positiveButtonTextId;
+
+        /** Optional: Resource ID of the String to show on the negative button. */
+        public @StringRes int negativeButtonTextId;
+    }
+
+    @IntDef({BUTTON_POSITIVE, BUTTON_NEGATIVE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ButtonType {}
+    public static final int BUTTON_POSITIVE = 0;
+    public static final int BUTTON_NEGATIVE = 1;
+
+    private final Controller mController;
+    private final Context mContext;
+    private final Params mParams;
+
+    private final View mDialogView;
+    private final TextView mTitleView;
+    private final TextView mMessageView;
+    private final ViewGroup mCustomView;
+    private final Button mPositiveButton;
+    private final Button mNegativeButton;
+
+    /**
+     * Constructor for initializing controller and views.
+     * @param controller The controller for this dialog.
+     */
+    public ModalDialogView(@NonNull Controller controller, @NonNull Params params) {
+        mController = controller;
+        mContext = new ContextThemeWrapper(
+                ContextUtils.getApplicationContext(), R.style.ModalDialogTheme);
+        mParams = params;
+
+        mDialogView = LayoutInflater.from(mContext).inflate(R.layout.modal_dialog_view, null);
+        mTitleView = mDialogView.findViewById(R.id.title);
+        mMessageView = mDialogView.findViewById(R.id.message);
+        mCustomView = mDialogView.findViewById(R.id.custom);
+        mPositiveButton = mDialogView.findViewById(R.id.positive_button);
+        mNegativeButton = mDialogView.findViewById(R.id.negative_button);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mPositiveButton) {
+            mController.onClick(BUTTON_POSITIVE);
+        } else if (view == mNegativeButton) {
+            mController.onClick(BUTTON_NEGATIVE);
+        }
+    }
+
+    /**
+     * Prepare the contents before showing the dialog.
+     */
+    protected void prepareBeforeShow() {
+        if (TextUtils.isEmpty(mParams.title)) {
+            mTitleView.setVisibility(View.GONE);
+        } else {
+            mTitleView.setText(mParams.title);
+        }
+
+        if (TextUtils.isEmpty(mParams.message)) {
+            ((View) mMessageView.getParent()).setVisibility(View.GONE);
+        } else {
+            assert mParams.customView == null;
+            mMessageView.setText(mParams.message);
+        }
+
+        if (mParams.customView != null) {
+            if (mParams.customView.getParent() != null) {
+                ((ViewGroup) mParams.customView.getParent()).removeView(mParams.customView);
+            }
+            mCustomView.addView(mParams.customView);
+        } else {
+            mCustomView.setVisibility(View.GONE);
+        }
+
+        if (mParams.positiveButtonTextId == 0) {
+            mPositiveButton.setVisibility(View.GONE);
+        } else {
+            mPositiveButton.setText(mParams.positiveButtonTextId);
+            mPositiveButton.setOnClickListener(this);
+        }
+
+        if (mParams.negativeButtonTextId == 0) {
+            mNegativeButton.setVisibility(View.GONE);
+        } else {
+            mNegativeButton.setText(mParams.negativeButtonTextId);
+            mNegativeButton.setOnClickListener(this);
+        }
+    }
+
+    /**
+     * @return The {@link Context} with the modal dialog theme set.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * @return The content view of this dialog.
+     */
+    public View getView() {
+        return mDialogView;
+    }
+
+    /**
+     * @return The controller that controls the actions on the dialogs.
+     */
+    public Controller getController() {
+        return mController;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java
new file mode 100644
index 0000000..0a4a2f91
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java
@@ -0,0 +1,84 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.modaldialog;
+
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.tab.EmptyTabObserver;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
+
+/**
+ * Class responsible for handling dismissal of a tab modal dialog on user actions outside the tab
+ * modal dialog.
+ */
+public class TabModalLifetimeHandler {
+    /** The observer to dismiss all dialogs when the attached tab is not interactable. */
+    private final TabObserver mTabObserver = new EmptyTabObserver() {
+        @Override
+        public void onInteractabilityChanged(boolean isInteractable) {
+            if (!isInteractable && mPresenter.getModalDialog() != null) {
+                mManager.cancelAllDialogs();
+            }
+        }
+    };
+
+    private final ModalDialogManager mManager;
+    private final TabModalPresenter mPresenter;
+    private final TabModelSelectorTabModelObserver mTabModelObserver;
+    private final boolean mHasBottomControls;
+
+    private Tab mActiveTab;
+
+    /**
+     * @param activity The {@link ChromeActivity} that this handler is attached to.
+     * @param manager The {@link ModalDialogManager} that this handler handles.
+     */
+    public TabModalLifetimeHandler(ChromeActivity activity, ModalDialogManager manager) {
+        mManager = manager;
+        mPresenter = new TabModalPresenter(activity);
+        mManager.registerPresenter(mPresenter, ModalDialogManager.TAB_MODAL);
+        mHasBottomControls = activity.getBottomSheet() != null;
+
+        TabModelSelector tabModelSelector = activity.getTabModelSelector();
+        mTabModelObserver = new TabModelSelectorTabModelObserver(tabModelSelector) {
+            @Override
+            public void didSelectTab(Tab tab, TabModel.TabSelectionType type, int lastId) {
+                if (mActiveTab != null) mActiveTab.removeObserver(mTabObserver);
+                mActiveTab = tabModelSelector.getCurrentTab();
+                if (mActiveTab != null) mActiveTab.addObserver(mTabObserver);
+            }
+        };
+    }
+
+    /**
+     * Notified when the focus of the omnibox has changed.
+     * @param hasFocus Whether the omnibox currently has focus.
+     */
+    public void onOmniboxFocusChanged(boolean hasFocus) {
+        // If has bottom controls, the view hierarchy will be updated by mBottomSheetObserver.
+        if (mPresenter.getModalDialog() != null && !mHasBottomControls) {
+            mPresenter.updateContainerHierarchy(!hasFocus);
+        }
+    }
+
+    /**
+     * Handle a back press event.
+     */
+    public boolean handleBackPress() {
+        if (mPresenter.getModalDialog() == null) return false;
+        mPresenter.cancelCurrentDialog();
+        return true;
+    }
+
+    /**
+     * Remove any remaining dependencies.
+     */
+    public void destroy() {
+        mTabModelObserver.destroy();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
new file mode 100644
index 0000000..17de173
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
@@ -0,0 +1,236 @@
+// 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.modaldialog;
+
+import android.content.res.Resources;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewStub;
+import android.widget.FrameLayout;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver;
+import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
+import org.chromium.content.browser.ContentViewCore;
+
+/**
+ * The presenter that displays a single tab modal dialog.
+ */
+public class TabModalPresenter extends ModalDialogManager.Presenter {
+    /** The activity displaying the dialogs. */
+    private final ChromeActivity mChromeActivity;
+
+    /** Whether browser controls are at the bottom */
+    private final boolean mHasBottomControls;
+
+    /** The active tab of which the dialog will be shown on top. */
+    private Tab mActiveTab;
+
+    /**
+     * The observer to change view hierarchy for the dialog container when the sheet is opened or
+     * closed.
+     */
+    private BottomSheetObserver mBottomSheetObserver;
+
+    /** The parent view that contains the dialog container. */
+    private ViewGroup mContainerParent;
+
+    /** The container view that a dialog to be shown will be attached to. */
+    private ViewGroup mDialogContainer;
+
+    /** Whether the dialog container is brought to the front in its parent. */
+    private boolean mContainerIsAtFront;
+
+    /** Whether the action bar on selected text is temporarily cleared for showing dialogs. */
+    private boolean mDidClearTextControls;
+
+    /**
+     * The sibling view of the dialog container drawn next in its parent when it should be behind
+     * browser controls. If BottomSheet is opened or UrlBar is focused, the dialog container should
+     * be behind the browser controls and the URL suggestions.
+     */
+    private View mDefaultNextSiblingView;
+
+    /**
+     * Constructor for initializing dialog container.
+     * @param chromeActivity The activity displaying the dialogs.
+     */
+    public TabModalPresenter(ChromeActivity chromeActivity) {
+        mChromeActivity = chromeActivity;
+        mHasBottomControls = mChromeActivity.getBottomSheet() != null;
+
+        if (mHasBottomControls) {
+            mBottomSheetObserver = new EmptyBottomSheetObserver() {
+                @Override
+                public void onSheetOpened(@BottomSheet.StateChangeReason int reason) {
+                    updateContainerHierarchy(false);
+                }
+
+                @Override
+                public void onSheetClosed(@BottomSheet.StateChangeReason int reason) {
+                    updateContainerHierarchy(true);
+                }
+            };
+        }
+    }
+
+    @Override
+    protected void addDialogView(View dialogView) {
+        if (mDialogContainer == null) initDialogContainer();
+        setBrowserControlsAccess(true);
+        mDialogContainer.setVisibility(View.VISIBLE);
+
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                MarginLayoutParams.MATCH_PARENT, MarginLayoutParams.WRAP_CONTENT, Gravity.CENTER);
+        mDialogContainer.addView(dialogView, params);
+        mChromeActivity.addViewObscuringAllTabs(mDialogContainer);
+    }
+
+    @Override
+    protected void removeDialogView(View dialogView) {
+        setBrowserControlsAccess(false);
+        mDialogContainer.removeView(dialogView);
+        mDialogContainer.setVisibility(View.GONE);
+        mChromeActivity.removeViewObscuringAllTabs(mDialogContainer);
+    }
+
+    /**
+     * Change view hierarchy for the dialog container to be either the front most or beneath the
+     * toolbar.
+     * @param toFront Whether the dialog container should be brought to the front.
+     */
+    void updateContainerHierarchy(boolean toFront) {
+        if (toFront == mContainerIsAtFront) return;
+        mContainerIsAtFront = toFront;
+        if (toFront) {
+            mDialogContainer.bringToFront();
+        } else {
+            mContainerParent.removeView(mDialogContainer);
+            mContainerParent.addView(
+                    mDialogContainer, mContainerParent.indexOfChild(mDefaultNextSiblingView));
+        }
+    }
+
+    /**
+     * Inflate the dialog container in the dialog container view stub.
+     */
+    private void initDialogContainer() {
+        ViewStub dialogContainerStub =
+                mChromeActivity.findViewById(R.id.tab_modal_dialog_container_stub);
+        dialogContainerStub.setLayoutResource(R.layout.modal_dialog_container);
+
+        mDialogContainer = (ViewGroup) dialogContainerStub.inflate();
+        mContainerParent = (ViewGroup) mDialogContainer.getParent();
+        // The default sibling view is the next view of the dialog container stub in main.xml and
+        // should not be removed from its parent.
+        mDefaultNextSiblingView =
+                mChromeActivity.findViewById(R.id.tab_modal_dialog_container_sibling_view);
+        assert mDefaultNextSiblingView != null;
+
+        // Set the margin of the container and the dialog scrim so that the scrim doesn't overlap
+        // the toolbar.
+        Resources resources = mChromeActivity.getResources();
+        int scrimVerticalMargin =
+                resources.getDimensionPixelSize(R.dimen.tab_modal_scrim_vertical_margin);
+        int containerVerticalMargin =
+                resources.getDimensionPixelSize(mChromeActivity.getControlContainerHeightResource())
+                - scrimVerticalMargin;
+
+        MarginLayoutParams params = (MarginLayoutParams) mDialogContainer.getLayoutParams();
+        params.width = ViewGroup.MarginLayoutParams.MATCH_PARENT;
+        params.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
+        params.topMargin = !mHasBottomControls ? containerVerticalMargin : 0;
+        params.bottomMargin = mHasBottomControls ? containerVerticalMargin : 0;
+        mDialogContainer.setLayoutParams(params);
+
+        View scrimView = mDialogContainer.findViewById(R.id.scrim);
+        params = (MarginLayoutParams) scrimView.getLayoutParams();
+        params.width = MarginLayoutParams.MATCH_PARENT;
+        params.height = MarginLayoutParams.MATCH_PARENT;
+        params.topMargin = !mHasBottomControls ? scrimVerticalMargin : 0;
+        params.bottomMargin = mHasBottomControls ? scrimVerticalMargin : 0;
+        scrimView.setLayoutParams(params);
+    }
+
+    /**
+     * Set whether the browser controls access should be restricted. If true, dialogs are expected
+     * to be showing and overflow menu would be disabled.
+     * @param restricted Whether the browser controls access should be restricted.
+     */
+    private void setBrowserControlsAccess(boolean restricted) {
+        BottomSheet bottomSheet = mChromeActivity.getBottomSheet();
+        View menuButton = mChromeActivity.getToolbarManager().getMenuButton();
+
+        if (restricted) {
+            mActiveTab = mChromeActivity.getActivityTab();
+            assert mActiveTab
+                    != null : "Tab modal dialogs should be shown on top of an active tab.";
+
+            // Hide contextual search panel so that bottom toolbar will not be
+            // obscured and back press is not overridden.
+            ContextualSearchManager contextualSearchManager =
+                    mChromeActivity.getContextualSearchManager();
+            if (contextualSearchManager != null) {
+                contextualSearchManager.hideContextualSearch(
+                        OverlayPanel.StateChangeReason.UNKNOWN);
+            }
+
+            // Dismiss the action bar that obscures the dialogs but preserve the text selection.
+            ContentViewCore contentViewCore = mActiveTab.getContentViewCore();
+            if (contentViewCore != null) {
+                contentViewCore.preserveSelectionOnNextLossOfFocus();
+                contentViewCore.getContainerView().clearFocus();
+                contentViewCore.updateTextSelectionUI(false);
+                mDidClearTextControls = true;
+            }
+
+            // Force toolbar to show and disable overflow menu.
+            // TODO(huayinz): figure out a way to avoid |UpdateBrowserControlsState| being blocked
+            // by render process stalled due to javascript dialog.
+            mActiveTab.onTabModalDialogStateChanged(true);
+
+            if (mHasBottomControls) {
+                bottomSheet.setSheetState(BottomSheet.SHEET_STATE_PEEK, true);
+                bottomSheet.addObserver(mBottomSheetObserver);
+            } else {
+                mChromeActivity.getToolbarManager().setUrlBarFocus(false);
+            }
+            menuButton.setEnabled(false);
+            updateContainerHierarchy(true);
+        } else {
+            // Show the action bar back if it was dismissed when the dialogs were showing.
+            ContentViewCore contentViewCore = mActiveTab.getContentViewCore();
+            if (mDidClearTextControls) {
+                mDidClearTextControls = false;
+                if (contentViewCore != null) {
+                    contentViewCore.updateTextSelectionUI(true);
+                }
+            }
+
+            mActiveTab.onTabModalDialogStateChanged(false);
+            menuButton.setEnabled(true);
+            if (mHasBottomControls) bottomSheet.removeObserver(mBottomSheetObserver);
+            mActiveTab = null;
+        }
+    }
+
+    @VisibleForTesting
+    View getDialogContainerForTest() {
+        return mDialogContainer;
+    }
+
+    @VisibleForTesting
+    ViewGroup getContainerParentForTest() {
+        return mContainerParent;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java
index eedc7497a..09b9cf9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java
@@ -34,11 +34,11 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.chrome.browser.ChromeApplication;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.database.SQLiteCursor;
 import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
-import org.chromium.content.app.ContentApplication;
 import org.chromium.content.browser.BrowserStartupController;
 
 import java.util.ArrayList;
@@ -249,7 +249,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                ContentApplication.initCommandLine(getContext());
+                ((ChromeApplication) getContext().getApplicationContext()).initCommandLine();
 
                 BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
                         .addStartupCompletedObserver(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index a299b0b..63b82c3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -234,6 +234,7 @@
 
     private boolean mIsClosing;
     private boolean mIsShowingErrorPage;
+    private boolean mIsShowingTabModalDialog;
 
     private Bitmap mFavicon;
 
@@ -806,6 +807,13 @@
     }
 
     /**
+     * @return Whether a tab modal dialog is showing.
+     */
+    public boolean isShowingTabModalDialog() {
+        return mIsShowingTabModalDialog;
+    }
+
+    /**
      * @return Whether the {@link Tab} is currently showing an error page.
      */
     public boolean isShowingErrorPage() {
@@ -3358,6 +3366,17 @@
         hideMediaDownloadInProductHelp();
     }
 
+    /**
+     * Handle browser controls when a tab modal dialog is shown.
+     * @param isShowing Whether a tab modal dialog is showing.
+     */
+    public void onTabModalDialogStateChanged(boolean isShowing) {
+        mIsShowingTabModalDialog = isShowing;
+        if (mFullscreenManager == null) return;
+        mFullscreenManager.setPositionsForTabToNonFullscreen();
+        updateBrowserControlsState(BrowserControlsState.SHOWN, false);
+    }
+
     @CalledByNative
     private void showMediaDownloadInProductHelp(int x, int y, int width, int height) {
         // If we are not currently showing the widget, ask the tracker if we can show it.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java
index d022cd3..556c616 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateBrowserControlsVisibilityDelegate.java
@@ -153,6 +153,7 @@
         enableHidingBrowserControls &= (mTab.getFullscreenManager() != null);
         enableHidingBrowserControls &= DeviceClassManager.enableFullscreen();
         enableHidingBrowserControls &= !mIsFullscreenWaitingForLoad;
+        enableHidingBrowserControls &= !mTab.isShowingTabModalDialog();
 
         return enableHidingBrowserControls;
     }
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 92038f3..f838d7b 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -492,6 +492,7 @@
   "java/src/org/chromium/chrome/browser/identity/UuidBasedUniqueIdentificationGenerator.java",
   "java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java",
   "java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java",
+  "java/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotController.java",
   "java/src/org/chromium/chrome/browser/infobar/AppBannerInfoBarAndroid.java",
   "java/src/org/chromium/chrome/browser/infobar/AppBannerInfoBarDelegateAndroid.java",
   "java/src/org/chromium/chrome/browser/infobar/AutofillCreditCardFillingInfoBar.java",
@@ -559,6 +560,7 @@
   "java/src/org/chromium/chrome/browser/invalidation/InvalidationController.java",
   "java/src/org/chromium/chrome/browser/invalidation/InvalidationServiceFactory.java",
   "java/src/org/chromium/chrome/browser/invalidation/UniqueIdInvalidationClientNameGenerator.java",
+  "java/src/org/chromium/chrome/browser/jsdialog/JavascriptModalDialogView.java",
   "java/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelper.java",
   "java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java",
   "java/src/org/chromium/chrome/browser/locale/LocaleManager.java",
@@ -632,6 +634,11 @@
   "java/src/org/chromium/chrome/browser/metrics/VariationsSession.java",
   "java/src/org/chromium/chrome/browser/metrics/WebApkUma.java",
   "java/src/org/chromium/chrome/browser/metrics/WebappUma.java",
+  "java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java",
+  "java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java",
+  "java/src/org/chromium/chrome/browser/modaldialog/ModalDialogView.java",
+  "java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java",
+  "java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java",
   "java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java",
   "java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceChromeTabbedActivity.java",
   "java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java",
@@ -1601,6 +1608,7 @@
   "javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java",
   "javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java",
   "javatests/src/org/chromium/chrome/browser/metrics/UkmIncognitoTest.java",
+  "javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java",
   "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java",
   "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java",
@@ -1892,6 +1900,7 @@
   "junit/src/org/chromium/chrome/browser/firstrun/ToSAckedReceiverTest.java",
   "junit/src/org/chromium/chrome/browser/fullscreen/BrowserStateBrowserControlsVisibilityDelegateTest.java",
   "junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java",
+  "junit/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotControllerTest.java",
   "junit/src/org/chromium/chrome/browser/infobar/IPHInfoBarSupportTest.java",
   "junit/src/org/chromium/chrome/browser/init/AsyncInitTaskRunnerTest.java",
   "junit/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
new file mode 100644
index 0000000..b3745d4
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
@@ -0,0 +1,395 @@
+// 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.modaldialog;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.not;
+
+import android.support.test.espresso.Espresso;
+import android.support.test.filters.SmallTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+
+/**
+ * Tests for displaying and functioning of modal dialogs on tabs.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+public class ModalDialogManagerTest {
+    @Rule
+    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+
+    private static final int MAX_DIALOGS = 3;
+    private ChromeTabbedActivity mActivity;
+    private ModalDialogManager mManager;
+    private ModalDialogView[] mModalDialogViews;
+
+    @Before
+    public void setUp() throws Exception {
+        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivity = mActivityTestRule.getActivity();
+        mManager = mActivity.getModalDialogManager();
+        mModalDialogViews = new ModalDialogView[MAX_DIALOGS];
+        for (int i = 0; i < MAX_DIALOGS; i++) mModalDialogViews[i] = createDialog(i);
+    }
+
+    @Test
+    @SmallTest
+    public void testOneDialog() throws Exception {
+        // Initially there are no dialogs in the pending list. Browser controls are not restricted.
+        checkPendingSize(0);
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+
+        // Show a dialog. The pending list should be empty, and the dialog should be showing.
+        // Browser controls should be restricted.
+        showDialog(0, ModalDialogManager.TAB_MODAL);
+        checkPendingSize(0);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(hasDescendant(withText("0"))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Dismiss the dialog by clicking positive button.
+        onView(withText(R.string.ok)).perform(click());
+        checkPendingSize(0);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(not(hasDescendant(withText("0")))));
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+    }
+
+    @Test
+    @SmallTest
+    public void testTwoDialogs() throws Exception {
+        // Initially there are no dialogs in the pending list. Browser controls are not restricted.
+        checkPendingSize(0);
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+
+        // Show the first dialog.
+        // The pending list should be empty, and the dialog should be showing.
+        // The tab modal container shouldn't be in the window hierarchy when an app modal dialog is
+        // showing.
+        showDialog(0, ModalDialogManager.APP_MODAL);
+        checkPendingSize(0);
+        onView(withText("0")).check(matches(isDisplayed()));
+        onView(withId(R.id.tab_modal_dialog_container)).check(doesNotExist());
+        checkCurrentPresenter(ModalDialogManager.APP_MODAL);
+
+        // Show the second dialog. It should be added to the pending list, and the first dialog
+        // should still be shown.
+        showDialog(1, ModalDialogManager.TAB_MODAL);
+        checkPendingSize(1);
+        onView(withText("0")).check(matches(isDisplayed()));
+        onView(withId(R.id.tab_modal_dialog_container)).check(doesNotExist());
+        checkCurrentPresenter(ModalDialogManager.APP_MODAL);
+
+        // Dismiss the first dialog by clicking cancel. The second dialog should be removed from
+        // pending list and shown immediately after.
+        onView(withText(R.string.cancel)).perform(click());
+        checkPendingSize(0);
+        onView(withText("0")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(
+                        allOf(not(hasDescendant(withText("0"))), hasDescendant(withText("1")))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Dismiss the second dialog by clicking ok. Browser controls should no longer be
+        // restricted.
+        onView(withText(R.string.ok)).perform(click());
+        checkPendingSize(0);
+        onView(withText("0")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(
+                        not(hasDescendant(withText("0"))), not(hasDescendant(withText("1"))))));
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+    }
+
+    @Test
+    @SmallTest
+    public void testThreeDialogs() throws Exception {
+        // Initially there are no dialogs in the pending list. Browser controls are not restricted.
+        checkPendingSize(0);
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+
+        // Show the first dialog.
+        // The pending list should be empty, and the dialog should be showing.
+        // Browser controls should be restricted.
+        showDialog(0, ModalDialogManager.TAB_MODAL);
+        checkPendingSize(0);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(hasDescendant(withText("0"))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Show the second dialog. It should be added to the pending list, and the first dialog
+        // should still be shown.
+        showDialog(1, ModalDialogManager.TAB_MODAL);
+        checkPendingSize(1);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(
+                        allOf(hasDescendant(withText("0")), not(hasDescendant(withText("1"))))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Show the third dialog. It should be added to the pending list, and the first dialog
+        // should still be shown.
+        showDialog(2, ModalDialogManager.APP_MODAL);
+        checkPendingSize(2);
+        onView(withText("2")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(hasDescendant(withText("0")),
+                        not(hasDescendant(withText("1"))), not(hasDescendant(withText("2"))))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Stimulate dismissing the dialog by non-user action. The second dialog should be removed
+        // from pending list without showing.
+        dismissDialog(1);
+        checkPendingSize(1);
+        onView(withText("2")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(hasDescendant(withText("0")),
+                        not(hasDescendant(withText("1"))), not(hasDescendant(withText("2"))))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Dismiss the second dialog twice and verify nothing breaks.
+        dismissDialog(1);
+        checkPendingSize(1);
+        onView(withText("2")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(hasDescendant(withText("0")),
+                        not(hasDescendant(withText("1"))), not(hasDescendant(withText("2"))))));
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Dismiss the first dialog. The third dialog should be removed from pending list and
+        // shown immediately after. The tab modal container shouldn't be in the window hierarchy
+        // when an app modal dialog is showing.
+        dismissDialog(0);
+        checkPendingSize(0);
+        onView(withText("2")).check(matches(isDisplayed()));
+        onView(withId(R.id.tab_modal_dialog_container)).check(doesNotExist());
+        checkCurrentPresenter(ModalDialogManager.APP_MODAL);
+
+        // Dismiss the third dialog by clicking OK. Browser controls should no longer be restricted.
+        onView(withText(R.string.ok)).perform(click());
+        checkPendingSize(0);
+        onView(withText("2")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(not(hasDescendant(withText("0"))),
+                        not(hasDescendant(withText("1"))), not(hasDescendant(withText("2"))))));
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+    }
+
+    @Test
+    @SmallTest
+    public void testShow_UrlBarFocused() throws Exception {
+        // Show a dialog. The dialog should be shown on top of the toolbar.
+        showDialog(0, ModalDialogManager.TAB_MODAL);
+
+        TabModalPresenter presenter =
+                (TabModalPresenter) mManager.getPresenterForTest(ModalDialogManager.TAB_MODAL);
+        final View dialogContainer = presenter.getDialogContainerForTest();
+        final View controlContainer = mActivity.findViewById(R.id.control_container);
+        final ViewGroup containerParent = presenter.getContainerParentForTest();
+
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(containerParent.indexOfChild(dialogContainer)
+                    > containerParent.indexOfChild(controlContainer));
+        });
+
+        // When editing URL, it should be shown on top of the dialog.
+        onView(withId(R.id.url_bar)).perform(click());
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(containerParent.indexOfChild(dialogContainer)
+                    < containerParent.indexOfChild(controlContainer));
+        });
+
+        // When URL bar is not focused, the dialog should be shown on top of the toolbar again.
+        Espresso.pressBack();
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(containerParent.indexOfChild(dialogContainer)
+                    > containerParent.indexOfChild(controlContainer));
+        });
+
+        // Dismiss the dialog by clicking OK.
+        onView(withText(R.string.ok)).perform(click());
+    }
+
+    @Test
+    @SmallTest
+    public void testDismiss_ToggleOverview() throws Exception {
+        // Initially there are no dialogs in the pending list. Browser controls are not restricted.
+        checkPendingSize(0);
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+
+        // Add two dialogs available for showing.
+        showDialog(0, ModalDialogManager.TAB_MODAL);
+        showDialog(1, ModalDialogManager.TAB_MODAL);
+        checkPendingSize(1);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(
+                        allOf(hasDescendant(withText("0")), not(hasDescendant(withText("1"))))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Dialogs should all be dismissed on entering tab switcher.
+        onView(withId(R.id.tab_switcher_button)).perform(click());
+        checkPendingSize(0);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(
+                        not(hasDescendant(withText("0"))), not(hasDescendant(withText("1"))))));
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+    }
+
+    @Test
+    @SmallTest
+    public void testDismiss_BackPressed() throws Exception {
+        // Initially there are no dialogs in the pending list. Browser controls are not restricted.
+        checkPendingSize(0);
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+
+        // Add two dialogs available for showing.
+        showDialog(0, ModalDialogManager.TAB_MODAL);
+        showDialog(1, ModalDialogManager.TAB_MODAL);
+        showDialog(2, ModalDialogManager.APP_MODAL);
+        checkPendingSize(2);
+        onView(withText("2")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(hasDescendant(withText("0")),
+                        not(hasDescendant(withText("1"))), not(hasDescendant(withText("2"))))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Perform back press. The first dialog should be dismissed.
+        // The second dialog should be shown.
+        Espresso.pressBack();
+        checkPendingSize(1);
+        onView(withText("2")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(not(hasDescendant(withText("0"))),
+                        hasDescendant(withText("1")), not(hasDescendant(withText("2"))))));
+        checkBrowserControls(true);
+        checkCurrentPresenter(ModalDialogManager.TAB_MODAL);
+
+        // Perform a second back press. The second dialog should be dismissed.
+        // The tab modal container shouldn't be in the window hierarchy when an app modal dialog is
+        // showing.
+        Espresso.pressBack();
+        checkPendingSize(0);
+        onView(withText("2")).check(matches(isDisplayed()));
+        onView(withId(R.id.tab_modal_dialog_container)).check(doesNotExist());
+        checkCurrentPresenter(ModalDialogManager.APP_MODAL);
+
+        // Perform a third back press. The third dialog should be dismissed.
+        Espresso.pressBack();
+        checkPendingSize(0);
+        onView(withText("2")).check(doesNotExist());
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(allOf(not(hasDescendant(withText("0"))),
+                        not(hasDescendant(withText("1"))), not(hasDescendant(withText("2"))))));
+        checkBrowserControls(false);
+        checkCurrentPresenter(null);
+    }
+
+    private ModalDialogView createDialog(final int index) throws Exception {
+        return ThreadUtils.runOnUiThreadBlocking(() -> {
+            ModalDialogView.Controller controller = new ModalDialogView.Controller() {
+                @Override
+                public void onCancel() {}
+
+                @Override
+                public void onClick(int buttonType) {
+                    switch (buttonType) {
+                        case ModalDialogView.BUTTON_POSITIVE:
+                        case ModalDialogView.BUTTON_NEGATIVE:
+                            dismissDialog(index);
+                            break;
+                        default:
+                            Assert.fail("Unknown button type: " + buttonType);
+                    }
+                }
+            };
+            final ModalDialogView.Params p = new ModalDialogView.Params();
+            p.title = Integer.toString(index);
+            p.positiveButtonTextId = R.string.ok;
+            p.negativeButtonTextId = R.string.cancel;
+            return new ModalDialogView(controller, p);
+        });
+    }
+
+    private void showDialog(
+            final int index, final @ModalDialogManager.ModalDialogType int dialogType) {
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> { mManager.showDialog(mModalDialogViews[index], dialogType); });
+    }
+
+    private void dismissDialog(final int index) {
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> { mManager.dismissDialog(mModalDialogViews[index]); });
+    }
+
+    private void checkPendingSize(final int expected) {
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertEquals(expected, mManager.getPendingDialogsForTest().size());
+        });
+    }
+
+    private void checkCurrentPresenter(final Integer dialogType) {
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            if (dialogType == null) {
+                Assert.assertFalse(mManager.isShowing());
+                Assert.assertNull(mManager.getCurrentPresenterForTest());
+            } else {
+                Assert.assertTrue(mManager.isShowing());
+                Assert.assertEquals(mManager.getPresenterForTest(dialogType),
+                        mManager.getCurrentPresenterForTest());
+            }
+        });
+    }
+
+    private void checkBrowserControls(boolean restricted) {
+        if (restricted) {
+            Assert.assertTrue("All tabs should be obscured", mActivity.isViewObscuringAllTabs());
+            onView(withId(R.id.menu_button)).check(matches(not(isEnabled())));
+        } else {
+            Assert.assertFalse("Tabs shouldn't be obscured", mActivity.isViewObscuringAllTabs());
+            onView(withId(R.id.menu_button)).check(matches(isEnabled()));
+        }
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/README.md b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/README.md
index ce2a6f9..bdab0f7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/README.md
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/README.md
@@ -13,7 +13,8 @@
 
 These are the files and directories that are relevant to VR instrumentation
 testing. Additional information on files in other directories can be found in
-those directories' README.md files.
+those directories' README.md files (if the files as a whole warrant special
+documentation) and the JavaDoc comments in each file.
 
 ### Subdirectories
 
@@ -167,7 +168,7 @@
 has support for test parameterization. While parameterization has many potential
 uses, the current use in VR is to allow a test case to be automatically run in
 multiple different activity types. For specifics, see
-`rules/vr_activity_restriction.md`.
+`rules/README.md`.
 
 If a class has test parameterization enabled, you have three options when adding
 a new test:
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/rules/README.md b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/rules/README.md
new file mode 100644
index 0000000..2182b4f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/rules/README.md
@@ -0,0 +1,49 @@
+# VR Instrumentation Rules
+
+## Introduction
+
+This directory contains all the Java files related to VR-specific JUnit4 rules.
+For the most part, the rules are identical to the standard activity-specific
+rules (e.g. `ChromeTabbedActivityTestRule` for `ChromeTabbedActivity`), but with
+additional code to work with JUnit4 test parameterization to support running
+a test case multiple times in different activities.
+
+Usage of this feature is already covered in `../README.md`, so this
+documentation concerns implementation details.
+
+## How It Works
+
+When a test is set up to use test parameterization (determined by whether the
+test class is annotated with `@RunWith(ParameterizedRunner.class)` or not), the
+test runner automatically runs each test case in the class once with every
+`ParameterSet` in the list of `ParameterSet`s that is annotated with
+`@ClassParameter`, passing each `ParameterSet`'s contents to the class constructor
+each time.
+
+In the case of VR tests, the `ParameterSet` list contains `Callable`s that
+construct VR test rules for the three supported activity types
+(`ChromeTabbedActivity`, `CustomTabActivity`, and `WebappActivity`). The
+constructor for parameterized VR tests runs the provided `Callable`, effectively
+running every test case once in each activity type.
+
+However, if the class were to assign the `Callable` return value to its member
+variable annotated with `@Rule`, then every test case in the class would always
+run in all three activity types, which isn't desirable since some test cases
+test scenarios that are only valid in some activities (e.g. tests that involve
+the VR browser are currently only supported in ChromeTabbedActivity). To avoid
+this, the `Rule` annotated with `@Rule` is actually a `RuleChain` that wraps the
+generated VR test rule in a `VrActivityRestrictionRule`.
+
+`VrActivityRestrictionRule` interacts with the `@VrActivityRestriction`
+annotation and the VR test rule for the current test case run. If the activity
+type of the current rule is contained in the list provided by
+`@VrActivityRestriction` (or there is no restriction annotation and the activity
+type is `ChromeTabbedActivity`), then the `VrActivityRestrictionRule` becomes a
+no-op and the test case runs normally. Otherwise, `VrActivityRestrictionRule`
+causes an assumption failure, which is interpreted by the test runner as a
+signal to skip that particular test case/parameter combination.
+
+Thus, the end result is that the same test case can be run multiple times in
+different activities without having to duplicate any code for use in different
+activity types, while still retaining the ability to not run every test in every
+activity type if necessary.
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/vr_test_framework.md b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/vr_test_framework.md
new file mode 100644
index 0000000..8372325
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/vr_test_framework.md
@@ -0,0 +1,104 @@
+# VR Test Framework
+
+## Introduction
+
+This is an overview of how the VR test framework functions. It is split into two
+parts, with Java code located in this directory in `VrTestFramework.java` and
+JavaScript code located in `//chrome/test/data/vr/e2e_test_files/`.
+It is primarily used for testing WebVR, but can also be useful for testing the
+VR Browser.
+
+A similar approach is used for testing VR on desktop, with the framework
+re-implemented in C++ for use in browser tests and many of the same JavaScript
+files used on both platforms. See the documentation in
+`//chrome/browser/vr/test` for more information on this.
+
+## Structure
+
+Tests utilizing this framework are split into separate Java and JavaScript files
+since WebVR interaction is done via JavaScript, but instrumentation tests are
+Java-based. In general, the JavaScript code handles any interactions with the
+WebVR API, and the Java code handles everything else (user gestures, controller
+emulation, etc.).
+
+In general, the flow of a test is:
+* In Java, load the HTML test file, which:
+  * Loads the WebVR boilerplate code and test code
+  * Sets up any test steps that need to be triggered by Java as separate
+    functions
+  * Creates an asynchronous test (denoted t here)
+* Repeat:
+  * Run any necessary Java-side code, e.g. trigger a user action
+  * Trigger the next JavaScript test step and wait for it to finish
+* Finally, call t.done() in JavaScript and endTest in Java
+
+### JavaScript
+
+The JavaScript test code mainly makes use of testharness.js, which is also used
+for layout tests. This allows the use of asserts in JavaScript code, and any
+assert failures will propagate up to Java. There are four main test-specific
+functions that you will need to use:
+
+#### async\_test
+
+This creates an asynchronous test from testharness.js which you will use
+throughout the JavaScript code. It serves two purposes:
+
+* By being an asynchronous test, it will prevent testharness.js from ending the
+  test run prematurely once the page is loaded. It will wait until all tests
+  are done before checking results.
+* Enables you to use asserts in JavaScript. Asserts must be within a test to
+  do anything.
+
+#### finishJavaScriptStep
+
+This signals that the current portion of JavaScript code is done and that the
+Java side can continue execution.
+
+#### t.step
+
+This defines a test step in an asynchronous test t. Any asserts must be within
+a test step, so assertions will look along the lines of:
+`t.step( () => {
+  assertTrue(someBool);
+});`
+
+#### t.done
+
+This signals that the asynchronous test is done. Once all tests in a file are
+completed (usually only one), testharness.js will check the results and
+automatically call finishJavaScriptStep when done.
+
+### Java
+
+There are many Java-side functions that enable VR testing, but only the three
+basic ones will be covered here. For specifics about less common functions, see
+the JavaDoc comments in `VrTestFramework.java` and the utility classes in
+`util/`.
+
+#### loadUrlAndAwaitInitialization
+
+This is similar to the standard loadUrl method in Chrome, but also waits until
+all initialization steps are complete and the page is ready for testing. By
+default, this only includes the page being loaded. Additionally, any files
+that include the WebVR boilerplate script (generally any test for WebVR) will
+wait until the promise returned by `navigator.getVRDisplays` has resolved or
+rejected. Tests can add their own initialization steps by adding an entry to
+the `initializationSteps` dictionary defined in
+`//chrome/test/data/android/webvr_instrumentation/resources/webvr_e2e.js` with
+the value set to `false`. Once the step is done, simply set the value to
+`true`.
+
+#### executeStepAndWait
+
+This executes the given JavaScript and waits until finishJavaScriptStep is
+called on the JavaScript side.
+
+#### endTest
+
+Performs any post-test checks after the JavaScript test code has finished
+running. Since test failures are caught after each step, all this really does
+is ensure that the the JavaScript code has actually finished running, throwing
+an error if this is not the case.
+Typically called at the very end of the test after everything else is
+completed.
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotControllerTest.java
new file mode 100644
index 0000000..cf44ab6
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotControllerTest.java
@@ -0,0 +1,164 @@
+// 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.incognito;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+/**
+ * Unit tests for IncognitoTabSnapshotController.java.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class IncognitoTabSnapshotControllerTest {
+    private IncognitoTabSnapshotController mController;
+    private WindowManager.LayoutParams mParams;
+
+    @Mock
+    Window mWindow;
+
+    @Mock
+    TabModelSelector mSelector;
+
+    @Mock
+    TabModel mTabModel;
+
+    @Mock
+    LayoutManagerChrome mLayoutManager;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+
+        mParams = new LayoutParams();
+    }
+
+    @Test
+    public void testUpdateIncognitoStateIncognitoAndEarlyReturn() {
+        mParams.flags = WindowManager.LayoutParams.FLAG_SECURE;
+        doReturn(mParams).when(mWindow).getAttributes();
+
+        mController = new TestIncognitoTabSnapshotController(true);
+        mController.updateIncognitoState();
+        verify(mWindow, never()).addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        verify(mWindow, never()).clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        Assert.assertEquals(
+                "Flag should be secure", WindowManager.LayoutParams.FLAG_SECURE, mParams.flags);
+    }
+
+    @Test
+    public void testUpdateIncognitoStateNotIncognitoAndEarlyReturn() {
+        mParams.flags = 0;
+        doReturn(mParams).when(mWindow).getAttributes();
+
+        mController = new TestIncognitoTabSnapshotController(false);
+        mController.updateIncognitoState();
+        verify(mWindow, never()).addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        Assert.assertEquals("Flag should be zero", 0, mParams.flags);
+    }
+
+    @Test
+    public void testUpdateIncognitoStateSwitchingToIncognito() {
+        mParams.flags = 0;
+        doReturn(mParams).when(mWindow).getAttributes();
+
+        mController = new TestIncognitoTabSnapshotController(true);
+        mController.updateIncognitoState();
+        verify(mWindow, atLeastOnce()).addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+    }
+
+    @Test
+    public void testUpdateIncognitoStateSwitchingToNonIncognito() {
+        mParams.flags = WindowManager.LayoutParams.FLAG_SECURE;
+        doReturn(mParams).when(mWindow).getAttributes();
+
+        mController = new TestIncognitoTabSnapshotController(false);
+        mController.updateIncognitoState();
+        verify(mWindow, atLeastOnce()).clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
+    }
+
+    @Test
+    public void testIsShowingIncognitoNotInOverviewMode() {
+        mController = new IncognitoTabSnapshotController(mWindow, mLayoutManager, mSelector);
+        mController.setInOverViewMode(false);
+        doReturn(mTabModel).when(mSelector).getCurrentModel();
+        doReturn(true).when(mTabModel).isIncognito();
+        Assert.assertTrue("isShowingIncognito should be true", mController.isShowingIncognito());
+
+        verify(mSelector, never()).getModel(true);
+    }
+
+    @Test
+    public void testIsShowingIncognitoInOverviewMode() {
+        mController = new IncognitoTabSnapshotController(mWindow, mLayoutManager, mSelector);
+        mController.setInOverViewMode(false);
+        doReturn(mTabModel).when(mSelector).getCurrentModel();
+        doReturn(true).when(mTabModel).isIncognito();
+        Assert.assertTrue("isShowingIncognito should be true", mController.isShowingIncognito());
+
+        verify(mSelector, never()).getModel(true);
+    }
+
+    @Test
+    public void testInOverviewModeWithIncognitoTab() {
+        mController = new IncognitoTabSnapshotController(mWindow, mLayoutManager, mSelector);
+        mController.setInOverViewMode(true);
+
+        doReturn(mTabModel).when(mSelector).getCurrentModel();
+        doReturn(false).when(mTabModel).isIncognito();
+        doReturn(mTabModel).when(mSelector).getModel(true);
+        doReturn(1).when(mTabModel).getCount();
+        Assert.assertTrue("isShowingIncognito should be true", mController.isShowingIncognito());
+
+        verify(mTabModel, atLeastOnce()).getCount();
+    }
+
+    @Test
+    public void testInOverviewModeWithNoIncognitoTab() {
+        mController = new IncognitoTabSnapshotController(mWindow, mLayoutManager, mSelector);
+        mController.setInOverViewMode(true);
+
+        doReturn(mTabModel).when(mSelector).getCurrentModel();
+        doReturn(false).when(mTabModel).isIncognito();
+        doReturn(mTabModel).when(mSelector).getModel(true);
+        doReturn(0).when(mTabModel).getCount();
+        Assert.assertFalse("isShowingIncognito should be false", mController.isShowingIncognito());
+
+        verify(mTabModel, atLeastOnce()).getCount();
+    }
+
+    class TestIncognitoTabSnapshotController extends IncognitoTabSnapshotController {
+        private boolean mIsShowingIncognito;
+
+        public TestIncognitoTabSnapshotController(boolean isShowingIncognito) {
+            super(mWindow, mLayoutManager, mSelector);
+            mIsShowingIncognito = isShowingIncognito;
+        }
+
+        @Override
+        boolean isShowingIncognito() {
+            return mIsShowingIncognito;
+        }
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/profiles/cipd.yaml b/chrome/android/profiles/cipd.yaml
new file mode 100644
index 0000000..1e68d45
--- /dev/null
+++ b/chrome/android/profiles/cipd.yaml
@@ -0,0 +1,13 @@
+# Copyright 2018 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.
+
+# To create a CIPD package, run the following command.
+# cipd create --pkg-def cipd.yaml -tag version:version-of-afdo-profile
+package: chromium/afdo/profiles/android
+description: AFDO profiles collected for Chromium on Android
+
+# FIXME(gbiv): When we can specify dependencies on the profile in the build
+# system, settle on a profile name that doesn't include the version.
+data:
+  - file: chrome-profile-3309-text.prof
diff --git a/chrome/android/third_party/widget_bottomsheet_base/java/src/org/chromium/chrome/browser/widget/bottomsheet/base/BottomNavigationMenuView.java b/chrome/android/third_party/widget_bottomsheet_base/java/src/org/chromium/chrome/browser/widget/bottomsheet/base/BottomNavigationMenuView.java
index d746e344..2e1c9ea 100644
--- a/chrome/android/third_party/widget_bottomsheet_base/java/src/org/chromium/chrome/browser/widget/bottomsheet/base/BottomNavigationMenuView.java
+++ b/chrome/android/third_party/widget_bottomsheet_base/java/src/org/chromium/chrome/browser/widget/bottomsheet/base/BottomNavigationMenuView.java
@@ -225,7 +225,7 @@
      * @param layoutHeight Height of the navigation menu's container.
      */
     public void updateMenuItemSpacingForMinWidth(int layoutWidth, int layoutHeight) {
-        if (mButtons.length == 0) return;
+        if (mButtons == null || mButtons.length == 0) return;
 
         int menuWidth = Math.min(layoutWidth, layoutHeight);
         if (menuWidth != mMenuWidth) {
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index c62ffd9..07aa5cca 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -762,9 +762,6 @@
         <message name="IDS_OPTIONS_PASSWORDS_MAC_WARNING" desc="The warning for OS X that passwords are shared across profiles in the keychain.">
           On Mac, passwords may be saved to your Keychain and accessed or synced by other Chromium users sharing this OS X account.
         </message>
-        <message name="IDS_AUTOFILL_ADDRESS_BOOK_PROMPT_DESCRIPTION" desc="Text to show in dialog requesting permission to access the user's Address Book contents.">
-          Details from your contacts can help you fill out forms more quickly in Chromium.
-        </message>
       </if>
 
       <message name="IDS_AUTOFILL_DIALOG_SAVE_LOCALLY_CHECKBOX" desc="Checkbox that controls whether info the user types into the autofill dialog is saved by chrome.">
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 84f1845..b4c8ee2 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -770,9 +770,6 @@
         <message name="IDS_OPTIONS_PASSWORDS_MAC_WARNING" desc="The warning for OS X that passwords are shared across profiles in the keychain.">
           On Mac, passwords may be saved to your Keychain and accessed or synced by other Chrome users sharing this OS X account.
         </message>
-        <message name="IDS_AUTOFILL_ADDRESS_BOOK_PROMPT_DESCRIPTION" desc="Text to show in dialog requesting permission to access the user's Address Book contents.">
-          Details from your contacts can help you fill out forms more quickly in Chrome.
-        </message>
       </if>
 
       <message name="IDS_AUTOFILL_DIALOG_SAVE_LOCALLY_CHECKBOX" desc="Checkbox that controls whether info the user types into the autofill dialog is saved by chrome.">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index f10dbacff..2b7c879 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2952,7 +2952,6 @@
     ]
     libs += [
       "Accelerate.framework",
-      "AddressBook.framework",
       "AudioUnit.framework",
       "DiskArbitration.framework",
       "IOKit.framework",
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 490133c3..64ef941 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -458,7 +458,6 @@
 using extensions::APIPermission;
 using extensions::ChromeContentBrowserClientExtensionsPart;
 using extensions::Extension;
-using extensions::InfoMap;
 using extensions::Manifest;
 #endif
 
diff --git a/chrome/browser/chromeos/arc/arc_util_unittest.cc b/chrome/browser/chromeos/arc/arc_util_unittest.cc
index 4f087d0..3167337 100644
--- a/chrome/browser/chromeos/arc/arc_util_unittest.cc
+++ b/chrome/browser/chromeos/arc/arc_util_unittest.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/chromeos/settings/install_attributes.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
@@ -155,6 +156,8 @@
         std::make_unique<FakeUserManagerWithLocalState>());
     // Used by FakeChromeUserManager.
     chromeos::WallpaperManager::Initialize();
+    chromeos::DeviceSettingsService::Initialize();
+    chromeos::CrosSettings::Initialize();
     wallpaper_controller_client_ =
         std::make_unique<WallpaperControllerClient>();
     wallpaper_controller_client_->InitForTesting(
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api_unittest.cc b/chrome/browser/chromeos/extensions/wallpaper_private_api_unittest.cc
index 91db9dc..b1811b0 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_private_api_unittest.cc
+++ b/chrome/browser/chromeos/extensions/wallpaper_private_api_unittest.cc
@@ -13,6 +13,8 @@
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
 #include "chrome/browser/ui/ash/test_wallpaper_controller.h"
@@ -194,6 +196,8 @@
 
 void WallpaperPrivateApiMultiUserUnittest::SetUp() {
   AshTestBase::SetUp();
+  DeviceSettingsService::Initialize();
+  CrosSettings::Initialize();
   WallpaperManager::Initialize();
   wallpaper_controller_client_ = std::make_unique<WallpaperControllerClient>();
   wallpaper_controller_client_->InitForTesting(
@@ -207,6 +211,8 @@
   AshTestBase::TearDown();
   WallpaperManager::Shutdown();
   wallpaper_controller_client_.reset();
+  CrosSettings::Shutdown();
+  DeviceSettingsService::Shutdown();
 }
 
 void WallpaperPrivateApiMultiUserUnittest::SetUpMultiUserWindowManager(
diff --git a/chrome/browser/chromeos/login/existing_user_controller.cc b/chrome/browser/chromeos/login/existing_user_controller.cc
index 26b8c1d..905dd65 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller.cc
@@ -435,6 +435,7 @@
     // just after the UI is closed but before the new credentials were stored
     // in the profile. Therefore we have to give it some time to make sure it
     // has been updated before we copy it.
+    // TODO(pmarko): Find a better way to do this, see https://crbug.com/796512.
     VLOG(1) << "Authentication was entered manually, possibly for proxyauth.";
     scoped_refptr<net::URLRequestContextGetter> browser_process_context_getter =
         g_browser_process->system_request_context();
diff --git a/chrome/browser/chromeos/login/signin_partition_manager.cc b/chrome/browser/chromeos/login/signin_partition_manager.cc
index 17652f4..a1e0f85 100644
--- a/chrome/browser/chromeos/login/signin_partition_manager.cc
+++ b/chrome/browser/chromeos/login/signin_partition_manager.cc
@@ -6,17 +6,26 @@
 
 #include "base/guid.h"
 #include "base/strings/stringprintf.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chromeos/chromeos_switches.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
 #include "net/base/escape.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
 #include "url/gurl.h"
 
+using content::BrowserThread;
+
 namespace chromeos {
 namespace login {
 
@@ -46,18 +55,50 @@
       base::Time::Max(), std::move(partition_data_cleared));
 }
 
+net::URLRequestContextGetter* GetSystemURLRequestContextGetter() {
+  return g_browser_process->system_request_context();
+}
+
+// Transfers HttpAuthCache content from |main_url_request_context_getter| into
+// |signin_url_request_context_getter|.
+void PrepareSigninURLRequestContextOnIOThread(
+    net::URLRequestContextGetter* main_url_request_context_getter,
+    net::URLRequestContextGetter* signin_url_request_context_getter) {
+  // Transfer proxy auth data from the main URLRequestContext.
+  net::HttpAuthCache* main_http_auth_cache =
+      main_url_request_context_getter->GetURLRequestContext()
+          ->http_transaction_factory()
+          ->GetSession()
+          ->http_auth_cache();
+  net::HttpAuthCache* signin_http_auth_cache =
+      signin_url_request_context_getter->GetURLRequestContext()
+          ->http_transaction_factory()
+          ->GetSession()
+          ->http_auth_cache();
+  signin_http_auth_cache->UpdateAllFrom(*main_http_auth_cache);
+}
+
+void InvokeStartSigninSessionDoneCallback(
+    SigninPartitionManager::StartSigninSessionDoneCallback callback,
+    const std::string& partition_name) {
+  std::move(callback).Run(partition_name);
+}
+
 }  // namespace
 
 SigninPartitionManager::SigninPartitionManager(
     content::BrowserContext* browser_context)
     : browser_context_(browser_context),
       clear_storage_partition_task_(
-          base::BindRepeating(&ClearStoragePartition)) {}
+          base::BindRepeating(&ClearStoragePartition)),
+      get_system_url_request_context_getter_task_(
+          base::BindRepeating(&GetSystemURLRequestContextGetter)) {}
 
 SigninPartitionManager::~SigninPartitionManager() {}
 
 void SigninPartitionManager::StartSigninSession(
-    const content::WebContents* embedder_web_contents) {
+    const content::WebContents* embedder_web_contents,
+    StartSigninSessionDoneCallback signin_session_started) {
   // If we already were in a sign-in session, close it first.
   // This clears stale data from the last-used StorageParittion.
   CloseCurrentSigninSession(base::BindOnce(&base::DoNothing));
@@ -74,6 +115,19 @@
   current_storage_partition_ =
       content::BrowserContext::GetStoragePartitionForSite(browser_context_,
                                                           guest_site, true);
+
+  base::OnceClosure invoke_callback = base::BindOnce(
+      &InvokeStartSigninSessionDoneCallback, std::move(signin_session_started),
+      current_storage_partition_name_);
+
+  BrowserThread::PostTaskAndReply(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(
+          &PrepareSigninURLRequestContextOnIOThread,
+          base::RetainedRef(get_system_url_request_context_getter_task_.Run()),
+          base::RetainedRef(
+              current_storage_partition_->GetURLRequestContext())),
+      std::move(invoke_callback));
 }
 
 void SigninPartitionManager::CloseCurrentSigninSession(
@@ -97,6 +151,13 @@
   clear_storage_partition_task_ = clear_storage_partition_task;
 }
 
+void SigninPartitionManager::SetGetSystemURLRequestContextGetterTaskForTesting(
+    GetSystemURLRequestContextGetterTask
+        get_system_url_request_context_getter_task) {
+  get_system_url_request_context_getter_task_ =
+      get_system_url_request_context_getter_task;
+}
+
 const std::string& SigninPartitionManager::GetCurrentStoragePartitionName()
     const {
   DCHECK(IsInSigninSession());
diff --git a/chrome/browser/chromeos/login/signin_partition_manager.h b/chrome/browser/chromeos/login/signin_partition_manager.h
index 66e790e..1a5478e 100644
--- a/chrome/browser/chromeos/login/signin_partition_manager.h
+++ b/chrome/browser/chromeos/login/signin_partition_manager.h
@@ -9,6 +9,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
 #include "base/memory/singleton.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -19,6 +20,10 @@
 class WebContents;
 }  // namespace content
 
+namespace net {
+class URLRequestContextGetter;
+}
+
 namespace chromeos {
 namespace login {
 
@@ -30,6 +35,12 @@
       base::RepeatingCallback<void(content::StoragePartition* storage_partition,
                                    base::OnceClosure data_cleared)>;
 
+  using GetSystemURLRequestContextGetterTask =
+      base::RepeatingCallback<net::URLRequestContextGetter*()>;
+
+  using StartSigninSessionDoneCallback =
+      base::OnceCallback<void(const std::string& partition_name)>;
+
   explicit SigninPartitionManager(content::BrowserContext* browser_context);
   ~SigninPartitionManager() override;
 
@@ -38,7 +49,11 @@
   // closed (and cleared).
   // |embedder_web_contents| is the WebContents instance embedding the webview
   // which will display the sign-in pages.
-  void StartSigninSession(const content::WebContents* embedder_web_contents);
+  // |signin_session_started| will be invoked with the partition name of the
+  // started signin session on completition.
+  void StartSigninSession(
+      const content::WebContents* embedder_web_contents,
+      StartSigninSessionDoneCallback signin_session_started);
 
   // Closes the current StoragePartition. All cached data in the
   // StoragePartition is cleared. |partition_data_cleared| will be called when
@@ -66,6 +81,9 @@
 
   void SetClearStoragePartitionTaskForTesting(
       ClearStoragePartitionTask clear_storage_partition_task);
+  void SetGetSystemURLRequestContextGetterTaskForTesting(
+      GetSystemURLRequestContextGetterTask
+          get_system_url_request_context_getter_task);
 
   class Factory : public BrowserContextKeyedServiceFactory {
    public:
@@ -93,6 +111,8 @@
   content::BrowserContext* const browser_context_;
 
   ClearStoragePartitionTask clear_storage_partition_task_;
+  GetSystemURLRequestContextGetterTask
+      get_system_url_request_context_getter_task_;
 
   // GuestView StoragePartitions use the host of the embedder site's URL as the
   // domain of their StoragePartition.
diff --git a/chrome/browser/chromeos/login/signin_partition_manager_unittest.cc b/chrome/browser/chromeos/login/signin_partition_manager_unittest.cc
index b5b6721..be492f2 100644
--- a/chrome/browser/chromeos/login/signin_partition_manager_unittest.cc
+++ b/chrome/browser/chromeos/login/signin_partition_manager_unittest.cc
@@ -12,13 +12,16 @@
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/web_contents_tester.h"
 #include "net/cookies/cookie_store.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -27,6 +30,39 @@
 
 namespace {
 constexpr char kEmbedderUrl[] = "http://www.whatever.com/";
+
+void StorePartitionNameAndQuitLoop(base::RunLoop* loop,
+                                   std::string* out_partition_name,
+                                   const std::string& partition_name) {
+  *out_partition_name = partition_name;
+  loop->Quit();
+}
+
+void AddEntryToHttpAuthCache(
+    net::URLRequestContextGetter* url_request_context_getter) {
+  net::HttpAuthCache* http_auth_cache =
+      url_request_context_getter->GetURLRequestContext()
+          ->http_transaction_factory()
+          ->GetSession()
+          ->http_auth_cache();
+  http_auth_cache->Add(GURL("http://whatever.com/"), "",
+                       net::HttpAuth::AUTH_SCHEME_BASIC, "",
+                       net::AuthCredentials(), "");
+}
+
+void IsEntryInHttpAuthCache(
+    net::URLRequestContextGetter* url_request_context_getter,
+    bool* out_entry_found) {
+  net::HttpAuthCache* http_auth_cache =
+      url_request_context_getter->GetURLRequestContext()
+          ->http_transaction_factory()
+          ->GetSession()
+          ->http_auth_cache();
+  *out_entry_found =
+      http_auth_cache->Lookup(GURL("http://whatever.com/"), "",
+                              net::HttpAuth::AUTH_SCHEME_BASIC) != nullptr;
+}
+
 }  // namespace
 
 class SigninPartitionManagerTest : public ChromeRenderViewHostTestHarness {
@@ -37,6 +73,10 @@
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
 
+    system_request_context_getter_ = new net::TestURLRequestContextGetter(
+        content::BrowserThread::GetTaskRunnerForThread(
+            content::BrowserThread::IO));
+
     signin_browser_context_ = base::MakeUnique<TestingProfile>();
 
     signin_ui_web_contents_ = base::WrapUnique<content::WebContents>(
@@ -51,6 +91,10 @@
     GetSigninPartitionManager()->SetClearStoragePartitionTaskForTesting(
         base::Bind(&SigninPartitionManagerTest::ClearStoragePartitionTask,
                    base::Unretained(this)));
+    GetSigninPartitionManager()
+        ->SetGetSystemURLRequestContextGetterTaskForTesting(base::BindRepeating(
+            &SigninPartitionManagerTest::GetSystemURLRequestContextGetter,
+            base::Unretained(this)));
   }
 
   void TearDown() override {
@@ -89,12 +133,29 @@
     pending_clear_tasks_.clear();
   }
 
+  std::string RunStartSigninSesssion(content::WebContents* webcontents) {
+    std::string partition_name;
+    base::RunLoop loop;
+    GetSigninPartitionManager()->StartSigninSession(
+        webcontents,
+        base::BindOnce(&StorePartitionNameAndQuitLoop, &loop, &partition_name));
+    loop.Run();
+    return partition_name;
+  }
+
+  net::URLRequestContextGetter* GetSystemURLRequestContextGetter() {
+    return system_request_context_getter_.get();
+  }
+
  private:
   void ClearStoragePartitionTask(content::StoragePartition* partition,
                                  base::OnceClosure clear_done_closure) {
     pending_clear_tasks_.push_back({partition, std::move(clear_done_closure)});
   }
 
+  scoped_refptr<net::TestURLRequestContextGetter>
+      system_request_context_getter_;
+
   std::unique_ptr<TestingProfile> signin_browser_context_;
 
   // Web contents of the sign-in UI, embedder of the signin-frame webview.
@@ -108,20 +169,22 @@
 
 TEST_F(SigninPartitionManagerTest, TestSubsequentAttempts) {
   // First sign-in attempt
-  GetSigninPartitionManager()->StartSigninSession(signin_ui_web_contents());
   std::string signin_partition_name_1 =
-      GetSigninPartitionManager()->GetCurrentStoragePartitionName();
+      RunStartSigninSesssion(signin_ui_web_contents());
   auto* signin_partition_1 =
       GetSigninPartitionManager()->GetCurrentStoragePartition();
   EXPECT_FALSE(signin_partition_name_1.empty());
+  EXPECT_EQ(signin_partition_name_1,
+            GetSigninPartitionManager()->GetCurrentStoragePartitionName());
 
   // Second sign-in attempt
-  GetSigninPartitionManager()->StartSigninSession(signin_ui_web_contents());
   std::string signin_partition_name_2 =
-      GetSigninPartitionManager()->GetCurrentStoragePartitionName();
+      RunStartSigninSesssion(signin_ui_web_contents());
   auto* signin_partition_2 =
       GetSigninPartitionManager()->GetCurrentStoragePartition();
   EXPECT_FALSE(signin_partition_name_2.empty());
+  EXPECT_EQ(signin_partition_name_2,
+            GetSigninPartitionManager()->GetCurrentStoragePartitionName());
 
   // Make sure that the StoragePartition has not been re-used.
   EXPECT_NE(signin_partition_name_1, signin_partition_name_2);
@@ -148,5 +211,32 @@
   EXPECT_TRUE(closure_called);
 }
 
+TEST_F(SigninPartitionManagerTest, HttpAuthCacheTransferred) {
+  base::RunLoop loop_prepare;
+  content::BrowserThread::PostTaskAndReply(
+      content::BrowserThread::IO, FROM_HERE,
+      base::BindOnce(AddEntryToHttpAuthCache,
+                     base::RetainedRef(GetSystemURLRequestContextGetter())),
+      loop_prepare.QuitClosure());
+  loop_prepare.Run();
+
+  RunStartSigninSesssion(signin_ui_web_contents());
+  net::URLRequestContextGetter* signin_url_request_context_getter =
+      GetSigninPartitionManager()
+          ->GetCurrentStoragePartition()
+          ->GetURLRequestContext();
+
+  bool entry_found = false;
+  base::RunLoop loop_check;
+  content::BrowserThread::PostTaskAndReply(
+      content::BrowserThread::IO, FROM_HERE,
+      base::BindOnce(IsEntryInHttpAuthCache,
+                     base::RetainedRef(signin_url_request_context_getter),
+                     &entry_found),
+      loop_check.QuitClosure());
+  loop_check.Run();
+  EXPECT_TRUE(entry_found);
+}
+
 }  // namespace login
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc
index 8e9c9a60..6cbf63ad 100644
--- a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc
+++ b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.cc
@@ -17,14 +17,11 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/command_line.h"
-#include "base/files/file_util.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/sequenced_task_runner.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/sys_info.h"
 #include "base/task_scheduler/post_task.h"
@@ -61,14 +58,12 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/service_manager_connection.h"
-#include "crypto/sha2.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/codec/jpeg_codec.h"
 #include "ui/gfx/geometry/safe_integer_conversions.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/skia_util.h"
-#include "url/gurl.h"
 
 using content::BrowserThread;
 using wallpaper::WallpaperInfo;
@@ -165,21 +160,6 @@
   }
 }
 
-// A helper function to check the existing/downloaded device wallpaper file's
-// hash value matches with the hash value provided in the policy settings.
-bool CheckDeviceWallpaperMatchHash(const base::FilePath& device_wallpaper_file,
-                                   const std::string& hash) {
-  std::string image_data;
-  if (base::ReadFileToString(device_wallpaper_file, &image_data)) {
-    std::string sha_hash = crypto::SHA256HashString(image_data);
-    if (base::ToLowerASCII(base::HexEncode(
-            sha_hash.c_str(), sha_hash.size())) == base::ToLowerASCII(hash)) {
-      return true;
-    }
-  }
-  return false;
-}
-
 }  // namespace
 
 void AssertCalledOnWallpaperSequence(base::SequencedTaskRunner* task_runner) {
@@ -281,7 +261,6 @@
 
 WallpaperManager::~WallpaperManager() {
   show_user_name_on_signin_subscription_.reset();
-  device_wallpaper_image_subscription_.reset();
   weak_factory_.InvalidateWeakPtrs();
 }
 
@@ -358,9 +337,14 @@
 
   // For a enterprise managed user, set the device wallpaper if we're at the
   // login screen.
-  if (!user_manager::UserManager::Get()->IsUserLoggedIn() &&
-      SetDeviceWallpaperIfApplicable(account_id))
+  // TODO(xdai): Replace these two functions ShouldSetDeviceWallpaper() and
+  // OnDeviceWallpaperChanged() with functions in WallpaperController.
+  std::string url;
+  std::string hash;
+  if (ShouldSetDeviceWallpaper(account_id, &url, &hash)) {
+    WallpaperControllerClient::Get()->OnDeviceWallpaperChanged();
     return;
+  }
 
   // TODO(crbug.com/776464): Move the above to
   // |WallpaperController::ShowUserWallpaper| after
@@ -369,8 +353,6 @@
 }
 
 void WallpaperManager::ShowSigninWallpaper() {
-  if (SetDeviceWallpaperIfApplicable(user_manager::SignInAccountId()))
-    return;
   WallpaperControllerClient::Get()->ShowSigninWallpaper();
 }
 
@@ -470,11 +452,6 @@
           kAccountsPrefShowUserNamesOnSignIn,
           base::Bind(&WallpaperManager::InitializeRegisteredDeviceWallpaper,
                      weak_factory_.GetWeakPtr()));
-  device_wallpaper_image_subscription_ =
-      CrosSettings::Get()->AddSettingsObserver(
-          kDeviceWallpaperImage,
-          base::Bind(&WallpaperManager::OnDeviceWallpaperPolicyChanged,
-                     weak_factory_.GetWeakPtr()));
 }
 
 void WallpaperManager::EnsureLoggedInUserWallpaperLoaded() {
@@ -643,24 +620,6 @@
       account_id, is_persistent);
 }
 
-bool WallpaperManager::SetDeviceWallpaperIfApplicable(
-    const AccountId& account_id) {
-  std::string url;
-  std::string hash;
-  if (ShouldSetDeviceWallpaper(account_id, &url, &hash)) {
-    // Check if the device wallpaper exists and matches the hash. If so, use it
-    // directly. Otherwise download it first.
-    base::PostTaskWithTraitsAndReplyWithResult(
-        FROM_HERE, {base::MayBlock()},
-        base::Bind(&base::PathExists,
-                   ash::WallpaperController::GetDeviceWallpaperFilePath()),
-        base::Bind(&WallpaperManager::OnDeviceWallpaperExists,
-                   weak_factory_.GetWeakPtr(), account_id, url, hash));
-    return true;
-  }
-  return false;
-}
-
 bool WallpaperManager::GetWallpaperFromCache(const AccountId& account_id,
                                              gfx::ImageSkia* image) {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -941,119 +900,6 @@
           ->IsUserLoggedIn() /* update wallpaper */);
 }
 
-void WallpaperManager::OnDeviceWallpaperPolicyChanged() {
-  SetDeviceWallpaperIfApplicable(
-      user_manager::UserManager::Get()->IsUserLoggedIn()
-          ? user_manager::UserManager::Get()->GetActiveUser()->GetAccountId()
-          : user_manager::SignInAccountId());
-}
-
-void WallpaperManager::OnDeviceWallpaperExists(const AccountId& account_id,
-                                               const std::string& url,
-                                               const std::string& hash,
-                                               bool exist) {
-  if (exist) {
-    base::PostTaskWithTraitsAndReplyWithResult(
-        FROM_HERE, {base::MayBlock()},
-        base::Bind(&CheckDeviceWallpaperMatchHash,
-                   ash::WallpaperController::GetDeviceWallpaperFilePath(),
-                   hash),
-        base::Bind(&WallpaperManager::OnCheckDeviceWallpaperMatchHash,
-                   weak_factory_.GetWeakPtr(), account_id, url, hash));
-  } else {
-    GURL device_wallpaper_url(url);
-    device_wallpaper_downloader_.reset(new CustomizationWallpaperDownloader(
-        g_browser_process->system_request_context(), device_wallpaper_url,
-        ash::WallpaperController::GetDeviceWallpaperDir(),
-        ash::WallpaperController::GetDeviceWallpaperFilePath(),
-        base::Bind(&WallpaperManager::OnDeviceWallpaperDownloaded,
-                   weak_factory_.GetWeakPtr(), account_id, hash)));
-    device_wallpaper_downloader_->Start();
-  }
-}
-
-void WallpaperManager::OnDeviceWallpaperDownloaded(const AccountId& account_id,
-                                                   const std::string& hash,
-                                                   bool success,
-                                                   const GURL& url) {
-  if (!success) {
-    LOG(ERROR) << "Failed to download the device wallpaper. Fallback to "
-                  "default wallpaper.";
-    SetDefaultWallpaperImpl(account_id, true /*show_wallpaper=*/);
-    return;
-  }
-
-  base::PostTaskWithTraitsAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()},
-      base::Bind(&CheckDeviceWallpaperMatchHash,
-                 ash::WallpaperController::GetDeviceWallpaperFilePath(), hash),
-      base::Bind(&WallpaperManager::OnCheckDeviceWallpaperMatchHash,
-                 weak_factory_.GetWeakPtr(), account_id, url.spec(), hash));
-}
-
-void WallpaperManager::OnCheckDeviceWallpaperMatchHash(
-    const AccountId& account_id,
-    const std::string& url,
-    const std::string& hash,
-    bool match) {
-  if (!match) {
-    if (retry_download_if_failed_) {
-      // We only retry to download the device wallpaper one more time if the
-      // hash doesn't match.
-      retry_download_if_failed_ = false;
-      GURL device_wallpaper_url(url);
-      device_wallpaper_downloader_.reset(new CustomizationWallpaperDownloader(
-          g_browser_process->system_request_context(), device_wallpaper_url,
-          ash::WallpaperController::GetDeviceWallpaperDir(),
-          ash::WallpaperController::GetDeviceWallpaperFilePath(),
-          base::Bind(&WallpaperManager::OnDeviceWallpaperDownloaded,
-                     weak_factory_.GetWeakPtr(), account_id, hash)));
-      device_wallpaper_downloader_->Start();
-    } else {
-      LOG(ERROR) << "The device wallpaper hash doesn't match with provided "
-                    "hash value. Fallback to default wallpaper! ";
-      SetDefaultWallpaperImpl(account_id, true /*show_wallpaper=*/);
-
-      // Reset the boolean variable so that it can retry to download when the
-      // next device wallpaper request comes in.
-      retry_download_if_failed_ = true;
-    }
-    return;
-  }
-
-  const base::FilePath file_path =
-      ash::WallpaperController::GetDeviceWallpaperFilePath();
-  if (!ash::Shell::HasInstance() || ash_util::IsRunningInMash()) {
-    user_image_loader::StartWithFilePath(
-        task_runner_, ash::WallpaperController::GetDeviceWallpaperFilePath(),
-        ImageDecoder::ROBUST_JPEG_CODEC,
-        0,  // Do not crop.
-        base::Bind(&WallpaperManager::OnDeviceWallpaperDecoded,
-                   weak_factory_.GetWeakPtr(), account_id));
-  } else {
-    ash::Shell::Get()->wallpaper_controller()->ReadAndDecodeWallpaper(
-        base::Bind(&WallpaperManager::OnDeviceWallpaperDecoded,
-                   weak_factory_.GetWeakPtr(), account_id),
-        task_runner_, file_path);
-  }
-}
-
-void WallpaperManager::OnDeviceWallpaperDecoded(
-    const AccountId& account_id,
-    std::unique_ptr<user_manager::UserImage> user_image) {
-  // It might be possible that the device policy controlled wallpaper finishes
-  // decoding after the user logs in. In this case do nothing.
-  if (!user_manager::UserManager::Get()->IsUserLoggedIn()) {
-    WallpaperInfo wallpaper_info = {
-        ash::WallpaperController::GetDeviceWallpaperFilePath().value(),
-        wallpaper::WALLPAPER_LAYOUT_CENTER_CROPPED, wallpaper::DEVICE,
-        base::Time::Now().LocalMidnight()};
-    // TODO(crbug.com/776464): This should go through PendingWallpaper after
-    // moving to WallpaperController.
-    SetWallpaper(user_image->image(), wallpaper_info);
-  }
-}
-
 void WallpaperManager::SetCustomizedDefaultWallpaperImpl(
     const base::FilePath& default_small_wallpaper_file,
     std::unique_ptr<gfx::ImageSkia> small_wallpaper_image,
diff --git a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h
index fb1252e3..790a134 100644
--- a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h
+++ b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h
@@ -24,7 +24,6 @@
 #include "base/threading/thread_checker.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/chromeos/customization/customization_wallpaper_downloader.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "components/signin/core/account_id/account_id.h"
 #include "components/user_manager/user_image/user_image.h"
@@ -236,10 +235,6 @@
   // A wrapper of |WallpaperController::InitializeUserWallpaperInfo|.
   void InitializeUserWallpaperInfo(const AccountId& account_id);
 
-  // If the device is enterprise managed and the device wallpaper policy exists,
-  // set the device wallpaper as the login screen wallpaper.
-  bool SetDeviceWallpaperIfApplicable(const AccountId& account_id);
-
   // A wrapper of |WallpaperController::GetWallpaperFromCache|.
   bool GetWallpaperFromCache(const AccountId& account_id,
                              gfx::ImageSkia* image);
@@ -262,6 +257,7 @@
                             wallpaper::WallpaperInfo* info) const;
 
   // Returns true if the device wallpaper should be set for the account.
+  // TODO(xdai): Remove this function after migrating ShowUserWallpaper().
   bool ShouldSetDeviceWallpaper(const AccountId& account_id,
                                 std::string* url,
                                 std::string* hash);
@@ -306,30 +302,6 @@
       const AccountId& account_id,
       std::unique_ptr<user_manager::UserImage> user_image);
 
-  // This is called when the device wallpaper policy changes.
-  void OnDeviceWallpaperPolicyChanged();
-  // This is call after checking if the device wallpaper exists.
-  void OnDeviceWallpaperExists(const AccountId& account_id,
-                               const std::string& url,
-                               const std::string& hash,
-                               bool exist);
-  // This is called after the device wallpaper is download (either successful or
-  // failed).
-  void OnDeviceWallpaperDownloaded(const AccountId& account_id,
-                                   const std::string& hash,
-                                   bool success,
-                                   const GURL& url);
-  // Check if the device wallpaper matches the hash that's provided in the
-  // device wallpaper policy setting.
-  void OnCheckDeviceWallpaperMatchHash(const AccountId& account_id,
-                                       const std::string& url,
-                                       const std::string& hash,
-                                       bool match);
-  // This is called when the device wallpaper is decoded successfully.
-  void OnDeviceWallpaperDecoded(
-      const AccountId& account_id,
-      std::unique_ptr<user_manager::UserImage> user_image);
-
   // A wrapper of |WallpaperController::SetCustomizedDefaultWallpaperImpl|.
   void SetCustomizedDefaultWallpaperImpl(
       const base::FilePath& customized_default_wallpaper_file_small,
@@ -350,11 +322,6 @@
   std::unique_ptr<CrosSettings::ObserverSubscription>
       show_user_name_on_signin_subscription_;
 
-  std::unique_ptr<CrosSettings::ObserverSubscription>
-      device_wallpaper_image_subscription_;
-  std::unique_ptr<CustomizationWallpaperDownloader>
-      device_wallpaper_downloader_;
-
   // The number of loaded wallpapers.
   int loaded_wallpapers_for_test_ = 0;
 
diff --git a/chrome/browser/chromeos/login/webview_login_browsertest.cc b/chrome/browser/chromeos/login/webview_login_browsertest.cc
index 48bce5d..2dde52f 100644
--- a/chrome/browser/chromeos/login/webview_login_browsertest.cc
+++ b/chrome/browser/chromeos/login/webview_login_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -17,8 +18,13 @@
 #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_webui.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chrome/browser/chromeos/policy/device_policy_builder.h"
 #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/policy/test/local_policy_test_server.h"
+#include "chrome/browser/ui/login/login_handler.h"
+#include "chrome/browser/ui/login/login_handler_test_utils.h"
 #include "chrome/browser/ui/webui/signin/signin_utils.h"
 #include "chromeos/chromeos_switches.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
@@ -27,9 +33,15 @@
 #include "components/guest_view/browser/guest_view_manager.h"
 #include "components/onc/onc_constants.h"
 #include "components/onc/onc_pref_names.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/policy_constants.h"
 #include "components/policy/proto/chrome_device_policy.pb.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
@@ -98,6 +110,12 @@
   return cookies;
 }
 
+void PolicyChangedCallback(base::RepeatingClosure callback,
+                           const base::Value* old_value,
+                           const base::Value* new_value) {
+  callback.Run();
+}
+
 // Spins the loop until a notification is received from |prefs| that the value
 // of |pref_name| has changed. If the notification is received before Wait()
 // has been called, Wait() returns immediately and no loop is spun.
@@ -397,6 +415,9 @@
     device_policy_test_helper_.InstallOwnerKey();
     device_policy_test_helper_.MarkAsEnterpriseOwned();
 
+    fake_session_manager_client_->set_device_policy(
+        device_policy_test_helper_.device_policy()->GetBlob());
+
     WebviewLoginTest::SetUpInProcessBrowserTestFixture();
   }
 
@@ -721,4 +742,141 @@
   EXPECT_EQ("got no client cert", https_reply_content);
 }
 
+class WebviewProxyAuthLoginTest : public WebviewLoginTest {
+ public:
+  WebviewProxyAuthLoginTest()
+      : auth_proxy_server_(std::make_unique<net::SpawnedTestServer>(
+            net::SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
+            base::FilePath())) {}
+
+  void SetUp() override {
+    // Start proxy server
+    auth_proxy_server_->set_redirect_connect_to_localhost(true);
+    ASSERT_TRUE(auth_proxy_server_->Start());
+
+    // Prepare device policy which will be used for two purposes:
+    // - given to |fake_session_manager_client_|, so the device appears to have
+    //   registered for policy.
+    // - the payload is given to |policy_test_server_|, so we can download fresh
+    //   policy.
+    device_policy_test_helper_.device_policy()
+        ->policy_data()
+        .set_public_key_version(1);
+    device_policy_test_helper_.device_policy()->Build();
+
+    // Start policy server. Use the DMToken and DeviceId from PolicyBuilder.
+    // These also used in |device_policy_test_helper_| and was passed to
+    // |fake_session_manager_client_| above, so the device will request policy
+    // with these identifiers.
+    policy_test_server_.RegisterClient(policy::PolicyBuilder::kFakeToken,
+                                       policy::PolicyBuilder::kFakeDeviceId);
+    UpdateServedPolicyFromDevicePolicyTestHelper();
+    ASSERT_TRUE(policy_test_server_.Start());
+
+    WebviewLoginTest::SetUp();
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(
+        ::switches::kProxyServer,
+        auth_proxy_server_->host_port_pair().ToString());
+    command_line->AppendSwitchASCII(policy::switches::kDeviceManagementUrl,
+                                    policy_test_server_.GetServiceURL().spec());
+    WebviewLoginTest::SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    WebviewLoginTest::SetUpInProcessBrowserTestFixture();
+
+    // Use a fake SessionManagerClient to be able to pretend that the device has
+    // been enrolled and registered for policy (and has a device DMToken).
+    auto fake_session_manager_client =
+        std::make_unique<FakeSessionManagerClient>();
+    fake_session_manager_client_ = fake_session_manager_client.get();
+    DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient(
+        std::move(fake_session_manager_client));
+    device_policy_test_helper_.InstallOwnerKey();
+    device_policy_test_helper_.MarkAsEnterpriseOwned();
+
+    fake_session_manager_client_->set_device_policy(
+        device_policy_test_helper_.device_policy()->GetBlob());
+
+    // Set some fake state keys to make sure they are not empty.
+    std::vector<std::string> state_keys;
+    state_keys.push_back("1");
+    fake_session_manager_client_->set_server_backed_state_keys(state_keys);
+  }
+
+  void UpdateServedPolicyFromDevicePolicyTestHelper() {
+    policy_test_server_.UpdatePolicy(
+        policy::dm_protocol::kChromeDevicePolicyType,
+        std::string() /* entity_id */,
+        device_policy_test_helper_.device_policy()
+            ->payload()
+            .SerializeAsString());
+  }
+
+  // A proxy server which requires authentication using the 'Basic'
+  // authentication method.
+  std::unique_ptr<net::SpawnedTestServer> auth_proxy_server_;
+  policy::LocalPolicyTestServer policy_test_server_;
+  policy::DevicePolicyCrosTestHelper device_policy_test_helper_;
+
+  // FakeDBusThreadManager uses FakeSessionManagerClient.
+  std::unique_ptr<chromeos::DBusThreadManagerSetter> dbus_setter_;
+  // Unowned pointer - owned by DBusThreadManager.
+  chromeos::FakeSessionManagerClient* fake_session_manager_client_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebviewProxyAuthLoginTest);
+};
+
+IN_PROC_BROWSER_TEST_F(WebviewProxyAuthLoginTest, ProxyAuthTransfer) {
+  WaitForSigninScreen();
+
+  LoginPromptBrowserTestObserver observer;
+  observer.Register(content::NotificationService::AllSources());
+
+  content::WindowedNotificationObserver auth_needed_waiter(
+      chrome::NOTIFICATION_AUTH_NEEDED,
+      content::NotificationService::AllSources());
+
+  auth_needed_waiter.Wait();
+  ASSERT_FALSE(observer.handlers().empty());
+  LoginHandler* login_handler = *observer.handlers().begin();
+
+  // Before entering auth data, make |policy_test_server_| serve a policy that
+  // we can use to detect if policies have been fetched.
+  em::ChromeDeviceSettingsProto& device_policy =
+      device_policy_test_helper_.device_policy()->payload();
+  device_policy.mutable_device_login_screen_auto_select_certificate_for_urls()
+      ->add_login_screen_auto_select_certificate_rules("test_pattern");
+  UpdateServedPolicyFromDevicePolicyTestHelper();
+
+  policy::PolicyChangeRegistrar policy_change_registrar(
+      g_browser_process->platform_part()
+          ->browser_policy_connector_chromeos()
+          ->GetPolicyService(),
+      policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME,
+                              std::string() /* component_id */));
+
+  // Now enter auth data
+  login_handler->SetAuth(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar"));
+  WaitForGaiaPageLoad();
+
+  base::RunLoop run_loop;
+  policy_change_registrar.Observe(
+      policy::key::kDeviceLoginScreenAutoSelectCertificateForUrls,
+      base::BindRepeating(&PolicyChangedCallback, run_loop.QuitClosure()));
+  run_loop.Run();
+
+  // Press the back button at a sign-in screen without pre-existing users to
+  // start a new sign-in attempt.
+  // This will re-load gaia, rotating the StoragePartition. The new
+  // StoragePartition must also have the proxy auth details.
+  JS().Evaluate("$('signin-back-button').fire('tap')");
+  WaitForGaiaPageReload();
+  // Expect that we got back to the identifier page, as there are no known users
+  // so the sign-in screen will not display user pods.
+  ExpectIdentifierPage();
+}
 }  // namespace chromeos
diff --git a/chrome/browser/devtools/devtools_sanity_browsertest.cc b/chrome/browser/devtools/devtools_sanity_browsertest.cc
index ef4ecbf..a47a3d0 100644
--- a/chrome/browser/devtools/devtools_sanity_browsertest.cc
+++ b/chrome/browser/devtools/devtools_sanity_browsertest.cc
@@ -2027,9 +2027,8 @@
 // This tests checks that window is correctly initialized when DevTools is
 // opened while navigation through history with forward and back actions.
 // (crbug.com/627407)
-// Flaky on Windows and ChromeOS. http://crbug.com/628174#c4
 IN_PROC_BROWSER_TEST_F(DevToolsSanityTest,
-                       DISABLED_TestWindowInitializedOnNavigateBack) {
+                       TestWindowInitializedOnNavigateBack) {
   TestChromeWebUIControllerFactory test_factory;
   MockWebUIProvider mock_provider("dummyurl",
                                   "<script>\n"
diff --git a/chrome/browser/mash_service_registry.cc b/chrome/browser/mash_service_registry.cc
index 149d2578..858aee8d 100644
--- a/chrome/browser/mash_service_registry.cc
+++ b/chrome/browser/mash_service_registry.cc
@@ -12,30 +12,40 @@
 #include "mash/quick_launch/quick_launch.h"
 #include "services/ui/public/interfaces/constants.mojom.h"
 
+using content::ContentBrowserClient;
+
 namespace mash_service_registry {
 namespace {
 
 struct Service {
   const char* name;
-  const char* description;
+  const char* display_name;
+  const char* process_group;  // If null, uses a separate process.
 };
 
 constexpr Service kServices[] = {
-    {mash::quick_launch::mojom::kServiceName, "Quick Launch"},
-    {ui::mojom::kServiceName, "UI Service"},
-    {ash::mojom::kServiceName, "Ash Window Manager and Shell"},
-    {"accessibility_autoclick", "Ash Accessibility Autoclick"},
-    {"touch_hud", "Ash Touch Hud"},
-    {font_service::mojom::kServiceName, "Font Service"},
+    {mash::quick_launch::mojom::kServiceName, "Quick Launch", nullptr},
+    {ui::mojom::kServiceName, "UI Service", kAshAndUiProcessGroup},
+    {ash::mojom::kServiceName, "Ash Window Manager and Shell",
+     kAshAndUiProcessGroup},
+    {"accessibility_autoclick", "Ash Accessibility Autoclick", nullptr},
+    {"touch_hud", "Ash Touch Hud", nullptr},
+    {font_service::mojom::kServiceName, "Font Service", nullptr},
 };
 
 }  // namespace
 
 void RegisterOutOfProcessServices(
-    content::ContentBrowserClient::OutOfProcessServiceMap* services) {
-  for (size_t i = 0; i < arraysize(kServices); ++i) {
-    (*services)[kServices[i].name] =
-        base::ASCIIToUTF16(kServices[i].description);
+    ContentBrowserClient::OutOfProcessServiceMap* services) {
+  for (const auto& service : kServices) {
+    base::string16 display_name = base::ASCIIToUTF16(service.display_name);
+    if (service.process_group) {
+      (*services)[service.name] = ContentBrowserClient::OutOfProcessServiceInfo(
+          display_name, service.process_group);
+    } else {
+      (*services)[service.name] =
+          ContentBrowserClient::OutOfProcessServiceInfo(display_name);
+    }
   }
 }
 
diff --git a/chrome/browser/mash_service_registry.h b/chrome/browser/mash_service_registry.h
index 76e72f1..a4848c25 100644
--- a/chrome/browser/mash_service_registry.h
+++ b/chrome/browser/mash_service_registry.h
@@ -11,6 +11,9 @@
 
 namespace mash_service_registry {
 
+// Process group used for the ash service and the ui service. Visible for test.
+constexpr char kAshAndUiProcessGroup[] = "ash_and_ui";
+
 // Starts one of Mash's embedded services.
 void RegisterOutOfProcessServices(
     content::ContentBrowserClient::OutOfProcessServiceMap* services);
diff --git a/chrome/browser/mash_service_registry_browsertest.cc b/chrome/browser/mash_service_registry_browsertest.cc
new file mode 100644
index 0000000..2629f4a
--- /dev/null
+++ b/chrome/browser/mash_service_registry_browsertest.cc
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/mash_service_registry.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "chrome/browser/chromeos/ash_config.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/browser/utility_process_host.h"
+#include "content/public/test/browser_test_utils.h"
+
+namespace {
+
+void VerifyProcessGroupOnIOThread() {
+  // Ash and ui are in the same process group.
+  std::map<std::string, base::WeakPtr<content::UtilityProcessHost>>* groups =
+      content::GetServiceManagerProcessGroups();
+  ASSERT_TRUE(groups);
+  ASSERT_TRUE(
+      base::ContainsKey(*groups, mash_service_registry::kAshAndUiProcessGroup));
+
+  // The process group has a process host.
+  base::WeakPtr<content::UtilityProcessHost> host =
+      groups->at(mash_service_registry::kAshAndUiProcessGroup);
+  ASSERT_TRUE(host);
+
+  // The host is associated with a real process.
+  EXPECT_NE(base::kNullProcessHandle, host->GetData().handle);
+}
+
+}  // namespace
+
+using MashServiceRegistryTest = InProcessBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(MashServiceRegistryTest, AshAndUiInSameProcess) {
+  // Test only applies to --mash (out-of-process ash).
+  if (chromeos::GetAshConfig() != ash::Config::MASH)
+    return;
+
+  // Process group information is owned by the IO thread.
+  base::RunLoop run_loop;
+  content::BrowserThread::PostTaskAndReply(
+      content::BrowserThread::IO, FROM_HERE,
+      base::BindOnce(&VerifyProcessGroupOnIOThread), run_loop.QuitClosure());
+  run_loop.Run();
+}
diff --git a/chrome/browser/mash_service_registry_unittest.cc b/chrome/browser/mash_service_registry_unittest.cc
new file mode 100644
index 0000000..e8f75bd
--- /dev/null
+++ b/chrome/browser/mash_service_registry_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/mash_service_registry.h"
+
+#include "ash/public/interfaces/constants.mojom.h"
+#include "base/stl_util.h"
+#include "content/public/browser/content_browser_client.h"
+#include "services/ui/public/interfaces/constants.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(MashServiceRegistryTest, AshAndUiInSameProcess) {
+  content::ContentBrowserClient::OutOfProcessServiceMap services;
+  mash_service_registry::RegisterOutOfProcessServices(&services);
+
+  // The ash service and ui service should be in the same process group.
+  ASSERT_TRUE(base::ContainsKey(services, ash::mojom::kServiceName));
+  ASSERT_TRUE(base::ContainsKey(services, ui::mojom::kServiceName));
+  std::string ash_process_group =
+      *services[ash::mojom::kServiceName].process_group;
+  std::string ui_process_group =
+      *services[ui::mojom::kServiceName].process_group;
+  EXPECT_FALSE(ash_process_group.empty());
+  EXPECT_FALSE(ui_process_group.empty());
+  EXPECT_EQ(ash_process_group, ui_process_group);
+}
diff --git a/chrome/browser/memory_details.cc b/chrome/browser/memory_details.cc
index 15a9106..5ef6e729 100644
--- a/chrome/browser/memory_details.cc
+++ b/chrome/browser/memory_details.cc
@@ -96,8 +96,7 @@
       num_open_fds(-1),
       open_fds_soft_limit(-1),
       renderer_type(RENDERER_UNKNOWN),
-      phys_footprint(0),
-      private_memory_footprint(0) {}
+      phys_footprint(0) {}
 
 ProcessMemoryInformation::ProcessMemoryInformation(
     const ProcessMemoryInformation& other) = default;
diff --git a/chrome/browser/memory_details.h b/chrome/browser/memory_details.h
index b0f84af9..5f6b259 100644
--- a/chrome/browser/memory_details.h
+++ b/chrome/browser/memory_details.h
@@ -72,9 +72,6 @@
   // The physical footprint is a macOS concept that tracks anonymous,
   // non-discardable memory.
   size_t phys_footprint;
-  // TODO(erikchen): Remove this temporary estimate for private memory once the
-  // memory infra service emits the same metric. https://crbug.com/720541.
-  size_t private_memory_footprint;
 };
 
 typedef std::vector<ProcessMemoryInformation> ProcessMemoryInformationList;
diff --git a/chrome/browser/memory_details_mac.cc b/chrome/browser/memory_details_mac.cc
index ffd655d..312cb98 100644
--- a/chrome/browser/memory_details_mac.cc
+++ b/chrome/browser/memory_details_mac.cc
@@ -14,7 +14,6 @@
 #include "base/file_version_info.h"
 #include "base/files/file_path.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/mac_util.h"
 #include "base/process/process_iterator.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -66,14 +65,6 @@
   base::ProcessMetrics::TaskVMInfo vm_info = metrics->GetTaskVMInfo();
   info.phys_footprint = vm_info.phys_footprint;
 
-  // TODO(erikchen): Remove this temporary estimate for private memory once the
-  // memory infra service emits the same metric. https://crbug.com/720541.
-  if (base::mac::IsAtLeastOS10_12()) {
-    info.private_memory_footprint = vm_info.phys_footprint;
-  } else {
-    info.private_memory_footprint = vm_info.internal + vm_info.compressed;
-  }
-
   processes->push_back(info);
 }
 
diff --git a/chrome/browser/metrics/metrics_memory_details.cc b/chrome/browser/metrics/metrics_memory_details.cc
index a7bedac..b87c127 100644
--- a/chrome/browser/metrics/metrics_memory_details.cc
+++ b/chrome/browser/metrics/metrics_memory_details.cc
@@ -21,10 +21,6 @@
 #include "ppapi/features/features.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
 
-#if defined(OS_MACOSX)
-#include "base/mac/mac_util.h"
-#endif
-
 MemoryGrowthTracker::MemoryGrowthTracker() {
 }
 
@@ -105,11 +101,6 @@
           UMA_HISTOGRAM_COUNTS_10000("Memory.Browser.OpenFDsSoftLimit",
                                      open_fds_soft_limit);
         }
-#if defined(OS_MACOSX)
-        UMA_HISTOGRAM_MEMORY_LARGE_MB(
-            "Memory.Experimental.Browser.PrivateMemoryFootprint.MacOS",
-            browser.processes[index].private_memory_footprint / 1024 / 1024);
-#endif
         continue;
       case content::PROCESS_TYPE_RENDERER: {
         UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.RendererAll.Committed",
@@ -129,12 +120,6 @@
                                          num_open_fds);
             }
             extension_count++;
-#if defined(OS_MACOSX)
-            UMA_HISTOGRAM_MEMORY_LARGE_MB(
-                "Memory.Experimental.Extension.PrivateMemoryFootprint.MacOS",
-                browser.processes[index].private_memory_footprint / 1024 /
-                    1024);
-#endif
             continue;
           case ProcessMemoryInformation::RENDERER_CHROME:
             if (num_open_fds != -1)
@@ -146,12 +131,6 @@
             continue;
           case ProcessMemoryInformation::RENDERER_NORMAL:
           default:
-#if defined(OS_MACOSX)
-            UMA_HISTOGRAM_MEMORY_LARGE_MB(
-                "Memory.Experimental.Renderer.PrivateMemoryFootprint.MacOS",
-                browser.processes[index].private_memory_footprint / 1024 /
-                    1024);
-#endif
             // TODO(erikkay): Should we bother splitting out the other subtypes?
             UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Renderer.Committed",
                                           committed / 1024);
@@ -181,17 +160,6 @@
         other_count++;
         continue;
       case content::PROCESS_TYPE_GPU:
-#if defined(OS_MACOSX)
-        // Physical footprint was introduced in macOS 10.12.
-        if (base::mac::IsAtLeastOS10_12()) {
-          UMA_HISTOGRAM_MEMORY_LARGE_MB(
-              "Memory.Experimental.Gpu.PhysicalFootprint.MacOS",
-              browser.processes[index].phys_footprint / 1024 / 1024);
-        }
-        UMA_HISTOGRAM_MEMORY_LARGE_MB(
-            "Memory.Experimental.Gpu.PrivateMemoryFootprint.MacOS",
-            browser.processes[index].private_memory_footprint / 1024 / 1024);
-#endif
         if (num_open_fds != -1 && open_fds_soft_limit != -1) {
           UMA_HISTOGRAM_COUNTS_10000("Memory.Gpu.OpenFDs", num_open_fds);
           UMA_HISTOGRAM_COUNTS_10000("Memory.Gpu.OpenFDsSoftLimit",
diff --git a/chrome/browser/metrics/thread_watcher.cc b/chrome/browser/metrics/thread_watcher.cc
index fce991e..904266f6 100644
--- a/chrome/browser/metrics/thread_watcher.cc
+++ b/chrome/browser/metrics/thread_watcher.cc
@@ -466,13 +466,11 @@
     crash_on_hang_thread_names =
         command_line.GetSwitchValueASCII(switches::kCrashOnHangThreads);
   } else if (channel != version_info::Channel::STABLE) {
-    // Default to crashing the browser if UI or IO or FILE threads are not
-    // responsive except in stable channel.
-    crash_on_hang_thread_names = base::StringPrintf(
-        "UI:%d:%d,IO:%d:%d,FILE:%d:%d",
-        kLiveThreadsThreshold, crash_seconds,
-        kLiveThreadsThreshold, crash_seconds,
-        kLiveThreadsThreshold, crash_seconds * 5);
+    // Default to crashing the browser if UI or IO threads are not responsive
+    // except in stable channel.
+    crash_on_hang_thread_names =
+        base::StringPrintf("UI:%d:%d,IO:%d:%d", kLiveThreadsThreshold,
+                           crash_seconds, kLiveThreadsThreshold, crash_seconds);
   }
 
   ParseCommandLineCrashOnHangThreads(crash_on_hang_thread_names,
@@ -552,6 +550,7 @@
                 unresponsive_threshold, crash_on_hang_threads);
   StartWatching(BrowserThread::IO, "IO", kSleepTime, kUnresponsiveTime,
                 unresponsive_threshold, crash_on_hang_threads);
+  // TODO(gab): Stop watching deprecated BrowserThreads, crbug.com/768886.
   StartWatching(BrowserThread::DB, "DB", kSleepTime, kUnresponsiveTime,
                 unresponsive_threshold, crash_on_hang_threads);
   StartWatching(BrowserThread::FILE, "FILE", kSleepTime, kUnresponsiveTime,
diff --git a/chrome/browser/metrics/thread_watcher_report_hang.cc b/chrome/browser/metrics/thread_watcher_report_hang.cc
index 93fd6c6..7bae67a 100644
--- a/chrome/browser/metrics/thread_watcher_report_hang.cc
+++ b/chrome/browser/metrics/thread_watcher_report_hang.cc
@@ -50,36 +50,12 @@
   ReportThreadHang();
 }
 
-NOINLINE void ThreadUnresponsive_DB() {
-  volatile int inhibit_comdat = __LINE__;
-  ALLOW_UNUSED_LOCAL(inhibit_comdat);
-  ReportThreadHang();
-}
-
-NOINLINE void ThreadUnresponsive_FILE() {
-  volatile int inhibit_comdat = __LINE__;
-  ALLOW_UNUSED_LOCAL(inhibit_comdat);
-  ReportThreadHang();
-}
-
-NOINLINE void ThreadUnresponsive_FILE_USER_BLOCKING() {
-  volatile int inhibit_comdat = __LINE__;
-  ALLOW_UNUSED_LOCAL(inhibit_comdat);
-  ReportThreadHang();
-}
-
 NOINLINE void ThreadUnresponsive_PROCESS_LAUNCHER() {
   volatile int inhibit_comdat = __LINE__;
   ALLOW_UNUSED_LOCAL(inhibit_comdat);
   ReportThreadHang();
 }
 
-NOINLINE void ThreadUnresponsive_CACHE() {
-  volatile int inhibit_comdat = __LINE__;
-  ALLOW_UNUSED_LOCAL(inhibit_comdat);
-  ReportThreadHang();
-}
-
 NOINLINE void ThreadUnresponsive_IO() {
   volatile int inhibit_comdat = __LINE__;
   ALLOW_UNUSED_LOCAL(inhibit_comdat);
@@ -91,19 +67,16 @@
   switch (thread_id) {
     case content::BrowserThread::UI:
       return ThreadUnresponsive_UI();
-    case content::BrowserThread::DB:
-      return ThreadUnresponsive_DB();
-    case content::BrowserThread::FILE:
-      return ThreadUnresponsive_FILE();
-    case content::BrowserThread::FILE_USER_BLOCKING:
-      return ThreadUnresponsive_FILE_USER_BLOCKING();
     case content::BrowserThread::PROCESS_LAUNCHER:
       return ThreadUnresponsive_PROCESS_LAUNCHER();
-    case content::BrowserThread::CACHE:
-      return ThreadUnresponsive_CACHE();
     case content::BrowserThread::IO:
       return ThreadUnresponsive_IO();
     case content::BrowserThread::ID_COUNT:
+    // TODO(gab): Get rid of deprecated BrowserThread IDs.
+    case content::BrowserThread::DB:
+    case content::BrowserThread::FILE:
+    case content::BrowserThread::FILE_USER_BLOCKING:
+    case content::BrowserThread::CACHE:
       NOTREACHED();
       break;
   }
diff --git a/chrome/browser/net/errorpage_browsertest.cc b/chrome/browser/net/errorpage_browsertest.cc
index 7b29449..e577d73 100644
--- a/chrome/browser/net/errorpage_browsertest.cc
+++ b/chrome/browser/net/errorpage_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 
@@ -63,12 +64,14 @@
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "net/base/filename_util.h"
 #include "net/base/net_errors.h"
+#include "net/dns/mock_host_resolver.h"
 #include "net/http/failing_http_transaction_factory.h"
 #include "net/http/http_cache.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/test/url_request/url_request_failed_job.h"
+#include "net/test/url_request/url_request_mock_data_job.h"
 #include "net/test/url_request/url_request_mock_http_job.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -144,12 +147,13 @@
 
 // Checks that the local error page is being displayed, without remotely
 // retrieved navigation corrections, and with the specified error string.
-void ExpectDisplayingLocalErrorPage(Browser* browser,
+void ExpectDisplayingLocalErrorPage(const std::string& url,
+                                    Browser* browser,
                                     const std::string& error_string) {
   EXPECT_TRUE(IsDisplayingText(browser, error_string));
 
   // Locally generated error pages should not have navigation corrections.
-  EXPECT_FALSE(IsDisplayingText(browser, "http://mock.http/title2.html"));
+  EXPECT_FALSE(IsDisplayingText(browser, url));
 
   // Locally generated error pages should not have a link with search terms.
   EXPECT_FALSE(IsDisplayingText(browser, "search query"));
@@ -157,19 +161,23 @@
 
 // Checks that the local error page is being displayed, without remotely
 // retrieved navigation corrections, and with the specified error code.
-void ExpectDisplayingLocalErrorPage(Browser* browser, net::Error error_code) {
-  ExpectDisplayingLocalErrorPage(browser, net::ErrorToShortString(error_code));
+void ExpectDisplayingLocalErrorPage(const std::string& url,
+                                    Browser* browser,
+                                    net::Error error_code) {
+  ExpectDisplayingLocalErrorPage(url, browser,
+                                 net::ErrorToShortString(error_code));
 }
 
 // Checks that an error page with information retrieved from the navigation
 // correction service is being displayed, with the specified specified error
 // string.
-void ExpectDisplayingNavigationCorrections(Browser* browser,
+void ExpectDisplayingNavigationCorrections(const std::string& url,
+                                           Browser* browser,
                                            const std::string& error_string) {
   EXPECT_TRUE(IsDisplayingText(browser, error_string));
 
   // Check that the mock navigation corrections are displayed.
-  EXPECT_TRUE(IsDisplayingText(browser, "http://mock.http/title2.html"));
+  EXPECT_TRUE(IsDisplayingText(browser, url));
 
   // Check that the search terms are displayed as a link.
   EXPECT_TRUE(IsDisplayingText(browser, "search query"));
@@ -182,9 +190,10 @@
 // Checks that an error page with information retrieved from the navigation
 // correction service is being displayed, with the specified specified error
 // code.
-void ExpectDisplayingNavigationCorrections(Browser* browser,
+void ExpectDisplayingNavigationCorrections(const std::string& url,
+                                           Browser* browser,
                                            net::Error error_code) {
-  ExpectDisplayingNavigationCorrections(browser,
+  ExpectDisplayingNavigationCorrections(url, browser,
                                         net::ErrorToShortString(error_code));
 }
 
@@ -428,8 +437,7 @@
 // provided owner every time there is a new request.
 class LinkDoctorInterceptor : public net::URLRequestInterceptor {
  public:
-  explicit LinkDoctorInterceptor(class DNSErrorPageTest* owner)
-      : owner_(owner) {}
+  explicit LinkDoctorInterceptor(class DNSErrorPageTest* owner);
   ~LinkDoctorInterceptor() override = default;
 
   // net::URLRequestInterceptor implementation
@@ -439,12 +447,15 @@
 
  private:
   DNSErrorPageTest* owner_;
+  GURL link_doctor_url;
 
   DISALLOW_COPY_AND_ASSIGN(LinkDoctorInterceptor);
 };
 
 class DNSErrorPageTest : public ErrorPageTest {
  public:
+  friend LinkDoctorInterceptor;
+
   DNSErrorPageTest() {
     if (!base::FeatureList::IsEnabled(features::kNetworkService))
       return;
@@ -479,15 +490,15 @@
                     BrowserThread::UI, FROM_HERE,
                     base::BindOnce(&DNSErrorPageTest::RequestCreated,
                                    base::Unretained(owner)));
-
-                return WriteFileToURLLoader(params, "mock-link-doctor.json");
+                return WriteFileToURLLoader(owner, params,
+                                            "mock-link-doctor.json");
               }
 
               // Add an interceptor for the search engine the error page will
               // use.
               if (params->url_request.url.host() ==
                   owner->search_term_url_.host()) {
-                return WriteFileToURLLoader(params, "title3.html");
+                return WriteFileToURLLoader(owner, params, "title3.html");
               }
 
               return false;
@@ -530,6 +541,7 @@
   void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
 
   static bool WriteFileToURLLoader(
+      DNSErrorPageTest* owner,
       content::URLLoaderInterceptor::RequestParams* params,
       std::string path) {
     base::ScopedAllowBlockingForTesting allow_blocking;
@@ -548,12 +560,26 @@
     const bool result = base::ReadFileToString(file_path, &contents);
     EXPECT_TRUE(result);
 
+    if (path == "mock-link-doctor.json") {
+      GURL url =
+          owner->embedded_test_server()->GetURL("mock.http", "/title2.html");
+
+      std::string placeholder = "http://mock.http/title2.html";
+      contents.replace(contents.find(placeholder), placeholder.length(),
+                       url.spec());
+    }
+
     content::URLLoaderInterceptor::WriteResponse(
         net::URLRequestTestJob::test_headers(), contents, params->client.get());
     return true;
   }
 
   void SetUpOnMainThread() override {
+    // All mock.http requests get served by the embedded test server.
+    host_resolver()->AddRule("mock.http", "127.0.0.1");
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+
     UIThreadSearchTermsData search_terms_data(browser()->profile());
     search_term_url_ = GURL(search_terms_data.GoogleBaseURLValue());
 
@@ -624,7 +650,9 @@
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), non_existent_file_url, 1);
 
-  ExpectDisplayingLocalErrorPage(browser(), net::ERR_FILE_NOT_FOUND);
+  ExpectDisplayingLocalErrorPage(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_FILE_NOT_FOUND);
   // Should not request Link Doctor corrections for local errors.
   EXPECT_EQ(0, num_requests());
   // Only errors on HTTP/HTTPS pages should display a diagnostics button.
@@ -637,7 +665,9 @@
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), URLRequestFailedJob::GetMockHttpUrl(net::ERR_FAILED), 1);
 
-  ExpectDisplayingLocalErrorPage(browser(), net::ERR_FAILED);
+  ExpectDisplayingLocalErrorPage(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_FAILED);
   // Should not request Link Doctor corrections for this error.
   EXPECT_EQ(0, num_requests());
 }
@@ -648,19 +678,21 @@
   // the Link Doctor response.  The second navigation is the Link Doctor.
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 }
 
 // Test that a DNS error occuring in the main frame does not result in an
 // additional session history entry.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack1) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   NavigateToFileURL("/title2.html");
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
   GoBackAndWaitForTitle("Title Of Awesomeness", 1);
   EXPECT_EQ(1, num_requests());
 }
@@ -668,19 +700,21 @@
 // Test that a DNS error occuring in the main frame does not result in an
 // additional session history entry.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   NavigateToFileURL("/title2.html");
 
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 
   NavigateToFileURL("/title3.html");
 
   GoBackAndWaitForNavigations(2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(2, num_requests());
 
   GoBackAndWaitForTitle("Title Of Awesomeness", 1);
@@ -690,50 +724,57 @@
 // Test that a DNS error occuring in the main frame does not result in an
 // additional session history entry.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2AndForward) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   NavigateToFileURL("/title2.html");
 
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+
+  std::string url =
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 
   NavigateToFileURL("/title3.html");
 
   GoBackAndWaitForNavigations(2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(2, num_requests());
 
   GoBackAndWaitForTitle("Title Of Awesomeness", 1);
 
   GoForwardAndWaitForNavigations(2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(3, num_requests());
 }
 
 // Test that a DNS error occuring in the main frame does not result in an
 // additional session history entry.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2Forward2) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   NavigateToFileURL("/title3.html");
 
+  std::string url =
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 
   NavigateToFileURL("/title2.html");
 
   GoBackAndWaitForNavigations(2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(2, num_requests());
 
   GoBackAndWaitForTitle("Title Of More Awesomeness", 1);
 
   GoForwardAndWaitForNavigations(2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(3, num_requests());
 
   GoForwardAndWaitForTitle("Title Of Awesomeness", 1);
@@ -746,7 +787,9 @@
   // page.
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 
   content::WebContents* web_contents =
@@ -782,7 +825,9 @@
 
   // Go back to the error page, to make sure the history is correct.
   GoBackAndWaitForNavigations(2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(3, num_requests());
 }
 
@@ -790,9 +835,12 @@
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoReload) {
   // The first navigation should fail, and the second one should be the error
   // page.
+  std::string url =
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 
   content::WebContents* web_contents =
@@ -807,7 +855,8 @@
   web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
       base::ASCIIToUTF16("document.getElementById('reload-button').click();"));
   nav_observer.Wait();
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
 
   // There should have been two more requests to the correction service:  One
   // for the new error page, and one for tracking purposes.  Have to make sure
@@ -823,9 +872,12 @@
                        DNSError_DoReloadAfterSameDocumentNavigation) {
   // The first navigation should fail, and the second one should be the error
   // page.
+  std::string url =
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 
   content::WebContents* web_contents =
@@ -837,7 +889,8 @@
       base::ASCIIToUTF16("document.location='#';"));
   content::WaitForLoadStop(web_contents);
   // Page being displayed should not change.
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
   // No new requests should have been issued.
   EXPECT_EQ(1, num_requests());
 
@@ -850,7 +903,8 @@
   web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
       base::ASCIIToUTF16("document.getElementById('reload-button').click();"));
   nav_observer2.Wait();
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(url, browser(),
+                                        net::ERR_NAME_NOT_RESOLVED);
 
   // There should have been two more requests to the correction service:  One
   // for the new error page, and one for tracking purposes.  Have to make sure
@@ -863,10 +917,12 @@
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoClickLink) {
   // The first navigation should fail, and the second one should be the error
   // page.
-
+  GURL url = embedded_test_server()->GetURL("mock.http", "/title2.html");
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GetDnsErrorURL(), 2);
-  ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
   EXPECT_EQ(1, num_requests());
 
   content::WebContents* web_contents =
@@ -878,7 +934,7 @@
       web_contents,
       base::ASCIIToUTF16("Title Of Awesomeness"));
   std::string link_selector =
-      "document.querySelector('a[href=\"http://mock.http/title2.html\"]')";
+      "document.querySelector('a[href=\"" + url.spec() + "\"]')";
   // The tracking request is triggered by onmousedown, so it catches middle
   // mouse button clicks, as well as left clicks.
   web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
@@ -900,7 +956,6 @@
 // Test that a DNS error occuring in an iframe does not result in showing
 // navigation corrections.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, IFrameDNSError_Basic) {
-  ASSERT_TRUE(embedded_test_server()->Start());
   NavigateToURLAndWaitForTitle(
       embedded_test_server()->GetURL("/iframe_dns_error.html"), "Blah", 1);
   // We expect to have two history entries, since we started off with navigation
@@ -920,8 +975,6 @@
 // Test that a DNS error occuring in an iframe does not result in an
 // additional session history entry.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, MAYBE_IFrameDNSError_GoBack) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title2.html"));
   ui_test_utils::NavigateToURL(
@@ -942,8 +995,6 @@
 // additional session history entry.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest,
                        MAYBE_IFrameDNSError_GoBackAndForward) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   NavigateToFileURL("/title2.html");
   NavigateToFileURL("/iframe_dns_error.html");
   GoBackAndWaitForTitle("Title Of Awesomeness", 1);
@@ -956,8 +1007,6 @@
 // To ensure that the main document has completed loading, JavaScript is used to
 // inject an iframe after loading is done.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, IFrameDNSError_JavaScript) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   content::WebContents* wc =
       browser()->tab_strip_model()->GetActiveWebContents();
   GURL fail_url =
@@ -1022,7 +1071,6 @@
 // Checks that navigation corrections are not loaded when we receive an actual
 // 404 page.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Page404) {
-  ASSERT_TRUE(embedded_test_server()->Start());
   NavigateToURLAndWaitForTitle(embedded_test_server()->GetURL("/page404.html"),
                                "SUCCESS", 1);
   EXPECT_EQ(0, num_requests());
@@ -1033,26 +1081,26 @@
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Empty404) {
   // The first navigation should fail and load a blank page, while it fetches
   // the Link Doctor response.  The second navigation is the Link Doctor.
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   GURL url = embedded_test_server()->GetURL("/errorpage/empty404.html");
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(), url, 2);
   // This depends on the non-internationalized error ID string in
   // localized_error.cc.
-  ExpectDisplayingNavigationCorrections(browser(), "HTTP ERROR 404");
+  ExpectDisplayingNavigationCorrections(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), "HTTP ERROR 404");
   EXPECT_EQ(1, num_requests());
 }
 
 // Checks that a local error page is shown in response to a 500 error page
 // without a body.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Empty500) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL("/errorpage/empty500.html"));
   // This depends on the non-internationalized error ID string in
   // localized_error.cc.
-  ExpectDisplayingLocalErrorPage(browser(), "HTTP ERROR 500");
+  ExpectDisplayingLocalErrorPage(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), "HTTP ERROR 500");
   EXPECT_EQ(0, num_requests());
 }
 
@@ -1060,7 +1108,6 @@
 // is correctly transferred, and that stale cached copied can be loaded
 // from the javascript.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, StaleCacheStatus) {
-  ASSERT_TRUE(embedded_test_server()->Start());
   // Load cache with entry with "nocache" set, to create stale
   // cache.  Currently it needs to at least have an etag for the cache to
   // not give up on it entirely, however. See https://crbug.com/784520
@@ -1138,20 +1185,36 @@
   EXPECT_EQ(1, result);
 }
 
+LinkDoctorInterceptor::LinkDoctorInterceptor(DNSErrorPageTest* owner)
+    : owner_(owner) {
+  link_doctor_url =
+      owner->embedded_test_server()->GetURL("mock.http", "/title2.html");
+}
+
 net::URLRequestJob* LinkDoctorInterceptor::MaybeInterceptRequest(
     net::URLRequest* request,
     net::NetworkDelegate* network_delegate) const {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  base::ScopedAllowBlockingForTesting allow_blocking;
 
   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                           base::BindOnce(&DNSErrorPageTest::RequestCreated,
                                          base::Unretained(owner_)));
 
-  base::FilePath root_http;
-  PathService::Get(chrome::DIR_TEST_DATA, &root_http);
-  return new net::URLRequestMockHTTPJob(
-      request, network_delegate,
-      root_http.AppendASCII("mock-link-doctor.json"));
+  base::FilePath file_path;
+  PathService::Get(chrome::DIR_TEST_DATA, &file_path);
+  file_path = file_path.AppendASCII("mock-link-doctor.json");
+
+  std::string contents;
+  const bool result = base::ReadFileToString(file_path, &contents);
+  EXPECT_TRUE(result);
+
+  std::string placeholder = "http://mock.http/title2.html";
+  contents.replace(contents.find(placeholder), placeholder.length(),
+                   link_doctor_url.spec());
+
+  return new net::URLRequestMockDataJob(request, network_delegate, contents, 1,
+                                        false);
 }
 
 class ErrorPageAutoReloadTest : public InProcessBrowserTest {
@@ -1402,13 +1465,16 @@
 // successfully loaded.
 IN_PROC_BROWSER_TEST_F(ErrorPageNavigationCorrectionsFailTest,
                        FetchCorrectionsFails) {
+  ASSERT_TRUE(embedded_test_server()->Start());
   ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
       browser(),
       URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED),
       2);
 
   // Verify that the expected error page is being displayed.
-  ExpectDisplayingLocalErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
+  ExpectDisplayingLocalErrorPage(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_NAME_NOT_RESOLVED);
 
   // Diagnostics button should be displayed, if available on this platform.
   EXPECT_EQ(PlatformSupportsDiagnosticsTool(),
@@ -1677,19 +1743,21 @@
 
 // Make sure HTTP/0.9 is disabled on non-default ports by default.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Http09WeirdPort) {
-  ASSERT_TRUE(embedded_test_server()->Start());
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL("/echo-raw?spam"));
-  ExpectDisplayingLocalErrorPage(browser(), net::ERR_INVALID_HTTP_RESPONSE);
+  ExpectDisplayingLocalErrorPage(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_INVALID_HTTP_RESPONSE);
 }
 
 // Test that redirects to invalid URLs show an error. See
 // https://crbug.com/462272.
 IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, RedirectToInvalidURL) {
-  ASSERT_TRUE(embedded_test_server()->Start());
   GURL url = embedded_test_server()->GetURL("/server-redirect?https://:");
   ui_test_utils::NavigateToURL(browser(), url);
-  ExpectDisplayingLocalErrorPage(browser(), net::ERR_INVALID_REDIRECT);
+  ExpectDisplayingLocalErrorPage(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_INVALID_REDIRECT);
   // The error page should commit before the redirect, not after.
   EXPECT_EQ(url, browser()
                      ->tab_strip_model()
@@ -1734,7 +1802,9 @@
 // is displayed. This tests the particular case in which the response body
 // is small enough that the entire response must be read before its MIME type
 // can be determined.
-IN_PROC_BROWSER_TEST_F(DNSErrorPageTest,
+class ErrorPageSniffTest : public InProcessBrowserTest {};
+
+IN_PROC_BROWSER_TEST_F(ErrorPageSniffTest,
                        SniffSmallHttpErrorResponseAsDownload) {
   const char kErrorPath[] = "/foo";
   embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
@@ -1744,7 +1814,9 @@
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL(kErrorPath));
 
-  ExpectDisplayingLocalErrorPage(browser(), net::ERR_INVALID_RESPONSE);
+  ExpectDisplayingLocalErrorPage(
+      embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+      browser(), net::ERR_INVALID_RESPONSE);
 }
 
 }  // namespace
diff --git a/chrome/browser/notifications/notification_platform_bridge_win.cc b/chrome/browser/notifications/notification_platform_bridge_win.cc
index 99dcef6..65e90c8 100644
--- a/chrome/browser/notifications/notification_platform_bridge_win.cc
+++ b/chrome/browser/notifications/notification_platform_bridge_win.cc
@@ -52,6 +52,10 @@
     winui::Notifications::ToastNotification*,
     winui::Notifications::ToastDismissedEventArgs*>
     ToastDismissedHandler;
+typedef winfoundtn::ITypedEventHandler<
+    winui::Notifications::ToastNotification*,
+    winui::Notifications::ToastFailedEventArgs*>
+    ToastFailedHandler;
 
 // Templated wrapper for winfoundtn::GetActivationFactory().
 template <unsigned int size, typename T>
@@ -272,6 +276,15 @@
       return;
     }
 
+    auto failed_handler = mswr::Callback<ToastFailedHandler>(
+        this, &NotificationPlatformBridgeWinImpl::OnFailed);
+    EventRegistrationToken failed_token;
+    hr = toast->add_Failed(failed_handler.Get(), &failed_token);
+    if (FAILED(hr)) {
+      LOG(ERROR) << "Unable to add toast failed event handler";
+      return;
+    }
+
     hr = notifier_->Show(toast.Get());
     if (FAILED(hr))
       LOG(ERROR) << "Unable to display the notification";
@@ -430,6 +443,14 @@
     return S_OK;
   }
 
+  HRESULT OnFailed(
+      winui::Notifications::IToastNotification* notification,
+      winui::Notifications::IToastFailedEventArgs* /* arguments */) {
+    // TODO(chengx): Investigate what the correct behavior should be here and
+    // implement it.
+    return S_OK;
+  }
+
   HRESULT InitializeToastNotifier() {
     mswr::ComPtr<winui::Notifications::IToastNotificationManagerStatics>
         toast_manager;
diff --git a/chrome/browser/resources/pdf/pdf.js b/chrome/browser/resources/pdf/pdf.js
index 5544dec..80ec971 100644
--- a/chrome/browser/resources/pdf/pdf.js
+++ b/chrome/browser/resources/pdf/pdf.js
@@ -227,7 +227,7 @@
   document.body.addEventListener('change-page', e => {
     this.viewport_.goToPage(e.detail.page);
     if (e.detail.origin == 'bookmark')
-      this.metrics.onBookmarkFollowed();
+      this.metrics.onFollowBookmark();
     else if (e.detail.origin == 'pageselector')
       this.metrics.onPageSelectorNavigation();
   });
diff --git a/chrome/browser/sync/sessions/browser_list_router_helper.cc b/chrome/browser/sync/sessions/browser_list_router_helper.cc
index 472ef4e..1f42b27 100644
--- a/chrome/browser/sync/sessions/browser_list_router_helper.cc
+++ b/chrome/browser/sync/sessions/browser_list_router_helper.cc
@@ -53,8 +53,19 @@
                                             int index,
                                             bool foreground) {
   if (web_contents && Profile::FromBrowserContext(
-                          web_contents->GetBrowserContext()) == profile_)
+                          web_contents->GetBrowserContext()) == profile_) {
     router_->NotifyTabModified(web_contents, false);
+  }
+}
+
+void BrowserListRouterHelper::TabReplacedAt(TabStripModel* tab_strip_model,
+                                            content::WebContents* old_contents,
+                                            content::WebContents* new_contents,
+                                            int index) {
+  if (new_contents && Profile::FromBrowserContext(
+                          new_contents->GetBrowserContext()) == profile_) {
+    router_->NotifyTabModified(new_contents, false);
+  }
 }
 
 }  // namespace sync_sessions
diff --git a/chrome/browser/sync/sessions/browser_list_router_helper.h b/chrome/browser/sync/sessions/browser_list_router_helper.h
index 9f528ef..93de5b3 100644
--- a/chrome/browser/sync/sessions/browser_list_router_helper.h
+++ b/chrome/browser/sync/sessions/browser_list_router_helper.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_SYNC_SESSIONS_BROWSER_LIST_ROUTER_HELPER_H_
 #define CHROME_BROWSER_SYNC_SESSIONS_BROWSER_LIST_ROUTER_HELPER_H_
 
+#include <set>
+
 #include "chrome/browser/sync/sessions/sync_sessions_web_contents_router.h"
 #include "chrome/browser/ui/browser_list_observer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
@@ -31,6 +33,10 @@
                      content::WebContents* web_contents,
                      int index,
                      bool foreground) override;
+  void TabReplacedAt(TabStripModel* tab_strip_model,
+                     content::WebContents* old_contents,
+                     content::WebContents* new_contents,
+                     int index) override;
 
   // |router_| owns |this|.
   SyncSessionsWebContentsRouter* router_;
diff --git a/chrome/browser/sync/sessions/browser_list_router_helper_unittest.cc b/chrome/browser/sync/sessions/browser_list_router_helper_unittest.cc
index f142765..f366640 100644
--- a/chrome/browser/sync/sessions/browser_list_router_helper_unittest.cc
+++ b/chrome/browser/sync/sessions/browser_list_router_helper_unittest.cc
@@ -4,9 +4,15 @@
 
 #include "chrome/browser/sync/sessions/browser_list_router_helper.h"
 
+#include <memory>
+#include <vector>
+
 #include "base/stl_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/resource_coordinator/tab_manager.h"
 #include "chrome/browser/sync/sessions/sync_sessions_web_contents_router.h"
 #include "chrome/browser/sync/sessions/sync_sessions_web_contents_router_factory.h"
+#include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
@@ -20,15 +26,18 @@
   void OnLocalTabModified(SyncedTabDelegate* modified_tab) override {
     seen_urls_.push_back(modified_tab->GetVirtualURLAtIndex(
         modified_tab->GetCurrentEntryIndex()));
+    seen_ids_.push_back(modified_tab->GetSessionId());
   }
 
   void OnFaviconsChanged(const std::set<GURL>& page_urls,
                          const GURL& icon_url) override {}
 
   std::vector<GURL>* seen_urls() { return &seen_urls_; }
+  std::vector<SessionID::id_type>* seen_ids() { return &seen_ids_; }
 
  private:
   std::vector<GURL> seen_urls_;
+  std::vector<SessionID::id_type> seen_ids_;
 };
 
 class BrowserListRouterHelperTest : public BrowserWithTestWindowTest {
@@ -48,15 +57,12 @@
   std::unique_ptr<Browser> browser_2(
       CreateBrowser(profile_2, browser()->type(), false, window_2.get()));
 
-  SyncSessionsWebContentsRouter* router_1 =
-      SyncSessionsWebContentsRouterFactory::GetInstance()->GetForProfile(
-          profile_1);
-  SyncSessionsWebContentsRouter* router_2 =
-      SyncSessionsWebContentsRouterFactory::GetInstance()->GetForProfile(
-          profile_2);
-
-  router_1->StartRoutingTo(&handler_1);
-  router_2->StartRoutingTo(&handler_2);
+  SyncSessionsWebContentsRouterFactory::GetInstance()
+      ->GetForProfile(profile_1)
+      ->StartRoutingTo(&handler_1);
+  SyncSessionsWebContentsRouterFactory::GetInstance()
+      ->GetForProfile(profile_2)
+      ->StartRoutingTo(&handler_2);
 
   GURL gurl_1("http://foo1.com");
   GURL gurl_2("http://foo2.com");
@@ -100,4 +106,61 @@
   new_browser_in_second_profile->tab_strip_model()->CloseAllTabs();
 }
 
+// Added when fixing https://crbug.com/777745, ensure tab discards are observed.
+TEST_F(BrowserListRouterHelperTest, NotifyOnDiscardTab) {
+  TestingProfile* profile_1 = profile();
+  TestingProfile* profile_2 =
+      profile_manager()->CreateTestingProfile("testing_profile2");
+
+  std::unique_ptr<BrowserWindow> window_2(CreateBrowserWindow());
+  std::unique_ptr<Browser> browser_2(
+      CreateBrowser(profile_2, browser()->type(), false, window_2.get()));
+
+  SyncSessionsWebContentsRouterFactory::GetInstance()
+      ->GetForProfile(profile_1)
+      ->StartRoutingTo(&handler_1);
+  SyncSessionsWebContentsRouterFactory::GetInstance()
+      ->GetForProfile(profile_2)
+      ->StartRoutingTo(&handler_2);
+
+  GURL gurl_1("http://foo1.com");
+  AddTab(browser(), gurl_1);
+
+  // Tab needs to have been active to be found when discarding.
+  BrowserList::GetInstance()->SetLastActive(browser());
+
+  EXPECT_EQ(gurl_1, *handler_1.seen_urls()->rbegin());
+  SessionID::id_type old_id = *handler_1.seen_ids()->rbegin();
+
+  // Remove previous any observations from setup to make checking expectations
+  // easier below.
+  handler_1.seen_urls()->clear();
+  handler_1.seen_ids()->clear();
+
+  g_browser_process->GetTabManager()->DiscardTabByExtension(
+      browser()->tab_strip_model()->GetWebContentsAt(0));
+
+  // We're typically notified twice while discarding tabs. Once for the
+  // destruction of the old web contents, and once for the new. This test case
+  // is really trying to make sure the TabReplacedAt() method is called, which
+  // is going to be invoked for the new web contents. We can tell it is the new
+  // one by finding |gurl_1| for an id that is not |old_id|.
+  bool found_new_id = false;
+  for (size_t i = 0; i < handler_1.seen_ids()->size(); ++i) {
+    if (handler_1.seen_ids()->at(i) != old_id &&
+        handler_1.seen_urls()->at(i) == gurl_1) {
+      found_new_id = true;
+      break;
+    }
+  }
+  EXPECT_TRUE(found_new_id);
+
+  // And of course |profile_2| shouldn't have seen anything.
+  EXPECT_EQ(0U, handler_2.seen_urls()->size());
+
+  // Cleanup needed for manually created browsers so they don't complain about
+  // having open tabs when destructing.
+  browser_2->tab_strip_model()->CloseAllTabs();
+}
+
 }  // namespace sync_sessions
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc
index 983bf56..2452ef3 100644
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc
+++ b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos_unittest.cc
@@ -31,6 +31,8 @@
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/ui/ash/chrome_new_window_client.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
@@ -308,6 +310,8 @@
 };
 
 void MultiUserWindowManagerChromeOSTest::SetUp() {
+  chromeos::DeviceSettingsService::Initialize();
+  chromeos::CrosSettings::Initialize();
   ash_test_helper()->set_test_shell_delegate(new TestShellDelegateChromeOS);
   ash::AshTestEnvironmentContent* test_environment =
       static_cast<ash::AshTestEnvironmentContent*>(
@@ -354,6 +358,8 @@
   chromeos::WallpaperManager::Shutdown();
   wallpaper_controller_client_.reset();
   profile_manager_.reset();
+  chromeos::CrosSettings::Shutdown();
+  chromeos::DeviceSettingsService::Shutdown();
 }
 
 std::string MultiUserWindowManagerChromeOSTest::GetStatus() {
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.cc b/chrome/browser/ui/ash/test_wallpaper_controller.cc
index 50a901c..018123a 100644
--- a/chrome/browser/ui/ash/test_wallpaper_controller.cc
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.cc
@@ -19,11 +19,12 @@
   return ptr;
 }
 
-void TestWallpaperController::SetClientAndPaths(
+void TestWallpaperController::Init(
     ash::mojom::WallpaperControllerClientPtr client,
     const base::FilePath& user_data_path,
     const base::FilePath& chromeos_wallpapers_path,
-    const base::FilePath& chromeos_custom_wallpapers_path) {
+    const base::FilePath& chromeos_custom_wallpapers_path,
+    bool is_device_wallpaper_policy_enforced) {
   was_client_set_ = true;
 }
 
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.h b/chrome/browser/ui/ash/test_wallpaper_controller.h
index 77bb6d2..5297f698 100644
--- a/chrome/browser/ui/ash/test_wallpaper_controller.h
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.h
@@ -32,11 +32,11 @@
   ash::mojom::WallpaperControllerPtr CreateInterfacePtr();
 
   // ash::mojom::WallpaperController:
-  void SetClientAndPaths(
-      ash::mojom::WallpaperControllerClientPtr client,
-      const base::FilePath& user_data_path,
-      const base::FilePath& chromeos_wallpapers_path,
-      const base::FilePath& chromeos_custom_wallpapers_path) override;
+  void Init(ash::mojom::WallpaperControllerClientPtr client,
+            const base::FilePath& user_data_path,
+            const base::FilePath& chromeos_wallpapers_path,
+            const base::FilePath& chromeos_custom_wallpapers_path,
+            bool is_device_wallpaper_policy_enforced) override;
   void SetCustomWallpaper(ash::mojom::WallpaperUserInfoPtr user_info,
                           const std::string& wallpaper_files_id,
                           const std::string& file_name,
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.cc b/chrome/browser/ui/ash/wallpaper_controller_client.cc
index 713b68fe..34bf1ce 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.cc
@@ -86,7 +86,8 @@
 
 }  // namespace
 
-WallpaperControllerClient::WallpaperControllerClient() : binding_(this) {
+WallpaperControllerClient::WallpaperControllerClient()
+    : policy_handler_(this), binding_(this) {
   DCHECK(!g_instance);
   g_instance = this;
 }
@@ -101,8 +102,6 @@
       ->GetConnector()
       ->BindInterface(ash::mojom::kServiceName, &wallpaper_controller_);
   BindAndSetClient();
-  // TODO(xdai): Get current device policy enforced flag from
-  // WallpaperPolicyHandler::IsDeviceWallpaperPolicyEnforced() and set here.
 }
 
 void WallpaperControllerClient::InitForTesting(
@@ -257,7 +256,8 @@
   ash::WallpaperController::dir_chrome_os_custom_wallpapers_path_ =
       chromeos_custom_wallpapers_path;
 
-  wallpaper_controller_->SetClientAndPaths(std::move(client), user_data_path,
-                                           chromeos_wallpapers_path,
-                                           chromeos_custom_wallpapers_path);
+  wallpaper_controller_->Init(
+      std::move(client), user_data_path, chromeos_wallpapers_path,
+      chromeos_custom_wallpapers_path,
+      policy_handler_.IsDeviceWallpaperPolicyEnforced());
 }
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.h b/chrome/browser/ui/ash/wallpaper_controller_client.h
index da17c0c..9c72ec48 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client.h
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.h
@@ -77,6 +77,8 @@
   // WallpaperController interface in ash.
   ash::mojom::WallpaperControllerPtr wallpaper_controller_;
 
+  WallpaperPolicyHandler policy_handler_;
+
   // Binds to the client interface.
   mojo::Binding<ash::mojom::WallpaperControllerClient> binding_;
 
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_unittest.cc b/chrome/browser/ui/ash/wallpaper_controller_client_unittest.cc
index 4c990ae3..3145497 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_unittest.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_unittest.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 
 #include "base/test/scoped_task_environment.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/ui/ash/test_wallpaper_controller.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -15,6 +17,12 @@
   WallpaperControllerClientTest() = default;
   ~WallpaperControllerClientTest() override = default;
 
+  void SetUp() override {
+    testing::Test::SetUp();
+    chromeos::DeviceSettingsService::Initialize();
+    chromeos::CrosSettings::Initialize();
+  }
+
  private:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 
@@ -34,4 +42,4 @@
   EXPECT_TRUE(controller.was_client_set());
 }
 
-}  // namespace
\ No newline at end of file
+}  // namespace
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.mm
index b91622f..d4e75f9 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.mm
@@ -88,6 +88,11 @@
 @synthesize startingChildIndex = startingChildIndex_;
 @synthesize drawFolderArrow = drawFolderArrow_;
 
+// Overridden from GradientButtonCell.
++ (CGFloat)insetInView:(NSView*)view {
+  return 0;
+}
+
 + (id)buttonCellForNode:(const BookmarkNode*)node
                    text:(NSString*)text
                   image:(NSImage*)image
diff --git a/chrome/browser/ui/cocoa/framed_browser_window.mm b/chrome/browser/ui/cocoa/framed_browser_window.mm
index 7b18d090..ac46eae5 100644
--- a/chrome/browser/ui/cocoa/framed_browser_window.mm
+++ b/chrome/browser/ui/cocoa/framed_browser_window.mm
@@ -34,8 +34,7 @@
 @implementation FramedBrowserWindow
 
 + (CGFloat)browserFrameViewPaintHeight {
-  return chrome::ShouldUseFullSizeContentView() ? chrome::kTabStripHeight
-                                                : 60.0;
+  return chrome::kTabStripHeight;
 }
 
 + (NSUInteger)defaultStyleMask {
diff --git a/chrome/browser/ui/cocoa/tabs/tab_window_controller.mm b/chrome/browser/ui/cocoa/tabs/tab_window_controller.mm
index 0d8474f3..8be82134 100644
--- a/chrome/browser/ui/cocoa/tabs/tab_window_controller.mm
+++ b/chrome/browser/ui/cocoa/tabs/tab_window_controller.mm
@@ -134,24 +134,21 @@
     if (hasTabStrip)
       [windowView addSubview:tabStripView_];
 
-    if (chrome::ShouldUseFullSizeContentView()) {
-      // |windowWillEnterFullScreen:| and |windowWillExitFullScreen:| are
-      // already called because self is a delegate for the window. However this
-      // class is designed for subclassing and can not implement
-      // NSWindowDelegate methods (because subclasses can do so as well and they
-      // should be able to). TODO(crbug.com/654656): Move |visualEffectView_| to
-      // subclass.
-      [[NSNotificationCenter defaultCenter]
-          addObserver:self
-             selector:@selector(windowWillEnterFullScreenNotification:)
-                 name:NSWindowWillEnterFullScreenNotification
-               object:window];
-      [[NSNotificationCenter defaultCenter]
-          addObserver:self
-             selector:@selector(windowWillExitFullScreenNotification:)
-                 name:NSWindowWillExitFullScreenNotification
-               object:window];
-    }
+    // |windowWillEnterFullScreen:| and |windowWillExitFullScreen:| are already
+    // called because self is a delegate for the window. However this class is
+    // designed for subclassing and can not implement NSWindowDelegate methods
+    // (because subclasses can do so as well and they should be able to).
+    // TODO(crbug.com/654656): Move |visualEffectView_| to subclass.
+    [[NSNotificationCenter defaultCenter]
+        addObserver:self
+           selector:@selector(windowWillEnterFullScreenNotification:)
+               name:NSWindowWillEnterFullScreenNotification
+             object:window];
+    [[NSNotificationCenter defaultCenter]
+        addObserver:self
+           selector:@selector(windowWillExitFullScreenNotification:)
+               name:NSWindowWillExitFullScreenNotification
+             object:window];
   }
   return self;
 }
@@ -460,18 +457,14 @@
 
     [visualEffectWrapperView addSubview:visualEffectView_];
 
-    [[window contentView] addSubview:visualEffectWrapperView
-                          positioned:NSWindowBelow
-                          relativeTo:nil];
+    [[window contentView] addSubview:visualEffectWrapperView];
 
     // Make the |tabStripBackgroundView_| a child of the NSVisualEffectView.
     [tabStripBackgroundView_ setFrame:[visualEffectView_ bounds]];
     [visualEffectView_ addSubview:tabStripBackgroundView_];
   } else {
     DCHECK(!chrome::ShouldUseFullSizeContentView());
-    [[window contentView] addSubview:tabStripBackgroundView_
-                          positioned:NSWindowBelow
-                          relativeTo:nil];
+    [[window contentView] addSubview:tabStripBackgroundView_];
   }
 }
 
@@ -482,11 +475,13 @@
 }
 
 - (void)windowWillEnterFullScreenNotification:(NSNotification*)notification {
-  [[visualEffectView_ animator] setAlphaValue:0.0];
+  [(visualEffectView_ ? visualEffectView_.get()
+                      : tabStripBackgroundView_.get()) setHidden:YES];
 }
 
 - (void)windowWillExitFullScreenNotification:(NSNotification*)notification {
-  [[visualEffectView_ animator] setAlphaValue:1.0];
+  [(visualEffectView_ ? visualEffectView_.get()
+                      : tabStripBackgroundView_.get()) setHidden:NO];
 }
 
 @end
diff --git a/chrome/browser/ui/tabs/tab_utils.cc b/chrome/browser/ui/tabs/tab_utils.cc
index b0bd2e8..616f0324 100644
--- a/chrome/browser/ui/tabs/tab_utils.cc
+++ b/chrome/browser/ui/tabs/tab_utils.cc
@@ -452,6 +452,13 @@
 
 bool IsSiteMuted(const TabStripModel& tab_strip, const int index) {
   content::WebContents* web_contents = tab_strip.GetWebContentsAt(index);
+
+  // TODO(steimel): Why was this not a problem for AreAllTabsMuted? Is this
+  // going to be a problem for SetSitesMuted?
+  // Prevent crashes with null WebContents (https://crbug.com/797647).
+  if (!web_contents)
+    return false;
+
   GURL url = web_contents->GetLastCommittedURL();
 
   // chrome:// URLs don't have content settings but can be muted, so just check
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index a1314ec..fee3a45 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -455,7 +455,7 @@
 
   // Regular results.
   if (base::FeatureList::IsEnabled(omnibox::kUIExperimentVerticalLayout)) {
-    // For no description, shift down halfways to draw contents in middle.
+    // For no description, shift down halfway to draw contents in middle.
     if (description_max_width == 0)
       y += GetTextHeight() / 2;
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views_browsertest.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views_browsertest.cc
index 24f1056..3f1b10c 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views_browsertest.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views_browsertest.cc
@@ -432,7 +432,7 @@
 
   // Execute a command and check if it deactivate touch editing. Paste & Go is
   // chosen since it is specific to Omnibox and its execution wouldn't be
-  // delgated to the base Textfield class.
+  // delegated to the base Textfield class.
   omnibox_view_views->ExecuteCommand(IDS_PASTE_AND_GO, ui::EF_NONE);
   EXPECT_FALSE(textfield_test_api.touch_selection_controller());
 }
diff --git a/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc b/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc
index 148f3d0..60feffe 100644
--- a/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc
+++ b/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc
@@ -286,6 +286,9 @@
 
 void CvcUnmaskViewController::ButtonPressed(views::Button* sender,
                                             const ui::Event& event) {
+  if (!dialog()->IsInteractive())
+    return;
+
   switch (sender->tag()) {
     case static_cast<int>(Tags::CONFIRM_TAG):
       CvcConfirmed();
@@ -396,6 +399,9 @@
 }
 
 void CvcUnmaskViewController::OnPerformAction(views::Combobox* combobox) {
+  if (!dialog()->IsInteractive())
+    return;
+
   UpdatePayButtonState();
 }
 
diff --git a/chrome/browser/ui/views/payments/payment_method_view_controller.cc b/chrome/browser/ui/views/payments/payment_method_view_controller.cc
index d8aa1c7..c5cc5ae 100644
--- a/chrome/browser/ui/views/payments/payment_method_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_method_view_controller.cc
@@ -229,6 +229,9 @@
 
 std::unique_ptr<views::View>
 PaymentMethodViewController::CreateExtraFooterView() {
+  if (!spec()->supports_basic_card())
+    return nullptr;
+
   auto extra_view = base::MakeUnique<views::View>();
 
   extra_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
diff --git a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
index a68b20e..58d79e0 100644
--- a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
@@ -166,6 +166,10 @@
     observer_for_testing_->OnProcessingSpinnerShown();
 }
 
+bool PaymentRequestDialogView::IsInteractive() const {
+  return !throbber_overlay_.visible();
+}
+
 void PaymentRequestDialogView::OnStartUpdating(
     PaymentRequestSpec::UpdateReason reason) {
   ShowProcessingSpinner();
diff --git a/chrome/browser/ui/views/payments/payment_request_dialog_view.h b/chrome/browser/ui/views/payments/payment_request_dialog_view.h
index 74983c7..2c1d074 100644
--- a/chrome/browser/ui/views/payments/payment_request_dialog_view.h
+++ b/chrome/browser/ui/views/payments/payment_request_dialog_view.h
@@ -109,6 +109,7 @@
   void CloseDialog() override;
   void ShowErrorMessage() override;
   void ShowProcessingSpinner() override;
+  bool IsInteractive() const override;
 
   // PaymentRequestSpec::Observer:
   void OnStartUpdating(PaymentRequestSpec::UpdateReason reason) override;
diff --git a/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc b/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc
index a609b34..af826ced 100644
--- a/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc
@@ -321,6 +321,9 @@
 
 void PaymentRequestSheetController::ButtonPressed(views::Button* sender,
                                                   const ui::Event& event) {
+  if (!dialog()->IsInteractive())
+    return;
+
   switch (static_cast<PaymentRequestCommonTags>(sender->tag())) {
     case PaymentRequestCommonTags::CLOSE_BUTTON_TAG:
       dialog()->CloseDialog();
@@ -349,11 +352,11 @@
       std::make_unique<views::GridLayout>(container.get()));
 
   views::ColumnSet* columns = layout->AddColumnSet(0);
-  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
-                     0, views::GridLayout::USE_PREF, 0, 0);
+  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
+                     views::GridLayout::USE_PREF, 0, 0);
   columns->AddPaddingColumn(1, 0);
-  columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
-                     0, views::GridLayout::USE_PREF, 0, 0);
+  columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
+                     views::GridLayout::USE_PREF, 0, 0);
 
   layout->StartRow(0, 0);
   std::unique_ptr<views::View> extra_view = CreateExtraFooterView();
@@ -397,6 +400,10 @@
 }
 
 bool PaymentRequestSheetController::PerformPrimaryButtonAction() {
+  // Return "true" to prevent other views from handling the event.
+  if (!dialog()->IsInteractive())
+    return true;
+
   if (primary_button_ && primary_button_->enabled())
     ButtonPressed(primary_button_.get(), DummyEvent());
   return true;
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
index 89d198c..51a23fe 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
@@ -474,6 +474,9 @@
 
 void PaymentSheetViewController::ButtonPressed(views::Button* sender,
                                                const ui::Event& event) {
+  if (!dialog()->IsInteractive())
+    return;
+
   switch (sender->tag()) {
     case static_cast<int>(
         PaymentSheetViewControllerTags::SHOW_ORDER_SUMMARY_BUTTON):
@@ -542,6 +545,9 @@
     views::StyledLabel* label,
     const gfx::Range& range,
     int event_flags) {
+  if (!dialog()->IsInteractive())
+    return;
+
   // The only thing that can trigger this is the user clicking on the "settings"
   // link in the data attribution text.
   chrome::ShowSettingsSubPageForProfile(dialog()->GetProfile(),
diff --git a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc
index 492913c..aaca6cae 100644
--- a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc
@@ -707,13 +707,16 @@
   login::SigninPartitionManager* signin_partition_manager =
       login::SigninPartitionManager::Factory::GetForBrowserContext(
           Profile::FromWebUI(web_ui()));
-  signin_partition_manager->StartSigninSession(web_ui()->GetWebContents());
+  signin_partition_manager->StartSigninSession(
+      web_ui()->GetWebContents(),
+      base::BindOnce(&EnrollmentScreenHandler::DoShowWithPartition,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
 
-  // Then leave it running forever.
+void EnrollmentScreenHandler::DoShowWithPartition(
+    const std::string& partition_name) {
   base::DictionaryValue screen_data;
-  screen_data.SetString(
-      "webviewPartitionName",
-      signin_partition_manager->GetCurrentStoragePartitionName());
+  screen_data.SetString("webviewPartitionName", partition_name);
   screen_data.SetString("gaiaUrl", GaiaUrls::GetInstance()->gaia_url().spec());
   screen_data.SetString("clientId",
                         GaiaUrls::GetInstance()->oauth2_chrome_client_id());
diff --git a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h
index 23b7d798..e97c73e 100644
--- a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h
@@ -118,6 +118,9 @@
   // Shows the screen.
   void DoShow();
 
+  // Shows the screen.
+  void DoShowWithPartition(const std::string& partition_name);
+
   // Returns true if current visible screen is the enrollment sign-in page.
   bool IsOnEnrollmentScreen() const;
 
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index a27ea28..f9d8266 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -300,31 +300,39 @@
 }
 
 void GaiaScreenHandler::LoadGaia(const GaiaContext& context) {
+  // Start a new session with SigninPartitionManager, generating a unique
+  // StoragePartition.
+  login::SigninPartitionManager* signin_partition_manager =
+      login::SigninPartitionManager::Factory::GetForBrowserContext(
+          Profile::FromWebUI(web_ui()));
+  signin_partition_manager->StartSigninSession(
+      web_ui()->GetWebContents(),
+      base::BindOnce(&GaiaScreenHandler::LoadGaiaWithPartition,
+                     weak_factory_.GetWeakPtr(), context));
+}
+
+void GaiaScreenHandler::LoadGaiaWithPartition(
+    const GaiaContext& context,
+    const std::string& partition_name) {
   std::unique_ptr<std::string> version = std::make_unique<std::string>();
   std::unique_ptr<bool> consent = std::make_unique<bool>();
   base::OnceClosure get_version_and_consent =
       base::BindOnce(&GetVersionAndConsent, base::Unretained(version.get()),
                      base::Unretained(consent.get()));
   base::OnceClosure load_gaia = base::BindOnce(
-      &GaiaScreenHandler::LoadGaiaWithVersionAndConsent,
-      weak_factory_.GetWeakPtr(), context, base::Owned(version.release()),
-      base::Owned(consent.release()));
+      &GaiaScreenHandler::LoadGaiaWithPartitionAndVersionAndConsent,
+      weak_factory_.GetWeakPtr(), context, partition_name,
+      base::Owned(version.release()), base::Owned(consent.release()));
   base::PostTaskWithTraitsAndReply(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
       std::move(get_version_and_consent), std::move(load_gaia));
 }
 
-void GaiaScreenHandler::LoadGaiaWithVersionAndConsent(
+void GaiaScreenHandler::LoadGaiaWithPartitionAndVersionAndConsent(
     const GaiaContext& context,
+    const std::string& partition_name,
     const std::string* platform_version,
     const bool* collect_stats_consent) {
-  // Start a new session with SigninPartitionManager, generating a a unique
-  // StoragePartition.
-  login::SigninPartitionManager* signin_partition_manager =
-      login::SigninPartitionManager::Factory::GetForBrowserContext(
-          Profile::FromWebUI(web_ui()));
-  signin_partition_manager->StartSigninSession(web_ui()->GetWebContents());
-
   base::DictionaryValue params;
 
   params.SetBoolean("forceReload", context.force_reload);
@@ -400,8 +408,8 @@
   // sending device statistics.
   if (*collect_stats_consent)
     params.SetString("lsbReleaseBoard", base::SysInfo::GetLsbReleaseBoard());
-  params.SetString("webviewPartitionName",
-                   signin_partition_manager->GetCurrentStoragePartitionName());
+
+  params.SetString("webviewPartitionName", partition_name);
 
   frame_state_ = FRAME_STATE_LOADING;
   CallJS("loadAuthExtension", params);
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
index 7e91022e..749c2de03 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
@@ -64,9 +64,16 @@
 
   // Callback that loads GAIA after version and stat consent information has
   // been retrieved.
-  void LoadGaiaWithVersionAndConsent(const GaiaContext& context,
-                                     const std::string* platform_version,
-                                     const bool* collect_stats_consent);
+  void LoadGaiaWithPartition(const GaiaContext& context,
+                             const std::string& partition_name);
+
+  // Callback that loads GAIA after version and stat consent information has
+  // been retrieved.
+  void LoadGaiaWithPartitionAndVersionAndConsent(
+      const GaiaContext& context,
+      const std::string& partition_name,
+      const std::string* platform_version,
+      const bool* collect_stats_consent);
 
   // Sends request to reload Gaia. If |force_reload| is true, request
   // will be sent in any case, otherwise it will be sent only when Gaia is
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index af4dfdc..4f2118f 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -685,7 +685,8 @@
   // Skip "update" notification about OFFLINE state from
   // NetworkStateInformer if previous notification already was
   // delayed.
-  if ((state == NetworkStateInformer::OFFLINE || has_pending_auth_ui_) &&
+  if ((state == NetworkStateInformer::OFFLINE ||
+       network_state_ignored_until_proxy_auth_) &&
       !force_update && !update_state_closure_.IsCancelled()) {
     return;
   }
@@ -693,7 +694,7 @@
   update_state_closure_.Cancel();
 
   if ((state == NetworkStateInformer::OFFLINE && !force_update) ||
-      has_pending_auth_ui_) {
+      network_state_ignored_until_proxy_auth_) {
     update_state_closure_.Reset(
         base::Bind(&SigninScreenHandler::UpdateStateInternal,
                    weak_factory_.GetWeakPtr(),
@@ -789,7 +790,6 @@
 
   if (is_gaia_loading_timeout) {
     LOG(WARNING) << "Retry frame load due to loading timeout.";
-    LOG(ERROR) << "UpdateStateInternal reload 4";
     reload_gaia.ScheduleCall();
   }
 
@@ -1082,20 +1082,33 @@
                                   const content::NotificationDetails& details) {
   switch (type) {
     case chrome::NOTIFICATION_AUTH_NEEDED: {
-      has_pending_auth_ui_ = true;
+      network_state_ignored_until_proxy_auth_ = true;
       break;
     }
-    case chrome::NOTIFICATION_AUTH_SUPPLIED:
-      has_pending_auth_ui_ = false;
-      // Reload auth extension as proxy credentials are supplied.
-      if (!IsSigninScreenHiddenByError() && ui_state_ == UI_STATE_GAIA_SIGNIN)
-        ReloadGaia(true);
-      update_state_closure_.Cancel();
+    case chrome::NOTIFICATION_AUTH_SUPPLIED: {
+      if (IsGaiaHiddenByError()) {
+        // Start listening to network state notifications immediately, hoping
+        // that the network will switch to ONLINE soon.
+        update_state_closure_.Cancel();
+        ReenableNetworkStateUpdatesAfterProxyAuth();
+      } else {
+        // Gaia is not hidden behind an error yet. Discard last cached network
+        // state notification and wait for |kOfflineTimeoutSec| before
+        // considering network update notifications again (hoping the network
+        // will become ONLINE by then).
+        update_state_closure_.Cancel();
+        base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+            FROM_HERE,
+            base::BindOnce(
+                &SigninScreenHandler::ReenableNetworkStateUpdatesAfterProxyAuth,
+                weak_factory_.GetWeakPtr()),
+            base::TimeDelta::FromSeconds(kOfflineTimeoutSec));
+      }
       break;
+    }
     case chrome::NOTIFICATION_AUTH_CANCELLED: {
-      // Don't reload auth extension if proxy auth dialog was cancelled.
-      has_pending_auth_ui_ = false;
       update_state_closure_.Cancel();
+      ReenableNetworkStateUpdatesAfterProxyAuth();
       break;
     }
     default:
@@ -1103,6 +1116,10 @@
   }
 }
 
+void SigninScreenHandler::ReenableNetworkStateUpdatesAfterProxyAuth() {
+  network_state_ignored_until_proxy_auth_ = false;
+}
+
 void SigninScreenHandler::SuspendDone(const base::TimeDelta& sleep_duration) {
   for (user_manager::User* user :
        user_manager::UserManager::Get()->GetUnlockUsers()) {
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
index 680ab51..d63b0652f 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -483,6 +483,10 @@
   // Called when the cros property controlling allowed input methods changes.
   void OnAllowedInputMethodsChanged();
 
+  // After proxy auth information has been supplied, this function re-enables
+  // responding to network state notifications.
+  void ReenableNetworkStateUpdatesAfterProxyAuth();
+
   // Current UI state of the signin screen.
   UIState ui_state_ = UI_STATE_UNKNOWN;
 
@@ -522,10 +526,10 @@
   std::unique_ptr<CrosSettings::ObserverSubscription>
       allowed_input_methods_subscription_;
 
-  // Whether there is an auth UI pending. This flag is set on receiving
-  // NOTIFICATION_AUTH_NEEDED and reset on either NOTIFICATION_AUTH_SUPPLIED or
-  // NOTIFICATION_AUTH_CANCELLED.
-  bool has_pending_auth_ui_ = false;
+  // Whether we're currently ignoring network state updates because a proxy auth
+  // UI pending (or we're waiting for a grace period after the proxy auth UI is
+  // finished for the network to switch into the ONLINE state).
+  bool network_state_ignored_until_proxy_auth_ = false;
 
   // Used for pending GAIA reloads.
   NetworkError::ErrorReason gaia_reload_reason_ =
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc b/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc
index 7a3af9af..f80653a 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_userlist_unittest.cc
@@ -13,6 +13,8 @@
 #include "chrome/browser/chromeos/login/users/multi_profile_user_controller.h"
 #include "chrome/browser/chromeos/login/users/multi_profile_user_controller_delegate.h"
 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/ui/ash/test_wallpaper_controller.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -67,6 +69,8 @@
     fake_user_manager_->set_owner_id(AccountId::FromUserEmail(kOwner));
 
     chromeos::WallpaperManager::Initialize();
+    chromeos::DeviceSettingsService::Initialize();
+    chromeos::CrosSettings::Initialize();
     wallpaper_controller_client_ =
         std::make_unique<WallpaperControllerClient>();
     wallpaper_controller_client_->InitForTesting(
diff --git a/chrome/browser/vr/test/vr_browser_tests.md b/chrome/browser/vr/test/vr_browser_tests.md
new file mode 100644
index 0000000..9f4d1d7
--- /dev/null
+++ b/chrome/browser/vr/test/vr_browser_tests.md
@@ -0,0 +1,42 @@
+# VR Browser Tests
+
+## Introduction
+
+This documentation concerns `vr_browser_test.h`, `vr_browser_test.cc`, and files
+that use them.
+
+These files port the framework used by VR instrumentation tests (located in
+`//chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/` and
+documented in
+`//chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/*.md`) for
+use in browser tests in order to test VR features on desktop platforms.
+
+This is pretty much a direct port, with the same JavaScript/HTML files being
+used for both and the Java/C++ code being functionally equivalent to each other,
+so the instrumentation test's documentation on writing tests using the framework
+is applicable here, too. As such, this documentation covers any notable
+differences between the two implementations.
+
+## Restrictions
+
+Both the instrumentation tests and browser tests have hardware/software
+restrictions - in the case of browser tests, VR is only supported on Windows 8
+and later (or Windows 7 with a non-standard patch applied) with a GPU that
+supports DirectX 11.1.
+
+Instrumentation tests handle restrictions with the `@Restriction` annotation,
+but browser tests don't have any equivalent functionality. Instead, test names
+should be wrapped in the REQUIRES_GPU macro defined in `vr_browser_test.h`,
+which simply disables the test by default. We then explicitly run tests that
+inherit from `VrBrowserTest` and enable the running of disabled tests on bots
+that meet the requirements.
+
+## Command Line Switches
+
+Instrumentation tests are able to add and remove command line switches on a
+per-test-case basis using `@CommandLine` annotations, but equivalent
+functionality does not exist in browser tests.
+
+Instead, if different command line flags are needed, a new class will need to
+be created that extends `VrBrowserTest` and overrides the flags that are set
+in its `SetUp` function.
\ No newline at end of file
diff --git a/chrome/browser/vr/testapp/vr_test_context.cc b/chrome/browser/vr/testapp/vr_test_context.cc
index f4a7006..aa720a71 100644
--- a/chrome/browser/vr/testapp/vr_test_context.cc
+++ b/chrome/browser/vr/testapp/vr_test_context.cc
@@ -87,10 +87,7 @@
 
   model_ = ui_->model_for_test();
 
-  ToolbarState state(GURL("https://dangerous.com/dir/file.html"),
-                     security_state::SecurityLevel::HTTP_SHOW_WARNING,
-                     &toolbar::kHttpIcon, base::string16(), true, false);
-  ui_->SetToolbarState(state);
+  CycleOrigin();
   ui_->SetHistoryButtonsEnabled(true, true);
   ui_->SetLoading(true);
   ui_->SetLoadProgress(0.4);
@@ -180,6 +177,9 @@
       case ui::DomCode::US_E:
         model_->exiting_vr = !model_->exiting_vr;
         break;
+      case ui::DomCode::US_O:
+        CycleOrigin();
+        break;
       default:
         break;
     }
@@ -471,4 +471,28 @@
   ui_->SetOmniboxSuggestions(base::MakeUnique<OmniboxSuggestions>());
 }
 
+void VrTestContext::CycleOrigin() {
+  const std::vector<ToolbarState> states = {
+      {GURL("http://www.domain.com/path/segment/directory/file.html"),
+       security_state::SecurityLevel::HTTP_SHOW_WARNING, &toolbar::kHttpIcon,
+       base::string16(), true, false},
+      {GURL("http://www.domain.com/"),
+       security_state::SecurityLevel::HTTP_SHOW_WARNING, &toolbar::kHttpIcon,
+       base::string16(), true, false},
+      {GURL("https://www.domain.com/path/segment/directory/file.html"),
+       security_state::SecurityLevel::SECURE, &toolbar::kHttpsValidIcon,
+       base::UTF8ToUTF16("Secure"), true, false},
+      {GURL("https://www.domain.com/path/segment/directory/file.html"),
+       security_state::SecurityLevel::DANGEROUS, &toolbar::kHttpsValidIcon,
+       base::UTF8ToUTF16("Dangerous"), true, false},
+      {GURL("https://www.domain.com/path/segment/directory/file.html"),
+       security_state::SecurityLevel::HTTP_SHOW_WARNING,
+       &toolbar::kOfflinePinIcon, base::UTF8ToUTF16("Offline"), true, true},
+  };
+
+  static int state = 0;
+  ui_->SetToolbarState(states[state]);
+  state = (state + 1) % states.size();
+}
+
 }  // namespace vr
diff --git a/chrome/browser/vr/testapp/vr_test_context.h b/chrome/browser/vr/testapp/vr_test_context.h
index daf3040..b6fd1d11 100644
--- a/chrome/browser/vr/testapp/vr_test_context.h
+++ b/chrome/browser/vr/testapp/vr_test_context.h
@@ -61,6 +61,7 @@
   void CreateFakeTextInputOrCommit(bool commit);
   void CycleWebVrModes();
   void ToggleSplashScreen();
+  void CycleOrigin();
   gfx::Transform ProjectionMatrix() const;
   gfx::Transform ViewProjectionMatrix() const;
   ControllerModel UpdateController();
diff --git a/chrome/common/chrome_utility_printing_messages.h b/chrome/common/chrome_utility_printing_messages.h
index cb60927..33e3a1b 100644
--- a/chrome/common/chrome_utility_printing_messages.h
+++ b/chrome/common/chrome_utility_printing_messages.h
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "base/strings/string16.h"
-#include "build/build_config.h"
 #include "components/printing/common/printing_param_traits_macros.h"
 #include "ipc/ipc_message_macros.h"
 #include "ipc/ipc_param_traits.h"
@@ -17,28 +16,11 @@
 #include "printing/backend/print_backend.h"
 #include "printing/features/features.h"
 #include "printing/page_range.h"
-#include "printing/pdf_render_settings.h"
-#include "printing/pwg_raster_settings.h"
-
-#if defined(OS_WIN)
-#include <windows.h>
-#endif
 
 #define IPC_MESSAGE_START ChromeUtilityPrintingMsgStart
 
-// Preview and Cloud Print messages.
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
-IPC_ENUM_TRAITS_MAX_VALUE(printing::PdfRenderSettings::Mode,
-                          printing::PdfRenderSettings::Mode::LAST)
-
-IPC_STRUCT_TRAITS_BEGIN(printing::PdfRenderSettings)
-  IPC_STRUCT_TRAITS_MEMBER(area)
-  IPC_STRUCT_TRAITS_MEMBER(offsets)
-  IPC_STRUCT_TRAITS_MEMBER(dpi)
-  IPC_STRUCT_TRAITS_MEMBER(autorotate)
-  IPC_STRUCT_TRAITS_MEMBER(mode)
-IPC_STRUCT_TRAITS_END()
-
+// Preview and Cloud Print messages.
 IPC_STRUCT_TRAITS_BEGIN(printing::PrinterCapsAndDefaults)
   IPC_STRUCT_TRAITS_MEMBER(printer_capabilities)
   IPC_STRUCT_TRAITS_MEMBER(caps_mime_type)
@@ -87,32 +69,11 @@
 // sandbox. Returns result as printing::PrinterSemanticCapsAndDefaults.
 IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_GetPrinterSemanticCapsAndDefaults,
                      std::string /* printer name */)
-#endif  // ENABLE_PRINT_PREVIEW
-
-// Windows uses messages for printing even without preview. crbug.com/170859
-// Primary user of Windows without preview is CEF. crbug.com/417967
-#if BUILDFLAG(ENABLE_PRINTING) && defined(OS_WIN)
-// Tell the utility process to start rendering the given PDF into a metafile.
-// Utility process would be alive until
-// ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop message.
-IPC_MESSAGE_CONTROL2(ChromeUtilityMsg_RenderPDFPagesToMetafiles,
-                     IPC::PlatformFileForTransit /* input_file */,
-                     printing::PdfRenderSettings /* settings */)
-
-// Requests conversion of the next page.
-IPC_MESSAGE_CONTROL2(ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage,
-                     int /* page_number */,
-                     IPC::PlatformFileForTransit /* output_file */)
-
-// Requests utility process to stop conversion and exit.
-IPC_MESSAGE_CONTROL0(ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop)
-#endif  //  BUILDFLAG(ENABLE_PRINTING) && defined(OS_WIN)
 
 //------------------------------------------------------------------------------
 // Utility process host messages:
 // These are messages from the utility process to the browser.
 
-#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
 // Reply when the utility process has succeeded in obtaining the printer
 // capabilities and defaults.
 IPC_MESSAGE_CONTROL2(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded,
@@ -136,26 +97,6 @@
 IPC_MESSAGE_CONTROL1(
   ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Failed,
   std::string /* printer name */)
-#endif  // ENABLE_PRINT_PREVIEW
-
-#if BUILDFLAG(ENABLE_PRINTING) && defined(OS_WIN)
-// Reply when the utility process loaded PDF. |page_count| is 0, if loading
-// failed.
-IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount,
-                     int /* page_count */)
-
-// Reply when the utility process rendered the PDF page.
-IPC_MESSAGE_CONTROL2(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone,
-                     bool /* success */,
-                     float /* scale_factor */)
-
-// Request that the given font characters be loaded by the browser so it's
-// cached by the OS. Please see
-// PdfToEmfUtilityProcessHostClient::OnPreCacheFontCharacters for details.
-IPC_SYNC_MESSAGE_CONTROL2_0(ChromeUtilityHostMsg_PreCacheFontCharacters,
-                            LOGFONT /* font_data */,
-                            base::string16 /* characters */)
-
-#endif  // ENABLE_PRINTING && OS_WIN
+#endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)
 
 #endif  // CHROME_COMMON_CHROME_UTILITY_PRINTING_MESSAGES_H_
diff --git a/chrome/service/BUILD.gn b/chrome/service/BUILD.gn
index 386f4e2..f4dcb26 100644
--- a/chrome/service/BUILD.gn
+++ b/chrome/service/BUILD.gn
@@ -57,6 +57,7 @@
     "//base",
     "//chrome:strings",
     "//chrome/common",
+    "//chrome/services/printing/public/interfaces",
     "//components/cloud_devices/common",
     "//components/data_use_measurement/core",
     "//components/network_session_configurator/browser",
diff --git a/chrome/service/DEPS b/chrome/service/DEPS
index 8052f77..4618fc5 100644
--- a/chrome/service/DEPS
+++ b/chrome/service/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+chrome/grit",
+  "+chrome/services/printing/public",
   "+components/data_use_measurement/core",
   "+components/network_session_configurator/common",
   "+components/prefs",
diff --git a/chrome/service/service_utility_process_host.cc b/chrome/service/service_utility_process_host.cc
index e991854..4359bb5 100644
--- a/chrome/service/service_utility_process_host.cc
+++ b/chrome/service/service_utility_process_host.cc
@@ -27,6 +27,7 @@
 #include "base/win/win_util.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/chrome_utility_printing_messages.h"
+#include "chrome/services/printing/public/interfaces/pdf_to_emf_converter.mojom.h"
 #include "content/public/common/child_process_host.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/mojo_channel_switches.h"
@@ -38,6 +39,8 @@
 #include "mojo/edk/embedder/named_platform_channel_pair.h"
 #include "mojo/edk/embedder/platform_channel_pair.h"
 #include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/platform_handle.h"
 #include "printing/emf_win.h"
 #include "sandbox/win/src/sandbox_policy.h"
 #include "sandbox/win/src/sandbox_types.h"
@@ -91,20 +94,72 @@
   DISALLOW_COPY_AND_ASSIGN(ServiceSandboxedProcessLauncherDelegate);
 };
 
+// This implementation does not do any font pre-caching.
+// TODO(thestig): Can this be deleted and the PdfToEmfConverterClient be made
+// optional?
+class ServicePdfToEmfConverterClientImpl
+    : public printing::mojom::PdfToEmfConverterClient {
+ public:
+  explicit ServicePdfToEmfConverterClientImpl(
+      printing::mojom::PdfToEmfConverterClientRequest request)
+      : binding_(this, std::move(request)) {}
+
+ private:
+  // mojom::PdfToEmfConverterClient:
+  void PreCacheFontCharacters(
+      const std::vector<uint8_t>& logfont_data,
+      const base::string16& characters,
+      PreCacheFontCharactersCallback callback) override {
+    std::move(callback).Run();
+  }
+
+  mojo::Binding<printing::mojom::PdfToEmfConverterClient> binding_;
+};
+
 }  // namespace
 
 class ServiceUtilityProcessHost::PdfToEmfState {
  public:
-  explicit PdfToEmfState(ServiceUtilityProcessHost* host) : host_(host) {}
+  explicit PdfToEmfState(base::WeakPtr<ServiceUtilityProcessHost> host)
+      : weak_host_(host) {}
+
   ~PdfToEmfState() { Stop(); }
 
   bool Start(base::File pdf_file,
              const printing::PdfRenderSettings& conversion_settings) {
     if (!temp_dir_.CreateUniqueTempDir())
       return false;
-    return host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles(
-        IPC::TakePlatformFileForTransit(std::move(pdf_file)),
-        conversion_settings));
+
+    weak_host_->BindInterface(
+        printing::mojom::PdfToEmfConverterFactory::Name_,
+        mojo::MakeRequest(&pdf_to_emf_converter_factory_).PassMessagePipe());
+
+    pdf_to_emf_converter_factory_.set_connection_error_handler(base::BindOnce(
+        &PdfToEmfState::OnFailed, weak_host_,
+        std::string("Connection to PdfToEmfConverterFactory error.")));
+
+    printing::mojom::PdfToEmfConverterClientPtr pdf_to_emf_converter_client_ptr;
+    pdf_to_emf_converter_client_impl_ =
+        std::make_unique<ServicePdfToEmfConverterClientImpl>(
+            mojo::MakeRequest(&pdf_to_emf_converter_client_ptr));
+
+    pdf_to_emf_converter_factory_->CreateConverter(
+        mojo::WrapPlatformFile(pdf_file.TakePlatformFile()),
+        conversion_settings, std::move(pdf_to_emf_converter_client_ptr),
+        base::BindOnce(
+            &ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageCount,
+            weak_host_));
+    return true;
+  }
+
+  void GotPageCount(printing::mojom::PdfToEmfConverterPtr converter,
+                    uint32_t page_count) {
+    DCHECK(!pdf_to_emf_converter_.is_bound());
+    pdf_to_emf_converter_ = std::move(converter);
+    pdf_to_emf_converter_.set_connection_error_handler(
+        base::BindOnce(&PdfToEmfState::OnFailed, weak_host_,
+                       std::string("Connection to PdfToEmfConverter error.")));
+    page_count_ = page_count;
   }
 
   void GetMorePages() {
@@ -113,10 +168,16 @@
            current_page_ < page_count_) {
       ++pages_in_progress_;
       emf_files_.push(CreateTempFile());
-      host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage(
+
+      // We need to dup the file as mojo::WrapPlatformFile takes ownership of
+      // the passed file.
+      base::File temp_file_copy = emf_files_.back().Duplicate();
+      pdf_to_emf_converter_->ConvertPage(
           current_page_++,
-          IPC::GetPlatformFileForTransit(
-              emf_files_.back().GetPlatformFile(), false)));
+          mojo::WrapPlatformFile(temp_file_copy.TakePlatformFile()),
+          base::BindOnce(
+              &ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageDone,
+              weak_host_));
     }
   }
 
@@ -140,12 +201,19 @@
     return file;
   }
 
-  void set_page_count(int page_count) { page_count_ = page_count; }
-  bool has_page_count() { return page_count_ > 0; }
+  bool has_page_count() const { return page_count_ > 0; }
 
  private:
+  static void OnFailed(const base::WeakPtr<ServiceUtilityProcessHost>& host,
+                       const std::string& error_message) {
+    LOG(ERROR) << "Failed to convert PDF: " << error_message;
+    host->OnChildDisconnected();
+  }
+
   void Stop() {
-    host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop());
+    // Disconnect interface ptrs so that the printing service process stop.
+    pdf_to_emf_converter_factory_.reset();
+    pdf_to_emf_converter_.reset();
   }
 
   base::File CreateTempFile() {
@@ -161,11 +229,18 @@
   }
 
   base::ScopedTempDir temp_dir_;
-  ServiceUtilityProcessHost* const host_;
+  base::WeakPtr<ServiceUtilityProcessHost> weak_host_;
   base::queue<base::File> emf_files_;
   int page_count_ = 0;
   int current_page_ = 0;
   int pages_in_progress_ = 0;
+
+  std::unique_ptr<ServicePdfToEmfConverterClientImpl>
+      pdf_to_emf_converter_client_impl_;
+
+  printing::mojom::PdfToEmfConverterPtr pdf_to_emf_converter_;
+
+  printing::mojom::PdfToEmfConverterFactoryPtr pdf_to_emf_converter_factory_;
 };
 
 ServiceUtilityProcessHost::ServiceUtilityProcessHost(
@@ -195,7 +270,8 @@
   DCHECK(!waiting_for_reply_);
   waiting_for_reply_ = true;
 
-  pdf_to_emf_state_ = std::make_unique<PdfToEmfState>(this);
+  pdf_to_emf_state_ =
+      std::make_unique<PdfToEmfState>(weak_ptr_factory_.GetWeakPtr());
   return pdf_to_emf_state_->Start(std::move(pdf_file), render_settings);
 }
 
@@ -316,11 +392,6 @@
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost, message)
     IPC_MESSAGE_HANDLER(
-        ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount,
-        OnRenderPDFPagesToMetafilesPageCount)
-    IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone,
-                        OnRenderPDFPagesToMetafilesPageDone)
-    IPC_MESSAGE_HANDLER(
         ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded,
         OnGetPrinterCapsAndDefaultsSucceeded)
     IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed,
@@ -359,13 +430,13 @@
 }
 
 void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageCount(
-    int page_count) {
+    printing::mojom::PdfToEmfConverterPtr converter,
+    uint32_t page_count) {
   DCHECK(waiting_for_reply_);
-  if (!pdf_to_emf_state_ || page_count <= 0 ||
-      pdf_to_emf_state_->has_page_count()) {
+  if (page_count == 0 || pdf_to_emf_state_->has_page_count())
     return OnPDFToEmfFinished(false);
-  }
-  pdf_to_emf_state_->set_page_count(page_count);
+
+  pdf_to_emf_state_->GotPageCount(std::move(converter), page_count);
   pdf_to_emf_state_->GetMorePages();
 }
 
diff --git a/chrome/service/service_utility_process_host.h b/chrome/service/service_utility_process_host.h
index cab95542..61bee76f 100644
--- a/chrome/service/service_utility_process_host.h
+++ b/chrome/service/service_utility_process_host.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/services/printing/public/interfaces/pdf_to_emf_converter.mojom.h"
 #include "content/public/common/child_process_host_delegate.h"
 #include "ipc/ipc_platform_file.h"
 #include "mojo/edk/embedder/outgoing_broker_client_invitation.h"
@@ -132,9 +133,13 @@
   void OnMetafileSpooled(bool success);
   void OnPDFToEmfFinished(bool success);
 
-  // Messages handlers:
-  void OnRenderPDFPagesToMetafilesPageCount(int page_count);
+  // PdfToEmfState callbacks:
+  void OnRenderPDFPagesToMetafilesPageCount(
+      printing::mojom::PdfToEmfConverterPtr converter,
+      uint32_t page_count);
   void OnRenderPDFPagesToMetafilesPageDone(bool success, float scale_factor);
+
+  // IPC Messages handlers:
   void OnGetPrinterCapsAndDefaultsSucceeded(
       const std::string& printer_name,
       const printing::PrinterCapsAndDefaults& caps_and_defaults);
diff --git a/chrome/services/printing/pdf_to_emf_converter_factory.cc b/chrome/services/printing/pdf_to_emf_converter_factory.cc
index 5e6c0e95..93689066 100644
--- a/chrome/services/printing/pdf_to_emf_converter_factory.cc
+++ b/chrome/services/printing/pdf_to_emf_converter_factory.cc
@@ -14,6 +14,8 @@
     std::unique_ptr<service_manager::ServiceContextRef> service_ref)
     : service_ref_(std::move(service_ref)) {}
 
+PdfToEmfConverterFactory::PdfToEmfConverterFactory() = default;
+
 PdfToEmfConverterFactory::~PdfToEmfConverterFactory() = default;
 
 void PdfToEmfConverterFactory::CreateConverter(
@@ -31,4 +33,10 @@
   std::move(callback).Run(std::move(converter_ptr), page_count);
 }
 
+// static
+void PdfToEmfConverterFactory::Create(
+    mojom::PdfToEmfConverterFactoryRequest request) {
+  mojo::MakeStrongBinding(base::MakeUnique<PdfToEmfConverterFactory>(),
+                          std::move(request));
+}
 }  // namespace printing
diff --git a/chrome/services/printing/pdf_to_emf_converter_factory.h b/chrome/services/printing/pdf_to_emf_converter_factory.h
index 10a658c..acb9743 100644
--- a/chrome/services/printing/pdf_to_emf_converter_factory.h
+++ b/chrome/services/printing/pdf_to_emf_converter_factory.h
@@ -17,6 +17,11 @@
       std::unique_ptr<service_manager::ServiceContextRef> service_ref);
   ~PdfToEmfConverterFactory() override;
 
+  // TODO(crbug.com/798782): remove when the Cloud print chrome/service is
+  // removed.
+  PdfToEmfConverterFactory();
+  static void Create(mojom::PdfToEmfConverterFactoryRequest request);
+
  private:
   // mojom::PdfToEmfConverterFactory implementation.
   void CreateConverter(mojo::ScopedHandle pdf_file_in,
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ad9d0ce2..c7f72a21 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -474,6 +474,7 @@
       "../browser/chrome_find_request_manager_browsertest.cc",
       "../browser/chrome_main_browsertest.cc",
       "../browser/chrome_navigation_browsertest.cc",
+      "../browser/chrome_network_service_restart_browsertest.cc",
       "../browser/chrome_origin_trials_browsertest.cc",
       "../browser/chrome_plugin_browsertest.cc",
       "../browser/chrome_security_exploit_browsertest.cc",
@@ -587,6 +588,7 @@
       "../browser/net/errorpage_browsertest.cc",
       "../browser/net/ftp_browsertest.cc",
       "../browser/net/load_timing_browsertest.cc",
+      "../browser/net/network_connection_tracker_browsertest.cc",
       "../browser/net/network_context_configuration_browsertest.cc",
       "../browser/net/nqe/ui_network_quality_estimator_service_browsertest.cc",
       "../browser/net/nqe/ui_network_quality_estimator_service_test_util.cc",
@@ -682,8 +684,6 @@
 
       # If this list is used on Android in the future, these browser/speech/*
       # files will probably not be applicable.
-      "../browser/chrome_network_service_restart_browsertest.cc",
-      "../browser/net/network_connection_tracker_browsertest.cc",
       "../browser/speech/extension_api/tts_extension_apitest.cc",
       "../browser/speech/speech_recognition_browsertest.cc",
       "../browser/spellchecker/spellcheck_mac_view_browsertest.mm",
@@ -1604,6 +1604,7 @@
         "../browser/extensions/api/networking_private/networking_private_apitest.cc",
         "../browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc",
         "../browser/extensions/api/vpn_provider/vpn_provider_apitest.cc",
+        "../browser/mash_service_registry_browsertest.cc",
         "../browser/signin/chromeos_mirror_account_consistency_browsertest.cc",
         "../browser/ui/ash/accelerator_commands_browsertest.cc",
         "../browser/ui/ash/app_list/app_list_browsertest.cc",
@@ -3068,6 +3069,7 @@
     ]
     sources += [
       "../browser/component_updater/cros_component_installer_unittest.cc",
+      "../browser/mash_service_registry_unittest.cc",
       "../browser/media/webrtc/desktop_media_list_ash_unittest.cc",
       "../browser/notifications/chrome_ash_message_center_client_unittest.cc",
       "../browser/renderer_context_menu/mock_render_view_context_menu.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java
index 6ab26c8..e21b055 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java
@@ -8,8 +8,8 @@
 
 import org.junit.runners.model.InitializationError;
 
-import org.chromium.base.BaseChromiumApplication;
 import org.chromium.base.CollectionUtil;
+import org.chromium.base.CommandLineInitUtil;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.BaseTestResult.PreTestHook;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -49,6 +49,7 @@
 
     @Override
     protected void initCommandLineForTest() {
-        BaseChromiumApplication.initCommandLine(InstrumentationRegistry.getTargetContext());
+        CommandLineInitUtil.initCommandLine(
+                InstrumentationRegistry.getTargetContext(), CommandLineFlags.getTestCmdLineFile());
     }
 }
diff --git a/chrome/test/data/extensions/api_test/cast_channel/api/test_open_error_send.html b/chrome/test/data/extensions/api_test/cast_channel/api/test_open_error_send.html
new file mode 100644
index 0000000..090ff78
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/cast_channel/api/test_open_error_send.html
@@ -0,0 +1,2 @@
+<script src="common.js"></script>
+<script src="test_open_error_send.js"></script>
diff --git a/chrome/test/data/extensions/api_test/cast_channel/api/test_open_error_send.js b/chrome/test/data/extensions/api_test/cast_channel/api/test_open_error_send.js
new file mode 100644
index 0000000..179ef9882
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/cast_channel/api/test_open_error_send.js
@@ -0,0 +1,31 @@
+// Copyright 2013 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.
+
+var message = {
+  'namespace_': 'foo',
+  'sourceId': 'src',
+  'destinationId': 'dest',
+  'data': 'some-string'
+};
+
+var onClose = function(channel) {
+  chrome.test.assertLastError('Channel socket error = 3');
+  chrome.test.succeed();
+};
+
+var onSend = function(channel) {
+  chrome.test.assertLastError('Channel error = 1');
+  chrome.cast.channel.close(channel, onClose);
+};
+
+var onOpen = function(channel) {
+  chrome.test.assertLastError('Channel socket error = 3');
+  assertClosedChannelWithError(channel, 'connect_error');
+  chrome.cast.channel.send(channel, message, onSend);
+};
+
+chrome.cast.channel.open({
+  ipAddress: '192.168.1.1',
+  port: 8009,
+  auth: 'ssl_verified'}, onOpen);
diff --git a/chrome/tools/build/mac/infoplist_strings_util.mm b/chrome/tools/build/mac/infoplist_strings_util.mm
index 6558aec..f1d158dc 100644
--- a/chrome/tools/build/mac/infoplist_strings_util.mm
+++ b/chrome/tools/build/mac/infoplist_strings_util.mm
@@ -256,10 +256,6 @@
         LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
                                IDS_ABOUT_VERSION_COPYRIGHT,
                                "IDS_ABOUT_VERSION_COPYRIGHT");
-    NSString* address_book_prompt_description =
-        LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
-                               IDS_AUTOFILL_ADDRESS_BOOK_PROMPT_DESCRIPTION,
-                               "IDS_AUTOFILL_ADDRESS_BOOK_PROMPT_DESCRIPTION");
 
     NSString* copyright = base::SysUTF16ToNSString(
         base::i18n::MessageFormatter::FormatWithNumberedArgs(
@@ -276,12 +272,10 @@
           @"CFBundleDisplayName = \"%@\";\n"
           @"CFBundleGetInfoString = \"%@\";\n"
           @"CFBundleName = \"%@\";\n"
-          @"NSContactsUsageDescription = \"%@\";\n"
           @"NSHumanReadableCopyright = \"%@\";\n",
           EscapeForStringsFileValue(name),
           EscapeForStringsFileValue(get_info),
           EscapeForStringsFileValue(short_name),
-          EscapeForStringsFileValue(address_book_prompt_description),
           EscapeForStringsFileValue(copyright)];
 
     // We set up Xcode projects expecting strings files to be UTF8, so make
diff --git a/chrome/utility/DEPS b/chrome/utility/DEPS
index 17bf4ca3..6797a1f 100644
--- a/chrome/utility/DEPS
+++ b/chrome/utility/DEPS
@@ -6,6 +6,9 @@
   "+chrome/services/file_util/public/interfaces",
   "+chrome/services/media_gallery_util/media_gallery_util_service.h",
   "+chrome/services/media_gallery_util/public/interfaces",
+  # TODO(crbug.com/798782): remove dependency to pdf_to_emf_converter_factory.h
+  # when Cloud print chrome/service is removed.
+  "+chrome/services/printing/pdf_to_emf_converter_factory.h",
   "+chrome/services/printing/printing_service.h",
   "+chrome/services/printing/public/interfaces",
   "+chrome/services/removable_storage_writer/removable_storage_writer_service.h",
diff --git a/chrome/utility/chrome_content_utility_client.cc b/chrome/utility/chrome_content_utility_client.cc
index b70e6b4..af47855 100644
--- a/chrome/utility/chrome_content_utility_client.cc
+++ b/chrome/utility/chrome_content_utility_client.cc
@@ -44,6 +44,7 @@
 #endif  // !defined(OS_ANDROID)
 
 #if defined(OS_WIN)
+#include "chrome/services/printing/pdf_to_emf_converter_factory.h"
 #include "chrome/services/util_win/public/interfaces/constants.mojom.h"
 #include "chrome/services/util_win/util_win_service.h"
 #endif
@@ -178,6 +179,13 @@
     registry->AddInterface(base::Bind(CreateResourceUsageReporter),
                            base::ThreadTaskRunnerHandle::Get());
 #endif  // !defined(OS_ANDROID)
+#if defined(OS_WIN)
+    // TODO(crbug.com/798782): remove when the Cloud print chrome/service is
+    // removed.
+    registry->AddInterface(
+        base::Bind(printing::PdfToEmfConverterFactory::Create),
+        base::ThreadTaskRunnerHandle::Get());
+#endif
   }
 
   connection->AddConnectionFilter(
diff --git a/chrome/utility/mash_service_factory.cc b/chrome/utility/mash_service_factory.cc
index 244ae60..f566bc98 100644
--- a/chrome/utility/mash_service_factory.cc
+++ b/chrome/utility/mash_service_factory.cc
@@ -34,8 +34,24 @@
 
 // NOTE: For --mus the UI service is created at the //chrome/browser layer,
 // not in //content. See ServiceManagerContext.
-std::unique_ptr<service_manager::Service> CreateUiService() {
-  return std::make_unique<ui::Service>();
+std::unique_ptr<service_manager::Service> CreateUiService(
+    const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) {
+  ui::Service::InProcessConfig config;
+  config.resource_runner = task_runner;
+  // TODO(jamescook): Fix cursor loading.
+  config.should_host_viz = true;
+  return std::make_unique<ui::Service>(&config);
+}
+
+void RegisterUiService(
+    content::ContentUtilityClient::StaticServiceMap* services) {
+  service_manager::EmbeddedServiceInfo service_info;
+  service_info.use_own_thread = true;
+  service_info.message_loop_type = base::MessageLoop::TYPE_UI;
+  service_info.thread_priority = base::ThreadPriority::DISPLAY;
+  service_info.factory = base::BindRepeating(
+      &CreateUiService, base::ThreadTaskRunnerHandle::Get());
+  services->emplace(ui::mojom::kServiceName, service_info);
 }
 
 std::unique_ptr<service_manager::Service> CreateAshService() {
@@ -64,7 +80,7 @@
 
 void RegisterOutOfProcessMashServices(
     content::ContentUtilityClient::StaticServiceMap* services) {
-  RegisterMashService(services, ui::mojom::kServiceName, &CreateUiService);
+  RegisterUiService(services);
   RegisterMashService(services, mash::quick_launch::mojom::kServiceName,
                       &CreateQuickLaunch);
   RegisterMashService(services, ash::mojom::kServiceName, &CreateAshService);
diff --git a/chrome/utility/printing_handler.cc b/chrome/utility/printing_handler.cc
index f12cc09c..1eb8186 100644
--- a/chrome/utility/printing_handler.cc
+++ b/chrome/utility/printing_handler.cc
@@ -40,10 +40,6 @@
 bool PrintingHandler::OnMessageReceived(const IPC::Message& message) {
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(PrintingHandler, message)
-#if defined(OS_WIN)
-    IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop,
-                        OnRenderPDFPagesToMetafileStop)
-#endif  // OS_WIN
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
     IPC_MESSAGE_HANDLER(ChromeUtilityMsg_GetPrinterCapsAndDefaults,
                         OnGetPrinterCapsAndDefaults)
@@ -55,12 +51,6 @@
   return handled;
 }
 
-#if defined(OS_WIN)
-void PrintingHandler::OnRenderPDFPagesToMetafileStop() {
-  ReleaseProcess();
-}
-#endif
-
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
 void PrintingHandler::OnGetPrinterCapsAndDefaults(
     const std::string& printer_name) {
diff --git a/chrome/utility/printing_handler.h b/chrome/utility/printing_handler.h
index 8ed7389..a9465916 100644
--- a/chrome/utility/printing_handler.h
+++ b/chrome/utility/printing_handler.h
@@ -31,13 +31,10 @@
 
  private:
   // IPC message handlers.
-#if defined(OS_WIN)
-  void OnRenderPDFPagesToMetafileStop();
-#endif  // OS_WIN
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
   void OnGetPrinterCapsAndDefaults(const std::string& printer_name);
   void OnGetPrinterSemanticCapsAndDefaults(const std::string& printer_name);
-#endif  // ENABLE_PRINT_PREVIEW
+#endif
 
   DISALLOW_COPY_AND_ASSIGN(PrintingHandler);
 };
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
index 7f072354..fec40f7 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
@@ -39,7 +39,6 @@
         ApplicationStatus.initialize(this);
     }
 
-    @Override
     public void initCommandLine() {
         CommandLineInitUtil.initCommandLine(this, COMMAND_LINE_FILE);
     }
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java
index 64f23e2..9cc9595 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java
@@ -14,7 +14,6 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.chromecast.base.ChromecastConfigAndroid;
-import org.chromium.content.app.ContentApplication;
 import org.chromium.content.browser.BrowserStartupController;
 import org.chromium.content.browser.DeviceUtils;
 import org.chromium.net.NetworkChangeNotifier;
@@ -45,7 +44,7 @@
 
         // Initializing the command line must occur before loading the library.
         if (!CommandLine.isInitialized()) {
-            ContentApplication.initCommandLine(context);
+            ((CastApplication) context.getApplicationContext()).initCommandLine();
 
             if (context instanceof Activity) {
                 Intent launchingIntent = ((Activity) context).getIntent();
diff --git a/components/assist_ranker/base_predictor.cc b/components/assist_ranker/base_predictor.cc
index bece7fb..8e061a52 100644
--- a/components/assist_ranker/base_predictor.cc
+++ b/components/assist_ranker/base_predictor.cc
@@ -57,9 +57,8 @@
 void BasePredictor::LogFeatureToUkm(const std::string& feature_name,
                                     const Feature& feature,
                                     ukm::UkmEntryBuilder* ukm_builder) {
-  if (!ukm_builder) {
+  if (!ukm_builder)
     return;
-  }
 
   if (!base::ContainsKey(*config_.feature_whitelist, feature_name)) {
     DVLOG(1) << "Feature not whitelisted: " << feature_name;
diff --git a/components/assist_ranker/binary_classifier_predictor.cc b/components/assist_ranker/binary_classifier_predictor.cc
index 7898588..cc595bf0 100644
--- a/components/assist_ranker/binary_classifier_predictor.cc
+++ b/components/assist_ranker/binary_classifier_predictor.cc
@@ -82,8 +82,8 @@
 bool BinaryClassifierPredictor::Initialize() {
   if (ranker_model_->proto().model_case() ==
       RankerModelProto::kLogisticRegression) {
-    inference_module_.reset(new GenericLogisticRegressionInference(
-        ranker_model_->proto().logistic_regression()));
+    inference_module_ = std::make_unique<GenericLogisticRegressionInference>(
+        ranker_model_->proto().logistic_regression());
     return true;
   }
 
diff --git a/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc b/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc
index c3eb4dc..4cb5beec 100644
--- a/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc
+++ b/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 #include <stddef.h>
+#include <memory>
 
 #include "base/macros.h"
-#include "base/memory/ptr_util.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -731,10 +731,10 @@
   // for "id" field.
   FieldValueAndPropertiesMaskMap user_input;
   user_input[control_elements[2]] = std::make_pair(  // id
-      base::MakeUnique<base::string16>(control_elements[2].Value().Utf16()),
+      std::make_unique<base::string16>(control_elements[2].Value().Utf16()),
       FieldPropertiesFlags::USER_TYPED);
   user_input[control_elements[3]] = std::make_pair(  // password
-      base::MakeUnique<base::string16>(control_elements[3].Value().Utf16()),
+      std::make_unique<base::string16>(control_elements[3].Value().Utf16()),
       FieldPropertiesFlags::USER_TYPED);
 
   std::unique_ptr<PasswordForm> password_form = CreatePasswordFormFromWebForm(
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index a8402deb..e1f163b 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -266,10 +266,6 @@
   }
 
   configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
-
-  if (is_mac) {
-    libs = [ "AddressBook.framework" ]
-  }
 }
 
 static_library("test_support") {
diff --git a/components/autofill/core/browser/address_combobox_model.cc b/components/autofill/core/browser/address_combobox_model.cc
index 940bcf53..e7e6fdb3 100644
--- a/components/autofill/core/browser/address_combobox_model.cc
+++ b/components/autofill/core/browser/address_combobox_model.cc
@@ -127,13 +127,8 @@
   AutofillProfile::CreateDifferentiatingLabels(profiles, app_locale_, &labels);
   DCHECK_EQ(labels.size(), profiles_cache_.size());
 
-  for (size_t i = 0; i < profiles_cache_.size(); ++i) {
-    // Skip showing auxiliary profiles (e.g. Mac Contacts).
-    if (profiles_cache_[i]->record_type() == AutofillProfile::AUXILIARY_PROFILE)
-      continue;
-
-    addresses_.push_back(std::make_pair(profiles_cache_[i]->guid(), labels[i]));
-  }
+  for (size_t i = 0; i < profiles_cache_.size(); ++i)
+    addresses_.emplace_back(profiles_cache_[i]->guid(), labels[i]);
 
   for (auto& observer : observers_) {
     observer.OnComboboxModelChanged(this);
diff --git a/components/autofill/core/browser/autofill_external_delegate.cc b/components/autofill/core/browser/autofill_external_delegate.cc
index 15daab1..2c8d17a3 100644
--- a/components/autofill/core/browser/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/autofill_external_delegate.cc
@@ -53,7 +53,6 @@
       should_show_scan_credit_card_(false),
       is_credit_card_popup_(false),
       should_show_cc_signin_promo_(false),
-      has_shown_address_book_prompt(false),
       weak_ptr_factory_(this) {
   DCHECK(manager);
 }
@@ -67,9 +66,6 @@
                                        const FormData& form,
                                        const FormFieldData& field,
                                        const gfx::RectF& element_bounds) {
-  if (!query_form_.SameFormAs(form))
-    has_shown_address_book_prompt = false;
-
   query_form_ = form;
   query_field_ = field;
   query_id_ = query_id;
diff --git a/components/autofill/core/browser/autofill_external_delegate.h b/components/autofill/core/browser/autofill_external_delegate.h
index fee1624..4bc3364 100644
--- a/components/autofill/core/browser/autofill_external_delegate.h
+++ b/components/autofill/core/browser/autofill_external_delegate.h
@@ -154,10 +154,6 @@
   // Whether the credit card signin promo should be shown to the user.
   bool should_show_cc_signin_promo_;
 
-  // Whether the access Address Book prompt has ever been shown for the current
-  // |query_form_|. This variable is only used on OSX.
-  bool has_shown_address_book_prompt;
-
   // The current data list values.
   std::vector<base::string16> data_list_values_;
   std::vector<base::string16> data_list_labels_;
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 6bbf9180..dfa7585 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -765,8 +765,7 @@
   if (!IsValidFormData(form) || !IsValidFormFieldData(field))
     return;
 
-  // NOTE: RefreshDataModels may invalidate |data_model| because it causes the
-  // PersonalDataManager to reload Mac address book entries. Thus it must come
+  // NOTE: RefreshDataModels may invalidate |data_model|. Thus it must come
   // before GetProfile or GetCreditCard.
   if (!RefreshDataModels() || !driver()->RendererIsAvailable())
     return;
@@ -1331,8 +1330,7 @@
       }
     }
 
-    // Note that this may invalidate |data_model|, particularly if it is a Mac
-    // address book entry.
+    // Note that this may invalidate |data_model|.
     if (action == AutofillDriver::FORM_DATA_ACTION_FILL)
       personal_data_->RecordUseOf(data_model);
 
@@ -1387,8 +1385,7 @@
   if (autofilled_form_signatures_.size() > kMaxRecentFormSignaturesToRemember)
     autofilled_form_signatures_.pop_back();
 
-  // Note that this may invalidate |data_model|, particularly if it is a Mac
-  // address book entry.
+  // Note that this may invalidate |data_model|.
   if (action == AutofillDriver::FORM_DATA_ACTION_FILL)
     personal_data_->RecordUseOf(data_model);
 
diff --git a/components/autofill/core/browser/autofill_metrics_unittest.cc b/components/autofill/core/browser/autofill_metrics_unittest.cc
index 0481bd01..33defae 100644
--- a/components/autofill/core/browser/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/autofill_metrics_unittest.cc
@@ -248,9 +248,6 @@
 void AutofillMetricsTest::SetUp() {
   autofill_client_.SetPrefs(test::PrefServiceForTesting());
 
-  // Ensure Mac OS X does not pop up a modal dialog for the Address Book.
-  test::DisableSystemServices(autofill_client_.GetPrefs());
-
   // Set up identity services.
   signin_client_ =
       std::make_unique<TestSigninClient>(autofill_client_.GetPrefs());
diff --git a/components/autofill/core/browser/autofill_policy_handler_unittest.cc b/components/autofill/core/browser/autofill_policy_handler_unittest.cc
index 6969366..09ccd18e 100644
--- a/components/autofill/core/browser/autofill_policy_handler_unittest.cc
+++ b/components/autofill/core/browser/autofill_policy_handler_unittest.cc
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
+
 #include "components/autofill/core/browser/autofill_policy_handler.h"
-#include "base/memory/ptr_util.h"
 #include "base/values.h"
 #include "components/autofill/core/common/autofill_pref_names.h"
 #include "components/policy/core/common/policy_map.h"
@@ -29,7 +30,7 @@
   policy::PolicyMap policy;
   policy.Set(policy::key::kAutoFillEnabled, policy::POLICY_LEVEL_MANDATORY,
              policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
-             base::MakeUnique<base::Value>(true), nullptr);
+             std::make_unique<base::Value>(true), nullptr);
   PrefValueMap prefs;
   AutofillPolicyHandler handler;
   handler.ApplyPolicySettings(policy, &prefs);
@@ -42,7 +43,7 @@
   policy::PolicyMap policy;
   policy.Set(policy::key::kAutoFillEnabled, policy::POLICY_LEVEL_MANDATORY,
              policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
-             base::MakeUnique<base::Value>(false), nullptr);
+             std::make_unique<base::Value>(false), nullptr);
   PrefValueMap prefs;
   AutofillPolicyHandler handler;
   handler.ApplyPolicySettings(policy, &prefs);
diff --git a/components/autofill/core/browser/autofill_profile.h b/components/autofill/core/browser/autofill_profile.h
index f489747..f5a06442 100644
--- a/components/autofill/core/browser/autofill_profile.h
+++ b/components/autofill/core/browser/autofill_profile.h
@@ -36,8 +36,6 @@
     LOCAL_PROFILE,
     // A profile synced down from the server. These are read-only locally.
     SERVER_PROFILE,
-    // An auxiliary profile, such as a Mac address book entry.
-    AUXILIARY_PROFILE,
   };
 
   enum ValidityState {
diff --git a/components/autofill/core/browser/credit_card_save_manager_unittest.cc b/components/autofill/core/browser/credit_card_save_manager_unittest.cc
index d50666f3..e79083f 100644
--- a/components/autofill/core/browser/credit_card_save_manager_unittest.cc
+++ b/components/autofill/core/browser/credit_card_save_manager_unittest.cc
@@ -108,7 +108,8 @@
                                       payments_client_, &personal_data_);
     autofill_manager_.reset(new TestAutofillManager(
         autofill_driver_.get(), &autofill_client_, &personal_data_,
-        credit_card_save_manager_, payments_client_));
+        std::unique_ptr<CreditCardSaveManager>(credit_card_save_manager_),
+        payments_client_));
     autofill_manager_->SetExpectedObservedSubmission(true);
   }
 
@@ -146,8 +147,7 @@
     scoped_feature_list_.InitWithFeatures(
         {kAutofillUpstreamRequestCvcIfMissing,
          kAutofillUpstreamSendDetectedValues},  // Enabled
-        {}                                      // Disabled
-        );
+        {});                                    // Disabled
   }
 
   void EnableAutofillUpstreamSendPanFirstSixExperiment() {
diff --git a/components/autofill/core/browser/form_data_importer.h b/components/autofill/core/browser/form_data_importer.h
index 68cb8bd8..d3c07d5 100644
--- a/components/autofill/core/browser/form_data_importer.h
+++ b/components/autofill/core/browser/form_data_importer.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "build/build_config.h"
@@ -49,8 +50,8 @@
  protected:
   // Exposed for testing.
   void set_credit_card_save_manager(
-      CreditCardSaveManager* credit_card_save_manager) {
-    credit_card_save_manager_.reset(credit_card_save_manager);
+      std::unique_ptr<CreditCardSaveManager> credit_card_save_manager) {
+    credit_card_save_manager_ = std::move(credit_card_save_manager);
   }
 
  private:
diff --git a/components/autofill/core/browser/personal_data_manager.h b/components/autofill/core/browser/personal_data_manager.h
index 02855477..ce8f498 100644
--- a/components/autofill/core/browser/personal_data_manager.h
+++ b/components/autofill/core/browser/personal_data_manager.h
@@ -100,8 +100,7 @@
   void MarkObserversInsufficientFormDataForImport();
 
   // Called to indicate |data_model| was used (to fill in a form). Updates
-  // the database accordingly. Can invalidate |data_model|, particularly if
-  // it's a Mac address book entry.
+  // the database accordingly. Can invalidate |data_model|.
   virtual void RecordUseOf(const AutofillDataModel& data_model);
 
   // Saves |imported_profile| to the WebDB if it exists. Returns the guid of
diff --git a/components/autofill/core/browser/test_autofill_manager.cc b/components/autofill/core/browser/test_autofill_manager.cc
index f368727..3291fecc 100644
--- a/components/autofill/core/browser/test_autofill_manager.cc
+++ b/components/autofill/core/browser/test_autofill_manager.cc
@@ -29,14 +29,14 @@
     AutofillDriver* driver,
     AutofillClient* client,
     TestPersonalDataManager* personal_data,
-    CreditCardSaveManager* credit_card_save_manager,
+    std::unique_ptr<CreditCardSaveManager> credit_card_save_manager,
     payments::TestPaymentsClient* payments_client)
     : AutofillManager(driver, client, personal_data),
       personal_data_(personal_data),
       test_form_data_importer_(
           new TestFormDataImporter(client,
                                    payments_client,
-                                   credit_card_save_manager,
+                                   std::move(credit_card_save_manager),
                                    personal_data,
                                    "en-US")) {
   set_payments_client(payments_client);
diff --git a/components/autofill/core/browser/test_autofill_manager.h b/components/autofill/core/browser/test_autofill_manager.h
index 5ad28798e3..56519fd 100644
--- a/components/autofill/core/browser/test_autofill_manager.h
+++ b/components/autofill/core/browser/test_autofill_manager.h
@@ -5,7 +5,9 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_MANAGER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_MANAGER_H_
 
+#include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/optional.h"
@@ -34,11 +36,12 @@
                       AutofillClient* client,
                       TestPersonalDataManager* personal_data);
   // Called by CreditCardSaveManagerTest.
-  TestAutofillManager(AutofillDriver* driver,
-                      AutofillClient* client,
-                      TestPersonalDataManager* personal_data,
-                      CreditCardSaveManager* credit_card_save_manager,
-                      payments::TestPaymentsClient* payments_client);
+  TestAutofillManager(
+      AutofillDriver* driver,
+      AutofillClient* client,
+      TestPersonalDataManager* personal_data,
+      std::unique_ptr<CreditCardSaveManager> credit_card_save_manager,
+      payments::TestPaymentsClient* payments_client);
   ~TestAutofillManager() override;
 
   // AutofillManager overrides.
diff --git a/components/autofill/core/browser/test_form_data_importer.cc b/components/autofill/core/browser/test_form_data_importer.cc
index 970b322..cd6943e1 100644
--- a/components/autofill/core/browser/test_form_data_importer.cc
+++ b/components/autofill/core/browser/test_form_data_importer.cc
@@ -9,14 +9,14 @@
 TestFormDataImporter::TestFormDataImporter(
     AutofillClient* client,
     payments::PaymentsClient* payments_client,
-    CreditCardSaveManager* credit_card_save_manager,
+    std::unique_ptr<CreditCardSaveManager> credit_card_save_manager,
     PersonalDataManager* personal_data_manager,
     const std::string& app_locale)
     : FormDataImporter(client,
                        payments_client,
                        personal_data_manager,
                        app_locale) {
-  set_credit_card_save_manager(credit_card_save_manager);
+  set_credit_card_save_manager(std::move(credit_card_save_manager));
 }
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/test_form_data_importer.h b/components/autofill/core/browser/test_form_data_importer.h
index c6bf838..c84887d 100644
--- a/components/autofill/core/browser/test_form_data_importer.h
+++ b/components/autofill/core/browser/test_form_data_importer.h
@@ -5,17 +5,21 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_FORM_DATA_IMPORTER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_FORM_DATA_IMPORTER_H_
 
+#include <memory>
+#include <utility>
+
 #include "components/autofill/core/browser/form_data_importer.h"
 
 namespace autofill {
 
 class TestFormDataImporter : public FormDataImporter {
  public:
-  TestFormDataImporter(AutofillClient* client,
-                       payments::PaymentsClient* payments_client,
-                       CreditCardSaveManager* credit_card_save_manager,
-                       PersonalDataManager* personal_data_manager,
-                       const std::string& app_locale);
+  TestFormDataImporter(
+      AutofillClient* client,
+      payments::PaymentsClient* payments_client,
+      std::unique_ptr<CreditCardSaveManager> credit_card_save_manager,
+      PersonalDataManager* personal_data_manager,
+      const std::string& app_locale);
 };
 
 }  // namespace autofill
diff --git a/components/client_update_protocol/ecdsa.cc b/components/client_update_protocol/ecdsa.cc
index 38faa9ab..1af829b7 100644
--- a/components/client_update_protocol/ecdsa.cc
+++ b/components/client_update_protocol/ecdsa.cc
@@ -68,13 +68,13 @@
   // the SignatureValidator class will handle the actual DER decoding and
   // ASN.1 parsing. Check for an expected size range only -- valid ECDSA
   // signatures are between 8 and 72 bytes.
-  if (!base::HexStringToBytes(sig_hex.as_string(), ecdsa_signature_out))
+  if (!base::HexStringToBytes(sig_hex, ecdsa_signature_out))
     return false;
   if (ecdsa_signature_out->size() < 8 || ecdsa_signature_out->size() > 72)
     return false;
 
   // Decode the SHA-256 hash; it should be exactly 32 bytes, no more or less.
-  if (!base::HexStringToBytes(hash_hex.as_string(), request_hash_out))
+  if (!base::HexStringToBytes(hash_hex, request_hash_out))
     return false;
   if (request_hash_out->size() != crypto::kSHA256Length)
     return false;
diff --git a/components/cronet/android/test/javaperftests/run.py b/components/cronet/android/test/javaperftests/run.py
index 9b960a0ad..0e1382d 100755
--- a/components/cronet/android/test/javaperftests/run.py
+++ b/components/cronet/android/test/javaperftests/run.py
@@ -65,7 +65,6 @@
 from devil.android.sdk import intent
 import lighttpd_server
 from pylib import constants
-from pylib import pexpect
 from telemetry import android
 from telemetry import benchmark
 from telemetry import benchmark_runner
@@ -80,9 +79,9 @@
 CERT_PATH = os.path.join('net', 'data', 'ssl', 'certificates')
 QUIC_CERT_DIR = os.path.join(REPOSITORY_ROOT, CERT_PATH)
 QUIC_CERT_HOST = 'test.example.com'
-QUIC_CERT_FILENAME = 'quic_%s.crt' % QUIC_CERT_HOST
+QUIC_CERT_FILENAME = 'quic-chain.pem'
 QUIC_CERT = os.path.join(QUIC_CERT_DIR, QUIC_CERT_FILENAME)
-QUIC_KEY = os.path.join(QUIC_CERT_DIR, 'quic_%s.key.pkcs8' % QUIC_CERT_HOST)
+QUIC_KEY = os.path.join(QUIC_CERT_DIR, 'quic-leaf-cert.key')
 APP_APK = os.path.join(BUILD_DIR, 'apks', 'CronetPerfTest.apk')
 APP_PACKAGE = 'org.chromium.net'
 APP_ACTIVITY = '.CronetPerfTestActivity'
@@ -229,15 +228,13 @@
     self._quic_server_doc_root = quic_server_doc_root
 
   def StartupQuicServer(self, device):
-    # Chromium's presubmit checks aren't smart enough to understand
-    # the redirect done in build/android/pylib/pexpect.py.
-    # pylint: disable=no-member
-    self._process = pexpect.spawn(QUIC_SERVER,
-                                  ['--quic_response_cache_dir=%s' %
-                                      self._quic_server_doc_root,
-                                   '--certificate_file=%s' % QUIC_CERT,
-                                   '--key_file=%s' % QUIC_KEY,
-                                   '--port=%d' % QUIC_PORT])
+    cmd = [QUIC_SERVER,
+           '--quic_response_cache_dir=%s' % self._quic_server_doc_root,
+           '--certificate_file=%s' % QUIC_CERT,
+           '--key_file=%s' % QUIC_KEY,
+           '--port=%d' % QUIC_PORT]
+    print "Starting Quic Server", cmd
+    self._process = subprocess.Popen(cmd)
     assert self._process != None
     # Wait for quic_server to start serving.
     waited_s = 0
diff --git a/components/cronet/cronet_prefs_manager.cc b/components/cronet/cronet_prefs_manager.cc
index c1d4ae7e1..e06f21ae 100644
--- a/components/cronet/cronet_prefs_manager.cc
+++ b/components/cronet/cronet_prefs_manager.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/location.h"
@@ -110,11 +111,15 @@
     return pref_service_->GetDictionary(path_);
   }
 
-  void SetServerProperties(const base::DictionaryValue& value) override {
-    return pref_service_->Set(path_, value);
+  void SetServerProperties(const base::DictionaryValue& value,
+                           base::OnceClosure callback) override {
+    pref_service_->Set(path_, value);
+    if (callback)
+      pref_service_->CommitPendingWrite(std::move(callback));
   }
 
-  void StartListeningForUpdates(const base::Closure& callback) override {
+  void StartListeningForUpdates(
+      const base::RepeatingClosure& callback) override {
     pref_change_registrar_.Add(path_, callback);
     // Notify the pref manager that settings are already loaded, as a result
     // of initializing the pref store synchornously.
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
index 65a3c6c6..ffca2f4 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
@@ -194,13 +194,6 @@
   }
 }
 
-void DataReductionProxyDelegate::OnTunnelConnectCompleted(
-    const net::HostPortPair& endpoint,
-    const net::HostPortPair& proxy_server,
-    int net_error) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-}
-
 void DataReductionProxyDelegate::OnFallback(const net::ProxyServer& bad_proxy,
                                             int net_error) {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -214,12 +207,6 @@
     bypass_stats_->OnProxyFallback(bad_proxy, net_error);
 }
 
-void DataReductionProxyDelegate::OnBeforeTunnelRequest(
-    const net::HostPortPair& proxy_server,
-    net::HttpRequestHeaders* extra_headers) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-}
-
 bool DataReductionProxyDelegate::IsTrustedSpdyProxy(
     const net::ProxyServer& proxy_server) {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -227,13 +214,6 @@
          config_->IsDataReductionProxy(proxy_server, nullptr);
 }
 
-void DataReductionProxyDelegate::OnTunnelHeadersReceived(
-    const net::HostPortPair& origin,
-    const net::HostPortPair& proxy_server,
-    const net::HttpResponseHeaders& response_headers) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-}
-
 void DataReductionProxyDelegate::SetTickClockForTesting(
     base::TickClock* tick_clock) {
   tick_clock_ = tick_clock;
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h
index 6bde0e5..78cddc6e 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.h
@@ -19,9 +19,6 @@
 }
 
 namespace net {
-class HostPortPair;
-class HttpRequestHeaders;
-class HttpResponseHeaders;
 class NetLog;
 class ProxyInfo;
 class ProxyServer;
@@ -58,16 +55,7 @@
                       const net::ProxyRetryInfoMap& proxy_retry_info,
                       net::ProxyInfo* result) override;
   void OnFallback(const net::ProxyServer& bad_proxy, int net_error) override;
-  void OnBeforeTunnelRequest(const net::HostPortPair& proxy_server,
-                             net::HttpRequestHeaders* extra_headers) override;
-  void OnTunnelConnectCompleted(const net::HostPortPair& endpoint,
-                                const net::HostPortPair& proxy_server,
-                                int net_error) override;
   bool IsTrustedSpdyProxy(const net::ProxyServer& proxy_server) override;
-  void OnTunnelHeadersReceived(
-      const net::HostPortPair& origin,
-      const net::HostPortPair& proxy_server,
-      const net::HttpResponseHeaders& response_headers) override;
 
   void SetTickClockForTesting(base::TickClock* tick_clock);
 
diff --git a/components/download/BUILD.gn b/components/download/BUILD.gn
index 985df7bab..4750fd24 100644
--- a/components/download/BUILD.gn
+++ b/components/download/BUILD.gn
@@ -5,15 +5,12 @@
 group("unit_tests") {
   testonly = true
 
-  deps = [
-    "//components/download/internal:unit_tests",
-  ]
-
   if (!is_ios) {
-    deps += [
+    deps = [
       "//components/download/content/internal:unit_tests",
       "//components/download/content/public:unit_tests",
       "//components/download/downloader/in_progress:unit_tests",
+      "//components/download/internal:unit_tests",
     ]
   }
 
diff --git a/components/download/components_unittests.filter b/components/download/components_unittests.filter
index a0360b2..9d1d8bd 100644
--- a/components/download/components_unittests.filter
+++ b/components/download/components_unittests.filter
@@ -10,6 +10,7 @@
 DownloadStoreTest.*
 FileMonitorTest.*
 InProgressConversionsTest.*
+InMemoryDownloadTest.*
 NavigationMonitorImplTest.*
 NetworkListenerTest.*
 ProtoConversionsTest.*
diff --git a/components/download/internal/BUILD.gn b/components/download/internal/BUILD.gn
index e8f2ef48..de298e9 100644
--- a/components/download/internal/BUILD.gn
+++ b/components/download/internal/BUILD.gn
@@ -16,6 +16,8 @@
   ]
 
   sources = [
+    "blob_task_proxy.cc",
+    "blob_task_proxy.h",
     "client_set.cc",
     "client_set.h",
     "config.cc",
@@ -39,6 +41,8 @@
     "file_monitor.h",
     "file_monitor_impl.cc",
     "file_monitor_impl.h",
+    "in_memory_download.cc",
+    "in_memory_download.h",
     "log_sink.h",
     "log_source.h",
     "logger_impl.cc",
@@ -75,6 +79,7 @@
     "//components/download/public",
     "//components/leveldb_proto",
     "//net",
+    "//storage/browser",
   ]
 
   if (is_android) {
@@ -122,6 +127,7 @@
     "download_store_unittest.cc",
     "entry_utils_unittest.cc",
     "file_monitor_unittest.cc",
+    "in_memory_download_unittest.cc",
     "model_impl_unittest.cc",
     "navigation_monitor_impl_unittests.cc",
     "proto_conversions_unittest.cc",
@@ -138,6 +144,7 @@
     "//components/download/public/test:test_support",
     "//components/leveldb_proto:test_support",
     "//net:test_support",
+    "//storage/browser",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/components/download/internal/DEPS b/components/download/internal/DEPS
index 85b754e..ed5bc8c 100644
--- a/components/download/internal/DEPS
+++ b/components/download/internal/DEPS
@@ -5,4 +5,6 @@
   "+base",
   "+jni",
   "+net",
+  "+storage/browser",
+  "+storage/common",
 ]
diff --git a/components/download/internal/blob_task_proxy.cc b/components/download/internal/blob_task_proxy.cc
new file mode 100644
index 0000000..1348f13
--- /dev/null
+++ b/components/download/internal/blob_task_proxy.cc
@@ -0,0 +1,83 @@
+// 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/download/internal/blob_task_proxy.h"
+
+#include "base/guid.h"
+#include "base/task_runner_util.h"
+#include "storage/browser/blob/blob_data_builder.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/blob/blob_storage_context.h"
+
+namespace download {
+
+// static
+std::unique_ptr<BlobTaskProxy> BlobTaskProxy::Create(
+    BlobContextGetter blob_context_getter,
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
+  return std::make_unique<BlobTaskProxy>(std::move(blob_context_getter),
+                                         io_task_runner);
+}
+
+BlobTaskProxy::BlobTaskProxy(
+    BlobContextGetter blob_context_getter,
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
+    : main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      io_task_runner_(io_task_runner),
+      weak_ptr_factory_(this) {
+  // Unretained the raw pointer because owner on UI thread should destroy this
+  // object on IO thread.
+  io_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&BlobTaskProxy::InitializeOnIO, base::Unretained(this),
+                     std::move(blob_context_getter)));
+}
+
+BlobTaskProxy::~BlobTaskProxy() {
+  io_task_runner_->BelongsToCurrentThread();
+}
+
+void BlobTaskProxy::InitializeOnIO(BlobContextGetter blob_context_getter) {
+  io_task_runner_->BelongsToCurrentThread();
+  blob_storage_context_ = std::move(blob_context_getter).Run();
+}
+
+void BlobTaskProxy::SaveAsBlob(std::unique_ptr<std::string> data,
+                               BlobDataHandleCallback callback) {
+  // Unretained the raw pointer because owner on UI thread should destroy this
+  // object on IO thread.
+  io_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&BlobTaskProxy::SaveAsBlobOnIO, base::Unretained(this),
+                     std::move(data), std::move(callback)));
+}
+
+void BlobTaskProxy::SaveAsBlobOnIO(std::unique_ptr<std::string> data,
+                                   BlobDataHandleCallback callback) {
+  io_task_runner_->BelongsToCurrentThread();
+
+  // Build blob data.
+  std::string blob_uuid = base::GenerateGUID();
+  storage::BlobDataBuilder builder(blob_uuid);
+  builder.AppendData(*data.get());
+  blob_data_handle_ = blob_storage_context_->AddFinishedBlob(builder);
+
+  // Wait for blob data construction complete.
+  auto cb = base::BindRepeating(&BlobTaskProxy::BlobSavedOnIO,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                base::Passed(std::move(callback)));
+  blob_data_handle_->RunOnConstructionComplete(cb);
+}
+
+void BlobTaskProxy::BlobSavedOnIO(BlobDataHandleCallback callback,
+                                  storage::BlobStatus status) {
+  io_task_runner_->BelongsToCurrentThread();
+
+  // Relay BlobDataHandle and |status| back to main thread.
+  auto cb = base::BindOnce(std::move(callback),
+                           base::Passed(std::move(blob_data_handle_)), status);
+  main_task_runner_->PostTask(FROM_HERE, std::move(cb));
+}
+
+}  // namespace download
diff --git a/components/download/internal/blob_task_proxy.h b/components/download/internal/blob_task_proxy.h
new file mode 100644
index 0000000..e757554
--- /dev/null
+++ b/components/download/internal/blob_task_proxy.h
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_BLOB_TASK_PROXY_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_BLOB_TASK_PROXY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "storage/common/blob_storage/blob_storage_constants.h"
+
+namespace storage {
+class BlobDataHandle;
+class BlobStorageContext;
+}  // namespace storage
+
+namespace download {
+
+// Proxy for blob related task on IO thread.
+// Created on main thread and do work on IO thread, destroyed on IO thread.
+class BlobTaskProxy {
+ public:
+  using BlobContextGetter =
+      base::OnceCallback<base::WeakPtr<storage::BlobStorageContext>()>;
+  using BlobDataHandleCallback =
+      base::OnceCallback<void(std::unique_ptr<storage::BlobDataHandle>,
+                              storage::BlobStatus status)>;
+
+  static std::unique_ptr<BlobTaskProxy> Create(
+      BlobContextGetter blob_context_getter,
+      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
+
+  BlobTaskProxy(BlobContextGetter blob_context_getter,
+                scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
+  ~BlobTaskProxy();
+
+  // Save blob data on UI thread. |callback| will be called on main thread after
+  // blob construction completes.
+  void SaveAsBlob(std::unique_ptr<std::string> data,
+                  BlobDataHandleCallback callback);
+
+ private:
+  void InitializeOnIO(BlobContextGetter blob_context_getter);
+
+  void SaveAsBlobOnIO(std::unique_ptr<std::string> data,
+                      BlobDataHandleCallback callback);
+
+  void BlobSavedOnIO(BlobDataHandleCallback callback,
+                     storage::BlobStatus status);
+
+  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+  // Used to build blob data, must accessed on |io_task_runner_|.
+  base::WeakPtr<storage::BlobStorageContext> blob_storage_context_;
+
+  // Used to access blob storage context.
+  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+  // BlobDataHandle that will be eventually passed to main thread.
+  std::unique_ptr<storage::BlobDataHandle> blob_data_handle_;
+
+  // Bounded to IO thread task runner.
+  base::WeakPtrFactory<BlobTaskProxy> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(BlobTaskProxy);
+};
+
+}  // namespace download
+
+#endif  // COMPONENTS_DOWNLOAD_INTERNAL_BLOB_TASK_PROXY_H_
diff --git a/components/download/internal/in_memory_download.cc b/components/download/internal/in_memory_download.cc
new file mode 100644
index 0000000..d1d0e55
--- /dev/null
+++ b/components/download/internal/in_memory_download.cc
@@ -0,0 +1,162 @@
+// 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/download/internal/in_memory_download.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "components/download/internal/blob_task_proxy.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "net/url_request/url_fetcher.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/blob/blob_storage_context.h"
+
+namespace download {
+
+namespace {
+
+// Converts a string to HTTP method used by URLFetcher.
+net::URLFetcher::RequestType ToRequestType(const std::string& method) {
+  // Only supports GET and POST.
+  if (base::EqualsCaseInsensitiveASCII(method, "GET"))
+    return net::URLFetcher::RequestType::GET;
+  if (base::EqualsCaseInsensitiveASCII(method, "POST"))
+    return net::URLFetcher::RequestType::POST;
+
+  NOTREACHED();
+  return net::URLFetcher::RequestType::GET;
+}
+
+}  // namespace
+
+InMemoryDownload::InMemoryDownload(
+    const std::string& guid,
+    const RequestParams& request_params,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation,
+    Delegate* delegate,
+    scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+    BlobTaskProxy::BlobContextGetter blob_context_getter,
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
+    : guid_(guid),
+      request_params_(request_params),
+      traffic_annotation_(traffic_annotation),
+      request_context_getter_(request_context_getter),
+      blob_task_proxy_(BlobTaskProxy::Create(std::move(blob_context_getter),
+                                             io_task_runner)),
+      io_task_runner_(io_task_runner),
+      state_(State::INITIAL),
+      delegate_(delegate),
+      bytes_downloaded_(0u),
+      weak_ptr_factory_(this) {
+  DCHECK(!guid_.empty());
+}
+
+InMemoryDownload::~InMemoryDownload() {
+  io_task_runner_->DeleteSoon(FROM_HERE, blob_task_proxy_.release());
+}
+
+void InMemoryDownload::Start() {
+  DCHECK(state_ == State::INITIAL);
+  url_fetcher_ = net::URLFetcher::Create(request_params_.url,
+                                         ToRequestType(request_params_.method),
+                                         this, traffic_annotation_);
+  url_fetcher_->SetRequestContext(request_context_getter_.get());
+  url_fetcher_->SetExtraRequestHeaders(
+      request_params_.request_headers.ToString());
+  url_fetcher_->Start();
+  state_ = State::IN_PROGRESS;
+}
+
+std::unique_ptr<storage::BlobDataHandle> InMemoryDownload::ResultAsBlob() {
+  DCHECK(state_ == State::COMPLETE || state_ == State::FAILED);
+  // Return a copy.
+  return std::make_unique<storage::BlobDataHandle>(*blob_data_handle_);
+}
+
+void InMemoryDownload::OnURLFetchDownloadProgress(
+    const net::URLFetcher* source,
+    int64_t current,
+    int64_t total,
+    int64_t current_network_bytes) {
+  bytes_downloaded_ = current;
+
+  if (delegate_)
+    delegate_->OnDownloadProgress(this);
+}
+
+void InMemoryDownload::OnURLFetchComplete(const net::URLFetcher* source) {
+  switch (source->GetStatus().status()) {
+    case net::URLRequestStatus::Status::SUCCESS:
+      if (HandleResponseCode(source->GetResponseCode())) {
+        SaveAsBlob();
+        return;
+      }
+
+      state_ = State::FAILED;
+      NotifyDelegateDownloadComplete();
+      return;
+    case net::URLRequestStatus::Status::IO_PENDING:
+      return;
+    case net::URLRequestStatus::Status::CANCELED:
+    case net::URLRequestStatus::Status::FAILED:
+      state_ = State::FAILED;
+      NotifyDelegateDownloadComplete();
+      break;
+  }
+}
+
+bool InMemoryDownload::HandleResponseCode(int response_code) {
+  switch (response_code) {
+    case -1:  // Non-HTTP request.
+    case net::HTTP_OK:
+    case net::HTTP_NON_AUTHORITATIVE_INFORMATION:
+    case net::HTTP_PARTIAL_CONTENT:
+    case net::HTTP_CREATED:
+    case net::HTTP_ACCEPTED:
+    case net::HTTP_NO_CONTENT:
+    case net::HTTP_RESET_CONTENT:
+      return true;
+    // All other codes are considered as failed.
+    default:
+      return false;
+  }
+}
+
+void InMemoryDownload::SaveAsBlob() {
+  DCHECK(url_fetcher_);
+
+  // This will copy the internal memory in |url_fetcher| into |data|.
+  // TODO(xingliu): Use response writer to avoid one extra copies. And destroy
+  // |url_fetcher_| at the correct time.
+  std::unique_ptr<std::string> data = std::make_unique<std::string>();
+  DCHECK(url_fetcher_->GetResponseAsString(data.get()));
+
+  auto callback = base::BindOnce(&InMemoryDownload::OnSaveBlobDone,
+                                 weak_ptr_factory_.GetWeakPtr());
+  blob_task_proxy_->SaveAsBlob(std::move(data), std::move(callback));
+}
+
+void InMemoryDownload::OnSaveBlobDone(
+    std::unique_ptr<storage::BlobDataHandle> blob_handle,
+    storage::BlobStatus status) {
+  // |status| is valid on IO thread, consumer of |blob_handle| should validate
+  // the data when using the blob data.
+  state_ =
+      (status == storage::BlobStatus::DONE) ? State::COMPLETE : State::FAILED;
+  blob_data_handle_ = std::move(blob_handle);
+
+  NotifyDelegateDownloadComplete();
+}
+
+void InMemoryDownload::NotifyDelegateDownloadComplete() {
+  if (delegate_)
+    delegate_->OnDownloadComplete(this);
+}
+
+}  // namespace download
diff --git a/components/download/internal/in_memory_download.h b/components/download/internal/in_memory_download.h
new file mode 100644
index 0000000..f499758e
--- /dev/null
+++ b/components/download/internal/in_memory_download.h
@@ -0,0 +1,163 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_IN_MEMORY_DOWNLOAD_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_IN_MEMORY_DOWNLOAD_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "components/download/internal/blob_task_proxy.h"
+#include "components/download/public/download_params.h"
+#include "net/base/completion_callback.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace net {
+class URLFetcher;
+struct NetworkTrafficAnnotationTag;
+}  // namespace net
+
+namespace storage {
+class BlobDataHandle;
+}  // namespace storage
+
+namespace download {
+
+struct RequestParams;
+
+// Object to start a single download and hold in-memory download data.
+// Used by download service in Incognito mode, where download files shouldn't
+// be persisted to disk.
+//
+// Life cycle: The object is created before creating the network request.
+// Call Start() to send the network request.
+//
+// Threading contract:
+// 1. This object lives on the main thread.
+// 2. Reading/writing IO buffer from network is done on another thread,
+// based on |request_context_getter_|. When complete, main thread is notified.
+// 3. After network IO is done, Blob related work is done on IO thread with
+// |blob_task_proxy_|, then notify the result to main thread.
+
+class InMemoryDownload : public net::URLFetcherDelegate {
+ public:
+  // Report download progress with in-memory download backend.
+  class Delegate {
+   public:
+    virtual void OnDownloadProgress(InMemoryDownload* download) = 0;
+    virtual void OnDownloadComplete(InMemoryDownload* download) = 0;
+
+   protected:
+    virtual ~Delegate() = default;
+  };
+
+  // States of the download.
+  enum class State {
+    // The object is created but network request has not been sent.
+    INITIAL,
+
+    // Download is in progress, including the following procedures.
+    // 1. Transfer network data.
+    // 2. Save to blob storage.
+    IN_PROGRESS,
+
+    // The download can fail due to:
+    // 1. network layer failure or unsuccessful HTTP server response code.
+    // 2. Blob system failures after blob construction is done.
+    FAILED,
+
+    // Download is completed, and data is successfully saved as a blob.
+    // 1. We guarantee the states of network responses.
+    // 2. Do not guarantee the state of blob data. The consumer of blob
+    // should validate its state when using it on IO thread.
+    COMPLETE,
+  };
+
+  InMemoryDownload(
+      const std::string& guid,
+      const RequestParams& request_params,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
+      Delegate* delegate,
+      scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+      BlobTaskProxy::BlobContextGetter blob_context_getter,
+      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
+  ~InMemoryDownload() override;
+
+  // Send the download request.
+  void Start();
+
+  // Get a copy of blob data handle.
+  std::unique_ptr<storage::BlobDataHandle> ResultAsBlob();
+
+  const std::string& guid() const { return guid_; }
+  uint64_t bytes_downloaded() const { return bytes_downloaded_; }
+  State state() const { return state_; }
+
+ private:
+  // net::URLFetcherDelegate implementation.
+  void OnURLFetchDownloadProgress(const net::URLFetcher* source,
+                                  int64_t current,
+                                  int64_t total,
+                                  int64_t current_network_bytes) override;
+  void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+  // Handles response code and change the state accordingly.
+  // Returns if the response code is considered as successful code.
+  bool HandleResponseCode(int response_code);
+
+  // Saves the download data into blob storage.
+  void SaveAsBlob();
+  void OnSaveBlobDone(std::unique_ptr<storage::BlobDataHandle> blob_handle,
+                      storage::BlobStatus status);
+
+  // Notifies the delegate about completion.
+  void NotifyDelegateDownloadComplete();
+
+  // GUID of the download.
+  const std::string guid_;
+
+  // Request parameters of the download.
+  const RequestParams request_params_;
+
+  // Traffic annotation of the request.
+  const net::NetworkTrafficAnnotationTag traffic_annotation_;
+
+  // Used to send requests to servers. Also contains the download data in its
+  // string buffer. We should avoid extra copy on the data and release the
+  // memory when needed.
+  std::unique_ptr<net::URLFetcher> url_fetcher_;
+
+  // Request context getter used by |url_fetcher_|.
+  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+
+  // Worker that does blob related task on IO thread.
+  std::unique_ptr<BlobTaskProxy> blob_task_proxy_;
+
+  // Owned blob data handle, so that blob system keeps at least one reference
+  // count of the underlying data.
+  std::unique_ptr<storage::BlobDataHandle> blob_data_handle_;
+
+  // Used to access blob storage context.
+  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+  State state_;
+
+  Delegate* delegate_;
+
+  uint64_t bytes_downloaded_;
+
+  // Bounded to main thread task runner.
+  base::WeakPtrFactory<InMemoryDownload> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(InMemoryDownload);
+};
+
+}  // namespace download
+
+#endif  // COMPONENTS_DOWNLOAD_INTERNAL_IN_MEMORY_DOWNLOAD_H_
diff --git a/components/download/internal/in_memory_download_unittest.cc b/components/download/internal/in_memory_download_unittest.cc
new file mode 100644
index 0000000..dce0b15
--- /dev/null
+++ b/components/download/internal/in_memory_download_unittest.cc
@@ -0,0 +1,127 @@
+// 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/download/internal/in_memory_download.h"
+
+#include "base/files/file_util.h"
+#include "base/guid.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request_test_util.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace download {
+namespace {
+
+class MockDelegate : public InMemoryDownload::Delegate {
+ public:
+  MockDelegate() = default;
+
+  void WaitForCompletion() {
+    DCHECK(!run_loop_.running());
+    run_loop_.Run();
+  }
+
+  // InMemoryDownload::Delegate implementation.
+  MOCK_METHOD1(OnDownloadProgress, void(InMemoryDownload*));
+  void OnDownloadComplete(InMemoryDownload* download) {
+    if (run_loop_.running())
+      run_loop_.Quit();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockDelegate);
+};
+
+// Must run on IO thread task runner.
+base::WeakPtr<storage::BlobStorageContext> BlobStorageContextGetter(
+    storage::BlobStorageContext* blob_context) {
+  DCHECK(blob_context);
+  return blob_context->AsWeakPtr();
+}
+
+class InMemoryDownloadTest : public testing::Test {
+ public:
+  InMemoryDownloadTest() = default;
+  ~InMemoryDownloadTest() override = default;
+
+  void SetUp() override {
+    test_server_.ServeFilesFromDirectory(GetTestDataDirectory());
+    ASSERT_TRUE(test_server_.Start());
+
+    io_thread_.reset(new base::Thread("Network and Blob IO thread"));
+    base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
+    io_thread_->StartWithOptions(options);
+    request_context_getter_ =
+        new net::TestURLRequestContextGetter(io_thread_->task_runner());
+    blob_storage_context_ = std::make_unique<storage::BlobStorageContext>();
+  }
+
+  void TearDown() override {
+    // Say goodbye to |blob_storage_context_| on IO thread.
+    io_thread_->task_runner()->DeleteSoon(FROM_HERE,
+                                          blob_storage_context_.release());
+  }
+
+ protected:
+  base::FilePath GetTestDataDirectory() {
+    base::FilePath test_data_dir;
+    EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
+    return test_data_dir.AppendASCII("components/test/data/download");
+  }
+
+  // Helper method to create a download with request_params.
+  void CreateDownload(const RequestParams& request_params) {
+    download_ = std::make_unique<InMemoryDownload>(
+        base::GenerateGUID(), request_params, TRAFFIC_ANNOTATION_FOR_TESTS,
+        delegate(), request_context_getter_,
+        base::BindOnce(&BlobStorageContextGetter, blob_storage_context_.get()),
+        io_thread_->task_runner());
+  }
+
+  InMemoryDownload* download() { return download_.get(); }
+  MockDelegate* delegate() { return &mock_delegate_; }
+  net::EmbeddedTestServer* test_server() { return &test_server_; }
+
+ private:
+  // IO thread used by network and blob IO tasks.
+  std::unique_ptr<base::Thread> io_thread_;
+
+  // Message loop for the main thread.
+  base::MessageLoop main_loop;
+
+  std::unique_ptr<InMemoryDownload> download_;
+  MockDelegate mock_delegate_;
+
+  scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+  std::unique_ptr<storage::BlobStorageContext> blob_storage_context_;
+  net::EmbeddedTestServer test_server_;
+
+  DISALLOW_COPY_AND_ASSIGN(InMemoryDownloadTest);
+};
+
+TEST_F(InMemoryDownloadTest, DownloadTest) {
+  RequestParams request_params;
+  request_params.url = test_server()->GetURL("/text_data.json");
+  CreateDownload(request_params);
+  download()->Start();
+  delegate()->WaitForCompletion();
+
+  base::FilePath path = GetTestDataDirectory().AppendASCII("text_data.json");
+  std::string data;
+  EXPECT_TRUE(ReadFileToString(path, &data));
+  EXPECT_EQ(InMemoryDownload::State::COMPLETE, download()->state());
+  // TODO(xingliu): Read the blob and verify data.
+}
+
+}  // namespace
+
+}  // namespace download
diff --git a/components/payments/content/payment_request_dialog.h b/components/payments/content/payment_request_dialog.h
index 04603fa..e334129 100644
--- a/components/payments/content/payment_request_dialog.h
+++ b/components/payments/content/payment_request_dialog.h
@@ -25,8 +25,12 @@
 
   virtual void ShowErrorMessage() = 0;
 
+  // Shows a "Processing..." spinner.
   virtual void ShowProcessingSpinner() = 0;
 
+  // Whether a "Processing..." spinner is showing.
+  virtual bool IsInteractive() const = 0;
+
   // Shows the CVC unmask sheet and starts a FullCardRequest with the info
   // entered by the user.
   virtual void ShowCvcUnmaskPrompt(
diff --git a/components/payments/content/payment_request_spec.h b/components/payments/content/payment_request_spec.h
index d95c3fe..4bcdf972 100644
--- a/components/payments/content/payment_request_spec.h
+++ b/components/payments/content/payment_request_spec.h
@@ -82,6 +82,8 @@
   bool request_payer_email() const override;
   PaymentShippingType shipping_type() const override;
 
+  bool supports_basic_card() const { return !supported_card_networks_.empty(); }
+
   const std::vector<std::string>& supported_card_networks() const {
     return supported_card_networks_;
   }
diff --git a/components/sync_sessions/sessions_sync_manager.cc b/components/sync_sessions/sessions_sync_manager.cc
index efa9b68c..9972d472 100644
--- a/components/sync_sessions/sessions_sync_manager.cc
+++ b/components/sync_sessions/sessions_sync_manager.cc
@@ -79,7 +79,8 @@
 }
 
 std::string TabNodeIdToTag(const std::string& machine_tag, int tab_node_id) {
-  CHECK_GT(tab_node_id, TabNodePool::kInvalidTabNodeID) << "crbug.com/673618";
+  CHECK_GT(tab_node_id, TabNodePool::kInvalidTabNodeID)
+      << "https://crbug.com/639009";
   return base::StringPrintf("%s %d", machine_tag.c_str(), tab_node_id);
 }
 
@@ -251,7 +252,7 @@
 #endif
 
   // Check if anything has changed on the local client side.
-  AssociateWindows(RELOAD_TABS, &new_changes);
+  AssociateWindows(RELOAD_TABS, ScanForTabbedWindow(), &new_changes);
   local_tab_pool_out_of_sync_ = false;
 
   merge_result.set_error(
@@ -263,6 +264,7 @@
 
 void SessionsSyncManager::AssociateWindows(
     ReloadTabsOption option,
+    bool has_tabbed_window,
     syncer::SyncChangeList* change_output) {
   // Note that |current_session| is a pointer owned by |session_tracker_|.
   // |session_tracker_| will continue to update |current_session| under
@@ -280,18 +282,10 @@
   SyncedWindowDelegatesGetter::SyncedWindowDelegateMap windows =
       synced_window_delegates_getter()->GetSyncedWindowDelegates();
 
-  // On Android, it's possible to not have any tabbed windows if this is a cold
-  // start triggered for a custom tab. In that case, the previous session must
-  // be restored, otherwise it will be lost. On the other hand, if there is at
-  // least one tabbed window open, it's safe to overwrite the previous session
-  // entirely. See crbug.com/639009 for more info.
-  bool found_tabbed_window = false;
-  for (auto& window_iter_pair : windows) {
-    if (window_iter_pair.second->IsTypeTabbed())
-      found_tabbed_window = true;
-  }
-
-  if (found_tabbed_window) {
+  // Without native data, we need be careful not to obliterate any old
+  // information, while at the same time handling updated tab ids. See
+  // https://crbug.com/639009 for more info.
+  if (has_tabbed_window) {
     // Just reset the session tracking. No need to worry about the previous
     // session; the current tabbed windows are now the source of truth.
     session_tracker_.ResetSessionTracking(current_machine_tag());
@@ -326,6 +320,9 @@
     }
   }
 
+  // TODO(skym): Scan for duplicate sync ids and remove,
+  // https://crbug.com/639009.
+
   for (auto& window_iter_pair : windows) {
     const SyncedWindowDelegate* window_delegate = window_iter_pair.second;
     if (option == RELOAD_TABS) {
@@ -340,7 +337,6 @@
     // about to be deleted, so we ignore it.
     if (window_delegate->ShouldSync() && window_delegate->GetTabCount() &&
         window_delegate->HasWindow()) {
-      sync_pb::SessionWindow window_s;
       SessionID::id_type window_id = window_delegate->GetSessionId();
       DVLOG(1) << "Associating window " << window_id << " with "
                << window_delegate->GetTabCount() << " tabs.";
@@ -372,7 +368,7 @@
             DVLOG(1) << "Placeholder tab " << tab_id << " has no sync id.";
           }
         } else if (RELOAD_TABS == option) {
-          AssociateTab(synced_tab, change_output);
+          AssociateTab(synced_tab, has_tabbed_window, change_output);
         }
 
         // If the tab was syncable, it would have been added to the tracker
@@ -432,6 +428,7 @@
 }
 
 void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab_delegate,
+                                       bool has_tabbed_window,
                                        syncer::SyncChangeList* change_output) {
   DCHECK(!tab_delegate->IsPlaceholderTab());
 
@@ -457,11 +454,20 @@
   if (session_tracker_.IsLocalTabNodeAssociated(tab_delegate->GetSyncId())) {
     tab_node_id = tab_delegate->GetSyncId();
     session_tracker_.ReassociateLocalTab(tab_node_id, tab_id);
-  } else {
+  } else if (has_tabbed_window) {
     existing_tab_node =
         session_tracker_.GetTabNodeFromLocalTabId(tab_id, &tab_node_id);
-    CHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id) << "crbug.com/673618";
+    CHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id)
+        << "https://crbug.com/639009";
     tab_delegate->SetSyncId(tab_node_id);
+  } else {
+    // Only allowed to allocate sync ids when we have native data, which is only
+    // true when we have a tabbed window. Without a sync id we cannot sync this
+    // data, the tracker cannot even really track it. So don't do any more work.
+    // This effectively breaks syncing custom tabs when the native browser isn't
+    // fully loaded. Ideally this is fixed by saving tab data and sync data
+    // atomically, see https://crbug.com/681921.
+    return;
   }
 
   sessions::SessionTab* session_tab =
@@ -604,13 +610,14 @@
     return;
   }
 
+  bool found_tabbed_window = ScanForTabbedWindow();
   syncer::SyncChangeList changes;
-  AssociateTab(modified_tab, &changes);
+  AssociateTab(modified_tab, found_tabbed_window, &changes);
   // Note, we always associate windows because it's possible a tab became
   // "interesting" by going to a valid URL, in which case it needs to be added
   // to the window's tab information. Similarly, if a tab became
   // "uninteresting", we remove it from the window's tab information.
-  AssociateWindows(DONT_RELOAD_TABS, &changes);
+  AssociateWindows(DONT_RELOAD_TABS, found_tabbed_window, &changes);
   sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
 }
 
@@ -658,7 +665,7 @@
     for (auto& tab : win_iter.second->wrapped_window.tabs) {
       // TODO(zea): replace with with the correct tab node id once there's a
       // sync specific wrapper for SessionTab. This method is only used in
-      // tests though, so it's fine for now. crbug.com/662597
+      // tests though, so it's fine for now. https://crbug.com/662597
       int tab_node_id = 0;
       sync_pb::EntitySpecifics entity;
       entity.mutable_session()->CopyFrom(
@@ -801,7 +808,7 @@
         // to that foreign data (like deletion through garbage collection) to
         // trigger a data type error because the tag looking mechanism fails. So
         // look for these and delete via remote SyncData, which uses a server id
-        // lookup mechanism instead, see crbug.com/604657.
+        // lookup mechanism instead, see https://crbug.com/604657.
         bad_foreign_hash_count++;
         new_changes->push_back(
             syncer::SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, remote));
@@ -1400,4 +1407,25 @@
   }
 }
 
+bool SessionsSyncManager::ScanForTabbedWindow() {
+  for (auto& window_iter_pair :
+       synced_window_delegates_getter()->GetSyncedWindowDelegates()) {
+    if (window_iter_pair.second->IsTypeTabbed()) {
+      const SyncedWindowDelegate* window_delegate = window_iter_pair.second;
+      if (window_delegate->ShouldSync() && window_delegate->GetTabCount() &&
+          window_delegate->HasWindow()) {
+        // When only custom tab windows are open, often we'll have a seemingly
+        // okay type tabbed window, but GetTabAt will return null for each
+        // index. This case is exactly what this method needs to protect
+        // against.
+        for (int j = 0; j < window_delegate->GetTabCount(); ++j) {
+          if (window_delegate->GetTabAt(j))
+            return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
 };  // namespace sync_sessions
diff --git a/components/sync_sessions/sessions_sync_manager.h b/components/sync_sessions/sessions_sync_manager.h
index a42a1fd1..eea4a2f 100644
--- a/components/sync_sessions/sessions_sync_manager.h
+++ b/components/sync_sessions/sessions_sync_manager.h
@@ -225,13 +225,16 @@
   // changes for processing later.
   enum ReloadTabsOption { RELOAD_TABS, DONT_RELOAD_TABS };
   void AssociateWindows(ReloadTabsOption option,
+                        bool has_tabbed_window,
                         syncer::SyncChangeList* change_output);
 
   // Loads and reassociates the local tabs referenced in |tabs|.
   // |change_output| *must* be provided as a link to the SyncChange pipeline
   // that exists in the caller's context. This function will append necessary
-  // changes for processing later.
+  // changes for processing later. Will only assign a new sync id if there is
+  // a tabbed window, which results in failure for tabs without sync ids yet.
   void AssociateTab(SyncedTabDelegate* const tab,
+                    bool has_tabbed_window,
                     syncer::SyncChangeList* change_output);
 
   // Set |session_tab| from |tab_delegate| and |mtime|.
@@ -282,7 +285,7 @@
   // specifics is the model type, ie us. We need to generate the tag because it
   // is not passed over the wire for remote data. The use case this function was
   // created for is detecting bad tag hashes from remote data, see
-  // crbug.com/604657.
+  // https://crbug.com/604657.
   static std::string TagHashFromSpecifics(
       const sync_pb::SessionSpecifics& specifics);
 
@@ -292,6 +295,12 @@
 
   void CleanupNavigationTracking();
 
+  // On Android, it's possible to not have any tabbed windows when only custom
+  // tabs are currently open. This means that there is tab data that will be
+  // restored later, but we cannot access it. This method is an elaborate way to
+  // check if we're currently in that state or not.
+  bool ScanForTabbedWindow();
+
   // The client of this sync sessions datatype.
   SyncSessionsClient* const sessions_client_;
 
diff --git a/components/sync_sessions/sessions_sync_manager_unittest.cc b/components/sync_sessions/sessions_sync_manager_unittest.cc
index 325edf7..c47117a 100644
--- a/components/sync_sessions/sessions_sync_manager_unittest.cc
+++ b/components/sync_sessions/sessions_sync_manager_unittest.cc
@@ -1025,6 +1025,17 @@
   ASSERT_EQ(
       restored_tab_id,
       out[1].sync_data().GetSpecifics().session().header().window(0).tab(0));
+  out.clear();
+
+  // Now actually resurrect the native data, which will end up having different
+  // native ids, but the tab has the same sync id as before.
+  AddWindow()->OverrideTabAt(0, tab);
+  NavigateTab(tab, kBar1);
+
+  ASSERT_TRUE(ChangeTypeMatches(
+      out, {SyncChange::ACTION_UPDATE, SyncChange::ACTION_UPDATE}));
+  VerifyLocalTabChange(out[0], 3, kBar1);
+  VerifyLocalHeaderChange(out[1], 1, 1);
 }
 
 // Ensure that tabbed windows from a previous session are preserved if only
@@ -1055,49 +1066,36 @@
   window->OverrideTabAt(0, custom_tab.get());
   InitWithSyncDataTakeOutput(ConvertToRemote(in), &out);
 
-  // The previous session should be preserved, and the transient window should
-  // be synced as a new transient window. This means that the original tab
-  // node will be updated with its new tab id, a new tab node will be created,
-  // and the header will be updated to reflect the two windows and two tabs.
-  ASSERT_TRUE(
-      ChangeTypeMatches(out, {SyncChange::ACTION_UPDATE, SyncChange::ACTION_ADD,
-                              SyncChange::ACTION_UPDATE}));
+  // The previous session should be preserved. The transient window cannot be
+  // synced because we do not have enough local data to ensure that we wouldn't
+  // vend the same sync id if our persistent storage didn't match upon the last
+  // shutdown.
+  ASSERT_TRUE(ChangeTypeMatches(
+      out, {SyncChange::ACTION_UPDATE, SyncChange::ACTION_UPDATE}));
   VerifyLocalTabChange(out[0], 2, kFoo2);
-  VerifyLocalTabChange(out[1], 1, kBar1);
-  VerifyLocalHeaderChange(out[2], 2, 2);
+  VerifyLocalHeaderChange(out[1], 1, 1);
+  out.clear();
 
-  // The two windows should have different window types.
-  ASSERT_EQ(sync_pb::SessionWindow::TYPE_CUSTOM_TAB, out[2]
-                                                         .sync_data()
-                                                         .GetSpecifics()
-                                                         .session()
-                                                         .header()
-                                                         .window(0)
-                                                         .browser_type());
-  ASSERT_EQ(sync_pb::SessionWindow::TYPE_TABBED, out[2]
-                                                     .sync_data()
-                                                     .GetSpecifics()
-                                                     .session()
-                                                     .header()
-                                                     .window(1)
-                                                     .browser_type());
+  // Now re-create local data and modify it.
+  TestSyncedWindowDelegate* alive_again = AddWindow();
+  alive_again->OverrideTabAt(0, tab);
+  NavigateTab(tab, kBaz1);
 
-  // Verify the tab id of the restored tab is updated and consistent.
-  int restored_tab_id =
-      out[0].sync_data().GetSpecifics().session().tab().tab_id();
-  // SessionId should be rewritten on restore.
-  ASSERT_NE(tab->GetSessionId(), restored_tab_id);
-  ASSERT_EQ(
-      restored_tab_id,
-      out[2].sync_data().GetSpecifics().session().header().window(1).tab(0));
+  // The local change should be created and tracked correctly. This doesn't
+  // actually start syncing the custom tab yet, because the tab itself isn't
+  // associated yet.
+  ASSERT_TRUE(ChangeTypeMatches(
+      out, {SyncChange::ACTION_UPDATE, SyncChange::ACTION_UPDATE}));
+  VerifyLocalTabChange(out[0], 3, kBaz1);
+  VerifyLocalHeaderChange(out[1], 1, 1);
+  out.clear();
 
-  // Verify the tab id of the custom tab is consistent.
-  int custom_tab_id =
-      out[1].sync_data().GetSpecifics().session().tab().tab_id();
-  ASSERT_EQ(custom_tab->GetSessionId(), custom_tab_id);
-  ASSERT_EQ(
-      custom_tab_id,
-      out[2].sync_data().GetSpecifics().session().header().window(0).tab(0));
+  // Now trigger OnLocalTabModified() for the custom tab again, it should sync.
+  NavigateTab(custom_tab.get(), kBar2);
+  ASSERT_TRUE(ChangeTypeMatches(
+      out, {SyncChange::ACTION_ADD, SyncChange::ACTION_UPDATE}));
+  VerifyLocalTabChange(out[0], 2, kBar2);
+  VerifyLocalHeaderChange(out[1], 2, 2);
 }
 
 // Tests MergeDataAndStartSyncing with sync data but no local data.
@@ -1787,7 +1785,7 @@
 
 // Verifies that we drop both headers and tabs during merge if their stored tag
 // hash doesn't match a computer tag hash. This mitigates potential failures
-// while cleaning up bad foreign data, see crbug.com/604657.
+// while cleaning up bad foreign data, see https://crbug.com/604657.
 TEST_F(SessionsSyncManagerTest, MergeDeletesBadHash) {
   SyncDataList foreign_data;
   std::vector<SessionID::id_type> empty_ids;
diff --git a/components/sync_sessions/synced_session_tracker.cc b/components/sync_sessions/synced_session_tracker.cc
index 4b2975a..2a6960c 100644
--- a/components/sync_sessions/synced_session_tracker.cc
+++ b/components/sync_sessions/synced_session_tracker.cc
@@ -296,7 +296,7 @@
     // TODO(zea): remove this once PutTabInWindow isn't crashing anymore.
     CHECK(tab) << " Unable to find tab " << tab_id
                << " within unmapped tabs or previously mapped windows."
-               << " crbug.com/639009";
+               << " https://crbug.com/639009";
   }
 
   tab->window_id.set_id(window_id);
@@ -316,7 +316,8 @@
 sessions::SessionTab* SyncedSessionTracker::GetTab(
     const std::string& session_tag,
     SessionID::id_type tab_id) {
-  CHECK_NE(TabNodePool::kInvalidTabNodeID, tab_id) << "crbug.com/673618";
+  CHECK_NE(TabNodePool::kInvalidTabNodeID, tab_id)
+      << "https://crbug.com/639009";
   sessions::SessionTab* tab_ptr = nullptr;
   auto iter = synced_tab_map_[session_tag].find(tab_id);
   if (iter != synced_tab_map_[session_tag].end()) {
diff --git a/components/sync_sessions/synced_session_tracker.h b/components/sync_sessions/synced_session_tracker.h
index 8426935..a8c9480f 100644
--- a/components/sync_sessions/synced_session_tracker.h
+++ b/components/sync_sessions/synced_session_tracker.h
@@ -131,7 +131,7 @@
   // |tab_id| for the session specified with |session_tag|.
   // Note: Ownership of the SessionTab remains within the SyncedSessionTracker.
   // TODO(zea): Replace SessionTab with a Sync specific wrapper.
-  // crbug.com/662597
+  // https://crbug.com/662597
   sessions::SessionTab* GetTab(const std::string& session_tag,
                                SessionID::id_type tab_id);
 
diff --git a/components/sync_sessions/tab_node_pool.cc b/components/sync_sessions/tab_node_pool.cc
index 1b4c1f88..19bdd43 100644
--- a/components/sync_sessions/tab_node_pool.cc
+++ b/components/sync_sessions/tab_node_pool.cc
@@ -124,7 +124,7 @@
   // If number of free nodes exceed kFreeNodesHighWatermark,
   // delete sync nodes till number reaches kFreeNodesLowWatermark.
   // Note: This logic is to mitigate temporary disassociation issues with old
-  // clients: http://crbug.com/259918. Newer versions do not need this.
+  // clients: https://crbug.com/259918. Newer versions do not need this.
   if (free_nodes_pool_.size() > kFreeNodesHighWatermark) {
     for (std::set<int>::iterator free_it = free_nodes_pool_.begin();
          free_it != free_nodes_pool_.end();) {
diff --git a/components/test/data/download/text_data.json b/components/test/data/download/text_data.json
new file mode 100644
index 0000000..369f7de
--- /dev/null
+++ b/components/test/data/download/text_data.json
@@ -0,0 +1,3 @@
+{  
+   "data":"In earlier tellings, the dog had a better reputation than the cat, however the president veto it."
+}
\ No newline at end of file
diff --git a/components/viz/common/display/renderer_settings.h b/components/viz/common/display/renderer_settings.h
index d78867d..653b258 100644
--- a/components/viz/common/display/renderer_settings.h
+++ b/components/viz/common/display/renderer_settings.h
@@ -30,6 +30,7 @@
   bool show_overdraw_feedback = false;
   bool enable_draw_occlusion = false;
   bool use_skia_renderer = false;
+  bool dont_round_texture_sizes_for_pixel_tests = false;
   int highp_threshold_min = 0;
 
   // Determines whether we disallow non-exact matches when finding resources
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index f49f853d..9d022c6 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -657,9 +657,18 @@
   return render_pass->damage_rect;
 }
 
-// static
 gfx::Size DirectRenderer::RenderPassTextureSize(const RenderPass* render_pass) {
-  return render_pass->output_rect.size();
+  // Round the size of the render pass backings to a multiple of 64 pixels. This
+  // reduces memory fragmentation. https://crbug.com/146070. This also allows
+  // backings to be more easily reused during a resize operation.
+  int width = render_pass->output_rect.width();
+  int height = render_pass->output_rect.height();
+  if (!settings_->dont_round_texture_sizes_for_pixel_tests) {
+    int multiple = 64;
+    width = cc::MathUtil::CheckedRoundUp(width, multiple);
+    height = cc::MathUtil::CheckedRoundUp(height, multiple);
+  }
+  return gfx::Size(width, height);
 }
 
 void DirectRenderer::SetCurrentFrameForTesting(const DrawingFrame& frame) {
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index f6fe330..ef89a16f 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -131,7 +131,7 @@
                       const gfx::Rect& render_pass_scissor);
   void SetScissorTestRectInDrawSpace(const gfx::Rect& draw_space_rect);
 
-  static gfx::Size RenderPassTextureSize(const RenderPass* render_pass);
+  gfx::Size RenderPassTextureSize(const RenderPass* render_pass);
 
   void FlushPolygons(
       base::circular_deque<std::unique_ptr<DrawPolygon>>* poly_list,
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index 31622f8..7f47941 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -3347,11 +3347,15 @@
   // Round the size of the IOSurface to a multiple of 64 pixels. This reduces
   // memory fragmentation. https://crbug.com/146070. This also allows IOSurfaces
   // to be more easily reused during a resize operation.
-  uint32_t iosurface_multiple = 64;
-  uint32_t iosurface_width = cc::MathUtil::UncheckedRoundUp(
-      static_cast<uint32_t>(updated_dst_rect.width()), iosurface_multiple);
-  uint32_t iosurface_height = cc::MathUtil::UncheckedRoundUp(
-      static_cast<uint32_t>(updated_dst_rect.height()), iosurface_multiple);
+  uint32_t iosurface_width = static_cast<uint32_t>(updated_dst_rect.width());
+  uint32_t iosurface_height = static_cast<uint32_t>(updated_dst_rect.height());
+  if (!settings_->dont_round_texture_sizes_for_pixel_tests) {
+    uint32_t iosurface_multiple = 64;
+    iosurface_width =
+        cc::MathUtil::CheckedRoundUp(iosurface_width, iosurface_multiple);
+    iosurface_height =
+        cc::MathUtil::CheckedRoundUp(iosurface_height, iosurface_multiple);
+  }
 
   *resource = overlay_resource_pool_->AcquireResource(
       gfx::Size(iosurface_width, iosurface_height), ResourceFormat::RGBA_8888,
diff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc
index 44f6ff0..75ee4ab 100644
--- a/content/browser/compositor/viz_process_transport_factory.cc
+++ b/content/browser/compositor/viz_process_transport_factory.cc
@@ -72,18 +72,12 @@
       gpu::SharedMemoryLimits(), attributes, nullptr /* share_context */, type);
 }
 
-bool CheckContextLost(viz::ContextProvider* context_provider) {
-  if (!context_provider)
-    return false;
-
+bool IsContextLost(viz::ContextProvider* context_provider) {
   return context_provider->ContextGL()->GetGraphicsResetStatusKHR() !=
          GL_NO_ERROR;
 }
 
-bool CheckWorkerContextLost(viz::RasterContextProvider* context_provider) {
-  if (!context_provider)
-    return false;
-
+bool IsWorkerContextLost(viz::RasterContextProvider* context_provider) {
   viz::RasterContextProvider::ScopedRasterContextLock lock(context_provider);
   return lock.RasterInterface()->GetGraphicsResetStatusKHR() != GL_NO_ERROR;
 }
@@ -124,6 +118,9 @@
 }
 
 VizProcessTransportFactory::~VizProcessTransportFactory() {
+  if (main_context_provider_)
+    main_context_provider_->RemoveObserver(this);
+
   task_graph_runner_->Shutdown();
 }
 
@@ -183,8 +180,15 @@
 
 scoped_refptr<viz::ContextProvider>
 VizProcessTransportFactory::SharedMainThreadContextProvider() {
-  NOTIMPLEMENTED();
-  return nullptr;
+  if (is_gpu_compositing_disabled_)
+    return nullptr;
+
+  if (!main_context_provider_) {
+    CreateContextProviders(
+        gpu_channel_establish_factory_->EstablishGpuChannelSync());
+  }
+
+  return main_context_provider_;
 }
 
 void VizProcessTransportFactory::RemoveCompositor(ui::Compositor* compositor) {
@@ -338,8 +342,7 @@
 }
 
 viz::GLHelper* VizProcessTransportFactory::GetGLHelper() {
-  // TODO(kylechar): Figure out if GLHelper in the host process makes sense.
-  NOTREACHED();
+  NOTREACHED();  // Readback happens in the GPU process and this isn't used.
   return nullptr;
 }
 
@@ -367,8 +370,11 @@
   OnLostMainThreadSharedContext();
 
   // Drop our reference on the gpu contexts for the compositors.
-  shared_worker_context_provider_ = nullptr;
-  compositor_context_provider_ = nullptr;
+  worker_context_provider_ = nullptr;
+  if (main_context_provider_) {
+    main_context_provider_->RemoveObserver(this);
+    main_context_provider_ = nullptr;
+  }
 
   // Here we remove the FrameSink from every compositor that needs to fall back
   // to software compositing.
@@ -394,11 +400,19 @@
   }
 }
 
+void VizProcessTransportFactory::OnContextLost() {
+  // TODO(kylechar): If the context is lost but the GPU process hasn't crashed
+  // then CompositorFrameSink data in HostFrameSinkManager (browser process) and
+  // FrameSinkManagerImpl (GPU process) needs to be cleaned up.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&VizProcessTransportFactory::OnLostMainThreadSharedContext,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
 void VizProcessTransportFactory::OnGpuProcessLost() {
   // Reconnect HostFrameSinkManager to new GPU process.
   ConnectHostFrameSinkManager();
-
-  OnLostMainThreadSharedContext();
 }
 
 void VizProcessTransportFactory::OnEstablishedGpuChannel(
@@ -429,8 +443,6 @@
       compositor->widget());
 #endif
 
-  // TODO(crbug.com/776050): Deal with context loss.
-
   // Create interfaces for a root CompositorFrameSink.
   viz::mojom::CompositorFrameSinkAssociatedPtrInfo sink_info;
   viz::mojom::CompositorFrameSinkAssociatedRequest sink_request =
@@ -476,9 +488,8 @@
   scoped_refptr<viz::RasterContextProvider> worker_context;
   if (gpu_compositing) {
     // Only pass the contexts to the compositor if it will use gpu compositing.
-    compositor_context = compositor_context_provider_;
-    if (shared_worker_context_provider_)
-      worker_context = shared_worker_context_provider_;
+    compositor_context = main_context_provider_;
+    worker_context = worker_context_provider_;
   }
   compositor->SetLayerTreeFrameSink(
       std::make_unique<viz::ClientLayerTreeFrameSink>(
@@ -499,38 +510,46 @@
   constexpr bool kCompositorContextSupportsGLES2 = true;
   constexpr bool kCompositorContextSupportsRaster = false;
 
-  if (CheckContextLost(compositor_context_provider_.get()))
-    compositor_context_provider_ = nullptr;
+  if (main_context_provider_ && IsContextLost(main_context_provider_.get())) {
+    main_context_provider_->RemoveObserver(this);
+    main_context_provider_ = nullptr;
+  }
 
-  if (CheckWorkerContextLost(shared_worker_context_provider_.get()))
-    shared_worker_context_provider_ = nullptr;
+  if (worker_context_provider_ &&
+      IsWorkerContextLost(worker_context_provider_.get()))
+    worker_context_provider_ = nullptr;
 
-  if (!shared_worker_context_provider_) {
-    shared_worker_context_provider_ = CreateContextProviderImpl(
+  if (!worker_context_provider_) {
+    worker_context_provider_ = CreateContextProviderImpl(
         gpu_channel_host, GetGpuMemoryBufferManager(),
         kSharedWorkerContextSupportsLocking, kSharedWorkerContextSupportsGLES2,
         kSharedWorkerContextSupportsRaster,
         ui::command_buffer_metrics::BROWSER_WORKER_CONTEXT);
 
-    auto result = shared_worker_context_provider_->BindToCurrentThread();
+    // Don't observer context loss on |worker_context_provider_| here, that is
+    // already observered by LayerTreeFrameSink. The lost context will be caught
+    // when recreating LayerTreeFrameSink(s).
+    auto result = worker_context_provider_->BindToCurrentThread();
     if (result != gpu::ContextResult::kSuccess) {
-      shared_worker_context_provider_ = nullptr;
+      worker_context_provider_ = nullptr;
       return false;
     }
   }
 
-  if (!compositor_context_provider_) {
-    compositor_context_provider_ = CreateContextProviderImpl(
+  if (!main_context_provider_) {
+    main_context_provider_ = CreateContextProviderImpl(
         std::move(gpu_channel_host), GetGpuMemoryBufferManager(),
         kCompositorContextSupportsLocking, kCompositorContextSupportsGLES2,
         kCompositorContextSupportsRaster,
         ui::command_buffer_metrics::UI_COMPOSITOR_CONTEXT);
-    compositor_context_provider_->SetDefaultTaskRunner(resize_task_runner_);
+    main_context_provider_->SetDefaultTaskRunner(resize_task_runner_);
+    main_context_provider_->AddObserver(this);
 
-    auto result = compositor_context_provider_->BindToCurrentThread();
+    auto result = main_context_provider_->BindToCurrentThread();
     if (result != gpu::ContextResult::kSuccess) {
-      compositor_context_provider_ = nullptr;
-      shared_worker_context_provider_ = nullptr;
+      main_context_provider_->RemoveObserver(this);
+      main_context_provider_ = nullptr;
+      worker_context_provider_ = nullptr;
       return false;
     }
   }
@@ -539,8 +558,13 @@
 }
 
 void VizProcessTransportFactory::OnLostMainThreadSharedContext() {
-  // TODO(danakj): When we implement making the shared context, we'll also
-  // have to recreate it here before calling OnLostResources().
+  // It's possible that |main_context_provider_| was already reset in
+  // OnEstablishedGpuChannel(), so check if it's lost before resetting here.
+  if (main_context_provider_ && IsContextLost(main_context_provider_.get())) {
+    main_context_provider_->RemoveObserver(this);
+    main_context_provider_ = nullptr;
+  }
+
   for (auto& observer : observer_list_)
     observer.OnLostResources();
 }
diff --git a/content/browser/compositor/viz_process_transport_factory.h b/content/browser/compositor/viz_process_transport_factory.h
index 034fa567..b7a8d77c 100644
--- a/content/browser/compositor/viz_process_transport_factory.h
+++ b/content/browser/compositor/viz_process_transport_factory.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "components/viz/common/display/renderer_settings.h"
+#include "components/viz/common/gpu/context_lost_observer.h"
 #include "components/viz/common/surfaces/frame_sink_id_allocator.h"
 #include "content/browser/compositor/image_transport_factory.h"
 #include "content/browser/compositor/in_process_display_client.h"
@@ -51,7 +52,8 @@
 class VizProcessTransportFactory : public ui::ContextFactory,
                                    public ui::ContextFactoryPrivate,
                                    public ImageTransportFactory,
-                                   public viz::mojom::CompositingModeWatcher {
+                                   public viz::mojom::CompositingModeWatcher,
+                                   public viz::ContextLostObserver {
  public:
   VizProcessTransportFactory(
       gpu::GpuChannelEstablishFactory* gpu_channel_establish_factory,
@@ -112,6 +114,9 @@
   // viz::mojom::CompositingModeWatcher implementation.
   void CompositingModeFallbackToSoftware() override;
 
+  // viz::ContextLostObserver implementation.
+  void OnContextLost() override;
+
  private:
   struct CompositorData {
     CompositorData();
@@ -157,12 +162,16 @@
   base::flat_map<ui::Compositor*, CompositorData> compositor_data_map_;
   bool is_gpu_compositing_disabled_ = false;
 
-  // TODO(kylechar): Call OnContextLost() on observers when GPU crashes.
   base::ObserverList<ui::ContextFactoryObserver> observer_list_;
 
   std::unique_ptr<viz::ClientSharedBitmapManager> shared_bitmap_manager_;
-  scoped_refptr<viz::RasterContextProvider> shared_worker_context_provider_;
-  scoped_refptr<ui::ContextProviderCommandBuffer> compositor_context_provider_;
+
+  // ContextProvider used on worker threads for rasterization.
+  scoped_refptr<viz::RasterContextProvider> worker_context_provider_;
+
+  // ContextProvider used on the main thread. Shared by ui::Compositors and also
+  // returned from GetSharedMainThreadContextProvider().
+  scoped_refptr<ui::ContextProviderCommandBuffer> main_context_provider_;
 
   viz::FrameSinkIdAllocator frame_sink_id_allocator_;
   std::unique_ptr<cc::SingleThreadTaskGraphRunner> task_graph_runner_;
diff --git a/content/browser/devtools/devtools_agent_host_impl.cc b/content/browser/devtools/devtools_agent_host_impl.cc
index a3f756f..0416602 100644
--- a/content/browser/devtools/devtools_agent_host_impl.cc
+++ b/content/browser/devtools/devtools_agent_host_impl.cc
@@ -235,7 +235,7 @@
   DevToolsSession* session = SessionById(session_id);
   if (!session)
     return false;
-  session->SendMessageToClient(message);
+  session->client()->DispatchProtocolMessage(this, message);
   return true;
 }
 
diff --git a/content/browser/devtools/devtools_manager_unittest.cc b/content/browser/devtools/devtools_manager_unittest.cc
index 9d1d08f..6275e13 100644
--- a/content/browser/devtools/devtools_manager_unittest.cc
+++ b/content/browser/devtools/devtools_manager_unittest.cc
@@ -167,36 +167,6 @@
   contents()->SetDelegate(nullptr);
 }
 
-TEST_F(DevToolsManagerTest, ReattachOnCancelPendingNavigation) {
-  // This test triggers incorrect notifications with PlzNavigate.
-  if (IsBrowserSideNavigationEnabled())
-    return;
-  // Navigate to URL.  First URL should use first RenderViewHost.
-  const GURL url("http://www.google.com");
-  NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url);
-  EXPECT_FALSE(contents()->CrossProcessNavigationPending());
-
-  TestDevToolsClientHost client_host;
-  client_host.InspectAgentHost(
-      DevToolsAgentHost::GetOrCreateFor(web_contents()).get());
-
-  // Navigate to new site which should get a new RenderViewHost.
-  const GURL url2("http://www.yahoo.com");
-  auto navigation =
-      NavigationSimulator::CreateBrowserInitiated(url2, web_contents());
-  navigation->ReadyToCommit();
-  EXPECT_TRUE(contents()->CrossProcessNavigationPending());
-  EXPECT_EQ(client_host.agent_host(),
-            DevToolsAgentHost::GetOrCreateFor(web_contents()).get());
-
-  // Interrupt pending navigation and navigate back to the original site.
-  NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url);
-  EXPECT_FALSE(contents()->CrossProcessNavigationPending());
-  EXPECT_EQ(client_host.agent_host(),
-            DevToolsAgentHost::GetOrCreateFor(web_contents()).get());
-  client_host.Close();
-}
-
 class TestExternalAgentDelegate: public DevToolsExternalAgentProxyDelegate {
  public:
   TestExternalAgentDelegate() {
diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc
index 9d42951..93a74b7 100644
--- a/content/browser/devtools/devtools_session.cc
+++ b/content/browser/devtools/devtools_session.cc
@@ -14,8 +14,9 @@
 
 namespace content {
 
-// static
-bool DevToolsSession::ShouldSendOnIO(const std::string& method) {
+namespace {
+
+bool ShouldSendOnIO(const std::string& method) {
   // Keep in sync with WebDevToolsAgent::ShouldInterruptForMethod.
   // TODO(dgozman): find a way to share this.
   return method == "Debugger.pause" || method == "Debugger.setBreakpoint" ||
@@ -25,6 +26,8 @@
          method == "Performance.getMetrics";
 }
 
+}  // namespace
+
 DevToolsSession::DevToolsSession(DevToolsAgentHostImpl* agent_host,
                                  DevToolsAgentHostClient* client,
                                  int session_id)
@@ -55,10 +58,6 @@
   handlers_[handler->name()] = std::move(handler);
 }
 
-void DevToolsSession::SetRenderFrameHost(RenderFrameHostImpl* frame_host) {
-  SetRenderer(frame_host ? frame_host->GetProcess() : nullptr, frame_host);
-}
-
 void DevToolsSession::SetRenderer(RenderProcessHost* process_host,
                                   RenderFrameHostImpl* frame_host) {
   process_ = process_host;
@@ -93,10 +92,6 @@
       &DevToolsSession::MojoConnectionDestroyed, base::Unretained(this)));
 }
 
-void DevToolsSession::SendMessageToClient(const std::string& message) {
-  client_->DispatchProtocolMessage(agent_host_, message);
-}
-
 void DevToolsSession::SendMessageFromProcessorIPC(int session_id,
                                                   const std::string& message) {
   if (session_id != session_id_)
diff --git a/content/browser/devtools/devtools_session.h b/content/browser/devtools/devtools_session.h
index bd9dedac..bc8e982 100644
--- a/content/browser/devtools/devtools_session.h
+++ b/content/browser/devtools/devtools_session.h
@@ -32,14 +32,12 @@
   int session_id() const { return session_id_; }
   DevToolsAgentHostClient* client() const { return client_; }
   void AddHandler(std::unique_ptr<protocol::DevToolsDomainHandler> handler);
-  void SetRenderFrameHost(RenderFrameHostImpl* frame_host);
   void SetRenderer(RenderProcessHost* process_host,
                    RenderFrameHostImpl* frame_host);
   void SetFallThroughForNotFound(bool value);
   void AttachToAgent(const mojom::DevToolsAgentAssociatedPtr& agent);
   void ReattachToAgent(const mojom::DevToolsAgentAssociatedPtr& agent);
 
-  static bool ShouldSendOnIO(const std::string& method);
   struct Message {
     std::string method;
     std::string message;
@@ -57,7 +55,6 @@
                                       const std::string& message);
   void InspectElement(const gfx::Point& point);
   bool ReceiveMessageChunk(const DevToolsMessageChunk& chunk);
-  void SendMessageToClient(const std::string& message);
 
   template <typename Handler>
   static std::vector<Handler*> HandlersForAgentHost(
diff --git a/content/browser/devtools/forwarding_agent_host.cc b/content/browser/devtools/forwarding_agent_host.cc
index 72e743c..4b4e439e 100644
--- a/content/browser/devtools/forwarding_agent_host.cc
+++ b/content/browser/devtools/forwarding_agent_host.cc
@@ -23,7 +23,7 @@
 
  private:
   void DispatchOnClientHost(const std::string& message) override {
-    session_->SendMessageToClient(message);
+    session_->client()->DispatchProtocolMessage(agent_host_, message);
   }
 
   void ConnectionClosed() override {
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index d5b6c4f..cade77a8 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -355,22 +355,17 @@
       gurl,
       Referrer(GURL(referrer.fromMaybe("")), blink::kWebReferrerPolicyDefault),
       type, std::string());
-  if (IsBrowserSideNavigationEnabled()) {
-    std::string frame_id =
-        web_contents->GetMainFrame()->GetDevToolsFrameToken().ToString();
-    if (navigate_callback_) {
-      std::string error_string = net::ErrorToString(net::ERR_ABORTED);
-      navigate_callback_->sendSuccess(frame_id, Maybe<std::string>(),
-                                      Maybe<std::string>(error_string));
-    }
-    if (web_contents->GetMainFrame()->frame_tree_node()->navigation_request())
-      navigate_callback_ = std::move(callback);
-    else
-      callback->sendSuccess(frame_id, Maybe<std::string>(),
-                            Maybe<std::string>());
-    return;
+  std::string frame_id =
+      web_contents->GetMainFrame()->GetDevToolsFrameToken().ToString();
+  if (navigate_callback_) {
+    std::string error_string = net::ErrorToString(net::ERR_ABORTED);
+    navigate_callback_->sendSuccess(frame_id, Maybe<std::string>(),
+                                    Maybe<std::string>(error_string));
   }
-  callback->fallThrough();
+  if (web_contents->GetMainFrame()->frame_tree_node()->navigation_request())
+    navigate_callback_ = std::move(callback);
+  else
+    callback->sendSuccess(frame_id, Maybe<std::string>(), Maybe<std::string>());
 }
 
 void PageHandler::NavigationReset(NavigationRequest* navigation_request) {
diff --git a/content/browser/devtools/protocol/target_auto_attacher.cc b/content/browser/devtools/protocol/target_auto_attacher.cc
index 114a5df6..f979ec9 100644
--- a/content/browser/devtools/protocol/target_auto_attacher.cc
+++ b/content/browser/devtools/protocol/target_auto_attacher.cc
@@ -141,8 +141,7 @@
 }
 
 bool TargetAutoAttacher::ShouldThrottleFramesNavigation() {
-  return IsBrowserSideNavigationEnabled() && auto_attach_ &&
-         attach_to_frames_ && wait_for_debugger_on_start_;
+  return auto_attach_ && attach_to_frames_ && wait_for_debugger_on_start_;
 }
 
 DevToolsAgentHost* TargetAutoAttacher::AutoAttachToFrame(
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 679f95a..af6628a 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -94,234 +94,6 @@
 
 }  // namespace
 
-// RenderFrameDevToolsAgentHost::FrameHostHolder -------------------------------
-
-class RenderFrameDevToolsAgentHost::FrameHostHolder {
- public:
-  FrameHostHolder(
-      RenderFrameDevToolsAgentHost* agent, RenderFrameHostImpl* host);
-  ~FrameHostHolder();
-
-  RenderFrameHostImpl* host() const { return host_; }
-
-  void Attach(DevToolsSession* session);
-  void Reattach(FrameHostHolder* old);
-  void Detach(int session_id);
-  void DispatchProtocolMessage(int session_id,
-                               int call_id,
-                               const std::string& method,
-                               const std::string& message);
-  void InspectElement(int session_id, int x, int y);
-  void Suspend();
-  void Resume();
-  std::string StateCookie(int session_id) const;
-  void ReattachWithCookie(DevToolsSession* session, std::string cookie);
-
- private:
-  struct Message {
-    std::string method;
-    std::string message;
-  };
-
-  class SessionHost : public mojom::DevToolsSessionHost {
-   public:
-    SessionHost(FrameHostHolder* owner,
-                int session_id,
-                const base::Optional<std::string>& reattach_state)
-        : owner_(owner),
-          session_id_(session_id),
-          binding_(this),
-          chunk_processor_(
-              base::Callback<void(int, const std::string&)>(),
-              base::Bind(&RenderFrameDevToolsAgentHost::FrameHostHolder::
-                             SessionHost::SendMessageFromProcessor,
-                         base::Unretained(this))) {
-      if (reattach_state.has_value())
-        chunk_processor_.set_state_cookie(reattach_state.value());
-      mojom::DevToolsSessionHostAssociatedPtrInfo host_ptr_info;
-      binding_.Bind(mojo::MakeRequest(&host_ptr_info));
-      owner_->agent_ptr_->AttachDevToolsSession(
-          std::move(host_ptr_info), mojo::MakeRequest(&session_ptr_),
-          mojo::MakeRequest(&io_session_ptr_), reattach_state);
-    }
-
-    void RedispatchProtocolMessagesFrom(SessionHost* other) {
-      for (const auto& pair : other->sent_messages_) {
-        DispatchProtocolMessage(pair.first, pair.second.method,
-                                pair.second.message);
-      }
-    }
-
-    const std::string& state_cookie() const {
-      return chunk_processor_.state_cookie();
-    }
-
-    void DispatchProtocolMessage(int call_id,
-                                 const std::string& method,
-                                 const std::string& message) {
-      if (DevToolsSession::ShouldSendOnIO(method))
-        io_session_ptr_->DispatchProtocolMessage(call_id, method, message);
-      else
-        session_ptr_->DispatchProtocolMessage(call_id, method, message);
-      sent_messages_[call_id] = {method, message};
-    }
-
-    void InspectElement(int x, int y) {
-      session_ptr_->InspectElement(gfx::Point(x, y));
-    }
-
-    // mojom::DevToolsSessionHost implementation.
-    void DispatchProtocolMessage(
-        mojom::DevToolsMessageChunkPtr chunk) override {
-      if (chunk_processor_.ProcessChunkedMessageFromAgent(std::move(chunk)))
-        return;
-
-      binding_.Close();
-      if (owner_->host_->GetProcess()) {
-        bad_message::ReceivedBadMessage(
-            owner_->host_->GetProcess(),
-            bad_message::RFH_INCONSISTENT_DEVTOOLS_MESSAGE);
-      }
-    }
-
-    void SendMessageFromProcessor(const std::string& message) {
-      int id = chunk_processor_.last_call_id();
-      Message sent_message = std::move(sent_messages_[id]);
-      sent_messages_.erase(id);
-      if (suspended_) {
-        pending_messages_.push_back(message);
-      } else {
-        DevToolsSession* session = owner_->agent_->SessionById(session_id_);
-        if (session)
-          session->SendMessageToClient(message);
-        // |this| may be deleted at this point.
-      }
-    }
-
-    void Suspend() { suspended_ = true; }
-
-    void Resume() {
-      suspended_ = false;
-      DevToolsSession* session = owner_->agent_->SessionById(session_id_);
-      std::vector<std::string> messages;
-      messages.swap(pending_messages_);
-      for (std::string& message : messages)
-        session->SendMessageToClient(message);
-    }
-
-   private:
-    FrameHostHolder* owner_;
-    int session_id_;
-    mojo::AssociatedBinding<mojom::DevToolsSessionHost> binding_;
-    mojom::DevToolsSessionAssociatedPtr session_ptr_;
-    mojom::DevToolsSessionPtr io_session_ptr_;
-    DevToolsMessageChunkProcessor chunk_processor_;
-    std::vector<std::string> pending_messages_;
-    using CallId = int;
-    std::map<CallId, Message> sent_messages_;
-    bool suspended_ = false;
-
-    DISALLOW_COPY_AND_ASSIGN(SessionHost);
-  };
-
-  RenderFrameDevToolsAgentHost* agent_;
-  RenderFrameHostImpl* host_;
-  mojom::DevToolsAgentAssociatedPtr agent_ptr_;
-  base::flat_map<int, std::unique_ptr<SessionHost>> session_hosts_;
-};
-
-RenderFrameDevToolsAgentHost::FrameHostHolder::FrameHostHolder(
-    RenderFrameDevToolsAgentHost* agent,
-    RenderFrameHostImpl* host)
-    : agent_(agent), host_(host) {
-  DCHECK(!IsBrowserSideNavigationEnabled());
-  DCHECK(agent_);
-  DCHECK(host_);
-}
-
-RenderFrameDevToolsAgentHost::FrameHostHolder::~FrameHostHolder() {
-  if (!session_hosts_.empty())
-    agent_->RevokePolicy(host_);
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::Attach(
-    DevToolsSession* session) {
-  agent_->GrantPolicy(host_);
-  // |agent_ptr_| is used by SessionHost in constructor to attach.
-  if (!agent_ptr_)
-    host_->GetRemoteAssociatedInterfaces()->GetInterface(&agent_ptr_);
-  session_hosts_[session->session_id()].reset(
-      new SessionHost(this, session->session_id(), base::nullopt));
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::Reattach(
-    FrameHostHolder* old) {
-  for (DevToolsSession* session : agent_->sessions()) {
-    int session_id = session->session_id();
-    std::string cookie = old ? old->StateCookie(session_id) : std::string();
-    ReattachWithCookie(session, std::move(cookie));
-    if (!old)
-      continue;
-    auto it = old->session_hosts_.find(session_id);
-    if (it != old->session_hosts_.end()) {
-      session_hosts_[session_id]->RedispatchProtocolMessagesFrom(
-          it->second.get());
-    }
-  }
-}
-
-std::string RenderFrameDevToolsAgentHost::FrameHostHolder::StateCookie(
-    int session_id) const {
-  auto it = session_hosts_.find(session_id);
-  return it == session_hosts_.end() ? std::string()
-                                    : it->second->state_cookie();
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::ReattachWithCookie(
-    DevToolsSession* session,
-    std::string cookie) {
-  agent_->GrantPolicy(host_);
-  // |agent_ptr_| is used by SessionHost in constructor to attach.
-  if (!agent_ptr_)
-    host_->GetRemoteAssociatedInterfaces()->GetInterface(&agent_ptr_);
-  session_hosts_[session->session_id()].reset(
-      new SessionHost(this, session->session_id(), cookie));
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::Detach(int session_id) {
-  agent_->RevokePolicy(host_);
-  session_hosts_.erase(session_id);
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::DispatchProtocolMessage(
-    int session_id,
-    int call_id,
-    const std::string& method,
-    const std::string& message) {
-  auto it = session_hosts_.find(session_id);
-  if (it != session_hosts_.end())
-    it->second->DispatchProtocolMessage(call_id, method, message);
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::InspectElement(
-    int session_id, int x, int y) {
-  auto it = session_hosts_.find(session_id);
-  if (it != session_hosts_.end())
-    it->second->InspectElement(x, y);
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::Suspend() {
-  for (auto& pair : session_hosts_)
-    pair.second->Suspend();
-}
-
-void RenderFrameDevToolsAgentHost::FrameHostHolder::Resume() {
-  for (auto& pair : session_hosts_)
-    pair.second->Resume();
-}
-
-// RenderFrameDevToolsAgentHost ------------------------------------------------
-
 // static
 scoped_refptr<DevToolsAgentHost>
 DevToolsAgentHost::GetOrCreateFor(WebContents* web_contents) {
@@ -389,33 +161,6 @@
 }
 
 // static
-void RenderFrameDevToolsAgentHost::OnCancelPendingNavigation(
-    RenderFrameHost* pending,
-    RenderFrameHost* current) {
-  if (IsBrowserSideNavigationEnabled())
-    return;
-
-  RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(
-      static_cast<RenderFrameHostImpl*>(pending)->frame_tree_node());
-  if (!agent_host)
-    return;
-  if (agent_host->pending_ && agent_host->pending_->host() == pending) {
-    DCHECK(agent_host->current_ && agent_host->current_->host() == current);
-    agent_host->DiscardPending();
-    DCHECK(agent_host->CheckConsistency());
-  }
-}
-
-// static
-void RenderFrameDevToolsAgentHost::OnBeforeNavigation(
-    RenderFrameHost* current, RenderFrameHost* pending) {
-  RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(
-      static_cast<RenderFrameHostImpl*>(current)->frame_tree_node());
-  if (agent_host)
-    agent_host->AboutToNavigateRenderFrame(current, pending);
-}
-
-// static
 void RenderFrameDevToolsAgentHost::OnResetNavigationRequest(
     NavigationRequest* navigation_request) {
   RenderFrameDevToolsAgentHost* agent_host =
@@ -517,73 +262,18 @@
 RenderFrameDevToolsAgentHost::RenderFrameDevToolsAgentHost(
     FrameTreeNode* frame_tree_node)
     : DevToolsAgentHostImpl(frame_tree_node->devtools_frame_token().ToString()),
-      frame_trace_recorder_(nullptr),
-      handlers_frame_host_(nullptr),
-      current_frame_crashed_(false),
       frame_tree_node_(frame_tree_node) {
-  if (IsBrowserSideNavigationEnabled()) {
-    frame_host_ = frame_tree_node->current_frame_host();
-    render_frame_alive_ = frame_host_ && frame_host_->IsRenderFrameLive();
-  } else {
-    if (frame_tree_node->current_frame_host()) {
-      SetPending(frame_tree_node->current_frame_host());
-      CommitPending();
-    }
-  }
+  frame_host_ = frame_tree_node->current_frame_host();
+  render_frame_alive_ = frame_host_ && frame_host_->IsRenderFrameLive();
   WebContentsObserver::Observe(
       WebContentsImpl::FromFrameTreeNode(frame_tree_node));
 
-  if (web_contents() && web_contents()->GetCrashedStatus() !=
-      base::TERMINATION_STATUS_STILL_RUNNING) {
-    current_frame_crashed_ = true;
-  }
-
   g_agent_host_instances.Get().push_back(this);
   AddRef();  // Balanced in RenderFrameHostDestroyed.
 
   NotifyCreated();
 }
 
-void RenderFrameDevToolsAgentHost::SetPending(RenderFrameHostImpl* host) {
-  DCHECK(!IsBrowserSideNavigationEnabled());
-  DCHECK(!pending_);
-  current_frame_crashed_ = false;
-  pending_.reset(new FrameHostHolder(this, host));
-  if (IsAttached())
-    pending_->Reattach(current_.get());
-
-  if (current_)
-    current_->Suspend();
-  pending_->Suspend();
-
-  UpdateProtocolHandlers(host);
-}
-
-void RenderFrameDevToolsAgentHost::CommitPending() {
-  DCHECK(!IsBrowserSideNavigationEnabled());
-  DCHECK(pending_);
-  current_frame_crashed_ = false;
-
-  if (!ShouldCreateDevToolsForHost(pending_->host())) {
-    DestroyOnRenderFrameGone();
-    // |this| may be deleted at this point.
-    return;
-  }
-
-  current_ = std::move(pending_);
-  UpdateProtocolHandlers(current_->host());
-  current_->Resume();
-}
-
-void RenderFrameDevToolsAgentHost::DiscardPending() {
-  DCHECK(!IsBrowserSideNavigationEnabled());
-  DCHECK(pending_);
-  DCHECK(current_);
-  pending_.reset();
-  UpdateProtocolHandlers(current_->host());
-  current_->Resume();
-}
-
 BrowserContext* RenderFrameDevToolsAgentHost::GetBrowserContext() {
   WebContents* contents = web_contents();
   return contents ? contents->GetBrowserContext() : nullptr;
@@ -595,10 +285,8 @@
 
 void RenderFrameDevToolsAgentHost::AttachSession(DevToolsSession* session) {
   session->SetFallThroughForNotFound(true);
-  if (IsBrowserSideNavigationEnabled())
-    session->SetRenderFrameHost(frame_host_);
-  else
-    session->SetRenderFrameHost(handlers_frame_host_);
+  session->SetRenderer(frame_host_ ? frame_host_->GetProcess() : nullptr,
+                       frame_host_);
 
   protocol::EmulationHandler* emulation_handler =
       new protocol::EmulationHandler();
@@ -624,31 +312,28 @@
     session->AddHandler(base::WrapUnique(new protocol::SecurityHandler()));
   }
 
-  if (IsBrowserSideNavigationEnabled()) {
-    if (EnsureAgent())
-      session->AttachToAgent(agent_ptr_);
-  } else {
-    if (current_)
-      current_->Attach(session);
-    if (pending_)
-      pending_->Attach(session);
+  if (EnsureAgent())
+    session->AttachToAgent(agent_ptr_);
+
+  if (sessions().size() == 1) {
+    frame_trace_recorder_.reset(new DevToolsFrameTraceRecorder());
+    GrantPolicy();
+#if defined(OS_ANDROID)
+    GetWakeLock()->RequestWakeLock();
+#endif
   }
-  if (sessions().size() == 1)
-    OnClientsAttached();
 }
 
 void RenderFrameDevToolsAgentHost::DetachSession(int session_id) {
-  if (IsBrowserSideNavigationEnabled()) {
-    // Destroying session automatically detaches in renderer.
-    suspended_messages_by_session_id_.erase(session_id);
-  } else {
-    if (current_)
-      current_->Detach(session_id);
-    if (pending_)
-      pending_->Detach(session_id);
+  // Destroying session automatically detaches in renderer.
+  suspended_messages_by_session_id_.erase(session_id);
+  if (sessions().empty()) {
+    frame_trace_recorder_.reset();
+    RevokePolicy();
+#if defined(OS_ANDROID)
+    GetWakeLock()->CancelWakeLock();
+#endif
   }
-  if (sessions().empty())
-    OnClientsDetached();
 }
 
 bool RenderFrameDevToolsAgentHost::DispatchProtocolMessage(
@@ -662,20 +347,14 @@
     return true;
   }
 
-  if (IsBrowserSideNavigationEnabled()) {
-    if (!navigation_handles_.empty()) {
-      suspended_messages_by_session_id_[session_id].push_back(
-          {call_id, method, message});
-      return true;
-    }
-    session->DispatchProtocolMessageToAgent(call_id, method, message);
-    session->waiting_messages()[call_id] = {method, message};
-  } else {
-    if (current_)
-      current_->DispatchProtocolMessage(session_id, call_id, method, message);
-    if (pending_)
-      pending_->DispatchProtocolMessage(session_id, call_id, method, message);
+  if (!navigation_handles_.empty()) {
+    suspended_messages_by_session_id_[session_id].push_back(
+        {call_id, method, message});
+    return true;
   }
+
+  session->DispatchProtocolMessageToAgent(call_id, method, message);
+  session->waiting_messages()[call_id] = {method, message};
   return true;
 }
 
@@ -683,32 +362,7 @@
     DevToolsSession* session,
     int x,
     int y) {
-  if (IsBrowserSideNavigationEnabled()) {
-    session->InspectElement(gfx::Point(x, y));
-  } else {
-    if (current_)
-      current_->InspectElement(session->session_id(), x, y);
-    if (pending_)
-      pending_->InspectElement(session->session_id(), x, y);
-  }
-}
-
-void RenderFrameDevToolsAgentHost::OnClientsAttached() {
-  frame_trace_recorder_.reset(new DevToolsFrameTraceRecorder());
-#if defined(OS_ANDROID)
-  GetWakeLock()->RequestWakeLock();
-#endif
-  if (IsBrowserSideNavigationEnabled())
-    GrantPolicy(frame_host_);
-}
-
-void RenderFrameDevToolsAgentHost::OnClientsDetached() {
-#if defined(OS_ANDROID)
-  GetWakeLock()->CancelWakeLock();
-#endif
-  frame_trace_recorder_.reset();
-  if (IsBrowserSideNavigationEnabled())
-    RevokePolicy(frame_host_);
+  session->InspectElement(gfx::Point(x, y));
 }
 
 RenderFrameDevToolsAgentHost::~RenderFrameDevToolsAgentHost() {
@@ -721,40 +375,18 @@
 
 void RenderFrameDevToolsAgentHost::ReadyToCommitNavigation(
     NavigationHandle* navigation_handle) {
-  if (!IsBrowserSideNavigationEnabled())
-    return;
   NavigationHandleImpl* handle =
       static_cast<NavigationHandleImpl*>(navigation_handle);
   if (handle->frame_tree_node() != frame_tree_node_)
     return;
 
-  // UpdateFrameHost may destruct |this|.
-  scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
   UpdateFrameHost(handle->GetRenderFrameHost());
-  DCHECK(CheckConsistency());
+  // UpdateFrameHost may destruct |this|.
 }
 
 void RenderFrameDevToolsAgentHost::DidFinishNavigation(
     NavigationHandle* navigation_handle) {
   NotifyNavigated();
-  if (!IsBrowserSideNavigationEnabled()) {
-    // CommitPending may destruct |this|.
-    scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
-    if (navigation_handle->HasCommitted() &&
-        !navigation_handle->IsErrorPage()) {
-      if (pending_ &&
-          pending_->host() == navigation_handle->GetRenderFrameHost()) {
-        CommitPending();
-      }
-      for (auto* target : protocol::TargetHandler::ForAgentHost(this))
-        target->DidCommitNavigation();
-    } else if (pending_ && pending_->host()->GetFrameTreeNodeId() ==
-                               navigation_handle->GetFrameTreeNodeId()) {
-      DiscardPending();
-    }
-    DCHECK(CheckConsistency());
-    return;
-  }
 
   NavigationHandleImpl* handle =
       static_cast<NavigationHandleImpl*>(navigation_handle);
@@ -765,7 +397,6 @@
   // UpdateFrameHost may destruct |this|.
   scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
   UpdateFrameHost(frame_tree_node_->current_frame_host());
-  DCHECK(CheckConsistency());
 
   if (navigation_handles_.empty()) {
     for (auto& pair : suspended_messages_by_session_id_) {
@@ -796,28 +427,28 @@
     return;
   }
 
-  if (IsAttached())
-    RevokePolicy(frame_host_);
-
   if (frame_host && !ShouldCreateDevToolsForHost(frame_host)) {
     DestroyOnRenderFrameGone();
     // |this| may be deleted at this point.
     return;
   }
 
+  if (IsAttached())
+    RevokePolicy();
   frame_host_ = frame_host;
   agent_ptr_.reset();
   render_frame_alive_ = true;
   if (IsAttached()) {
-    GrantPolicy(frame_host_);
-    for (DevToolsSession* session : sessions())
-      session->SetRenderFrameHost(frame_host);
+    GrantPolicy();
+    for (DevToolsSession* session : sessions()) {
+      session->SetRenderer(frame_host ? frame_host->GetProcess() : nullptr,
+                           frame_host);
+    }
     MaybeReattachToRenderFrame();
   }
 }
 
 void RenderFrameDevToolsAgentHost::MaybeReattachToRenderFrame() {
-  DCHECK(IsBrowserSideNavigationEnabled());
   if (!EnsureAgent())
     return;
   for (DevToolsSession* session : sessions()) {
@@ -831,38 +462,27 @@
   }
 }
 
-void RenderFrameDevToolsAgentHost::GrantPolicy(RenderFrameHostImpl* host) {
-  if (!host)
+void RenderFrameDevToolsAgentHost::GrantPolicy() {
+  if (!frame_host_)
     return;
-  uint32_t process_id = host->GetProcess()->GetID();
+  uint32_t process_id = frame_host_->GetProcess()->GetID();
   if (base::FeatureList::IsEnabled(features::kNetworkService))
     GetNetworkService()->SetRawHeadersAccess(process_id, true);
   ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadRawCookies(
       process_id);
 }
 
-void RenderFrameDevToolsAgentHost::RevokePolicy(RenderFrameHostImpl* host) {
-  if (!host)
+void RenderFrameDevToolsAgentHost::RevokePolicy() {
+  if (!frame_host_)
     return;
 
   bool process_has_agents = false;
-  RenderProcessHost* process_host = host->GetProcess();
+  RenderProcessHost* process_host = frame_host_->GetProcess();
   for (RenderFrameDevToolsAgentHost* agent : g_agent_host_instances.Get()) {
     if (!agent->IsAttached())
       continue;
-    if (IsBrowserSideNavigationEnabled()) {
-      if (agent->frame_host_ && agent->frame_host_ != host &&
-          agent->frame_host_->GetProcess() == process_host) {
-        process_has_agents = true;
-      }
-      continue;
-    }
-    if (agent->current_ && agent->current_->host() != host &&
-        agent->current_->host()->GetProcess() == process_host) {
-      process_has_agents = true;
-    }
-    if (agent->pending_ && agent->pending_->host() != host &&
-        agent->pending_->host()->GetProcess() == process_host) {
+    if (agent->frame_host_ && agent->frame_host_ != frame_host_ &&
+        agent->frame_host_->GetProcess() == process_host) {
       process_has_agents = true;
     }
   }
@@ -876,43 +496,13 @@
   }
 }
 
-void RenderFrameDevToolsAgentHost::AboutToNavigateRenderFrame(
-    RenderFrameHost* old_host,
-    RenderFrameHost* new_host) {
-  if (IsBrowserSideNavigationEnabled())
-    return;
-
-  // CommitPending may destruct |this|.
-  scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
-
-  DCHECK(!pending_ || pending_->host() != old_host);
-  if (!current_ || current_->host() != old_host) {
-    DCHECK(CheckConsistency());
-    return;
-  }
-  if (old_host == new_host && !current_frame_crashed_) {
-    DCHECK(CheckConsistency());
-    return;
-  }
-  DCHECK(!pending_);
-  SetPending(static_cast<RenderFrameHostImpl*>(new_host));
-  // Commit when navigating the same frame after crash, avoiding the same
-  // host in current_ and pending_.
-  if (old_host == new_host)
-    CommitPending();
-  DCHECK(CheckConsistency());
-}
-
 void RenderFrameDevToolsAgentHost::DidStartNavigation(
     NavigationHandle* navigation_handle) {
-  if (!IsBrowserSideNavigationEnabled())
-    return;
   NavigationHandleImpl* handle =
       static_cast<NavigationHandleImpl*>(navigation_handle);
   if (handle->frame_tree_node() != frame_tree_node_)
     return;
   navigation_handles_.insert(handle);
-  DCHECK(CheckConsistency());
 }
 
 void RenderFrameDevToolsAgentHost::RenderFrameHostChanged(
@@ -921,105 +511,39 @@
   for (auto* target : protocol::TargetHandler::ForAgentHost(this))
     target->RenderFrameHostChanged();
 
-  if (IsBrowserSideNavigationEnabled()) {
-    if (old_host != frame_host_)
-      return;
-
-    // UpdateFrameHost may destruct |this|.
-    scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
-    UpdateFrameHost(nullptr);
-    DCHECK(CheckConsistency());
+  if (old_host != frame_host_)
     return;
-  }
 
-  // CommitPending may destruct |this|.
-  scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
-
-  DCHECK(!pending_ || pending_->host() != old_host);
-  if (!current_ || current_->host() != old_host) {
-    DCHECK(CheckConsistency());
-    return;
-  }
-
-  // AboutToNavigateRenderFrame was not called for renderer-initiated
-  // navigation.
-  if (!pending_)
-    SetPending(static_cast<RenderFrameHostImpl*>(new_host));
-  CommitPending();
-  DCHECK(CheckConsistency());
+  UpdateFrameHost(nullptr);
+  // UpdateFrameHost may destruct |this|.
 }
 
 void RenderFrameDevToolsAgentHost::FrameDeleted(RenderFrameHost* rfh) {
-  if (IsBrowserSideNavigationEnabled()) {
-    if (static_cast<RenderFrameHostImpl*>(rfh)->frame_tree_node() ==
-        frame_tree_node_)
-      DestroyOnRenderFrameGone();  // |this| may be deleted at this point.
-    else
-      DCHECK(CheckConsistency());
-    return;
-  }
-
-  if (pending_ && pending_->host() == rfh) {
-    if (!IsBrowserSideNavigationEnabled())
-      DiscardPending();
-    DCHECK(CheckConsistency());
-    return;
-  }
-
-  if (current_ && current_->host() == rfh)
+  if (static_cast<RenderFrameHostImpl*>(rfh)->frame_tree_node() ==
+      frame_tree_node_) {
     DestroyOnRenderFrameGone();  // |this| may be deleted at this point.
+  }
 }
 
 void RenderFrameDevToolsAgentHost::RenderFrameDeleted(RenderFrameHost* rfh) {
-  if (IsBrowserSideNavigationEnabled()) {
-    if (rfh == frame_host_) {
-      render_frame_alive_ = false;
-      agent_ptr_.reset();
-    }
-    DCHECK(CheckConsistency());
-    return;
+  if (rfh == frame_host_) {
+    render_frame_alive_ = false;
+    agent_ptr_.reset();
   }
-
-  if (!current_frame_crashed_)
-    FrameDeleted(rfh);
-  else
-    DCHECK(CheckConsistency());
 }
 
 void RenderFrameDevToolsAgentHost::DestroyOnRenderFrameGone() {
   scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
-  UpdateProtocolHandlers(nullptr);
   if (IsAttached())
-    OnClientsDetached();
+    RevokePolicy();
   ForceDetachAllClients();
   frame_host_ = nullptr;
   agent_ptr_.reset();
-  pending_.reset();
-  current_.reset();
   frame_tree_node_ = nullptr;
   WebContentsObserver::Observe(nullptr);
   Release();
 }
 
-bool RenderFrameDevToolsAgentHost::CheckConsistency() {
-  if (!IsBrowserSideNavigationEnabled()) {
-    if (current_ && pending_ && current_->host() == pending_->host())
-      return false;
-  } else {
-    if (current_ || pending_)
-      return false;
-  }
-
-  if (!frame_tree_node_)
-    return !frame_host_;
-
-  RenderFrameHostManager* manager = frame_tree_node_->render_manager();
-  RenderFrameHostImpl* current = manager->current_frame_host();
-  RenderFrameHostImpl* speculative = manager->speculative_frame_host();
-  RenderFrameHostImpl* host = frame_host_;
-  return host == current || host == speculative;
-}
-
 #if defined(OS_ANDROID)
 device::mojom::WakeLock* RenderFrameDevToolsAgentHost::GetWakeLock() {
   // Here is a lazy binding, and will not reconnect after connection error.
@@ -1053,32 +577,17 @@
     case base::TERMINATION_STATUS_LAUNCH_FAILED:
       for (auto* inspector : protocol::InspectorHandler::ForAgentHost(this))
         inspector->TargetCrashed();
-      current_frame_crashed_ = true;
       break;
     default:
       for (auto* inspector : protocol::InspectorHandler::ForAgentHost(this))
         inspector->TargetDetached("Render process gone.");
       break;
   }
-  DCHECK(CheckConsistency());
 }
 
 void RenderFrameDevToolsAgentHost::DidAttachInterstitialPage() {
   for (auto* page : protocol::PageHandler::ForAgentHost(this))
     page->DidAttachInterstitialPage();
-
-  if (IsBrowserSideNavigationEnabled())
-    return;
-
-  // TODO(dgozman): this may break for cross-process subframes.
-  if (!pending_) {
-    DCHECK(CheckConsistency());
-    return;
-  }
-  // Pending set in AboutToNavigateRenderFrame turned out to be interstitial.
-  // Connect back to the real one.
-  DiscardPending();
-  DCHECK(CheckConsistency());
 }
 
 void RenderFrameDevToolsAgentHost::DidDetachInterstitialPage() {
@@ -1113,84 +622,26 @@
   bool did_initiate_recording = false;
   for (auto* tracing : protocol::TracingHandler::ForAgentHost(this))
     did_initiate_recording |= tracing->did_initiate_recording();
-  if (did_initiate_recording) {
-    if (IsBrowserSideNavigationEnabled()) {
-      frame_trace_recorder_->OnSwapCompositorFrame(frame_host_, metadata);
-    } else {
-      frame_trace_recorder_->OnSwapCompositorFrame(
-          current_ ? current_->host() : nullptr, metadata);
-    }
-  }
-}
-
-void RenderFrameDevToolsAgentHost::UpdateProtocolHandlers(
-    RenderFrameHostImpl* host) {
-  if (IsBrowserSideNavigationEnabled())
-    return;
-#if DCHECK_IS_ON()
-  // Check that we don't have stale host object here by accessing some random
-  // properties inside.
-  if (handlers_frame_host_ && handlers_frame_host_->GetRenderWidgetHost())
-    handlers_frame_host_->GetRenderWidgetHost()->GetRoutingID();
-#endif
-  handlers_frame_host_ = host;
-  for (DevToolsSession* session : sessions())
-    session->SetRenderFrameHost(host);
+  if (did_initiate_recording)
+    frame_trace_recorder_->OnSwapCompositorFrame(frame_host_, metadata);
 }
 
 void RenderFrameDevToolsAgentHost::DisconnectWebContents() {
-  if (IsBrowserSideNavigationEnabled()) {
-    UpdateFrameHost(nullptr);
-    frame_tree_node_ = nullptr;
-    navigation_handles_.clear();
-    WebContentsObserver::Observe(nullptr);
-    return;
-  }
-  if (pending_)
-    DiscardPending();
-  UpdateProtocolHandlers(nullptr);
-  for (DevToolsSession* session : sessions()) {
-    int session_id = session->session_id();
-    disconnected_cookie_for_session_[session_id] =
-        current_->StateCookie(session_id);
-    current_->Detach(session_id);
-  }
-  current_.reset();
   frame_tree_node_ = nullptr;
+  navigation_handles_.clear();
   WebContentsObserver::Observe(nullptr);
+  UpdateFrameHost(nullptr);
+  // UpdateFrameHost may destruct |this|.
 }
 
 void RenderFrameDevToolsAgentHost::ConnectWebContents(WebContents* wc) {
-  if (IsBrowserSideNavigationEnabled()) {
-    RenderFrameHostImpl* host =
-        static_cast<RenderFrameHostImpl*>(wc->GetMainFrame());
-    DCHECK(host);
-    frame_tree_node_ = host->frame_tree_node();
-    WebContentsObserver::Observe(wc);
-    UpdateFrameHost(host);
-    return;
-  }
-
-  // CommitPending may destruct |this|.
-  scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
-
-  DCHECK(!current_);
-  DCHECK(!pending_);
   RenderFrameHostImpl* host =
       static_cast<RenderFrameHostImpl*>(wc->GetMainFrame());
   DCHECK(host);
-  current_frame_crashed_ = false;
-  current_.reset(new FrameHostHolder(this, host));
-  for (DevToolsSession* session : sessions()) {
-    current_->ReattachWithCookie(
-        session,
-        std::move(disconnected_cookie_for_session_[session->session_id()]));
-  }
-  disconnected_cookie_for_session_.clear();
-
-  UpdateProtocolHandlers(host);
   frame_tree_node_ = host->frame_tree_node();
-  WebContentsObserver::Observe(WebContents::FromRenderFrameHost(host));
+  WebContentsObserver::Observe(wc);
+  UpdateFrameHost(host);
+  // UpdateFrameHost may destruct |this|.
 }
 
 std::string RenderFrameDevToolsAgentHost::GetParentId() {
@@ -1240,13 +691,8 @@
     if (!title.empty())
       return title;
   }
-  if (IsBrowserSideNavigationEnabled()) {
-    if (IsChildFrame() && frame_host_)
-      return frame_host_->GetLastCommittedURL().spec();
-  } else {
-    if (current_ && current_->host()->GetParent())
-      return current_->host()->GetLastCommittedURL().spec();
-  }
+  if (IsChildFrame() && frame_host_)
+    return frame_host_->GetLastCommittedURL().spec();
   if (web_contents())
     return base::UTF16ToUTF8(web_contents()->GetTitle());
   return GetURL().spec();
@@ -1264,15 +710,8 @@
   WebContents* web_contents = GetWebContents();
   if (web_contents && !IsChildFrame())
     return web_contents->GetVisibleURL();
-  if (IsBrowserSideNavigationEnabled()) {
-    if (frame_host_)
-      return frame_host_->GetLastCommittedURL();
-  } else {
-    if (pending_)
-      return pending_->host()->GetLastCommittedURL();
-    if (current_)
-      return current_->host()->GetLastCommittedURL();
-  }
+  if (frame_host_)
+    return frame_host_->GetLastCommittedURL();
   return GURL();
 }
 
@@ -1343,13 +782,8 @@
   for (auto* tracing : protocol::TracingHandler::ForAgentHost(this))
     did_initiate_recording |= tracing->did_initiate_recording();
   if (did_initiate_recording) {
-    if (IsBrowserSideNavigationEnabled()) {
-      frame_trace_recorder_->OnSynchronousSwapCompositorFrame(frame_host_,
-                                                              frame_metadata);
-    } else {
-      frame_trace_recorder_->OnSynchronousSwapCompositorFrame(
-          current_ ? current_->host() : nullptr, frame_metadata);
-    }
+    frame_trace_recorder_->OnSynchronousSwapCompositorFrame(frame_host_,
+                                                            frame_metadata);
   }
 }
 
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.h b/content/browser/devtools/render_frame_devtools_agent_host.h
index 809c965..cc49c35 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.h
+++ b/content/browser/devtools/render_frame_devtools_agent_host.h
@@ -59,10 +59,6 @@
       mojom::BeginNavigationParams* begin_params,
       bool* report_raw_headers);
 
-  static void OnCancelPendingNavigation(RenderFrameHost* pending,
-                                        RenderFrameHost* current);
-  static void OnBeforeNavigation(RenderFrameHost* current,
-                                 RenderFrameHost* pending);
   static void OnResetNavigationRequest(NavigationRequest* navigation_request);
 
   static std::vector<std::unique_ptr<NavigationThrottle>>
@@ -94,7 +90,6 @@
   bool Close() override;
   base::TimeTicks GetLastActivityTime() override;
 
-  // PlzNavigate
   RenderFrameHostImpl* GetFrameHostForTesting() { return frame_host_; }
 
  private:
@@ -125,28 +120,14 @@
   void WasHidden() override;
   void DidReceiveCompositorFrame() override;
 
-  void AboutToNavigateRenderFrame(RenderFrameHost* old_host,
-                                  RenderFrameHost* new_host);
-
-  void SetPending(RenderFrameHostImpl* host);
-  void CommitPending();
-  void DiscardPending();
-  void UpdateProtocolHandlers(RenderFrameHostImpl* host);
-
   bool IsChildFrame();
 
-  void OnClientsAttached();
-  void OnClientsDetached();
-
-  void RenderFrameCrashed();
   void OnSwapCompositorFrame(const IPC::Message& message);
   void DestroyOnRenderFrameGone();
-
-  bool CheckConsistency();
   void UpdateFrameHost(RenderFrameHostImpl* frame_host);
   void MaybeReattachToRenderFrame();
-  void GrantPolicy(RenderFrameHostImpl* host);
-  void RevokePolicy(RenderFrameHostImpl* host);
+  void GrantPolicy();
+  void RevokePolicy();
 
 #if defined(OS_ANDROID)
   device::mojom::WakeLock* GetWakeLock();
@@ -156,22 +137,10 @@
       viz::CompositorFrameMetadata frame_metadata);
   bool EnsureAgent();
 
-  class FrameHostHolder;
-
-  std::unique_ptr<FrameHostHolder> current_;
-  std::unique_ptr<FrameHostHolder> pending_;
-
-  // Stores per-host state between DisconnectWebContents and ConnectWebContents.
-  base::flat_map<int, std::string> disconnected_cookie_for_session_;
-
   std::unique_ptr<DevToolsFrameTraceRecorder> frame_trace_recorder_;
 #if defined(OS_ANDROID)
   device::mojom::WakeLockPtr wake_lock_;
 #endif
-  RenderFrameHostImpl* handlers_frame_host_;
-  bool current_frame_crashed_;
-
-  // PlzNavigate
 
   // The active host we are talking to.
   RenderFrameHostImpl* frame_host_ = nullptr;
diff --git a/content/browser/devtools/render_frame_devtools_agent_host_browsertest.cc b/content/browser/devtools/render_frame_devtools_agent_host_browsertest.cc
index d166e08..d4d30a5b 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host_browsertest.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host_browsertest.cc
@@ -31,9 +31,6 @@
 // See https://crbug.com/695203.
 IN_PROC_BROWSER_TEST_F(RenderFrameDevToolsAgentHostBrowserTest,
                        CancelCrossOriginNavigationAfterReadyToCommit) {
-  if (!IsBrowserSideNavigationEnabled())
-    return;
-
   ControllableHttpResponse response_b(embedded_test_server(), "/response_b");
   ControllableHttpResponse response_c(embedded_test_server(), "/response_c");
   EXPECT_TRUE(embedded_test_server()->Start());
diff --git a/content/browser/file_url_loader_factory.cc b/content/browser/file_url_loader_factory.cc
index 30b8caa0..afd1a75 100644
--- a/content/browser/file_url_loader_factory.cc
+++ b/content/browser/file_url_loader_factory.cc
@@ -38,6 +38,7 @@
 #include "net/http/http_byte_range.h"
 #include "net/http/http_util.h"
 #include "net/url_request/redirect_info.h"
+#include "storage/common/fileapi/file_system_util.h"
 #include "url/gurl.h"
 
 #if defined(OS_WIN)
@@ -90,13 +91,14 @@
   static void CreateAndStart(const base::FilePath& profile_path,
                              const ResourceRequest& request,
                              mojom::URLLoaderRequest loader,
-                             mojom::URLLoaderClientPtrInfo client_info) {
+                             mojom::URLLoaderClientPtrInfo client_info,
+                             std::unique_ptr<FileURLLoaderObserver> observer) {
     // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
     // bindings are alive - essentially until either the client gives up or all
     // file data has been sent to it.
     auto* file_url_loader = new FileURLDirectoryLoader;
     file_url_loader->Start(profile_path, request, std::move(loader),
-                           std::move(client_info));
+                           std::move(client_info), std::move(observer));
   }
 
   // mojom::URLLoader:
@@ -114,7 +116,8 @@
   void Start(const base::FilePath& profile_path,
              const ResourceRequest& request,
              mojom::URLLoaderRequest loader,
-             mojom::URLLoaderClientPtrInfo client_info) {
+             mojom::URLLoaderClientPtrInfo client_info,
+             std::unique_ptr<content::FileURLLoaderObserver> observer) {
     binding_.Bind(std::move(loader));
     binding_.set_connection_error_handler(base::BindOnce(
         &FileURLDirectoryLoader::OnConnectionError, base::Unretained(this)));
@@ -289,14 +292,16 @@
                              mojom::URLLoaderClientPtrInfo client_info,
                              DirectoryLoadingPolicy directory_loading_policy,
                              FileAccessPolicy file_access_policy,
-                             LinkFollowingPolicy link_following_policy) {
+                             LinkFollowingPolicy link_following_policy,
+                             std::unique_ptr<FileURLLoaderObserver> observer) {
     // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
     // bindings are alive - essentially until either the client gives up or all
     // file data has been sent to it.
     auto* file_url_loader = new FileURLLoader;
     file_url_loader->Start(profile_path, request, std::move(loader),
                            std::move(client_info), directory_loading_policy,
-                           file_access_policy, link_following_policy);
+                           file_access_policy, link_following_policy,
+                           std::move(observer));
   }
 
   // mojom::URLLoader:
@@ -317,7 +322,8 @@
              mojom::URLLoaderClientPtrInfo client_info,
              DirectoryLoadingPolicy directory_loading_policy,
              FileAccessPolicy file_access_policy,
-             LinkFollowingPolicy link_following_policy) {
+             LinkFollowingPolicy link_following_policy,
+             std::unique_ptr<FileURLLoaderObserver> observer) {
     ResourceResponseHead head;
     head.request_start = base::TimeTicks::Now();
     head.response_start = base::TimeTicks::Now();
@@ -331,6 +337,8 @@
     base::FilePath path;
     if (!net::FileURLToFilePath(request.url, &path)) {
       client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
+      if (observer)
+        observer->OnDoneReading();
       return;
     }
 
@@ -338,6 +346,8 @@
     if (!base::GetFileInfo(path, &info)) {
       client->OnComplete(
           network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
+      if (observer)
+        observer->OnDoneReading();
       return;
     }
 
@@ -345,6 +355,8 @@
       if (directory_loading_policy == DirectoryLoadingPolicy::kFail) {
         client->OnComplete(
             network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND));
+        if (observer)
+          observer->OnDoneReading();
         return;
       }
 
@@ -372,7 +384,8 @@
       ResourceRequest new_request = request;
       new_request.url = directory_url;
       FileURLDirectoryLoader::CreateAndStart(
-          profile_path, new_request, binding_.Unbind(), client.PassInterface());
+          profile_path, new_request, binding_.Unbind(), client.PassInterface(),
+          std::move(observer));
       MaybeDeleteSelf();
       return;
     }
@@ -397,7 +410,8 @@
       new_request.url = redirect_info.new_url;
       return Start(profile_path, request, binding_.Unbind(),
                    client.PassInterface(), directory_loading_policy,
-                   file_access_policy, link_following_policy);
+                   file_access_policy, link_following_policy,
+                   std::move(observer));
     }
 #endif  // defined(OS_WIN)
 
@@ -406,12 +420,16 @@
             path, base::MakeAbsoluteFilePath(path), profile_path)) {
       client->OnComplete(
           network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
+      if (observer)
+        observer->OnDoneReading();
       return;
     }
 
     mojo::DataPipe pipe(kDefaultFileUrlPipeSize);
     if (!pipe.consumer_handle.is_valid()) {
       client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
+      if (observer)
+        observer->OnDoneReading();
       return;
     }
 
@@ -420,14 +438,28 @@
     // path didn't have a trailing path separator. In that case we finish with
     // a redirect above which will in turn be handled by FileURLDirectoryLoader.
     DCHECK(!info.is_directory);
+    if (observer)
+      observer->OnStart();
 
     base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+    if (observer)
+      observer->OnOpenComplete(net::FileErrorToNetError(file.error_details()));
     char initial_read_buffer[net::kMaxBytesToSniff];
     int initial_read_result =
         file.ReadAtCurrentPos(initial_read_buffer, net::kMaxBytesToSniff);
     if (initial_read_result < 0) {
-      client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
+      base::File::Error read_error = base::File::GetLastFileError();
+      DCHECK_NE(base::File::FILE_OK, read_error);
+      if (observer) {
+        observer->OnBytesRead(nullptr, 0u, read_error);
+        observer->OnDoneReading();
+      }
+      net::Error net_error = net::FileErrorToNetError(read_error);
+      client->OnComplete(network::URLLoaderCompletionStatus(net_error));
       return;
+    } else if (observer) {
+      observer->OnBytesRead(initial_read_buffer, initial_read_result,
+                            base::File::FILE_OK);
     }
     size_t initial_read_size = static_cast<size_t>(initial_read_result);
 
@@ -450,6 +482,8 @@
       if (fail) {
         client->OnComplete(network::URLLoaderCompletionStatus(
             net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+        if (observer)
+          observer->OnDoneReading();
         return;
       }
     }
@@ -478,7 +512,7 @@
           &initial_read_buffer[first_byte_to_send], &write_size,
           MOJO_WRITE_DATA_FLAG_NONE);
       if (result != MOJO_RESULT_OK || write_size != expected_write_size) {
-        OnFileWritten(result);
+        OnFileWritten(std::move(observer), result);
         return;
       }
 
@@ -497,20 +531,24 @@
 
     if (total_bytes_to_send == 0) {
       // There's definitely no more data, so we're already done.
-      OnFileWritten(MOJO_RESULT_OK);
+      OnFileWritten(std::move(observer), MOJO_RESULT_OK);
       return;
     }
 
     // In case of a range request, seek to the appropriate position before
     // sending the remaining bytes asynchronously. Under normal conditions
     // (i.e., no range request) this Seek is effectively a no-op.
-    file.Seek(base::File::FROM_BEGIN, static_cast<int64_t>(first_byte_to_send));
+    int new_position = file.Seek(base::File::FROM_BEGIN,
+                                 static_cast<int64_t>(first_byte_to_send));
+    if (observer)
+      observer->OnSeekComplete(new_position);
 
     data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
-        std::move(pipe.producer_handle));
+        std::move(pipe.producer_handle), std::move(observer));
     data_producer_->WriteFromFile(
         std::move(file), total_bytes_to_send,
-        base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this)));
+        base::BindOnce(&FileURLLoader::OnFileWritten, base::Unretained(this),
+                       nullptr));
   }
 
   void OnConnectionError() {
@@ -523,10 +561,13 @@
       delete this;
   }
 
-  void OnFileWritten(MojoResult result) {
+  void OnFileWritten(std::unique_ptr<FileURLLoaderObserver> observer,
+                     MojoResult result) {
     // All the data has been written now. Close the data pipe. The consumer will
     // be notified that there will be no more data to read from now.
     data_producer_.reset();
+    if (observer)
+      observer->OnDoneReading();
 
     if (result == MOJO_RESULT_OK)
       client_->OnComplete(network::URLLoaderCompletionStatus(net::OK));
@@ -566,7 +607,8 @@
     task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&FileURLDirectoryLoader::CreateAndStart, profile_path_,
-                       request, std::move(loader), client.PassInterface()));
+                       request, std::move(loader), client.PassInterface(),
+                       std::unique_ptr<FileURLLoaderObserver>()));
   } else {
     task_runner_->PostTask(
         FROM_HERE,
@@ -574,7 +616,8 @@
                        std::move(loader), client.PassInterface(),
                        DirectoryLoadingPolicy::kRespondWithListing,
                        FileAccessPolicy::kRestricted,
-                       LinkFollowingPolicy::kFollow));
+                       LinkFollowingPolicy::kFollow,
+                       std::unique_ptr<FileURLLoaderObserver>()));
   }
 }
 
@@ -584,7 +627,8 @@
 
 void CreateFileURLLoader(const ResourceRequest& request,
                          mojom::URLLoaderRequest loader,
-                         mojom::URLLoaderClientPtr client) {
+                         mojom::URLLoaderClientPtr client,
+                         std::unique_ptr<FileURLLoaderObserver> observer) {
   auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
       {base::MayBlock(), base::TaskPriority::BACKGROUND,
        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
@@ -594,7 +638,7 @@
                      std::move(loader), client.PassInterface(),
                      DirectoryLoadingPolicy::kFail,
                      FileAccessPolicy::kUnrestricted,
-                     LinkFollowingPolicy::kDoNotFollow));
+                     LinkFollowingPolicy::kDoNotFollow, std::move(observer)));
 }
 
 }  // namespace content
diff --git a/content/browser/histogram_synchronizer.cc b/content/browser/histogram_synchronizer.cc
index bbfb0e48..76d0bf9 100644
--- a/content/browser/histogram_synchronizer.cc
+++ b/content/browser/histogram_synchronizer.cc
@@ -21,6 +21,8 @@
 #include "content/public/browser/histogram_fetcher.h"
 #include "content/public/common/content_constants.h"
 
+namespace content {
+
 using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
@@ -36,8 +38,6 @@
 
 }  // anonymous namespace
 
-namespace content {
-
 // The "RequestContext" structure describes an individual request received from
 // the UI. All methods are accessible on UI thread.
 class HistogramSynchronizer::RequestContext {
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index 563f9a88..36c4ff74 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -123,15 +123,15 @@
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_constants.h"
 
+// ----------------------------------------------------------------------------
+
+namespace content {
+
 using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
 using storage::ShareableFileReference;
 
-// ----------------------------------------------------------------------------
-
-namespace content {
-
 namespace {
 
 static ResourceDispatcherHostImpl* g_resource_dispatcher_host;
diff --git a/content/browser/renderer_host/input/fling_controller.cc b/content/browser/renderer_host/input/fling_controller.cc
index fb43d429..bef1223d 100644
--- a/content/browser/renderer_host/input/fling_controller.cc
+++ b/content/browser/renderer_host/input/fling_controller.cc
@@ -228,8 +228,10 @@
               : blink::WebMouseWheelEvent::kPhaseBegan;
       GenerateAndSendWheelEvents(delta_to_scroll, phase);
       has_fling_animation_started_ = true;
-      ScheduleFlingProgress();
     }
+    // As long as the fling curve is active, the fling progress must get
+    // scheduled even when the last delta to scroll was zero.
+    ScheduleFlingProgress();
   } else {  // !is_fling_active
     CancelCurrentFling();
   }
diff --git a/content/browser/renderer_host/input/fling_controller_unittest.cc b/content/browser/renderer_host/input/fling_controller_unittest.cc
index 2fb8725..591a88f 100644
--- a/content/browser/renderer_host/input/fling_controller_unittest.cc
+++ b/content/browser/renderer_host/input/fling_controller_unittest.cc
@@ -17,16 +17,6 @@
 using blink::WebInputEvent;
 using blink::WebMouseWheelEvent;
 
-namespace {
-void GiveItSomeTime(uint64_t delay_in_ms) {
-  base::RunLoop run_loop;
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE, run_loop.QuitClosure(),
-      base::TimeDelta::FromMillisecondsD(delay_in_ms));
-  run_loop.Run();
-}
-}  // namespace
-
 namespace content {
 
 class FakeFlingController : public FlingController {
@@ -111,10 +101,10 @@
       fling_controller_->ProcessGestureFlingCancel(fling_cancel_with_latency);
   }
 
-  void ProgressFling() {
+  void ProgressFling(base::TimeTicks current_time) {
     DCHECK(scheduled_next_fling_progress_);
     scheduled_next_fling_progress_ = false;
-    fling_controller_->ProgressFling(base::TimeTicks::Now());
+    fling_controller_->ProgressFling(current_time);
   }
 
   bool FlingInProgress() { return fling_controller_->fling_in_progress(); }
@@ -142,53 +132,46 @@
   EXPECT_EQ(0.f, last_sent_wheel_.delta_y);
 }
 
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
-TEST_F(FlingControllerTest, DISABLED_ControllerHandlesGestureFling) {
+TEST_F(FlingControllerTest, ControllerHandlesGestureFling) {
+  base::TimeTicks progress_time = base::TimeTicks::Now();
   SimulateFlingStart(blink::kWebGestureDeviceTouchpad, gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
   // The first wheel event must have momentum_phase == KPhaseBegan.
-  GiveItSomeTime(17);
-  ProgressFling();
+  progress_time += base::TimeDelta::FromMilliseconds(17);
+  ProgressFling(progress_time);
   EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
   // The rest of the wheel events must have momentum_phase == KPhaseChanged.
-  GiveItSomeTime(17);
-  ProgressFling();
+  progress_time += base::TimeDelta::FromMilliseconds(17);
+  ProgressFling(progress_time);
   EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
-  // Now cancel the fling. The GFC will get suppressed by fling booster.
+  // Now cancel the fling. The GFC will end the fling.
   SimulateFlingCancel(blink::kWebGestureDeviceTouchpad);
-  EXPECT_TRUE(last_fling_cancel_filtered_);
-  EXPECT_TRUE(FlingInProgress());
-
-  // Wait for the boosting timer to expire. The delayed cancelation must work.
-  GiveItSomeTime(500);
-  ProgressFling();
-  EXPECT_FALSE(FlingInProgress());
   EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, last_sent_wheel_.momentum_phase);
   EXPECT_EQ(0.f, last_sent_wheel_.delta_x);
   EXPECT_EQ(0.f, last_sent_wheel_.delta_y);
 }
 
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
-TEST_F(FlingControllerTest, DISABLED_ControllerSendsWheelEndWhenFlingIsOver) {
+TEST_F(FlingControllerTest, ControllerSendsWheelEndWhenFlingIsOver) {
+  base::TimeTicks progress_time = base::TimeTicks::Now();
   SimulateFlingStart(blink::kWebGestureDeviceTouchpad, gfx::Vector2dF(100, 0));
   EXPECT_TRUE(FlingInProgress());
-  GiveItSomeTime(17);
-  ProgressFling();
+  progress_time += base::TimeDelta::FromMilliseconds(17);
+  ProgressFling(progress_time);
   EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
-  GiveItSomeTime(17);
-  ProgressFling();
+  progress_time += base::TimeDelta::FromMilliseconds(17);
+  ProgressFling(progress_time);
   while (FlingInProgress()) {
     EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged,
               last_sent_wheel_.momentum_phase);
     EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
-    GiveItSomeTime(17);
-    ProgressFling();
+    progress_time += base::TimeDelta::FromMilliseconds(17);
+    ProgressFling(progress_time);
   }
 
   EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, last_sent_wheel_.momentum_phase);
@@ -196,13 +179,12 @@
   EXPECT_EQ(0.f, last_sent_wheel_.delta_y);
 }
 
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
-TEST_F(FlingControllerTest,
-       DISABLED_EarlyFlingCancelationOnInertialGSUAckNotConsumed) {
+TEST_F(FlingControllerTest, EarlyFlingCancelationOnInertialGSUAckNotConsumed) {
+  base::TimeTicks progress_time = base::TimeTicks::Now();
   SimulateFlingStart(blink::kWebGestureDeviceTouchpad, gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
-  GiveItSomeTime(17);
-  ProgressFling();
+  progress_time += base::TimeDelta::FromMilliseconds(17);
+  ProgressFling(progress_time);
   EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
@@ -222,19 +204,21 @@
   EXPECT_EQ(0.f, last_sent_wheel_.delta_y);
 }
 
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
+// TODO(sahel): Enable the test once boosting is enabled for touchpad fling.
+// https://crbug.com/249063
 TEST_F(FlingControllerTest, DISABLED_ControllerBoostsFling) {
+  base::TimeTicks progress_time = base::TimeTicks::Now();
   SimulateFlingStart(blink::kWebGestureDeviceTouchpad, gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
   // The first wheel event must have momentum_phase == KPhaseBegan.
-  GiveItSomeTime(17);
-  ProgressFling();
+  progress_time += base::TimeDelta::FromMilliseconds(17);
+  ProgressFling(progress_time);
   EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
   // The rest of the wheel events must have momentum_phase == KPhaseChanged.
-  GiveItSomeTime(17);
-  ProgressFling();
+  progress_time += base::TimeDelta::FromMilliseconds(17);
+  ProgressFling(progress_time);
   EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc
index 793a1941..ab3df8f 100644
--- a/content/browser/renderer_host/input/input_router_impl.cc
+++ b/content/browser/renderer_host/input/input_router_impl.cc
@@ -34,6 +34,8 @@
 #include "ui/events/event.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
+namespace content {
+
 using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
@@ -45,7 +47,6 @@
 using blink::WebTouchEvent;
 using ui::WebInputEventTraits;
 
-namespace content {
 namespace {
 
 bool WasHandled(InputEventAckState state) {
@@ -551,11 +552,14 @@
 }
 
 void InputRouterImpl::OnSetTouchAction(cc::TouchAction touch_action) {
-  // Synthetic touchstart events should get filtered out in RenderWidget.
-  DCHECK(touch_event_queue_.IsPendingAckTouchStart());
   TRACE_EVENT1("input", "InputRouterImpl::OnSetTouchAction", "action",
                touch_action);
 
+  // It is possible we get a touch action for a touch start that is no longer
+  // in the queue. eg. Events that have fired the Touch ACK timeout.
+  if (!touch_event_queue_.IsPendingAckTouchStart())
+    return;
+
   touch_action_filter_.OnSetTouchAction(touch_action);
 
   // kTouchActionNone should disable the touch ack timeout.
diff --git a/content/browser/renderer_host/input/legacy_input_router_impl.cc b/content/browser/renderer_host/input/legacy_input_router_impl.cc
index 4bfe6481..4331c9f 100644
--- a/content/browser/renderer_host/input/legacy_input_router_impl.cc
+++ b/content/browser/renderer_host/input/legacy_input_router_impl.cc
@@ -484,11 +484,14 @@
 }
 
 void LegacyInputRouterImpl::OnSetTouchAction(cc::TouchAction touch_action) {
-  // Synthetic touchstart events should get filtered out in RenderWidget.
-  DCHECK(touch_event_queue_->IsPendingAckTouchStart());
   TRACE_EVENT1("input", "LegacyInputRouterImpl::OnSetTouchAction", "action",
                touch_action);
 
+  // It is possible we get a touch action for a touch start that is no longer
+  // in the queue. eg. Events that have fired the Touch ACK timeout.
+  if (!touch_event_queue_->IsPendingAckTouchStart())
+    return;
+
   touch_action_filter_.OnSetTouchAction(touch_action);
 
   // kTouchActionNone should disable the touch ack timeout.
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
index 2d7d1d2..b77cf6b 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
@@ -144,7 +144,7 @@
   }
 
   void ExpectUkmReported(const char* event_name,
-                         const char* metric_name,
+                         const std::vector<std::string>& metric_names,
                          size_t expected_count) {
     const ukm::TestUkmRecorder* ukm_recoder =
         test_browser_client_.GetTestUkmRecorder();
@@ -153,7 +153,9 @@
     EXPECT_EQ(expected_count, entries.size());
     for (const auto* const entry : entries) {
       ukm_recoder->ExpectEntrySourceHasUrl(entry, GURL(kUrl));
-      EXPECT_TRUE(ukm_recoder->EntryHasMetric(entry, metric_name));
+      for (const auto& metric_name : metric_names) {
+        EXPECT_TRUE(ukm_recoder->EntryHasMetric(entry, metric_name.c_str()));
+      }
     }
   }
 
@@ -289,8 +291,10 @@
 
       // UKM metrics.
       total_ukm_entry_count++;
-      ExpectUkmReported("Event.ScrollBegin.Wheel",
-                        "TimeToScrollUpdateSwapBegin", total_ukm_entry_count);
+      ExpectUkmReported(
+          "Event.ScrollBegin.Wheel",
+          {"TimeToScrollUpdateSwapBegin", "TimeToHandled", "IsMainThread"},
+          total_ukm_entry_count);
       // Rappor metrics.
       EXPECT_TRUE(
           RapporSampleAssert("Event.Latency.ScrollUpdate.Touch."
@@ -402,8 +406,10 @@
 
       // UKM metrics.
       total_ukm_entry_count++;
-      ExpectUkmReported("Event.ScrollUpdate.Wheel",
-                        "TimeToScrollUpdateSwapBegin", total_ukm_entry_count);
+      ExpectUkmReported(
+          "Event.ScrollUpdate.Wheel",
+          {"TimeToScrollUpdateSwapBegin", "TimeToHandled", "IsMainThread"},
+          total_ukm_entry_count);
       // Rappor metrics.
       EXPECT_TRUE(
           RapporSampleAssert("Event.Latency.ScrollUpdate.Touch."
@@ -538,8 +544,10 @@
 
     // UKM metrics.
     total_ukm_entry_count++;
-    ExpectUkmReported("Event.ScrollBegin.Touch", "TimeToScrollUpdateSwapBegin",
-                      total_ukm_entry_count);
+    ExpectUkmReported(
+        "Event.ScrollBegin.Touch",
+        {"TimeToScrollUpdateSwapBegin", "TimeToHandled", "IsMainThread"},
+        total_ukm_entry_count);
     // Rappor metrics.
     EXPECT_TRUE(
         RapporSampleAssert("Event.Latency.ScrollUpdate.Touch."
@@ -659,8 +667,10 @@
 
     // UKM metrics.
     total_ukm_entry_count++;
-    ExpectUkmReported("Event.ScrollUpdate.Touch", "TimeToScrollUpdateSwapBegin",
-                      total_ukm_entry_count);
+    ExpectUkmReported(
+        "Event.ScrollUpdate.Touch",
+        {"TimeToScrollUpdateSwapBegin", "TimeToHandled", "IsMainThread"},
+        total_ukm_entry_count);
 
     // Rappor metrics.
     EXPECT_TRUE(
diff --git a/content/browser/renderer_host/input/synthetic_gesture_target_base.cc b/content/browser/renderer_host/input/synthetic_gesture_target_base.cc
index 680b676c..f451534b 100644
--- a/content/browser/renderer_host/input/synthetic_gesture_target_base.cc
+++ b/content/browser/renderer_host/input/synthetic_gesture_target_base.cc
@@ -55,27 +55,35 @@
         static_cast<const WebTouchEvent&>(event);
 
     // Check that all touch pointers are within the content bounds.
-    for (unsigned i = 0; i < web_touch.touches_length; i++)
-      CHECK(web_touch.touches[i].state != WebTouchPoint::kStatePressed ||
-            PointIsWithinContents(web_touch.touches[i].PositionInWidget().x,
-                                  web_touch.touches[i].PositionInWidget().y))
-          << "Touch coordinates are not within content bounds on TouchStart.";
-
+    for (unsigned i = 0; i < web_touch.touches_length; i++) {
+      if (web_touch.touches[i].state == WebTouchPoint::kStatePressed &&
+          !PointIsWithinContents(web_touch.touches[i].PositionInWidget().x,
+                                 web_touch.touches[i].PositionInWidget().y)) {
+        LOG(WARNING)
+            << "Touch coordinates are not within content bounds on TouchStart.";
+        return;
+      }
+    }
     DispatchWebTouchEventToPlatform(web_touch, latency_info);
   } else if (event.GetType() == WebInputEvent::kMouseWheel) {
     const WebMouseWheelEvent& web_wheel =
         static_cast<const WebMouseWheelEvent&>(event);
-    CHECK(PointIsWithinContents(web_wheel.PositionInWidget().x,
-                                web_wheel.PositionInWidget().y))
-        << "Mouse wheel position is not within content bounds.";
+    if (!PointIsWithinContents(web_wheel.PositionInWidget().x,
+                               web_wheel.PositionInWidget().y)) {
+      LOG(WARNING) << "Mouse wheel position is not within content bounds.";
+      return;
+    }
     DispatchWebMouseWheelEventToPlatform(web_wheel, latency_info);
   } else if (WebInputEvent::IsMouseEventType(event.GetType())) {
     const WebMouseEvent& web_mouse =
         static_cast<const WebMouseEvent&>(event);
-    CHECK(event.GetType() != WebInputEvent::kMouseDown ||
-          PointIsWithinContents(web_mouse.PositionInWidget().x,
-                                web_mouse.PositionInWidget().y))
-        << "Mouse pointer is not within content bounds on MouseDown.";
+    if (event.GetType() == WebInputEvent::kMouseDown &&
+        !PointIsWithinContents(web_mouse.PositionInWidget().x,
+                               web_mouse.PositionInWidget().y)) {
+      LOG(WARNING)
+          << "Mouse pointer is not within content bounds on MouseDown.";
+      return;
+    }
     DispatchWebMouseEventToPlatform(web_mouse, latency_info);
   } else {
     NOTREACHED();
diff --git a/content/browser/renderer_host/media/video_capture_controller.cc b/content/browser/renderer_host/media/video_capture_controller.cc
index d4fa9fb..66487ed 100644
--- a/content/browser/renderer_host/media/video_capture_controller.cc
+++ b/content/browser/renderer_host/media/video_capture_controller.cc
@@ -160,7 +160,14 @@
 
 mojo::ScopedSharedBufferHandle
 VideoCaptureController::BufferContext::CloneHandle() {
-  return buffer_handle_->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY);
+  // Special behavior here: If the handle was already read-only, the Clone()
+  // call here will maintain that read-only permission. If it was read-write,
+  // the cloned handle will have read-write permission.
+  //
+  // TODO(crbug.com/797470): We should be able to demote read-write to read-only
+  // permissions when Clone()'ing handles. Currently, this causes a crash.
+  return buffer_handle_->Clone(
+      mojo::SharedBufferHandle::AccessMode::READ_WRITE);
 }
 
 VideoCaptureController::VideoCaptureController(
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index f87d722b..6123277 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -4372,27 +4372,25 @@
   EXPECT_EQ(OVERSCROLL_EAST, overscroll_mode());
   EXPECT_EQ(OverscrollSource::TOUCHPAD, overscroll_source());
 
-  // ProgressFling will send a nonblocking wheel end event. The generated GSE
-  // resets the overscroll state.
-  widget_host_->ProgressFling(base::TimeTicks::Now() +
-                              base::TimeDelta::FromMilliseconds(20));
-
-  EXPECT_EQ(OVERSCROLL_NONE, overscroll_mode());
+  base::TimeTicks progress_time =
+      base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(17);
+  // Overscroll mode will get reset at the end of the fling progress.
+  while (overscroll_mode() != OVERSCROLL_NONE) {
+    widget_host_->ProgressFling(progress_time);
+    progress_time += base::TimeDelta::FromMilliseconds(17);
+  }
   EXPECT_EQ(OverscrollSource::NONE, overscroll_source());
 }
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
 TEST_F(RenderWidgetHostViewAuraOverscrollTest,
-       DISABLED_ScrollEventsOverscrollWithFling) {
+       ScrollEventsOverscrollWithFling) {
   ScrollEventsOverscrollWithFling();
 }
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
 TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest,
-       DISABLED_ScrollEventsOverscrollWithFling) {
+       ScrollEventsOverscrollWithFling) {
   ScrollEventsOverscrollWithFling();
 }
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
 TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
-       DISABLED_ScrollEventsOverscrollWithFling) {
+       ScrollEventsOverscrollWithFling) {
   ScrollEventsOverscrollWithFling();
 }
 
@@ -5829,27 +5827,23 @@
   // queued in gesture event queue.
   EXPECT_EQ(0U, events.size());
 
-  // The first ProgressFling will send a nonblocking wheel end event. The GSE
-  // generated from the wheel end event resets the overscroll state.
-  widget_host_->ProgressFling(base::TimeTicks::Now() +
-                              base::TimeDelta::FromMilliseconds(20));
-
-  EXPECT_EQ(0.f, overscroll_delta_x());
-  EXPECT_EQ(0.f, overscroll_delta_y());
+  base::TimeTicks progress_time =
+      base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(17);
+  // Overscroll delta will get reset at the end of the fling progress.
+  while (overscroll_delta_y() != 0.f) {
+    widget_host_->ProgressFling(progress_time);
+    progress_time += base::TimeDelta::FromMilliseconds(17);
+  }
 }
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
-TEST_F(RenderWidgetHostViewAuraOverscrollTest,
-       DISABLED_ScrollDeltasResetOnEnd) {
+TEST_F(RenderWidgetHostViewAuraOverscrollTest, ScrollDeltasResetOnEnd) {
   ScrollDeltasResetOnEnd();
 }
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
 TEST_F(RenderWidgetHostViewAuraOverscrollWithoutWheelScrollLatchingTest,
-       DISABLED_ScrollDeltasResetOnEnd) {
+       ScrollDeltasResetOnEnd) {
   ScrollDeltasResetOnEnd();
 }
-// TODO(crbug.com/795617): Test timing expectations make it flaky.
 TEST_F(RenderWidgetHostViewAuraOverScrollAsyncWheelEventsEnabledTest,
-       DISABLED_ScrollDeltasResetOnEnd) {
+       ScrollDeltasResetOnEnd) {
   ScrollDeltasResetOnEnd();
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.cc b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
index 4a2d0fc5..abecc15 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
@@ -605,7 +605,8 @@
 void RenderWidgetHostViewChildFrame::SendSurfaceInfoToEmbedderImpl(
     const viz::SurfaceInfo& surface_info,
     const viz::SurfaceSequence& sequence) {
-  frame_connector_->SetChildFrameSurface(surface_info, sequence);
+  if (frame_connector_)
+    frame_connector_->SetChildFrameSurface(surface_info, sequence);
 }
 
 void RenderWidgetHostViewChildFrame::SubmitCompositorFrame(
diff --git a/content/browser/renderer_interface_binders.cc b/content/browser/renderer_interface_binders.cc
index 17af3972..79948d6 100644
--- a/content/browser/renderer_interface_binders.cc
+++ b/content/browser/renderer_interface_binders.cc
@@ -87,7 +87,7 @@
   connector->BindInterface(service_name, std::move(request));
 }
 
-void GetRestrictedCookieManager(
+void GetRestrictedCookieManagerForWorker(
     network::mojom::RestrictedCookieManagerRequest request,
     RenderProcessHost* render_process_host,
     const url::Origin& origin) {
@@ -163,7 +163,7 @@
   parameterized_binder_registry_.AddInterface(
       base::BindRepeating(&BackgroundFetchServiceImpl::Create));
   parameterized_binder_registry_.AddInterface(
-      base::BindRepeating(GetRestrictedCookieManager));
+      base::BindRepeating(GetRestrictedCookieManagerForWorker));
 }
 
 RendererInterfaceBinders& GetRendererInterfaceBinders() {
diff --git a/content/browser/service_manager/service_manager_context.cc b/content/browser/service_manager/service_manager_context.cc
index dc20985..a7e9505 100644
--- a/content/browser/service_manager/service_manager_context.cc
+++ b/content/browser/service_manager/service_manager_context.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/service_manager/service_manager_context.h"
 
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -14,6 +15,7 @@
 #include "base/json/json_reader.h"
 #include "base/lazy_instance.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/message_loop/message_loop.h"
 #include "base/process/process_handle.h"
 #include "base/single_thread_task_runner.h"
@@ -101,12 +103,16 @@
 base::LazyInstance<std::unique_ptr<service_manager::Connector>>::Leaky
     g_io_thread_connector = LAZY_INSTANCE_INITIALIZER;
 
+base::LazyInstance<std::map<std::string, base::WeakPtr<UtilityProcessHost>>>::
+    Leaky g_active_process_groups;
+
 void DestroyConnectorOnIOThread() { g_io_thread_connector.Get().reset(); }
 
 // Launch a process for a service once its sandbox type is known.
 void StartServiceInUtilityProcess(
     const std::string& service_name,
     const base::string16& process_name,
+    base::Optional<std::string> process_group,
     service_manager::mojom::ServiceRequest request,
     service_manager::mojom::ConnectResult query_result,
     const std::string& sandbox_string) {
@@ -114,16 +120,30 @@
   service_manager::SandboxType sandbox_type =
       service_manager::UtilitySandboxTypeFromString(sandbox_string);
 
-  UtilityProcessHostImpl* process_host =
-      new UtilityProcessHostImpl(nullptr, nullptr);
+  // Look for an existing process group.
+  base::WeakPtr<UtilityProcessHost>* weak_host = nullptr;
+  if (process_group)
+    weak_host = &g_active_process_groups.Get()[*process_group];
+
+  UtilityProcessHost* process_host = nullptr;
+  if (weak_host && *weak_host) {
+    // Start service in an existing process.
+    process_host = weak_host->get();
+  } else {
+    // Start a new process for this service.
+    UtilityProcessHostImpl* impl = new UtilityProcessHostImpl(nullptr, nullptr);
 #if defined(OS_WIN)
-  if (sandbox_type == service_manager::SANDBOX_TYPE_PDF_COMPOSITOR)
-    process_host->AddFilter(new DWriteFontProxyMessageFilter());
+    if (sandbox_type == service_manager::SANDBOX_TYPE_PDF_COMPOSITOR)
+      impl->AddFilter(new DWriteFontProxyMessageFilter());
 #endif
-  process_host->SetName(process_name);
-  process_host->SetServiceIdentity(service_manager::Identity(service_name));
-  process_host->SetSandboxType(sandbox_type);
-  process_host->Start();
+    impl->SetName(process_name);
+    impl->SetServiceIdentity(service_manager::Identity(service_name));
+    impl->SetSandboxType(sandbox_type);
+    impl->Start();
+    if (weak_host)
+      *weak_host = impl->AsWeakPtr();
+    process_host = impl;
+  }
 
   service_manager::mojom::ServiceFactoryPtr service_factory;
   BindInterface(process_host, mojo::MakeRequest(&service_factory));
@@ -134,12 +154,13 @@
 void QueryAndStartServiceInUtilityProcess(
     const std::string& service_name,
     const base::string16& process_name,
+    base::Optional<std::string> process_group,
     service_manager::mojom::ServiceRequest request) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   ServiceManagerContext::GetConnectorForIOThread()->QueryService(
       service_manager::Identity(service_name),
       base::BindOnce(&StartServiceInUtilityProcess, service_name, process_name,
-                     std::move(request)));
+                     std::move(process_group), std::move(request)));
 }
 
 // Request service_manager::mojom::ServiceFactory from GPU process host. Must be
@@ -556,8 +577,10 @@
 
   for (const auto& service : out_of_process_services) {
     packaged_services_connection_->AddServiceRequestHandler(
-        service.first, base::Bind(&QueryAndStartServiceInUtilityProcess,
-                                  service.first, service.second));
+        service.first,
+        base::BindRepeating(&QueryAndStartServiceInUtilityProcess,
+                            service.first, service.second.process_name,
+                            service.second.process_group));
   }
 
 #if BUILDFLAG(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS)
@@ -603,4 +626,11 @@
   return g_io_thread_connector.Get().get();
 }
 
+// static
+std::map<std::string, base::WeakPtr<UtilityProcessHost>>*
+ServiceManagerContext::GetProcessGroupsForTesting() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  return &g_active_process_groups.Get();
+}
+
 }  // namespace content
diff --git a/content/browser/service_manager/service_manager_context.h b/content/browser/service_manager/service_manager_context.h
index d5efe08..4fa4ce6f 100644
--- a/content/browser/service_manager/service_manager_context.h
+++ b/content/browser/service_manager/service_manager_context.h
@@ -5,8 +5,11 @@
 #ifndef CONTENT_BROWSER_SERVICE_MANAGER_SERVICE_MANAGER_CONTEXT_H_
 #define CONTENT_BROWSER_SERVICE_MANAGER_SERVICE_MANAGER_CONTEXT_H_
 
+#include <map>
+
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "content/common/content_export.h"
 
 namespace service_manager {
@@ -16,6 +19,7 @@
 namespace content {
 
 class ServiceManagerConnection;
+class UtilityProcessHost;
 
 // ServiceManagerContext manages the browser's connection to the ServiceManager,
 // hosting a new in-process ServiceManagerContext if the browser was not
@@ -28,6 +32,9 @@
   // Returns a service_manager::Connector that can be used on the IO thread.
   static service_manager::Connector* GetConnectorForIOThread();
 
+  static std::map<std::string, base::WeakPtr<UtilityProcessHost>>*
+  GetProcessGroupsForTesting();
+
  private:
   class InProcessServiceManagerContext;
 
diff --git a/content/child/service_factory.cc b/content/child/service_factory.cc
index cb1fcd5..3810699 100644
--- a/content/child/service_factory.cc
+++ b/content/child/service_factory.cc
@@ -37,6 +37,11 @@
 
   auto it = services_.find(name);
   if (it == services_.end()) {
+    // DCHECK in developer builds to make these errors easier to identify.
+    // Otherwise they result only in cryptic browser error messages.
+    NOTREACHED() << "Unable to start service \"" << name << "\". Did you "
+                 << "forget to register the service in the utility process's"
+                 << "ServiceFactory?";
     OnLoadFailed();
     return;
   }
diff --git a/content/network/BUILD.gn b/content/network/BUILD.gn
index 54cbb91..64c24690 100644
--- a/content/network/BUILD.gn
+++ b/content/network/BUILD.gn
@@ -36,8 +36,6 @@
   sources = [
     "cache_url_loader.cc",
     "cache_url_loader.h",
-    "cookie_manager.cc",
-    "cookie_manager.h",
     "data_pipe_element_reader.cc",
     "data_pipe_element_reader.h",
     "http_server_properties_pref_delegate.cc",
@@ -96,6 +94,7 @@
     "//net",
     "//net:extras",
     "//net:net_browser_services",
+    "//services/network:network_service",
     "//services/network/public/cpp",
     "//services/proxy_resolver/public/interfaces",
     "//services/service_manager/public/cpp",
diff --git a/content/network/http_server_properties_pref_delegate.cc b/content/network/http_server_properties_pref_delegate.cc
index af2aebc9..8621b81 100644
--- a/content/network/http_server_properties_pref_delegate.cc
+++ b/content/network/http_server_properties_pref_delegate.cc
@@ -32,12 +32,15 @@
 }
 
 void HttpServerPropertiesPrefDelegate::SetServerProperties(
-    const base::DictionaryValue& value) {
-  return pref_service_->Set(kPrefPath, value);
+    const base::DictionaryValue& value,
+    base::OnceClosure callback) {
+  pref_service_->Set(kPrefPath, value);
+  if (callback)
+    pref_service_->CommitPendingWrite(std::move(callback));
 }
 
 void HttpServerPropertiesPrefDelegate::StartListeningForUpdates(
-    const base::Closure& callback) {
+    const base::RepeatingClosure& callback) {
   pref_change_registrar_.Add(kPrefPath, callback);
   // PrefChangeRegistrar isn't notified of initial pref load, so watch for that,
   // too.
diff --git a/content/network/http_server_properties_pref_delegate.h b/content/network/http_server_properties_pref_delegate.h
index 8e5ed906..a2f88b59 100644
--- a/content/network/http_server_properties_pref_delegate.h
+++ b/content/network/http_server_properties_pref_delegate.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_NETWORK_HTTP_SERVER_PROPERTIES_PREF_DELEGATE_H_
 #define CONTENT_NETWORK_HTTP_SERVER_PROPERTIES_PREF_DELEGATE_H_
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "net/http/http_server_properties.h"
@@ -26,8 +27,10 @@
 
   // net::HttpServerPropertiesManager::PrefDelegate implementation.
   const base::DictionaryValue* GetServerProperties() const override;
-  void SetServerProperties(const base::DictionaryValue& value) override;
-  void StartListeningForUpdates(const base::Closure& callback) override;
+  void SetServerProperties(const base::DictionaryValue& value,
+                           base::OnceClosure callback) override;
+  void StartListeningForUpdates(
+      const base::RepeatingClosure& callback) override;
 
  private:
   PrefService* pref_service_;
diff --git a/content/network/network_context.cc b/content/network/network_context.cc
index b5afdd3d..c9434e5 100644
--- a/content/network/network_context.cc
+++ b/content/network/network_context.cc
@@ -61,8 +61,8 @@
       binding_(this, std::move(request)) {
   url_request_context_owner_ = MakeURLRequestContext(params_.get());
   url_request_context_ = url_request_context_owner_.url_request_context.get();
-  cookie_manager_ =
-      std::make_unique<CookieManager>(url_request_context_->cookie_store());
+  cookie_manager_ = std::make_unique<network::CookieManager>(
+      url_request_context_->cookie_store());
   network_service_->RegisterNetworkContext(this);
   binding_.set_connection_error_handler(base::BindOnce(
       &NetworkContext::OnConnectionError, base::Unretained(this)));
@@ -84,8 +84,8 @@
       network_service->net_log());
   url_request_context_ = url_request_context_owner_.url_request_context.get();
   network_service_->RegisterNetworkContext(this);
-  cookie_manager_ =
-      std::make_unique<CookieManager>(url_request_context_->cookie_store());
+  cookie_manager_ = std::make_unique<network::CookieManager>(
+      url_request_context_->cookie_store());
 }
 
 NetworkContext::NetworkContext(NetworkServiceImpl* network_service,
@@ -94,7 +94,7 @@
     : network_service_(network_service),
       url_request_context_(url_request_context),
       binding_(this, std::move(request)),
-      cookie_manager_(std::make_unique<CookieManager>(
+      cookie_manager_(std::make_unique<network::CookieManager>(
           url_request_context->cookie_store())) {
   // May be nullptr in tests.
   if (network_service_)
@@ -373,8 +373,8 @@
   url_request_context_->transport_security_state()->DeleteAllDynamicDataSince(
       time);
 
-  url_request_context_->http_server_properties()->Clear();
-  std::move(completion_callback).Run();
+  url_request_context_->http_server_properties()->Clear(
+      std::move(completion_callback));
 }
 
 void NetworkContext::SetNetworkConditions(
diff --git a/content/network/network_context.h b/content/network/network_context.h
index a877ede..3e65451 100644
--- a/content/network/network_context.h
+++ b/content/network/network_context.h
@@ -14,12 +14,12 @@
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
-#include "content/network/cookie_manager.h"
 #include "content/public/common/network_service.mojom.h"
 #include "content/public/common/url_loader_factory.mojom.h"
 #include "content/public/network/url_request_context_owner.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/strong_binding_set.h"
+#include "services/network/cookie_manager.h"
 
 namespace net {
 class URLRequestContext;
@@ -142,7 +142,7 @@
 
   mojo::Binding<mojom::NetworkContext> binding_;
 
-  std::unique_ptr<CookieManager> cookie_manager_;
+  std::unique_ptr<network::CookieManager> cookie_manager_;
 
   DISALLOW_COPY_AND_ASSIGN(NetworkContext);
 };
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 89486de..445422c 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -519,6 +519,26 @@
   return nullptr;
 }
 
+ContentBrowserClient::OutOfProcessServiceInfo::OutOfProcessServiceInfo() =
+    default;
+
+ContentBrowserClient::OutOfProcessServiceInfo::OutOfProcessServiceInfo(
+    const base::string16& process_name)
+    : process_name(process_name) {
+  DCHECK(!process_name.empty());
+}
+
+ContentBrowserClient::OutOfProcessServiceInfo::OutOfProcessServiceInfo(
+    const base::string16& process_name,
+    const std::string& process_group)
+    : process_name(process_name), process_group(process_group) {
+  DCHECK(!process_name.empty());
+  DCHECK(!process_group.empty());
+}
+
+ContentBrowserClient::OutOfProcessServiceInfo::~OutOfProcessServiceInfo() =
+    default;
+
 bool ContentBrowserClient::ShouldTerminateOnServiceQuit(
     const service_manager::Identity& id) {
   return false;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index d83ad513..bb21f85 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -14,6 +14,7 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/optional.h"
 #include "base/task_scheduler/task_scheduler.h"
 #include "base/values.h"
 #include "build/build_config.h"
@@ -788,7 +789,22 @@
       const std::string& name,
       mojo::ScopedMessagePipeHandle* handle) {}
 
-  using OutOfProcessServiceMap = std::map<std::string, base::string16>;
+  struct CONTENT_EXPORT OutOfProcessServiceInfo {
+    OutOfProcessServiceInfo();
+    OutOfProcessServiceInfo(const base::string16& process_name);
+    OutOfProcessServiceInfo(const base::string16& process_name,
+                            const std::string& process_group);
+    ~OutOfProcessServiceInfo();
+
+    // The display name of the service process launched for the service.
+    base::string16 process_name;
+
+    // If provided, a string which groups this service into a process shared
+    // by other services using the same string.
+    base::Optional<std::string> process_group;
+  };
+
+  using OutOfProcessServiceMap = std::map<std::string, OutOfProcessServiceInfo>;
 
   // Registers services to be loaded out of the browser process in an
   // utility process. The value of each map entry should be a process name,
diff --git a/content/public/browser/file_url_loader.h b/content/public/browser/file_url_loader.h
index e467256a..8fce1a4 100644
--- a/content/public/browser/file_url_loader.h
+++ b/content/public/browser/file_url_loader.h
@@ -5,16 +5,34 @@
 #ifndef CONTENT_PUBLIC_BROWSER_FILE_URL_LOADER_H_
 #define CONTENT_PUBLIC_BROWSER_FILE_URL_LOADER_H_
 
+#include <memory>
+
 #include "content/common/content_export.h"
 #include "content/public/common/url_loader.mojom.h"
+#include "mojo/public/cpp/system/file_data_pipe_producer.h"
 
 namespace content {
 
 struct ResourceRequest;
 
+class CONTENT_EXPORT FileURLLoaderObserver
+    : public mojo::FileDataPipeProducer::Observer {
+ public:
+  FileURLLoaderObserver() {}
+  ~FileURLLoaderObserver() override {}
+
+  virtual void OnStart() {}
+  virtual void OnOpenComplete(int result) {}
+  virtual void OnSeekComplete(int64_t result) {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileURLLoaderObserver);
+};
+
 // Helper to create a self-owned URLLoader instance which fulfills |request|
 // using the contents of the file at |path|. The URL in |request| must be a
-// file:// URL.
+// file:// URL. The *optionally* supplied |observer| will be called to report
+// progress during the file loading.
 //
 // Note that this does not restrict filesystem access *in any way*, so if the
 // file at |path| is accessible to the browser, it will be loaded and used to
@@ -23,9 +41,11 @@
 // The URLLoader created by this function does *not* automatically follow
 // filesytem links (e.g. Windows shortcuts) or support directory listing.
 // A directory path will always yield a FILE_NOT_FOUND network error.
-CONTENT_EXPORT void CreateFileURLLoader(const ResourceRequest& request,
-                                        mojom::URLLoaderRequest loader,
-                                        mojom::URLLoaderClientPtr client);
+CONTENT_EXPORT void CreateFileURLLoader(
+    const ResourceRequest& request,
+    mojom::URLLoaderRequest loader,
+    mojom::URLLoaderClientPtr client,
+    std::unique_ptr<FileURLLoaderObserver> observer);
 
 }  // namespace content
 
diff --git a/content/public/test/android/javatests/src/org/chromium/content/browser/test/ContentJUnit4ClassRunner.java b/content/public/test/android/javatests/src/org/chromium/content/browser/test/ContentJUnit4ClassRunner.java
index 7f98748..0d48cc9 100644
--- a/content/public/test/android/javatests/src/org/chromium/content/browser/test/ContentJUnit4ClassRunner.java
+++ b/content/public/test/android/javatests/src/org/chromium/content/browser/test/ContentJUnit4ClassRunner.java
@@ -8,9 +8,9 @@
 
 import org.junit.runners.model.InitializationError;
 
-import org.chromium.base.BaseChromiumApplication;
 import org.chromium.base.CollectionUtil;
 import org.chromium.base.CommandLine;
+import org.chromium.base.CommandLineInitUtil;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.BaseTestResult.PreTestHook;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -51,6 +51,7 @@
 
     @Override
     protected void initCommandLineForTest() {
-        BaseChromiumApplication.initCommandLine(InstrumentationRegistry.getTargetContext());
+        CommandLineInitUtil.initCommandLine(
+                InstrumentationRegistry.getTargetContext(), CommandLineFlags.getTestCmdLineFile());
     }
 }
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 611e51e..29f4a84 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -44,6 +44,7 @@
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
 #include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
+#include "content/browser/service_manager/service_manager_context.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/browser/web_contents/web_contents_view.h"
 #include "content/common/fileapi/file_system_messages.h"
@@ -2405,4 +2406,9 @@
   return simple_loader->NetError();
 }
 
+std::map<std::string, base::WeakPtr<UtilityProcessHost>>*
+GetServiceManagerProcessGroups() {
+  return ServiceManagerContext::GetProcessGroupsForTesting();
+}
+
 }  // namespace content
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 3a08b5b..905a078 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -15,6 +15,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "base/process/process.h"
 #include "base/run_loop.h"
 #include "base/strings/string16.h"
@@ -75,6 +76,7 @@
 class RenderViewHost;
 class RenderWidgetHost;
 class RenderWidgetHostView;
+class UtilityProcessHost;
 class WebContents;
 
 // Navigate a frame with ID |iframe_id| to |url|, blocking until the navigation
@@ -1077,6 +1079,9 @@
                      int process_id = 0,
                      int render_frame_id = 0);
 
+std::map<std::string, base::WeakPtr<UtilityProcessHost>>*
+GetServiceManagerProcessGroups();
+
 }  // namespace content
 
 #endif  // CONTENT_PUBLIC_TEST_BROWSER_TEST_UTILS_H_
diff --git a/content/renderer/device_sensors/device_sensor_event_pump.h b/content/renderer/device_sensors/device_sensor_event_pump.h
index acfa17d..3fdc3d9 100644
--- a/content/renderer/device_sensors/device_sensor_event_pump.h
+++ b/content/renderer/device_sensors/device_sensor_event_pump.h
@@ -102,6 +102,11 @@
 
     // Mojo callback for SensorProvider::GetSensor().
     void OnSensorCreated(device::mojom::SensorInitParamsPtr params) {
+      // TODO(798409): `OnSensorCreated` can be called twice in some cases, this
+      // is a workaround to avoid hitting unexpected code paths.
+      if (sensor.is_bound())
+        return;
+
       if (!params) {
         HandleSensorError();
         event_pump->DidStartIfPossible();
diff --git a/content/renderer/media/video_capture_impl.cc b/content/renderer/media/video_capture_impl.cc
index 6c26751d..f658e91 100644
--- a/content/renderer/media/video_capture_impl.cc
+++ b/content/renderer/media/video_capture_impl.cc
@@ -309,7 +309,7 @@
 
   const auto& iter = client_buffers_.find(buffer_id);
   DCHECK(iter != client_buffers_.end());
-  const scoped_refptr<ClientBuffer> buffer = iter->second;
+  scoped_refptr<ClientBuffer> buffer = iter->second;
   scoped_refptr<media::VideoFrame> frame =
       media::VideoFrame::WrapExternalSharedMemory(
           static_cast<media::VideoPixelFormat>(info->pixel_format),
@@ -322,12 +322,11 @@
     return;
   }
 
-  BufferFinishedCallback buffer_finished_callback = media::BindToCurrentLoop(
-      base::Bind(&VideoCaptureImpl::OnClientBufferFinished,
-                 weak_factory_.GetWeakPtr(), buffer_id, buffer));
-  frame->AddDestructionObserver(
-      base::BindOnce(&VideoCaptureImpl::DidFinishConsumingFrame,
-                     frame->metadata(), buffer_finished_callback));
+  frame->AddDestructionObserver(base::BindOnce(
+      &VideoCaptureImpl::DidFinishConsumingFrame, frame->metadata(),
+      media::BindToCurrentLoop(base::BindOnce(
+          &VideoCaptureImpl::OnClientBufferFinished, weak_factory_.GetWeakPtr(),
+          buffer_id, std::move(buffer)))));
 
   frame->metadata()->MergeInternalValuesFrom(*info->metadata);
 
@@ -350,9 +349,29 @@
 
 void VideoCaptureImpl::OnClientBufferFinished(
     int buffer_id,
-    const scoped_refptr<ClientBuffer>& /* ignored_buffer */,
+    scoped_refptr<ClientBuffer> buffer,
     double consumer_resource_utilization) {
   DCHECK(io_thread_checker_.CalledOnValidThread());
+
+// Subtle race note: It's important that the |buffer| argument be
+// std::move()'ed to this method and never copied. This is so that the caller,
+// DidFinishConsumingFrame(), does not implicitly retain a reference while it
+// is running the trampoline callback on another thread. This is necessary to
+// ensure the reference count on the ClientBuffer will be correct at the time
+// OnBufferDestroyed() is called. http://crbug.com/797851
+#if DCHECK_IS_ON()
+  // The ClientBuffer should have exactly two references to it at this point,
+  // one is this method's second argument and the other is from
+  // |client_buffers_|.
+  DCHECK(!buffer->HasOneRef());
+  ClientBuffer* const buffer_raw_ptr = buffer.get();
+  buffer = nullptr;
+  // Now there should be only one reference, from |client_buffers_|.
+  DCHECK(buffer_raw_ptr->HasOneRef());
+#else
+  buffer = nullptr;
+#endif
+
   GetVideoCaptureHost()->ReleaseBuffer(
       device_id_, buffer_id, consumer_resource_utilization);
 }
@@ -436,7 +455,7 @@
 // static
 void VideoCaptureImpl::DidFinishConsumingFrame(
     const media::VideoFrameMetadata* metadata,
-    const BufferFinishedCallback& callback_to_io_thread) {
+    BufferFinishedCallback callback_to_io_thread) {
   // Note: This function may be called on any thread by the VideoFrame
   // destructor.  |metadata| is still valid for read-access at this point.
   double consumer_resource_utilization = -1.0;
@@ -444,7 +463,7 @@
                            &consumer_resource_utilization)) {
     consumer_resource_utilization = -1.0;
   }
-  callback_to_io_thread.Run(consumer_resource_utilization);
+  std::move(callback_to_io_thread).Run(consumer_resource_utilization);
 }
 
 }  // namespace content
diff --git a/content/renderer/media/video_capture_impl.h b/content/renderer/media/video_capture_impl.h
index 55e8b53..40ffb2d 100644
--- a/content/renderer/media/video_capture_impl.h
+++ b/content/renderer/media/video_capture_impl.h
@@ -88,12 +88,12 @@
   using ClientInfoMap = std::map<int, ClientInfo>;
 
   using BufferFinishedCallback =
-      base::Callback<void(double consumer_resource_utilization)>;
+      base::OnceCallback<void(double consumer_resource_utilization)>;
 
   // Sends an IPC message to browser process when all clients are done with the
   // buffer.
   void OnClientBufferFinished(int buffer_id,
-                              const scoped_refptr<ClientBuffer>& buffer,
+                              scoped_refptr<ClientBuffer> buffer,
                               double consumer_resource_utilization);
 
   void StopDevice();
@@ -118,7 +118,7 @@
   // callback, to trampoline back to the IO thread with the values.
   static void DidFinishConsumingFrame(
       const media::VideoFrameMetadata* metadata,
-      const BufferFinishedCallback& callback_to_io_thread);
+      BufferFinishedCallback callback_to_io_thread);
 
   // |device_id_| and |session_id_| are different concepts, but we reuse the
   // same numerical value, passed on construction.
diff --git a/content/renderer/mouse_lock_dispatcher.cc b/content/renderer/mouse_lock_dispatcher.cc
index eba09d86..c4702c5 100644
--- a/content/renderer/mouse_lock_dispatcher.cc
+++ b/content/renderer/mouse_lock_dispatcher.cc
@@ -44,6 +44,10 @@
   }
 }
 
+void MouseLockDispatcher::ClearLockTarget() {
+  OnLockTargetDestroyed(target_);
+}
+
 bool MouseLockDispatcher::IsMouseLockedTo(LockTarget* target) {
   return mouse_locked_ && target_ == target;
 }
diff --git a/content/renderer/mouse_lock_dispatcher.h b/content/renderer/mouse_lock_dispatcher.h
index b3bb8f3..d5c3be8 100644
--- a/content/renderer/mouse_lock_dispatcher.h
+++ b/content/renderer/mouse_lock_dispatcher.h
@@ -40,6 +40,8 @@
   // Clears out the reference to the |target| because it has or is being
   // destroyed. Unlocks if locked. The pointer will not be accessed.
   void OnLockTargetDestroyed(LockTarget* target);
+  // Clears out any reference to a lock target. Unlocks if locked.
+  void ClearLockTarget();
   bool IsMouseLockedTo(LockTarget* target);
 
   // Allow lock target to consumed a mouse event, if it does return true.
diff --git a/content/renderer/render_widget_fullscreen_pepper.cc b/content/renderer/render_widget_fullscreen_pepper.cc
index e8bdb50..44ff027f 100644
--- a/content/renderer/render_widget_fullscreen_pepper.cc
+++ b/content/renderer/render_widget_fullscreen_pepper.cc
@@ -303,6 +303,11 @@
 }
 
 void RenderWidgetFullscreenPepper::Destroy() {
+  // The plugin instance is going away reset any lock target that is set
+  // on the dispatcher since this object can still live and receive IPC
+  // responses and may call a dangling lock_target.
+  mouse_lock_dispatcher_->ClearLockTarget();
+
   // This function is called by the plugin instance as it's going away, so reset
   // plugin_ to NULL to avoid calling into a dangling pointer e.g. on Close().
   plugin_ = nullptr;
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java
index 85e8964..1ce2777 100644
--- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java
+++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java
@@ -17,7 +17,6 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
-import org.chromium.content.app.ContentApplication;
 import org.chromium.content.browser.BrowserStartupController;
 import org.chromium.content.browser.ContentViewCore;
 import org.chromium.content.browser.DeviceUtils;
@@ -50,7 +49,7 @@
 
         // Initializing the command line must occur before loading the library.
         if (!CommandLine.isInitialized()) {
-            ContentApplication.initCommandLine(this);
+            ((ContentShellApplication) getApplication()).initCommandLine();
             String[] commandLineParams = getCommandLineParamsFromIntent(getIntent());
             if (commandLineParams != null) {
                 CommandLine.getInstance().appendSwitchesAndArguments(commandLineParams);
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java
index 5c84c28..9384f7b 100644
--- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java
+++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java
@@ -29,7 +29,6 @@
         ApplicationStatus.initialize(this);
     }
 
-    @Override
     public void initCommandLine() {
         if (!CommandLine.isInitialized()) {
             CommandLine.initFromFile(COMMAND_LINE_FILE);
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 8387597d..1733b98 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1513,7 +1513,6 @@
     "../common/throttling_url_loader_unittest.cc",
     "../common/unique_name_helper_unittest.cc",
     "../common/webplugininfo_unittest.cc",
-    "../network/cookie_manager_unittest.cc",
     "../network/data_pipe_element_reader_unittest.cc",
     "../network/network_change_manager_unittest.cc",
     "../network/network_context_unittest.cc",
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index 7100cbb..6aba7a00 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -696,7 +696,7 @@
       'filter_effects.html',
       base_name + '_CSSFilterEffects_NoOverlays',
       test_rect=[0, 0, 300, 300],
-      revision=6,
+      revision=7,
       tolerance=10,
       browser_args=['--disable-mac-overlays']),
   ]
diff --git a/content/test/layouttest_support.cc b/content/test/layouttest_support.cc
index 2269d75..ae2a8ae7 100644
--- a/content/test/layouttest_support.cc
+++ b/content/test/layouttest_support.cc
@@ -331,6 +331,9 @@
     renderer_settings.allow_antialiasing &=
         !cmd->HasSwitch(cc::switches::kDisableCompositedAntialiasing);
     renderer_settings.highp_threshold_min = 2048;
+    // Keep texture sizes exactly matching the bounds of the RenderPass to avoid
+    // floating point badness in texcoords.
+    renderer_settings.dont_round_texture_sizes_for_pixel_tests = true;
 
     constexpr bool disable_display_vsync = false;
     constexpr double refresh_rate = 60.0;
diff --git a/device/geolocation/BUILD.gn b/device/geolocation/BUILD.gn
index 3da82acf..b3673ae2 100644
--- a/device/geolocation/BUILD.gn
+++ b/device/geolocation/BUILD.gn
@@ -53,6 +53,7 @@
     "wifi_data_provider_manager.h",
     "wifi_data_provider_win.cc",
     "wifi_data_provider_win.h",
+    "wifi_polling_policy.cc",
     "wifi_polling_policy.h",
   ]
 
diff --git a/device/geolocation/location_arbitrator.cc b/device/geolocation/location_arbitrator.cc
index fa0ffb01..4b8e546 100644
--- a/device/geolocation/location_arbitrator.cc
+++ b/device/geolocation/location_arbitrator.cc
@@ -14,6 +14,7 @@
 #include "build/build_config.h"
 #include "device/geolocation/network_location_provider.h"
 #include "device/geolocation/public/cpp/geoposition.h"
+#include "device/geolocation/wifi_polling_policy.h"
 
 namespace device {
 
@@ -33,7 +34,12 @@
       is_permission_granted_(false),
       is_running_(false) {}
 
-LocationArbitrator::~LocationArbitrator() = default;
+LocationArbitrator::~LocationArbitrator() {
+  // Destroy the global WifiPollingPolicy. The policy is created and used by the
+  // network location provider but should be retained across network provider
+  // restarts to ensure the time of the most recent WiFi scan is not lost.
+  WifiPollingPolicy::Shutdown();
+}
 
 bool LocationArbitrator::HasPermissionBeenGrantedForTest() const {
   return is_permission_granted_;
diff --git a/device/geolocation/wifi_data_provider_chromeos.cc b/device/geolocation/wifi_data_provider_chromeos.cc
index b4b4f1a..b53d30ee 100644
--- a/device/geolocation/wifi_data_provider_chromeos.cc
+++ b/device/geolocation/wifi_data_provider_chromeos.cc
@@ -36,20 +36,16 @@
 void WifiDataProviderChromeOs::StartDataProvider() {
   DCHECK(CalledOnClientThread());
 
-  DCHECK(polling_policy_ == nullptr);
-  polling_policy_.reset(
-      new GenericWifiPollingPolicy<kDefaultPollingIntervalMilliseconds,
-                                   kNoChangePollingIntervalMilliseconds,
-                                   kTwoNoChangePollingIntervalMilliseconds,
-                                   kNoWifiPollingIntervalMilliseconds>);
+  if (!WifiPollingPolicy::IsInitialized())
+    WifiPollingPolicy::Initialize(CreatePollingPolicy());
+  DCHECK(WifiPollingPolicy::IsInitialized());
 
-  ScheduleStart();
+  ScheduleStart(WifiPollingPolicy::Get()->PollingInterval());
 }
 
 void WifiDataProviderChromeOs::StopDataProvider() {
   DCHECK(CalledOnClientThread());
 
-  polling_policy_.reset();
   ScheduleStop();
 }
 
@@ -83,7 +79,7 @@
   // Schedule next scan if started (StopDataProvider could have been called
   // in between DoWifiScanTaskOnNetworkHandlerThread and this method).
   if (started_)
-    ScheduleNextScan(polling_policy_->NoWifiInterval());
+    ScheduleNextScan(WifiPollingPolicy::Get()->NoWifiInterval());
 }
 
 void WifiDataProviderChromeOs::DidWifiScanTask(const WifiData& new_data) {
@@ -93,8 +89,8 @@
   // Schedule next scan if started (StopDataProvider could have been called
   // in between DoWifiScanTaskOnNetworkHandlerThread and this method).
   if (started_) {
-    polling_policy_->UpdatePollingInterval(update_available);
-    ScheduleNextScan(polling_policy_->PollingInterval());
+    WifiPollingPolicy::Get()->UpdatePollingInterval(update_available);
+    ScheduleNextScan(WifiPollingPolicy::Get()->PollingInterval());
   }
 
   if (update_available || !is_first_scan_complete_) {
@@ -124,7 +120,7 @@
   started_ = false;
 }
 
-void WifiDataProviderChromeOs::ScheduleStart() {
+void WifiDataProviderChromeOs::ScheduleStart(int interval) {
   DCHECK(CalledOnClientThread());
   DCHECK(!started_);
   if (!NetworkHandler::IsInitialized()) {
@@ -132,13 +128,12 @@
     return;
   }
   started_ = true;
-  // Perform first scan ASAP regardless of the polling policy. If this scan
-  // fails we'll retry at a rate in line with the polling policy.
-  NetworkHandler::Get()->task_runner()->PostTask(
+  NetworkHandler::Get()->task_runner()->PostDelayedTask(
       FROM_HERE,
       base::Bind(
           &WifiDataProviderChromeOs::DoWifiScanTaskOnNetworkHandlerThread,
-          this));
+          this),
+      base::TimeDelta::FromMilliseconds(interval));
 }
 
 bool WifiDataProviderChromeOs::GetAccessPointData(
@@ -176,6 +171,14 @@
   return true;
 }
 
+std::unique_ptr<WifiPollingPolicy>
+WifiDataProviderChromeOs::CreatePollingPolicy() {
+  return std::make_unique<GenericWifiPollingPolicy<
+      kDefaultPollingIntervalMilliseconds, kNoChangePollingIntervalMilliseconds,
+      kTwoNoChangePollingIntervalMilliseconds,
+      kNoWifiPollingIntervalMilliseconds>>();
+}
+
 // static
 WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() {
   return new WifiDataProviderChromeOs();
diff --git a/device/geolocation/wifi_data_provider_chromeos.h b/device/geolocation/wifi_data_provider_chromeos.h
index f9630f6f..09fcff8 100644
--- a/device/geolocation/wifi_data_provider_chromeos.h
+++ b/device/geolocation/wifi_data_provider_chromeos.h
@@ -39,7 +39,7 @@
   void ScheduleNextScan(int interval);
 
   // Will schedule starting of the scanning process.
-  void ScheduleStart();
+  void ScheduleStart(int interval);
 
   // Will schedule stopping of the scanning process.
   void ScheduleStop();
@@ -47,8 +47,8 @@
   // Get access point data from chromeos.
   bool GetAccessPointData(WifiData::AccessPointDataSet* data);
 
-  // Controls the polling update interval. (client thread)
-  std::unique_ptr<WifiPollingPolicy> polling_policy_;
+  // Create the policy for controlling the polling update interval.
+  std::unique_ptr<WifiPollingPolicy> CreatePollingPolicy();
 
   // The latest wifi data. (client thread)
   WifiData wifi_data_;
diff --git a/device/geolocation/wifi_data_provider_common.cc b/device/geolocation/wifi_data_provider_common.cc
index 0084432..2343b65 100644
--- a/device/geolocation/wifi_data_provider_common.cc
+++ b/device/geolocation/wifi_data_provider_common.cc
@@ -35,18 +35,15 @@
     return;
   }
 
-  DCHECK(!polling_policy_);
-  polling_policy_ = CreatePollingPolicy();
-  DCHECK(polling_policy_);
+  if (!WifiPollingPolicy::IsInitialized())
+    WifiPollingPolicy::Initialize(CreatePollingPolicy());
+  DCHECK(WifiPollingPolicy::IsInitialized());
 
-  // Perform first scan ASAP regardless of the polling policy. If this scan
-  // fails we'll retry at a rate in line with the polling policy.
-  ScheduleNextScan(0);
+  ScheduleNextScan(WifiPollingPolicy::Get()->PollingInterval());
 }
 
 void WifiDataProviderCommon::StopDataProvider() {
   wlan_api_.reset();
-  polling_policy_.reset();
 }
 
 bool WifiDataProviderCommon::GetData(WifiData* data) {
@@ -60,12 +57,12 @@
   bool update_available = false;
   WifiData new_data;
   if (!wlan_api_->GetAccessPointData(&new_data.access_point_data)) {
-    ScheduleNextScan(polling_policy_->NoWifiInterval());
+    ScheduleNextScan(WifiPollingPolicy::Get()->NoWifiInterval());
   } else {
     update_available = wifi_data_.DiffersSignificantly(new_data);
     wifi_data_ = new_data;
-    polling_policy_->UpdatePollingInterval(update_available);
-    ScheduleNextScan(polling_policy_->PollingInterval());
+    WifiPollingPolicy::Get()->UpdatePollingInterval(update_available);
+    ScheduleNextScan(WifiPollingPolicy::Get()->PollingInterval());
   }
   if (update_available || !is_first_scan_complete_) {
     is_first_scan_complete_ = true;
diff --git a/device/geolocation/wifi_data_provider_common.h b/device/geolocation/wifi_data_provider_common.h
index 44a312c..ad39a54e 100644
--- a/device/geolocation/wifi_data_provider_common.h
+++ b/device/geolocation/wifi_data_provider_common.h
@@ -72,9 +72,6 @@
   // Underlying OS wifi API.
   std::unique_ptr<WlanApiInterface> wlan_api_;
 
-  // Controls the polling update interval.
-  std::unique_ptr<WifiPollingPolicy> polling_policy_;
-
   // Holder for delayed tasks; takes care of cleanup.
   base::WeakPtrFactory<WifiDataProviderCommon> weak_factory_;
 
diff --git a/device/geolocation/wifi_data_provider_common_unittest.cc b/device/geolocation/wifi_data_provider_common_unittest.cc
index ec20a12..7db568f 100644
--- a/device/geolocation/wifi_data_provider_common_unittest.cc
+++ b/device/geolocation/wifi_data_provider_common_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "device/geolocation/wifi_data_provider_manager.h"
+#include "device/geolocation/wifi_polling_policy.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -60,7 +61,8 @@
 class WifiDataProviderCommonWithMock : public WifiDataProviderCommon {
  public:
   WifiDataProviderCommonWithMock()
-      : wlan_api_(new MockWlanApi), polling_policy_(new MockPollingPolicy) {}
+      : wlan_api_(new MockWlanApi),
+        polling_policy_(std::make_unique<MockPollingPolicy>()) {}
 
   // WifiDataProviderCommon
   std::unique_ptr<WlanApiInterface> CreateWlanApi() override {
@@ -97,6 +99,7 @@
   void TearDown() override {
     provider_->RemoveCallback(&wifi_data_callback_);
     provider_->StopDataProvider();
+    WifiPollingPolicy::Shutdown();
   }
 
  protected:
diff --git a/device/geolocation/wifi_polling_policy.cc b/device/geolocation/wifi_polling_policy.cc
new file mode 100644
index 0000000..6cd52bbc
--- /dev/null
+++ b/device/geolocation/wifi_polling_policy.cc
@@ -0,0 +1,36 @@
+// Copyright 2018 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 "device/geolocation/wifi_polling_policy.h"
+
+namespace device {
+
+namespace {
+WifiPollingPolicy* g_wifi_polling_policy;
+}  // namespace
+
+// static
+void WifiPollingPolicy::Initialize(std::unique_ptr<WifiPollingPolicy> policy) {
+  g_wifi_polling_policy = policy.release();
+}
+
+// static
+void WifiPollingPolicy::Shutdown() {
+  if (g_wifi_polling_policy)
+    delete g_wifi_polling_policy;
+  g_wifi_polling_policy = nullptr;
+}
+
+// static
+WifiPollingPolicy* WifiPollingPolicy::Get() {
+  DCHECK(g_wifi_polling_policy);
+  return g_wifi_polling_policy;
+}
+
+// static
+bool WifiPollingPolicy::IsInitialized() {
+  return g_wifi_polling_policy != nullptr;
+}
+
+}  // namespace device
diff --git a/device/geolocation/wifi_polling_policy.h b/device/geolocation/wifi_polling_policy.h
index 0ac9203..226b3852 100644
--- a/device/geolocation/wifi_polling_policy.h
+++ b/device/geolocation/wifi_polling_policy.h
@@ -5,21 +5,36 @@
 #ifndef DEVICE_GEOLOCATION_WIFI_POLLING_POLICY_H_
 #define DEVICE_GEOLOCATION_WIFI_POLLING_POLICY_H_
 
+#include <memory>
+
 #include "base/macros.h"
+#include "base/time/time.h"
+#include "device/geolocation/geolocation_export.h"
 
 namespace device {
 
 // Allows sharing and mocking of the update polling policy function.
-class WifiPollingPolicy {
+class DEVICE_GEOLOCATION_EXPORT WifiPollingPolicy {
  public:
-  WifiPollingPolicy() {}
-  virtual ~WifiPollingPolicy() {}
+  virtual ~WifiPollingPolicy() = default;
+
+  // Methods for managing the single instance of WifiPollingPolicy. The WiFi
+  // policy is global so it can outlive the WifiDataProvider instance, which is
+  // shut down and destroyed when no WiFi scanning is active.
+  static void Initialize(std::unique_ptr<WifiPollingPolicy>);
+  static void Shutdown();
+  static WifiPollingPolicy* Get();
+  static bool IsInitialized();
+
   // Calculates the new polling interval for wiFi scans, given the previous
   // interval and whether the last scan produced new results.
   virtual void UpdatePollingInterval(bool scan_results_differ) = 0;
   virtual int PollingInterval() = 0;
   virtual int NoWifiInterval() = 0;
 
+ protected:
+  WifiPollingPolicy() = default;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(WifiPollingPolicy);
 };
@@ -33,6 +48,7 @@
 class GenericWifiPollingPolicy : public WifiPollingPolicy {
  public:
   GenericWifiPollingPolicy() : polling_interval_(DEFAULT_INTERVAL) {}
+
   // WifiPollingPolicy
   void UpdatePollingInterval(bool scan_results_differ) override {
     if (scan_results_differ) {
@@ -45,11 +61,37 @@
       polling_interval_ = TWO_NO_CHANGE_INTERVAL;
     }
   }
-  int PollingInterval() override { return polling_interval_; }
-  int NoWifiInterval() override { return NO_WIFI_INTERVAL; }
+  int PollingInterval() override { return ComputeInterval(polling_interval_); }
+  int NoWifiInterval() override { return ComputeInterval(NO_WIFI_INTERVAL); }
 
  private:
+  int ComputeInterval(int polling_interval) {
+    base::Time now = base::Time::Now();
+
+    int64_t remaining_millis = 0;
+    if (!next_scan_.is_null()) {
+      // Compute the remaining duration of the current interval. If the interval
+      // is not yet complete, we will schedule a scan to occur once it is.
+      base::TimeDelta remaining = next_scan_ - now;
+      remaining_millis = remaining.InMilliseconds();
+    }
+
+    // If the current interval is complete (or if this is our first scan), scan
+    // now and schedule the next scan to occur at |polling_interval|
+    // milliseconds into the future.
+    if (remaining_millis <= 0) {
+      next_scan_ = now + base::TimeDelta::FromMilliseconds(polling_interval);
+      remaining_millis = 0;
+    }
+
+    return remaining_millis;
+  }
+
   int polling_interval_;
+
+  // The scheduled time of the next scan, or a null value if no scan has
+  // occurred yet.
+  base::Time next_scan_;
 };
 
 }  // namespace device
diff --git a/device/u2f/BUILD.gn b/device/u2f/BUILD.gn
index b8ed68e..6213b36 100644
--- a/device/u2f/BUILD.gn
+++ b/device/u2f/BUILD.gn
@@ -136,6 +136,7 @@
       "//base",
       "//mojo/public/cpp/bindings",
       "//services/device/public/interfaces",
+      "//testing/gmock",
     ]
   }
 }
diff --git a/device/u2f/fake_hid_impl_for_testing.cc b/device/u2f/fake_hid_impl_for_testing.cc
index 08495a3d..4a7a15a 100644
--- a/device/u2f/fake_hid_impl_for_testing.cc
+++ b/device/u2f/fake_hid_impl_for_testing.cc
@@ -4,8 +4,47 @@
 
 #include "device/u2f/fake_hid_impl_for_testing.h"
 
+#include <utility>
+
+#include "base/optional.h"
+
 namespace device {
 
+MockHidConnection::MockHidConnection(
+    device::mojom::HidDeviceInfoPtr device,
+    device::mojom::HidConnectionRequest request,
+    std::vector<uint8_t> connection_channel_id)
+    : binding_(this, std::move(request)),
+      device_(std::move(device)),
+      connection_channel_id_(connection_channel_id) {}
+
+MockHidConnection::~MockHidConnection() {}
+
+void MockHidConnection::Read(ReadCallback callback) {
+  return ReadPtr(&callback);
+}
+
+void MockHidConnection::Write(uint8_t report_id,
+                              const std::vector<uint8_t>& buffer,
+                              WriteCallback callback) {
+  return WritePtr(report_id, buffer, &callback);
+}
+
+void MockHidConnection::GetFeatureReport(uint8_t report_id,
+                                         GetFeatureReportCallback callback) {
+  NOTREACHED();
+}
+
+void MockHidConnection::SendFeatureReport(uint8_t report_id,
+                                          const std::vector<uint8_t>& buffer,
+                                          SendFeatureReportCallback callback) {
+  NOTREACHED();
+}
+
+void MockHidConnection::SetNonce(base::span<uint8_t const> nonce) {
+  nonce_ = std::vector<uint8_t>(nonce.begin(), nonce.end());
+}
+
 bool FakeHidConnection::mock_connection_error_ = false;
 
 FakeHidConnection::FakeHidConnection(device::mojom::HidDeviceInfoPtr device)
@@ -73,18 +112,14 @@
 
 void FakeHidManager::Connect(const std::string& device_guid,
                              ConnectCallback callback) {
-  auto it = devices_.find(device_guid);
-  if (it == devices_.end()) {
+  auto device_it = devices_.find(device_guid);
+  auto connection_it = connections_.find(device_guid);
+  if (device_it == devices_.end() || connection_it == connections_.end()) {
     std::move(callback).Run(nullptr);
     return;
   }
 
-  // Strongly binds an instance of FakeHidConnctionImpl.
-  device::mojom::HidConnectionPtr client;
-  mojo::MakeStrongBinding(
-      base::MakeUnique<FakeHidConnection>(it->second->Clone()),
-      mojo::MakeRequest(&client));
-  std::move(callback).Run(std::move(client));
+  std::move(callback).Run(std::move(connection_it->second));
 }
 
 void FakeHidManager::AddDevice(device::mojom::HidDeviceInfoPtr device) {
@@ -96,6 +131,13 @@
   devices_[device->guid] = std::move(device);
 }
 
+void FakeHidManager::AddDeviceAndSetConnection(
+    device::mojom::HidDeviceInfoPtr device,
+    device::mojom::HidConnectionPtr connection) {
+  connections_[device->guid] = std::move(connection);
+  AddDevice(std::move(device));
+}
+
 void FakeHidManager::RemoveDevice(const std::string device_guid) {
   auto it = devices_.find(device_guid);
   if (it == devices_.end())
diff --git a/device/u2f/fake_hid_impl_for_testing.h b/device/u2f/fake_hid_impl_for_testing.h
index 9aebb43..ac44ed1 100644
--- a/device/u2f/fake_hid_impl_for_testing.h
+++ b/device/u2f/fake_hid_impl_for_testing.h
@@ -2,15 +2,62 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef DEVICE_U2F_FAKE_HID_IMPL_FOR_TESTING_H_
+#define DEVICE_U2F_FAKE_HID_IMPL_FOR_TESTING_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
 #include "base/bind.h"
+#include "base/containers/span.h"
 #include "base/memory/ptr_util.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/device/public/interfaces/hid.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
 
 namespace device {
 
+class MockHidConnection : public device::mojom::HidConnection {
+ public:
+  explicit MockHidConnection(device::mojom::HidDeviceInfoPtr device,
+                             device::mojom::HidConnectionRequest request,
+                             std::vector<uint8_t> connection_channel_id);
+
+  ~MockHidConnection() override;
+  MOCK_METHOD1(ReadPtr, void(ReadCallback* callback));
+  MOCK_METHOD3(WritePtr,
+               void(uint8_t report_id,
+                    const std::vector<uint8_t>& buffer,
+                    WriteCallback* callback));
+
+  void Read(ReadCallback callback) override;
+
+  void Write(uint8_t report_id,
+             const std::vector<uint8_t>& buffer,
+             WriteCallback callback) override;
+
+  void GetFeatureReport(uint8_t report_id,
+                        GetFeatureReportCallback callback) override;
+  void SendFeatureReport(uint8_t report_id,
+                         const std::vector<uint8_t>& buffer,
+                         SendFeatureReportCallback callback) override;
+  void SetNonce(base::span<uint8_t const> nonce);
+
+  const std::vector<uint8_t>& connection_channel_id() const {
+    return connection_channel_id_;
+  }
+  const std::vector<uint8_t>& nonce() const { return nonce_; }
+
+ private:
+  mojo::Binding<device::mojom::HidConnection> binding_;
+  device::mojom::HidDeviceInfoPtr device_;
+  std::vector<uint8_t> nonce_;
+  std::vector<uint8_t> connection_channel_id_;
+};
+
 class FakeHidConnection : public device::mojom::HidConnection {
  public:
   explicit FakeHidConnection(device::mojom::HidDeviceInfoPtr device);
@@ -49,12 +96,17 @@
   void AddBinding(mojo::ScopedMessagePipeHandle handle);
   void AddBinding2(device::mojom::HidManagerRequest request);
   void AddDevice(device::mojom::HidDeviceInfoPtr device);
+  void AddDeviceAndSetConnection(device::mojom::HidDeviceInfoPtr device,
+                                 device::mojom::HidConnectionPtr connection);
   void RemoveDevice(const std::string device_guid);
 
  private:
   std::map<std::string, device::mojom::HidDeviceInfoPtr> devices_;
+  std::map<std::string, device::mojom::HidConnectionPtr> connections_;
   mojo::AssociatedInterfacePtrSet<device::mojom::HidManagerClient> clients_;
   mojo::BindingSet<device::mojom::HidManager> bindings_;
 };
 
 }  // namespace device
+
+#endif  // DEVICE_U2F_FAKE_HID_IMPL_FOR_TESTING_H_
diff --git a/device/u2f/u2f_hid_device.cc b/device/u2f/u2f_hid_device.cc
index 29d2397..9f5d526 100644
--- a/device/u2f/u2f_hid_device.cc
+++ b/device/u2f/u2f_hid_device.cc
@@ -27,7 +27,9 @@
 
 U2fHidDevice::U2fHidDevice(device::mojom::HidDeviceInfoPtr device_info,
                            device::mojom::HidManager* hid_manager)
-    : hid_manager_(hid_manager),
+    : U2fDevice(),
+      state_(State::INIT),
+      hid_manager_(hid_manager),
       device_info_(std::move(device_info)),
       weak_factory_(this) {}
 
@@ -77,6 +79,7 @@
     default:
       base::WeakPtr<U2fHidDevice> self = weak_factory_.GetWeakPtr();
       repeating_callback.Run(false, nullptr);
+
       // Executing callbacks may free |this|. Check |self| first.
       while (self && !pending_transactions_.empty()) {
         // Respond to any pending requests
@@ -138,6 +141,7 @@
     Transition(nullptr, std::move(callback));
     return;
   }
+
   // Channel allocation response is defined as:
   // 0: 8 byte nonce
   // 8: 4 byte channel id
@@ -152,12 +156,17 @@
     Transition(nullptr, std::move(callback));
     return;
   }
+  auto received_nonce = base::make_span(payload).first(8);
+  // Received a broadcast message for a different client. Disregard and continue
+  // reading.
+  if (base::make_span(nonce) != received_nonce) {
+    auto repeating_callback =
+        base::AdaptCallbackForRepeating(std::move(callback));
+    ArmTimeout(repeating_callback);
+    ReadMessage(base::BindOnce(&U2fHidDevice::OnAllocateChannel,
+                               weak_factory_.GetWeakPtr(), nonce,
+                               std::move(command), repeating_callback));
 
-  std::vector<uint8_t> received_nonce(std::begin(payload),
-                                      std::begin(payload) + 8);
-  if (nonce != received_nonce) {
-    state_ = State::DEVICE_ERROR;
-    Transition(nullptr, std::move(callback));
     return;
   }
 
@@ -167,7 +176,6 @@
   channel_id_ |= payload[index++] << 8;
   channel_id_ |= payload[index++];
   capabilities_ = payload[16];
-
   state_ = State::IDLE;
   Transition(std::move(command), std::move(callback));
 }
@@ -180,8 +188,9 @@
     return;
   }
 
+  const auto& packet = message->PopNextPacket();
   connection_->Write(
-      kReportId, message->PopNextPacket(),
+      kReportId, packet,
       base::BindOnce(&U2fHidDevice::PacketWritten, weak_factory_.GetWeakPtr(),
                      std::move(message), true, std::move(callback)));
 }
@@ -219,6 +228,7 @@
   }
 
   DCHECK(buf);
+
   std::unique_ptr<U2fMessage> read_message =
       U2fMessage::CreateFromSerializedData(*buf);
 
diff --git a/device/u2f/u2f_hid_device_unittest.cc b/device/u2f/u2f_hid_device_unittest.cc
index 47fcde7..e006125 100644
--- a/device/u2f/u2f_hid_device_unittest.cc
+++ b/device/u2f/u2f_hid_device_unittest.cc
@@ -5,20 +5,33 @@
 #include <list>
 
 #include "base/bind.h"
+#include "base/containers/span.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_mock_time_message_loop_task_runner.h"
 #include "base/test/scoped_task_environment.h"
 #include "device/u2f/fake_hid_impl_for_testing.h"
 #include "device/u2f/u2f_apdu_command.h"
 #include "device/u2f/u2f_apdu_response.h"
+#include "device/u2f/u2f_command_type.h"
 #include "device/u2f/u2f_hid_device.h"
+#include "device/u2f/u2f_message.h"
 #include "device/u2f/u2f_packet.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/device/public/cpp/hid/hid_device_filter.h"
 #include "services/device/public/interfaces/hid.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace device {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
 namespace {
 
 void ResponseCallback(std::unique_ptr<device::U2fApduResponse>* output,
@@ -27,9 +40,58 @@
   *output = std::move(response);
 }
 
-}  // namespace
+std::string HexEncode(base::span<const uint8_t> in) {
+  return base::HexEncode(in.data(), in.size());
+}
 
-namespace device {
+std::vector<uint8_t> HexDecode(base::StringPiece in) {
+  std::vector<uint8_t> out;
+  bool result = base::HexStringToBytes(in.as_string(), &out);
+  DCHECK(result);
+  return out;
+}
+
+// Converts hex encoded StringPiece to byte vector and pads zero to fit HID
+// packet size.
+std::vector<uint8_t> MakePacket(base::StringPiece hex) {
+  std::vector<uint8_t> out = HexDecode(hex);
+  out.resize(64);
+  return out;
+}
+
+// Returns HID_INIT request to send to device with mock connection.
+std::vector<uint8_t> CreateMockHidInitResponse(
+    std::vector<uint8_t> nonce,
+    std::vector<uint8_t> channel_id) {
+  // 4 bytes of broadcast channel identifier(ffffffff), followed by
+  // HID_INIT command(86) and 2 byte payload length(11)
+  return MakePacket("ffffffff860011" + HexEncode(nonce) +
+                    HexEncode(channel_id));
+}
+
+// Returns "U2F_v2" as a mock response to version request with given channel id.
+std::vector<uint8_t> CreateMockVersionResponse(
+    std::vector<uint8_t> channel_id) {
+  // HID_MSG command(83), followed by payload length(0008), followed by
+  // hex encoded "U2F_V2" and  NO_ERROR response code(9000).
+  return MakePacket(HexEncode(channel_id) + "8300085532465f56329000");
+}
+
+device::mojom::HidDeviceInfoPtr TestHidDevice() {
+  auto c_info = device::mojom::HidCollectionInfo::New();
+  c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
+  auto hid_device = device::mojom::HidDeviceInfo::New();
+  hid_device->guid = "A";
+  hid_device->product_name = "Test Fido device";
+  hid_device->serial_number = "123FIDO";
+  hid_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
+  hid_device->collections.push_back(std::move(c_info));
+  hid_device->max_input_report_size = 64;
+  hid_device->max_output_report_size = 64;
+  return hid_device;
+}
+
+}  // namespace
 
 class U2fDeviceEnumerate {
  public:
@@ -145,8 +207,6 @@
  protected:
   device::mojom::HidManagerPtr hid_manager_;
   std::unique_ptr<FakeHidManager> fake_hid_manager_;
-
- private:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 };
 
@@ -192,19 +252,7 @@
 TEST_F(U2fHidDeviceTest, TestConnectionFailure) {
   // Setup and enumerate mock device
   U2fDeviceEnumerate callback(hid_manager_.get());
-
-  auto c_info = device::mojom::HidCollectionInfo::New();
-  c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
-
-  auto hid_device = device::mojom::HidDeviceInfo::New();
-  hid_device->guid = "A";
-  hid_device->product_name = "Test Fido device";
-  hid_device->serial_number = "123FIDO";
-  hid_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
-  hid_device->collections.push_back(std::move(c_info));
-  hid_device->max_input_report_size = 64;
-  hid_device->max_output_report_size = 64;
-
+  auto hid_device = TestHidDevice();
   fake_hid_manager_->AddDevice(std::move(hid_device));
   hid_manager_->GetDevices(callback.callback());
 
@@ -244,17 +292,7 @@
   // Setup and enumerate mock device
   U2fDeviceEnumerate callback(hid_manager_.get());
 
-  auto c_info = device::mojom::HidCollectionInfo::New();
-  c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
-
-  auto hid_device = device::mojom::HidDeviceInfo::New();
-  hid_device->guid = "A";
-  hid_device->product_name = "Test Fido device";
-  hid_device->serial_number = "123FIDO";
-  hid_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
-  hid_device->collections.push_back(std::move(c_info));
-  hid_device->max_input_report_size = 64;
-  hid_device->max_output_report_size = 64;
+  auto hid_device = TestHidDevice();
 
   fake_hid_manager_->AddDevice(std::move(hid_device));
   hid_manager_->GetDevices(callback.callback());
@@ -297,4 +335,76 @@
   EXPECT_EQ(nullptr, response3);
 }
 
+TEST_F(U2fHidDeviceTest, TestRetryChannelAllocation) {
+  const std::vector<uint8_t> kIncorrectNonce = {0x00, 0x00, 0x00, 0x00,
+                                                0x00, 0x00, 0x00, 0x00};
+
+  const std::vector<uint8_t> kChannelId = {0x01, 0x02, 0x03, 0x04};
+
+  U2fDeviceEnumerate callback(hid_manager_.get());
+  auto hid_device = TestHidDevice();
+
+  // Replace device HID connection with custom client connection bound to mock
+  // server-side mojo connection.
+  device::mojom::HidConnectionPtr connection_client;
+  MockHidConnection mock_connection(
+      hid_device.Clone(), mojo::MakeRequest(&connection_client), kChannelId);
+
+  // Delegate custom functions to be invoked for mock hid connection
+  EXPECT_CALL(mock_connection, WritePtr(_, _, _))
+      // HID_INIT request to authenticator for channel allocation.
+      .WillOnce(WithArgs<1, 2>(
+          Invoke([&](const std::vector<uint8_t>& buffer,
+                     device::mojom::HidConnection::WriteCallback* cb) {
+            mock_connection.SetNonce(base::make_span(buffer).subspan(7, 8));
+            std::move(*cb).Run(true);
+          })))
+
+      // HID_MSG request to authenticator for version request.
+      .WillOnce(WithArgs<2>(
+          Invoke([](device::mojom::HidConnection::WriteCallback* cb) {
+            std::move(*cb).Run(true);
+          })));
+
+  EXPECT_CALL(mock_connection, ReadPtr(_))
+      // First response to HID_INIT request with incorrect nonce.
+      .WillOnce(WithArg<0>(
+          Invoke([kIncorrectNonce, &mock_connection](
+                     device::mojom::HidConnection::ReadCallback* cb) {
+            std::move(*cb).Run(
+                true, 0,
+                CreateMockHidInitResponse(
+                    kIncorrectNonce, mock_connection.connection_channel_id()));
+          })))
+      // Second response to HID_INIT request with correct nonce.
+      .WillOnce(WithArg<0>(Invoke(
+          [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
+            std::move(*cb).Run(true, 0,
+                               CreateMockHidInitResponse(
+                                   mock_connection.nonce(),
+                                   mock_connection.connection_channel_id()));
+          })))
+      // Version response from the authenticator.
+      .WillOnce(WithArg<0>(Invoke(
+          [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
+            std::move(*cb).Run(true, 0,
+                               CreateMockVersionResponse(
+                                   mock_connection.connection_channel_id()));
+          })));
+
+  // Add device and set mock connection to fake hid manager
+  fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
+                                               std::move(connection_client));
+
+  hid_manager_->GetDevices(callback.callback());
+  std::list<std::unique_ptr<U2fHidDevice>>& u2f_devices =
+      callback.WaitForCallback();
+
+  ASSERT_EQ(1u, u2f_devices.size());
+  auto& device = u2f_devices.front();
+  TestVersionCallback vc;
+  device->Version(vc.callback());
+  EXPECT_EQ(vc.WaitForCallback(), U2fDevice::ProtocolVersion::U2F_V2);
+}
+
 }  // namespace device
diff --git a/docs/mac_build_instructions.md b/docs/mac_build_instructions.md
index bf811b4..782319b5 100644
--- a/docs/mac_build_instructions.md
+++ b/docs/mac_build_instructions.md
@@ -41,12 +41,19 @@
 
 ## Get the code
 
+Ensure that unicode filenames aren't mangled by HFS:
+
+```shell
+$ git config --global core.precomposeUnicode true
+```
+
 Create a `chromium` directory for the checkout and change to it (you can call
 this whatever you like and put it wherever you like, as long as the full path
 has no spaces):
 
 ```shell
 $ mkdir chromium && cd chromium
+$ git config --global core.precomposeUnicode true
 ```
 
 Run the `fetch` tool from `depot_tools` to check out the code and its
diff --git a/docs/speed/benchmark_harnesses/system_health.md b/docs/speed/benchmark_harnesses/system_health.md
new file mode 100644
index 0000000..5ec3d68
--- /dev/null
+++ b/docs/speed/benchmark_harnesses/system_health.md
@@ -0,0 +1,200 @@
+# System Health tests
+
+[TOC]
+
+## Overview
+
+The Chrome System Health benchmarking effort aims to create a common set of user
+stories on the web that can be used for all Chrome speed projects. Our
+benchmarks mimic average web users’ activities and cover all major web platform
+APIs & browser features.
+
+The web is vast, the possible combination of user activities is endless. Hence,
+to get a useful benchmarking tool for engineers to use for preventing
+regressions, during launches and day to day work, we use data analysis and work
+with teams within Chrome to create a limited set of stories that can fit a
+budget of 90 minutes machine time.
+
+These are our key cover areas for the browser:
+* Different user gestures: swipe, fling, text input, scroll & infinite scroll
+* Video
+* Audio
+* Flash
+* Graphics: css, svg, canvas, webGL
+* Background tabs
+* Multi-tab switching
+* Back button
+* Follow a link
+* Restore tabs
+* Reload a page
+* ... ([Full tracking sheet](https://docs.google.com/spreadsheets/d/1t15Ya5ssYBeXAZhHm3RJqfwBRpgWsxoib8_kwQEHMwI/edit#gid=0))
+
+Success to us means System Health benchmarks cast a wide enough net to
+catch major regressions before they make it to the users. This also means
+performance improvements to System Health benchmarks translate to actual wins
+on the web, enabling teams to use these benchmarks for tracking progress with
+the confidence that their improvement on the suite matters to real users.
+
+To achieve this goal, just simulating user’s activities on the web is not
+enough. We also partner with
+[chrome-speed-metrics](https://groups.google.com/a/chromium.org/forum/#!forum/progressive-web-metrics)
+team to track key user metrics on our user stories.
+
+
+## Where are the System Health stories?
+
+All the System Health stories are located in
+[tools/perf/page_sets/system_health/](../../../tools/perf/page_sets/system_health/).
+
+There are few groups of stories:
+1. [Accessibility stories](../../../tools/perf/page_sets/system_health/accessibility_stories.py)
+2. [Background stories](../../../tools/perf/page_sets/system_health/background_stories.py)
+3. [Browsing stories](../../../tools/perf/page_sets/system_health/browsing_stories.py)
+4. [Chrome stories](../../../tools/perf/page_sets/system_health/chrome_stories.py)
+5. [Loading stories](../../../tools/perf/page_sets/system_health/loading_stories.py)
+6. [Multi-tab stories](../../../tools/perf/page_sets/system_health/multi_tab_stories.py)
+7. [Media stories](../../../tools/perf/page_sets/system_health/media_stories.py)
+
+## What is the structure of a System Health story?
+A System Health story is a subclass of
+[SystemHealthStory](https://cs.chromium.org/chromium/src/tools/perf/page_sets/system_health/system_health_story.py?l=44&rcl=d5f1f0821489a8311dc437fc6b70ac0b0d72b28b), for example:
+```
+class NewSystemHealthStory(SystemHealthStory):
+  NAME = 'case:group:page:2018'
+  URL = 'https://the.starting.url'
+  TAGS = [story_tags.JAVASCRIPT_HEAVY, story_tags.INFINITE_SCROLL]
+  SUPPORTED_PLATFORMS = platforms.ALL_PLATFORMS  # Default.
+                                                 # or platforms.DESKTOP_ONLY
+                                                 # or platforms.MOBILE_ONLY
+                                                 # or platforms.NO_PLATFORMS
+
+  def _Login(self, action_runner):
+    # Optional. Called before the starting URL is loaded.
+
+  def _DidLoadDocument(self, action_runner):
+    # Optional. Called after the starting URL is loaded
+    # (but before potentially measuring memory).
+```
+
+The name must have the following structure:
+1.  **Case** (load, browse, search, …). User action/journey that the story
+    simulates (verb). Stories for each case are currently kept in a separate
+    file.
+    Benchmarks using the System Health story set can specify which cases they want to
+    include (see
+    [SystemHealthStorySet](https://cs.chromium.org/chromium/src/tools/perf/page_sets/system_health/system_health_stories.py?l=16&rcl=e3eb21e24dbe0530356003fd9f9a8a94fb91d00b)).
+2.  **Group** (social, news, tools, …). General category to which the page
+    (item 3) belongs.
+3.  **Page** (google, facebook, nytimes, …). The name of the individual page. In
+    case there are multi pages, one can use the general grouping name like
+    "top_pages", or "typical_pages".
+4.  **Year** (2017, 2018, 2018_q3, ...). The year (and quarter if necessary for
+    disambiguating) when the page is added. Note: this rule was added later,
+    so the old System Health stories do not have this field.
+
+In addition, each story also has accompanied tags that define its important
+characteristics.
+[Tags](../../../tools/perf/page_sets/system_health/story_tags.py) are used as
+the way to track coverage of System Health stories, so they should be as
+detailed as needed to distinguish each System Health story from the others.
+
+## How are System Health stories executed?
+Given a System Health story set with N stories, each story is executed sequentially as
+follows:
+
+1.  Launch the browser
+2.  Start tracing
+3.  Run `story._Login` (no-op by default)
+4.  Load `story.URL`
+5.  Run `story._DidLoadDocument` (no-op by default)
+6.  Measure memory (disabled by default)
+7.  Stop tracing
+8.  Tear down the browser
+
+All the benchmarks using System Health stories tear down the browser after single story.
+This ensures that every story is completely independent and modifications to the
+System Health story set won’t cause as many regressions/improvements on the perf dashboard.
+
+## Should I add new System Health stories and how?
+
+First, check this list of [System Health stories](https://docs.google.com/spreadsheets/d/1t15Ya5ssYBeXAZhHm3RJqfwBRpgWsxoib8_kwQEHMwI/edit#gid=0)
+to see if your intended user stories are already covered by existing ones.
+
+If there is a good reason for your stories to be added, please make one CL for
+each of the new stories so they can be landed (and reverted if needed)
+individually. On each CL, make sure that the perf trybots all pass before
+comitting.
+
+Once your patch makes it through the CQ, you’re done… unless your story starts
+failing on some random platform, in which case the perf bot sheriff will very
+likely revert your patch and assign a bug to you. It is then up to you to figure
+out why the story fails, fix it and re-land the patch.
+
+Add new SystemHealthStory subclass(es) to either one of the existing files or a
+new file in [tools/perf/page_sets/system_health/](../../tools/perf/page_sets/system_health).
+The new class(es) will automatically be picked up and added to the story set.
+To run the story through the memory benchmark against live sites, use the
+following commands:
+
+```
+$ tools/perf/run_benchmark system_health.memory_desktop \
+      --browser=reference --device=desktop \
+      --story-filter=<NAME-OF-YOUR-STORY> \
+      --use-live-sites
+$ tools/perf/run_benchmark system_health.memory_mobile \
+      --browser=reference --device=android \
+      --story-filter=<NAME-OF-YOUR-STORY> \
+      --also-run-disabled-tests --use-live-sites
+```
+
+Once you’re happy with the stories, record them:
+
+```
+$ tools/perf/record_wpr --story desktop_system_health_story_set \
+      --browser=reference --device=desktop \
+      --story-filter=<NAME-OF-YOUR-STORY>
+$ tools/perf/record_wpr --story mobile_system_health_story_set \
+      --browser=reference --device=android \
+      --story-filter=<NAME-OF-YOUR-STORY>
+```
+
+You can now replay the stories from the recording by omitting the
+`--use-live-sites` flag:
+
+```
+$ tools/perf/run_benchmark system_health.memory_desktop \
+      --browser=reference --device=desktop \
+      --story-filter=<NAME-OF-YOUR-STORY> \
+      --also-run-disabled-tests
+$ tools/perf/run_benchmark system_health.memory_mobile \
+      --browser=reference --device=android \
+      --story-filter=<NAME-OF-YOUR-STORY> \
+      --also-run-disabled-tests
+```
+
+The recordings are stored in `system_health_desktop_MMM.wprgo` and
+`system_health_mobile_NNN.wprgo` files in the
+[tools/perf/page_sets/data](../../../tools/perf/page_sets/data) directory.
+You can find the MMM and NNN values by inspecting the changes to
+`system_health_desktop.json` and `system_health_mobile.json`:
+
+```
+$ git diff tools/perf/page_sets/data/system_health_desktop.json
+$ git diff tools/perf/page_sets/data/system_health_mobile.json
+```
+
+Once you verified that the replay works as you expect, you can upload the .wprgo
+files to the cloud and include the .wprgo.sha1 files in your patch:
+
+```
+$ upload_to_google_storage.py --bucket chrome-partner-telemetry \
+      system_health_desktop_MMM.wprgo
+$ upload_to_google_storage.py --bucket chrome-partner-telemetry \
+      system_health_mobile_NNN.wprgo
+$ git add tools/perf/page_sets/data/system_health_desktop_MMM.wprgo.sha1
+$ git add tools/perf/page_sets/data/system_health_mobile_NNN.wprgo.sha1
+```
+
+If the stories work as they should (certain website features don’t work well
+under WPR and need to be worked around), send them out for review in the patch
+that is adding the new story.
diff --git a/extensions/browser/api/cast_channel/cast_channel_api.cc b/extensions/browser/api/cast_channel/cast_channel_api.cc
index c5d40367..a200778 100644
--- a/extensions/browser/api/cast_channel/cast_channel_api.cc
+++ b/extensions/browser/api/cast_channel/cast_channel_api.cc
@@ -393,6 +393,14 @@
     return;
   }
 
+  if (socket->ready_state() == cast_channel::ReadyState::CLOSED ||
+      !socket->transport()) {
+    SetResultFromError(params_->channel.channel_id,
+                       api::cast_channel::CHANNEL_ERROR_CHANNEL_NOT_OPEN);
+    AsyncWorkCompleted();
+    return;
+  }
+
   CastMessage message_to_send;
   if (!MessageInfoToCastMessage(params_->message, &message_to_send)) {
     SetResultFromError(params_->channel.channel_id,
diff --git a/extensions/browser/api/cast_channel/cast_channel_apitest.cc b/extensions/browser/api/cast_channel/cast_channel_apitest.cc
index f984eb2..66f8f4f4 100644
--- a/extensions/browser/api/cast_channel/cast_channel_apitest.cc
+++ b/extensions/browser/api/cast_channel/cast_channel_apitest.cc
@@ -114,7 +114,7 @@
                 callback.Run(mock_cast_socket_);
               })));
       EXPECT_CALL(*mock_cast_socket_, ready_state())
-          .WillOnce(Return(ReadyState::OPEN));
+          .WillRepeatedly(Return(ReadyState::OPEN));
       EXPECT_CALL(*mock_cast_socket_->mock_transport(),
                   SendMessage(A<const CastMessage&>(), _, _))
           .WillOnce(InvokeCompletionCallback<1>(net::OK));
@@ -127,6 +127,28 @@
     }
   }
 
+  void SetUpOpenErrorSend() {
+    SetUpMockCastSocket();
+    mock_cast_socket_->SetErrorState(ChannelError::CONNECT_ERROR);
+    {
+      InSequence sequence;
+
+      EXPECT_CALL(*mock_cast_socket_, ConnectInternal(_))
+          .WillOnce(WithArgs<0>(
+              Invoke([&](const MockCastSocket::MockOnOpenCallback& callback) {
+                callback.Run(mock_cast_socket_);
+              })));
+      EXPECT_CALL(*mock_cast_socket_, ready_state())
+          .WillRepeatedly(Return(ReadyState::CLOSED));
+      EXPECT_CALL(*mock_cast_socket_->mock_transport(), SendMessage(_, _, _))
+          .Times(0);
+      EXPECT_CALL(*mock_cast_socket_, Close(_))
+          .WillOnce(InvokeCompletionCallback<0>(net::OK));
+      EXPECT_CALL(*mock_cast_socket_, ready_state())
+          .WillOnce(Return(ReadyState::CLOSED));
+    }
+  }
+
   void SetUpOpenPingTimeout() {
     SetUpMockCastSocket();
     mock_cast_socket_->SetErrorState(ChannelError::NONE);
@@ -246,6 +268,22 @@
 // TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest
 // always return true without actually running the test. Remove when fixed.
 #if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestOpenErrorSend DISABLED_TestOpenErrorSend
+#else
+#define MAYBE_TestOpenErrorSend TestOpenErrorSend
+#endif
+// Test loading extension, failing to open a channel with ConnectInfo, sending
+// message on closed channel, and closing.
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenErrorSend) {
+  SetUpOpenErrorSend();
+
+  EXPECT_TRUE(
+      RunExtensionSubtest("cast_channel/api", "test_open_error_send.html"));
+}
+
+// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
 #define MAYBE_TestPingTimeout DISABLED_TestPingTimeout
 #else
 #define MAYBE_TestPingTimeout TestPingTimeout
diff --git a/extensions/browser/content_verify_job.cc b/extensions/browser/content_verify_job.cc
index f3c72b7..94a7127 100644
--- a/extensions/browser/content_verify_job.cc
+++ b/extensions/browser/content_verify_job.cc
@@ -49,11 +49,7 @@
       current_hash_byte_count_(0),
       hash_reader_(hash_reader),
       failure_callback_(std::move(failure_callback)),
-      failed_(false) {
-  // It's ok for this object to be constructed on a different thread from where
-  // it's used.
-  thread_checker_.DetachFromThread();
-}
+      failed_(false) {}
 
 ContentVerifyJob::~ContentVerifyJob() {
   UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS",
@@ -61,7 +57,7 @@
 }
 
 void ContentVerifyJob::Start() {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  base::AutoLock auto_lock(lock_);
   if (g_content_verify_job_test_observer)
     g_content_verify_job_test_observer->JobStarted(
         hash_reader_->extension_id(), hash_reader_->relative_path());
@@ -73,7 +69,7 @@
 
 void ContentVerifyJob::BytesRead(int count, const char* data) {
   ScopedElapsedTimer timer(&time_spent_);
-  DCHECK(thread_checker_.CalledOnValidThread());
+  base::AutoLock auto_lock(lock_);
   if (failed_)
     return;
   if (g_test_delegate) {
@@ -120,7 +116,7 @@
 
 void ContentVerifyJob::DoneReading() {
   ScopedElapsedTimer timer(&time_spent_);
-  DCHECK(thread_checker_.CalledOnValidThread());
+  base::AutoLock auto_lock(lock_);
   if (failed_)
     return;
   if (g_test_delegate) {
diff --git a/extensions/browser/content_verify_job.h b/extensions/browser/content_verify_job.h
index 9b72f807..b93e34fd5 100644
--- a/extensions/browser/content_verify_job.h
+++ b/extensions/browser/content_verify_job.h
@@ -13,7 +13,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "base/threading/thread_checker.h"
+#include "base/synchronization/lock.h"
 
 namespace base {
 class FilePath;
@@ -29,8 +29,7 @@
 
 // Objects of this class are responsible for verifying that the actual content
 // read from an extension file matches an expected set of hashes. This class
-// can be created on any thread but the rest of the methods should be called
-// from only one thread.
+// can be created and used on any thread.
 class ContentVerifyJob : public base::RefCountedThreadSafe<ContentVerifyJob> {
  public:
   enum FailureReason {
@@ -146,8 +145,8 @@
   // Set to true if we detected a mismatch and called the failure callback.
   bool failed_;
 
-  // For ensuring methods on called on the right thread.
-  base::ThreadChecker thread_checker_;
+  // Used to synchronize all public methods.
+  base::Lock lock_;
 
   DISALLOW_COPY_AND_ASSIGN(ContentVerifyJob);
 };
diff --git a/extensions/browser/extension_protocols.cc b/extensions/browser/extension_protocols.cc
index 2d3c273..75bdf38 100644
--- a/extensions/browser/extension_protocols.cc
+++ b/extensions/browser/extension_protocols.cc
@@ -10,6 +10,7 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/base64.h"
@@ -37,6 +38,7 @@
 #include "base/timer/elapsed_timer.h"
 #include "build/build_config.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/file_url_loader.h"
 #include "content/public/browser/navigation_ui_data.h"
 #include "content/public/browser/render_frame_host.h"
@@ -50,6 +52,7 @@
 #include "extensions/browser/content_verify_job.h"
 #include "extensions/browser/extension_navigation_ui_data.h"
 #include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
@@ -652,23 +655,130 @@
                                     verify_job);
 }
 
+class FileLoaderObserver : public content::FileURLLoaderObserver {
+ public:
+  explicit FileLoaderObserver(scoped_refptr<ContentVerifyJob> verify_job)
+      : verify_job_(std::move(verify_job)) {}
+  ~FileLoaderObserver() override {
+    base::AutoLock auto_lock(lock_);
+    UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.TotalKbRead", bytes_read_ / 1024);
+    UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.SeekPosition", seek_position_);
+    if (request_timer_.get())
+      UMA_HISTOGRAM_TIMES("ExtensionUrlRequest.Latency",
+                          request_timer_->Elapsed());
+  }
+
+  void OnStart() override {
+    base::AutoLock auto_lock(lock_);
+    request_timer_.reset(new base::ElapsedTimer());
+  }
+
+  void OnOpenComplete(int result) override {
+    if (result < 0) {
+      // This can happen when the file is unreadable (which can happen during
+      // corruption or third-party interaction). We need to be sure to inform
+      // the verification job that we've finished reading so that it can
+      // proceed; see crbug.com/703888.
+      if (verify_job_.get()) {
+        std::string tmp;
+        verify_job_->BytesRead(0, base::string_as_array(&tmp));
+        verify_job_->DoneReading();
+      }
+    }
+  }
+
+  void OnSeekComplete(int64_t result) override {
+    DCHECK_EQ(seek_position_, 0);
+    base::AutoLock auto_lock(lock_);
+    seek_position_ = result;
+    // TODO(asargent) - we'll need to add proper support for range headers.
+    // crbug.com/369895.
+    if (result > 0 && verify_job_.get())
+      verify_job_ = nullptr;
+  }
+
+  void OnBytesRead(const void* data,
+                   size_t num_bytes_read,
+                   base::File::Error read_result) override {
+    if (read_result == base::File::FILE_OK) {
+      UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.OnReadCompleteResult",
+                           read_result);
+      base::AutoLock auto_lock(lock_);
+      bytes_read_ += num_bytes_read;
+      if (verify_job_.get())
+        verify_job_->BytesRead(num_bytes_read, static_cast<const char*>(data));
+    } else {
+      net::Error net_error = net::FileErrorToNetError(read_result);
+      UMA_HISTOGRAM_SPARSE_SLOWLY("ExtensionUrlRequest.OnReadCompleteError",
+                                  net_error);
+    }
+  }
+
+  void OnDoneReading() override {
+    base::AutoLock auto_lock(lock_);
+    if (verify_job_.get())
+      verify_job_->DoneReading();
+  }
+
+ private:
+  int64_t bytes_read_ = 0;
+  int64_t seek_position_ = 0;
+  std::unique_ptr<base::ElapsedTimer> request_timer_;
+  scoped_refptr<ContentVerifyJob> verify_job_;
+  // To synchronize access to all members.
+  base::Lock lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileLoaderObserver);
+};
+
 void LoadExtensionResourceFromFileOnBackgroundSequence(
     const content::ResourceRequest& request,
     const std::string& extension_id,
     const base::FilePath& directory_path,
     const base::FilePath& relative_path,
     content::mojom::URLLoaderRequest loader,
-    content::mojom::URLLoaderClientPtrInfo client_info) {
+    content::mojom::URLLoaderClientPtrInfo client_info,
+    scoped_refptr<ContentVerifyJob> verify_job) {
   // NOTE: ExtensionResource::GetFilePath() must be called on a sequence which
   // tolerates blocking operations.
   ExtensionResource resource(extension_id, directory_path, relative_path);
   content::mojom::URLLoaderClientPtr client;
   client.Bind(std::move(client_info));
 
+  auto loader_observer =
+      std::make_unique<FileLoaderObserver>(std::move(verify_job));
+
   content::ResourceRequest file_request = request;
   file_request.url = net::FilePathToFileURL(resource.GetFilePath());
   content::CreateFileURLLoader(file_request, std::move(loader),
-                               std::move(client));
+                               std::move(client), std::move(loader_observer));
+}
+
+void CreateVerifierAndLoadFile(
+    const content::ResourceRequest& request,
+    const std::string& extension_id,
+    const base::FilePath& directory_path,
+    const base::FilePath& relative_path,
+    content::mojom::URLLoaderRequest loader,
+    content::mojom::URLLoaderClientPtr client,
+    scoped_refptr<extensions::InfoMap> extension_info_map) {
+  scoped_refptr<ContentVerifyJob> verify_job;
+  ContentVerifier* verifier = extension_info_map->content_verifier();
+  if (verifier) {
+    verify_job =
+        verifier->CreateJobFor(extension_id, directory_path, relative_path);
+    if (verify_job)
+      verify_job->Start();
+  }
+
+  auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
+      {base::MayBlock(), base::TaskPriority::BACKGROUND});
+  task_runner->PostTask(
+      FROM_HERE,
+      base::BindOnce(&LoadExtensionResourceFromFileOnBackgroundSequence,
+                     request, extension_id, directory_path, relative_path,
+                     std::move(loader), client.PassInterface(),
+                     std::move(verify_job)));
 }
 
 class ExtensionURLLoaderFactory : public content::mojom::URLLoaderFactory {
@@ -679,7 +789,9 @@
   // |frame_host|.
   explicit ExtensionURLLoaderFactory(content::RenderFrameHost* frame_host,
                                      const GURL& frame_url)
-      : frame_host_(frame_host), frame_url_(frame_url) {}
+      : frame_host_(frame_host),
+        frame_url_(frame_url),
+        extension_info_map_(nullptr) {}
   ~ExtensionURLLoaderFactory() override = default;
 
   // content::mojom::URLLoaderFactory:
@@ -758,7 +870,7 @@
       return;
     }
 
-    // TODO(crbug.com/782015): Support component extension resource loading from
+    // TODO(crbug.com/782025): Support component extension resource loading from
     // the embedder's resource files. This would be the right place to try to
     // resolve such resources, before we attempt to hit other files on disk.
 
@@ -796,18 +908,16 @@
       }
     }
 
-    // TODO(crbug.com/782015): Support content verification on extension
-    // resource requests. This is roughly the point at which we'd want to create
-    // a ContentVerifyJob and somehow hook it into the file URLLoader we set up
-    // below.
+    if (!extension_info_map_) {
+      extension_info_map_ =
+          extensions::ExtensionSystem::Get(browser_context)->info_map();
+    }
 
-    auto task_runner = base::CreateSequencedTaskRunnerWithTraits(
-        {base::MayBlock(), base::TaskPriority::BACKGROUND});
-    task_runner->PostTask(
-        FROM_HERE,
-        base::BindOnce(&LoadExtensionResourceFromFileOnBackgroundSequence,
-                       request, extension_id, directory_path, relative_path,
-                       std::move(loader), client.PassInterface()));
+    content::BrowserThread::PostTask(
+        content::BrowserThread::IO, FROM_HERE,
+        base::BindOnce(&CreateVerifierAndLoadFile, request, extension_id,
+                       directory_path, relative_path, std::move(loader),
+                       std::move(client), extension_info_map_));
   }
 
   void Clone(content::mojom::URLLoaderFactoryRequest request) override {
@@ -817,6 +927,7 @@
  private:
   content::RenderFrameHost* const frame_host_;
   const GURL frame_url_;
+  scoped_refptr<extensions::InfoMap> extension_info_map_;
 
   mojo::BindingSet<content::mojom::URLLoaderFactory> bindings_;
 
diff --git a/gpu/ipc/service/image_transport_surface_fuchsia.cc b/gpu/ipc/service/image_transport_surface_fuchsia.cc
index 65d3013c..cb967fb7 100644
--- a/gpu/ipc/service/image_transport_surface_fuchsia.cc
+++ b/gpu/ipc/service/image_transport_surface_fuchsia.cc
@@ -5,6 +5,7 @@
 #include "gpu/ipc/service/image_transport_surface.h"
 
 #include "base/logging.h"
+#include "ui/gl/gl_surface_osmesa.h"
 #include "ui/gl/gl_surface_stub.h"
 
 namespace gpu {
@@ -14,6 +15,10 @@
     base::WeakPtr<ImageTransportSurfaceDelegate> delegate,
     SurfaceHandle surface_handle,
     gl::GLSurfaceFormat format) {
+  if (gl::GetGLImplementation() == gl::kGLImplementationOSMesaGL) {
+    return new gl::GLSurfaceOSMesa(format, gfx::Size(1, 1));
+  }
+
   DCHECK(gl::GetGLImplementation() == gl::kGLImplementationMockGL ||
          gl::GetGLImplementation() == gl::kGLImplementationStubGL);
   return new gl::GLSurfaceStub;
diff --git a/ios/chrome/app/chrome_overlay_window.mm b/ios/chrome/app/chrome_overlay_window.mm
index 869e50c..fcb4a8b 100644
--- a/ios/chrome/app/chrome_overlay_window.mm
+++ b/ios/chrome/app/chrome_overlay_window.mm
@@ -63,10 +63,6 @@
       self.traitCollection.horizontalSizeClass);
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 #pragma mark - UITraitEnvironment
 
 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 623e0d7..fc3d96a 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -531,7 +531,6 @@
 }
 
 - (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
   net::HTTPProtocolHandlerDelegate::SetInstance(nullptr);
   net::RequestTracker::SetRequestTrackerFactory(nullptr);
   [NSObject cancelPreviousPerformRequestsWithTarget:self];
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm b/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
index a94bf30..142f31c9 100644
--- a/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
+++ b/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
@@ -268,7 +268,6 @@
 }
 
 - (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
   if (_webState) {
     _webState->RemoveObserver(_webStateObserverBridge.get());
     _webStateObserverBridge.reset();
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm b/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
index 3f1317a..20311271 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_impl_io_data.mm
@@ -351,6 +351,9 @@
   DCHECK(transport_security_state());
   // Completes synchronously.
   transport_security_state()->DeleteAllDynamicDataSince(time);
-  http_server_properties()->Clear();
-  web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, completion);
+  http_server_properties()->Clear(base::BindOnce(
+      [](const base::Closure& completion) {
+        web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, completion);
+      },
+      completion));
 }
diff --git a/ios/chrome/browser/find_in_page/find_in_page_controller.mm b/ios/chrome/browser/find_in_page/find_in_page_controller.mm
index 7efc159e..748deb8 100644
--- a/ios/chrome/browser/find_in_page/find_in_page_controller.mm
+++ b/ios/chrome/browser/find_in_page/find_in_page_controller.mm
@@ -133,8 +133,6 @@
 }
 
 - (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-
   if (_webState) {
     _webState->RemoveObserver(_webStateObserverBridge.get());
     _webStateObserverBridge.reset();
diff --git a/ios/chrome/browser/geolocation/location_manager.mm b/ios/chrome/browser/geolocation/location_manager.mm
index 10c5ce0..d793c97a 100644
--- a/ios/chrome/browser/geolocation/location_manager.mm
+++ b/ios/chrome/browser/geolocation/location_manager.mm
@@ -77,10 +77,6 @@
   return self;
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 - (CLAuthorizationStatus)authorizationStatus {
   return [CLLocationManager authorizationStatus];
 }
diff --git a/ios/chrome/browser/memory/memory_debugger.mm b/ios/chrome/browser/memory/memory_debugger.mm
index f7ff214..8cac1d14 100644
--- a/ios/chrome/browser/memory/memory_debugger.mm
+++ b/ios/chrome/browser/memory/memory_debugger.mm
@@ -75,10 +75,6 @@
   [_memoryWarningTimer invalidate];
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 #pragma mark UIView methods
 
 - (CGSize)sizeThatFits:(CGSize)size {
diff --git a/ios/chrome/browser/net/http_server_properties_manager_factory.cc b/ios/chrome/browser/net/http_server_properties_manager_factory.cc
index 032bedb..2e7ed4c 100644
--- a/ios/chrome/browser/net/http_server_properties_manager_factory.cc
+++ b/ios/chrome/browser/net/http_server_properties_manager_factory.cc
@@ -8,6 +8,7 @@
 
 #include "base/values.h"
 #include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/json_pref_store.h"
 #include "ios/chrome/browser/pref_names.h"
 #include "ios/web/public/web_thread.h"
 #include "net/http/http_server_properties_manager.h"
@@ -18,7 +19,7 @@
     : public net::HttpServerPropertiesManager::PrefDelegate,
       public PrefStore::Observer {
  public:
-  explicit PrefServiceAdapter(scoped_refptr<WriteablePrefStore> pref_store)
+  explicit PrefServiceAdapter(scoped_refptr<JsonPrefStore> pref_store)
       : pref_store_(std::move(pref_store)),
         path_(prefs::kHttpServerProperties) {
     pref_store_->AddObserver(this);
@@ -37,11 +38,15 @@
 
     return nullptr;
   }
-  void SetServerProperties(const base::DictionaryValue& value) override {
-    return pref_store_->SetValue(path_, value.CreateDeepCopy(),
-                                 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+  void SetServerProperties(const base::DictionaryValue& value,
+                           base::OnceClosure callback) override {
+    pref_store_->SetValue(path_, value.CreateDeepCopy(),
+                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+    if (callback)
+      pref_store_->CommitPendingWrite(std::move(callback));
   }
-  void StartListeningForUpdates(const base::Closure& callback) override {
+  void StartListeningForUpdates(
+      const base::RepeatingClosure& callback) override {
     on_changed_callback_ = callback;
   }
 
@@ -56,7 +61,7 @@
   }
 
  private:
-  scoped_refptr<WriteablePrefStore> pref_store_;
+  scoped_refptr<JsonPrefStore> pref_store_;
   const std::string path_;
 
   base::Closure on_changed_callback_;
@@ -69,7 +74,7 @@
 // static
 std::unique_ptr<net::HttpServerPropertiesManager>
 HttpServerPropertiesManagerFactory::CreateManager(
-    scoped_refptr<WriteablePrefStore> pref_store,
+    scoped_refptr<JsonPrefStore> pref_store,
     net::NetLog* net_log) {
   DCHECK_CURRENTLY_ON(web::WebThread::IO);
   return std::make_unique<net::HttpServerPropertiesManager>(
diff --git a/ios/chrome/browser/net/http_server_properties_manager_factory.h b/ios/chrome/browser/net/http_server_properties_manager_factory.h
index 8ae4f9b..6902ea1 100644
--- a/ios/chrome/browser/net/http_server_properties_manager_factory.h
+++ b/ios/chrome/browser/net/http_server_properties_manager_factory.h
@@ -9,7 +9,8 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "components/prefs/writeable_pref_store.h"
+
+class JsonPrefStore;
 
 namespace net {
 class HttpServerPropertiesManager;
@@ -21,7 +22,7 @@
  public:
   // Create an instance of HttpServerPropertiesManager.
   static std::unique_ptr<net::HttpServerPropertiesManager> CreateManager(
-      scoped_refptr<WriteablePrefStore> pref_store,
+      scoped_refptr<JsonPrefStore> pref_store,
       net::NetLog* net_log);
 
  private:
diff --git a/ios/chrome/browser/prerender/preload_controller.mm b/ios/chrome/browser/prerender/preload_controller.mm
index a841343..07372d9a 100644
--- a/ios/chrome/browser/prerender/preload_controller.mm
+++ b/ios/chrome/browser/prerender/preload_controller.mm
@@ -179,7 +179,6 @@
 - (void)dealloc {
   UMA_HISTOGRAM_COUNTS(kPrerendersPerSessionCountHistogramName,
                        successfulPrerendersPerSessionCount_);
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
   [self cancelPrerender];
 }
 
diff --git a/ios/chrome/browser/resources/Settings.bundle/Experimental.plist b/ios/chrome/browser/resources/Settings.bundle/Experimental.plist
index 9a91bb4..d691157cf 100644
--- a/ios/chrome/browser/resources/Settings.bundle/Experimental.plist
+++ b/ios/chrome/browser/resources/Settings.bundle/Experimental.plist
@@ -66,28 +66,6 @@
 			<key>Type</key>
 			<string>PSMultiValueSpecifier</string>
 			<key>Title</key>
-			<string>Enable Signin Promo</string>
-			<key>Key</key>
-			<string>EnableSigninPromo</string>
-			<key>DefaultValue</key>
-			<string></string>
-			<key>Values</key>
-			<array>
-				<string></string>
-				<string>Enabled</string>
-				<string>Disabled</string>
-			</array>
-			<key>Titles</key>
-			<array>
-				<string>Default</string>
-				<string>Enabled</string>
-				<string>Disabled</string>
-			</array>
-		</dict>
-		<dict>
-			<key>Type</key>
-			<string>PSMultiValueSpecifier</string>
-			<key>Title</key>
 			<string>Force What&apos;s New Promo</string>
 			<key>Key</key>
 			<string>WhatsNewPromoStatus</string>
diff --git a/ios/chrome/browser/tabs/tab_model_unittest.mm b/ios/chrome/browser/tabs/tab_model_unittest.mm
index 8320df467..e04452d 100644
--- a/ios/chrome/browser/tabs/tab_model_unittest.mm
+++ b/ios/chrome/browser/tabs/tab_model_unittest.mm
@@ -130,8 +130,7 @@
       session_storage.lastCommittedItemIndex = -1;
       [sessions addObject:session_storage];
     }
-    return base::scoped_nsobject<SessionWindowIOS>(
-        [[SessionWindowIOS alloc] initWithSessions:sessions selectedIndex:1]);
+    return [[SessionWindowIOS alloc] initWithSessions:sessions selectedIndex:1];
   }
 
   web::TestWebThreadBundle thread_bundle_;
diff --git a/ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm b/ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm
index c908a01..a22a790 100644
--- a/ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/chrome_signin_view_controller.mm
@@ -199,7 +199,6 @@
   [_secondaryButton removeTarget:self
                           action:@selector(onSecondaryButtonPressed:)
                 forControlEvents:UIControlEventTouchDown];
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
 }
 
 - (void)cancel {
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index 81ba938..ecdbe3a 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -122,6 +122,7 @@
 #include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
 #import "ios/chrome/browser/ui/browser_container_view.h"
 #import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/bubble/bubble_util.h"
 #import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
 #import "ios/chrome/browser/ui/chrome_web_view_factory.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
@@ -191,6 +192,7 @@
 #import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
 #include "ios/chrome/browser/ui/toolbar/legacy_toolbar_coordinator.h"
 #import "ios/chrome/browser/ui/toolbar/legacy_toolbar_ui_updater.h"
+#import "ios/chrome/browser/ui/toolbar/public/toolbar_controller_base_feature.h"
 #include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
 #include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
 #import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
@@ -1472,7 +1474,6 @@
   _bookmarkModelBridge.reset();
   [_model removeObserver:self];
   [[UpgradeCenter sharedInstance] unregisterClient:self];
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
   [_toolbarCoordinator setToolbarDelegate:nil];
   if (_voiceSearchController)
     _voiceSearchController->SetDelegate(nil);
@@ -2085,6 +2086,8 @@
     AddNamedGuide(kOmniboxGuide, self.view);
     AddNamedGuide(kBackButtonGuide, self.view);
     AddNamedGuide(kForwardButtonGuide, self.view);
+    AddNamedGuide(kToolsMenuGuide, self.view);
+    AddNamedGuide(kTabSwitcherGuide, self.view);
   }
   minY += CGRectGetHeight(toolbarFrame);
   if (initialLayout)
@@ -2428,10 +2431,19 @@
     tabSwitcherAnchor = [self.tabStripCoordinator
         anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
   } else {
-    DCHECK([_toolbarCoordinator
-        respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
-    tabSwitcherAnchor = [_toolbarCoordinator
-        anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
+    if (base::FeatureList::IsEnabled(kCleanToolbar)) {
+      UILayoutGuide* guide = FindNamedGuide(kTabSwitcherGuide, self.view);
+      CGPoint anchorPoint =
+          bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
+      tabSwitcherAnchor =
+          [guide.owningView convertPoint:anchorPoint
+                                  toView:guide.owningView.window];
+    } else {
+      DCHECK([_toolbarCoordinator
+          respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
+      tabSwitcherAnchor = [_toolbarCoordinator
+          anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
+    }
   }
 
   // If the feature engagement tracker does not consider it valid to display
@@ -2489,8 +2501,17 @@
 
   NSString* text = l10n_util::GetNSStringWithFixup(
       IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
-  CGPoint toolsButtonAnchor = [_toolbarCoordinator
-      anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
+  CGPoint toolsButtonAnchor;
+  if (base::FeatureList::IsEnabled(kCleanToolbar)) {
+    UILayoutGuide* guide = FindNamedGuide(kToolsMenuGuide, self.view);
+    CGPoint anchorPoint =
+        bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
+    toolsButtonAnchor = [guide.owningView convertPoint:anchorPoint
+                                                toView:guide.owningView.window];
+  } else {
+    toolsButtonAnchor = [_toolbarCoordinator
+        anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
+  }
 
   // If the feature engagement tracker does not consider it valid to display
   // the incognito tab tip, then end early to prevent the potential reassignment
@@ -2680,8 +2701,8 @@
     NSString* contentType = base::SysUTF8ToNSString(postData->first);
     NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
                                   length:postData->second.length()];
-    params.post_data.reset(data);
-    params.extra_headers.reset(@{ @"Content-Type" : contentType });
+    params.post_data = data;
+    params.extra_headers = @{@"Content-Type" : contentType};
   }
 
   if (tabAddedCompletion) {
diff --git a/ios/chrome/browser/ui/bubble/bubble_view_anchor_point_provider.h b/ios/chrome/browser/ui/bubble/bubble_view_anchor_point_provider.h
index 31dd9b2a..17e338a3b 100644
--- a/ios/chrome/browser/ui/bubble/bubble_view_anchor_point_provider.h
+++ b/ios/chrome/browser/ui/bubble/bubble_view_anchor_point_provider.h
@@ -14,10 +14,13 @@
 @optional
 // Returns either the top-middle or bottom-middle of the tab switcher button
 // based on |direction|. Point is in window-coordinates.
+// TODO(crbug.com/788705): This method can probably becomes required if it is
+// the only one. It would allow to remove the DCHECK.
 - (CGPoint)anchorPointForTabSwitcherButton:(BubbleArrowDirection)direction;
 
 // Returns either the top-middle or bottom-middle of the tools menu button
 // based on |direction|. Point is in window-coordinates.
+// TODO(crbug.com/788705): Remove this methods during old toolbar's cleanup.
 - (CGPoint)anchorPointForToolsMenuButton:(BubbleArrowDirection)direction;
 
 @end
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_coordinator_unittest.mm b/ios/chrome/browser/ui/context_menu/context_menu_coordinator_unittest.mm
index 2b58812..bd07942 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/context_menu/context_menu_coordinator_unittest.mm
@@ -42,7 +42,7 @@
 TEST_F(ContextMenuCoordinatorTest, ValidateIsVisible) {
   web::ContextMenuParams params;
   params.location = CGPointZero;
-  params.view.reset([view_controller_ view]);
+  params.view = [view_controller_ view];
   menu_coordinator_ = [[ContextMenuCoordinator alloc]
       initWithBaseViewController:view_controller_
                           params:params];
@@ -55,7 +55,7 @@
 TEST_F(ContextMenuCoordinatorTest, ValidateDismissalOnStop) {
   web::ContextMenuParams params;
   params.location = CGPointZero;
-  params.view.reset([view_controller_ view]);
+  params.view = [view_controller_ view];
   menu_coordinator_ = [[ContextMenuCoordinator alloc]
       initWithBaseViewController:view_controller_
                           params:params];
@@ -70,7 +70,7 @@
 TEST_F(ContextMenuCoordinatorTest, ValidateActions) {
   web::ContextMenuParams params;
   params.location = CGPointZero;
-  params.view.reset([view_controller_ view]);
+  params.view = [view_controller_ view];
   menu_coordinator_ = [[ContextMenuCoordinator alloc]
       initWithBaseViewController:view_controller_
                           params:params];
@@ -105,7 +105,7 @@
 TEST_F(ContextMenuCoordinatorTest, CancelButtonExists) {
   web::ContextMenuParams params;
   params.location = CGPointZero;
-  params.view.reset([view_controller_ view]);
+  params.view = [view_controller_ view];
   menu_coordinator_ = [[ContextMenuCoordinator alloc]
       initWithBaseViewController:view_controller_
                           params:params];
@@ -130,8 +130,8 @@
 
   web::ContextMenuParams params;
   params.location = location;
-  params.menu_title.reset(title);
-  params.view.reset([view_controller_ view]);
+  params.menu_title = title;
+  params.view = [view_controller_ view];
   menu_coordinator_ = [[ContextMenuCoordinator alloc]
       initWithBaseViewController:view_controller_
                           params:params];
diff --git a/ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.mm b/ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.mm
index 118a1345..44a6e37 100644
--- a/ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.mm
+++ b/ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.mm
@@ -322,7 +322,6 @@
 }
 
 - (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
   scopedObserver_.reset();
   observerBridge_.reset();
 }
diff --git a/ios/chrome/browser/ui/history/history_collection_view_controller.mm b/ios/chrome/browser/ui/history/history_collection_view_controller.mm
index 7219ca8..30e489cf 100644
--- a/ios/chrome/browser/ui/history/history_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/history/history_collection_view_controller.mm
@@ -821,10 +821,10 @@
   __weak HistoryCollectionViewController* weakSelf = self;
   web::ContextMenuParams params;
   params.location = touchLocation;
-  params.view.reset(self.collectionView);
+  params.view = self.collectionView;
   NSString* menuTitle =
       base::SysUTF16ToNSString(url_formatter::FormatUrl(entry.URL));
-  params.menu_title.reset([menuTitle copy]);
+  params.menu_title = [menuTitle copy];
 
   // Present sheet/popover using controller that is added to view hierarchy.
   // TODO(crbug.com/754642): Remove TopPresentedViewController().
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
index b2f9f7c..d2cee67 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
@@ -340,7 +340,6 @@
   [self.homePanel setDelegate:nil];
   [_bookmarkController setDelegate:nil];
   [_openTabsCoordinator setDelegate:nil];
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
 }
 
 #pragma mark - CRWNativeContent
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
index 8f44bcb..0bcb1586 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
@@ -632,7 +632,7 @@
     // Get view coordinates in local space.
     CGPoint viewCoordinate = [longPressGesture locationInView:self.tableView];
     params.location = viewCoordinate;
-    params.view.reset(self.tableView);
+    params.view = self.tableView;
 
     // Present sheet/popover using controller that is added to view hierarchy.
     // TODO(crbug.com/754642): Remove TopPresentedViewController().
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm b/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm
index 508e0f0..0981f4d8 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_popup_view_controller.mm
@@ -86,7 +86,6 @@
 
 - (void)dealloc {
   self.tableView.delegate = nil;
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
 }
 
 - (UIScrollView*)scrollView {
diff --git a/ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm b/ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm
index 56ef9a8..2878937 100644
--- a/ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/accounts_collection_view_controller.mm
@@ -159,10 +159,6 @@
   return self;
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 - (void)stopBrowserStateServiceObservers {
   _tokenServiceObserver.reset();
   _syncObserver.reset();
diff --git a/ios/chrome/browser/ui/settings/autofill_edit_collection_view_controller.mm b/ios/chrome/browser/ui/settings/autofill_edit_collection_view_controller.mm
index 712dc97a..fb72c0d 100644
--- a/ios/chrome/browser/ui/settings/autofill_edit_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/autofill_edit_collection_view_controller.mm
@@ -73,10 +73,6 @@
               object:nil];
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 #pragma mark - SettingsRootCollectionViewController
 
 - (BOOL)shouldShowEditButton {
diff --git a/ios/chrome/browser/ui/settings/password_details_collection_view_controller.mm b/ios/chrome/browser/ui/settings/password_details_collection_view_controller.mm
index 5ee7676..393ba663 100644
--- a/ios/chrome/browser/ui/settings/password_details_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password_details_collection_view_controller.mm
@@ -244,10 +244,6 @@
       toSectionWithIdentifier:SectionIdentifierDelete];
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 #pragma mark - Items
 
 - (CollectionViewItem*)siteCopyButtonItem {
diff --git a/ios/chrome/browser/ui/toolbar/clean/BUILD.gn b/ios/chrome/browser/ui/toolbar/clean/BUILD.gn
index 0e1bbe8..b539d5fd 100644
--- a/ios/chrome/browser/ui/toolbar/clean/BUILD.gn
+++ b/ios/chrome/browser/ui/toolbar/clean/BUILD.gn
@@ -73,7 +73,6 @@
     "//ios/chrome/app/theme",
     "//ios/chrome/browser/ui",
     "//ios/chrome/browser/ui/activity_services/requirements",
-    "//ios/chrome/browser/ui/bubble",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/fullscreen:new_fullscreen_ui",
     "//ios/chrome/browser/ui/history_popup/requirements",
diff --git a/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.h b/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.h
index 5a78180..b919e893 100644
--- a/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.h
+++ b/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.h
@@ -14,7 +14,6 @@
 
 @protocol ActivityServicePositioner;
 @protocol ApplicationCommands;
-@protocol BubbleViewAnchorPointProvider;
 @protocol BrowserCommands;
 @class ToolbarButtonUpdater;
 @protocol ToolbarCoordinatorDelegate;
@@ -50,8 +49,6 @@
 
 // Returns the ActivityServicePositioner for this toolbar.
 - (id<ActivityServicePositioner>)activityServicePositioner;
-// Returns the BubbleViewAnchorPointProvider for this toolbar.
-- (id<BubbleViewAnchorPointProvider>)bubbleAnchorPointProvider;
 
 // Start this coordinator.
 - (void)start;
diff --git a/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.mm b/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.mm
index 3c85536..b1e542e 100644
--- a/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.mm
+++ b/ios/chrome/browser/ui/toolbar/clean/toolbar_coordinator.mm
@@ -207,10 +207,6 @@
   return self.toolbarViewController;
 }
 
-- (id<BubbleViewAnchorPointProvider>)bubbleAnchorPointProvider {
-  return self.toolbarViewController;
-}
-
 - (void)updateOmniboxState {
   _locationBar->SetShouldShowHintText(
       [self.delegate toolbarModelIOS]->ShouldDisplayHintText());
diff --git a/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.h b/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.h
index 5f4c0913..6590e24 100644
--- a/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.h
+++ b/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.h
@@ -8,7 +8,6 @@
 #import <UIKit/UIKit.h>
 
 #import "ios/chrome/browser/ui/activity_services/requirements/activity_service_positioner.h"
-#import "ios/chrome/browser/ui/bubble/bubble_view_anchor_point_provider.h"
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h"
 #import "ios/chrome/browser/ui/toolbar/clean/toolbar_consumer.h"
 
@@ -23,7 +22,6 @@
 // controls and/or labels.
 @interface ToolbarViewController
     : UIViewController<ActivityServicePositioner,
-                       BubbleViewAnchorPointProvider,
                        FullscreenUIElement,
                        ToolbarConsumer>
 
diff --git a/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.mm b/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.mm
index 78f9864..81e3d0a 100644
--- a/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.mm
+++ b/ios/chrome/browser/ui/toolbar/clean/toolbar_view_controller.mm
@@ -6,7 +6,6 @@
 
 #import "base/mac/foundation_util.h"
 #include "base/metrics/user_metrics.h"
-#import "ios/chrome/browser/ui/bubble/bubble_util.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/history_popup_commands.h"
@@ -424,13 +423,15 @@
 }
 
 - (void)didMoveToParentViewController:(UIViewController*)parent {
-  UILayoutGuide* omniboxPopupGuide = FindNamedGuide(kOmniboxGuide, self.view);
-  AddSameConstraints(self.view.locationBarContainer, omniboxPopupGuide);
-  UILayoutGuide* backButtonGuide = FindNamedGuide(kBackButtonGuide, self.view);
-  AddSameConstraints(self.view.backButton.imageView, backButtonGuide);
-  UILayoutGuide* forwardButtonGuide =
-      FindNamedGuide(kForwardButtonGuide, self.view);
-  AddSameConstraints(self.view.forwardButton.imageView, forwardButtonGuide);
+  ConstrainNamedGuideToView(kOmniboxGuide, self.view.locationBarContainer);
+  ConstrainNamedGuideToView(kBackButtonGuide, self.view.backButton.imageView);
+  ConstrainNamedGuideToView(kForwardButtonGuide,
+                            self.view.forwardButton.imageView);
+  ConstrainNamedGuideToView(kToolsMenuGuide, self.view.toolsMenuButton);
+  if (!IsIPadIdiom()) {
+    ConstrainNamedGuideToView(kTabSwitcherGuide,
+                              self.view.tabSwitchStripButton.imageView);
+  }
 }
 
 #pragma mark - Trait Collection Changes
@@ -551,24 +552,6 @@
   return self.view.shareButton;
 }
 
-#pragma mark - BubbleViewAnchorPointProvider
-
-- (CGPoint)anchorPointForTabSwitcherButton:(BubbleArrowDirection)direction {
-  CGPoint anchorPoint = bubble_util::AnchorPoint(
-      self.view.tabSwitchStripButton.imageView.frame, direction);
-  return [self.view.tabSwitchStripButton.imageView.superview
-      convertPoint:anchorPoint
-            toView:self.view.tabSwitchStripButton.imageView.window];
-}
-
-- (CGPoint)anchorPointForToolsMenuButton:(BubbleArrowDirection)direction {
-  CGPoint anchorPoint =
-      bubble_util::AnchorPoint(self.view.toolsMenuButton.frame, direction);
-  return [self.view.toolsMenuButton.superview
-      convertPoint:anchorPoint
-            toView:self.view.toolsMenuButton.window];
-}
-
 #pragma mark - FullscreenUIElement
 
 - (void)updateForFullscreenProgress:(CGFloat)progress {
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_adapter.mm b/ios/chrome/browser/ui/toolbar/toolbar_adapter.mm
index 4caa045..5af08ab 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_adapter.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_adapter.mm
@@ -56,7 +56,7 @@
 #pragma mark - Abstract WebToolbar
 
 - (void)browserStateDestroyed {
-  return;
+  [self.toolbarCoordinator stop];
 }
 
 - (void)updateToolbarState {
@@ -174,13 +174,13 @@
 #pragma mark - BubbleViewAnchorPointProvider
 
 - (CGPoint)anchorPointForTabSwitcherButton:(BubbleArrowDirection)direction {
-  return [[self.toolbarCoordinator bubbleAnchorPointProvider]
-      anchorPointForTabSwitcherButton:direction];
+  // No-op. The Clean Toolbar uses named layout guides.
+  return CGPointZero;
 }
 
 - (CGPoint)anchorPointForToolsMenuButton:(BubbleArrowDirection)direction {
-  return [[self.toolbarCoordinator bubbleAnchorPointProvider]
-      anchorPointForToolsMenuButton:direction];
+  // No-op. The Clean Toolbar uses named layout guides.
+  return CGPointZero;
 }
 
 #pragma mark - FullscreenUIElement
diff --git a/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm b/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm
index f72d79a..f1011d4e 100644
--- a/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm
+++ b/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm
@@ -568,10 +568,6 @@
   return self;
 }
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 - (UIViewController*)viewController {
   return self;
 }
diff --git a/ios/chrome/browser/ui/util/BUILD.gn b/ios/chrome/browser/ui/util/BUILD.gn
index 8b46d82d..b12b95b 100644
--- a/ios/chrome/browser/ui/util/BUILD.gn
+++ b/ios/chrome/browser/ui/util/BUILD.gn
@@ -38,6 +38,7 @@
     "unicode_util.mm",
   ]
   deps = [
+    ":constraints_ui",
     "//base",
     "//base:i18n",
     "//ios/chrome/browser",
diff --git a/ios/chrome/browser/ui/util/named_guide.h b/ios/chrome/browser/ui/util/named_guide.h
index 67316d9..e911e03 100644
--- a/ios/chrome/browser/ui/util/named_guide.h
+++ b/ios/chrome/browser/ui/util/named_guide.h
@@ -19,6 +19,11 @@
 extern GuideName* const kBackButtonGuide;
 // A guide that is constrained to match the frame of the forward button's image.
 extern GuideName* const kForwardButtonGuide;
+// A guide that is constrained to match the frame of the TabSwitcher button's
+// image.
+extern GuideName* const kTabSwitcherGuide;
+// A guide that is constrained to match the frame of the ToolsMenu button.
+extern GuideName* const kToolsMenuGuide;
 
 //
 //////////////////////////////////////////
@@ -32,4 +37,8 @@
 // with the same name as an existing guide.  Returns the newly-created guide.
 UILayoutGuide* AddNamedGuide(GuideName* name, UIView* view);
 
+// Adds constraints such as the layoutGuide with |guideName| matches the |view|.
+// The layout guide has to be owned by |view| or one of its superview.
+void ConstrainNamedGuideToView(GuideName* guideName, UIView* view);
+
 #endif  // IOS_CHROME_BROWSER_UI_UTIL_NAMED_GUIDE_H_
diff --git a/ios/chrome/browser/ui/util/named_guide.mm b/ios/chrome/browser/ui/util/named_guide.mm
index 5a3107a..fab95be0 100644
--- a/ios/chrome/browser/ui/util/named_guide.mm
+++ b/ios/chrome/browser/ui/util/named_guide.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/util/named_guide.h"
 
 #include "base/logging.h"
+#import "ios/chrome/browser/ui/util/constraints_ui_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -14,6 +15,8 @@
 GuideName* const kOmniboxGuide = @"kOmniboxGuide";
 GuideName* const kBackButtonGuide = @"kBackButtonGuide";
 GuideName* const kForwardButtonGuide = @"kForwardButtonGuide";
+GuideName* const kTabSwitcherGuide = @"kTabSwitcherGuide";
+GuideName* const kToolsMenuGuide = @"kToolsMenuGuide";
 
 UILayoutGuide* FindNamedGuide(GuideName* name, UIView* view) {
   while (view) {
@@ -33,3 +36,8 @@
   [view addLayoutGuide:guide];
   return guide;
 }
+
+void ConstrainNamedGuideToView(GuideName* guideName, UIView* view) {
+  UILayoutGuide* layoutGuide = FindNamedGuide(guideName, view);
+  AddSameConstraints(view, layoutGuide);
+}
diff --git a/ios/chrome/browser/ui/voice/text_to_speech_player.mm b/ios/chrome/browser/ui/voice/text_to_speech_player.mm
index e4d23c1..1dc2544 100644
--- a/ios/chrome/browser/ui/voice/text_to_speech_player.mm
+++ b/ios/chrome/browser/ui/voice/text_to_speech_player.mm
@@ -45,7 +45,6 @@
 }
 
 - (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
   [self cancelPlayback];
 }
 
diff --git a/ios/chrome/browser/ui/voice/text_to_speech_player_unittest.mm b/ios/chrome/browser/ui/voice/text_to_speech_player_unittest.mm
index 8f6f203..9ee7cd25 100644
--- a/ios/chrome/browser/ui/voice/text_to_speech_player_unittest.mm
+++ b/ios/chrome/browser/ui/voice/text_to_speech_player_unittest.mm
@@ -46,10 +46,6 @@
 @synthesize willStartNotificationReceived = _willStartNotificationReceived;
 @synthesize didStopNotificationReceived = _didStopNotificationReceived;
 
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
 - (void)setPlayer:(TextToSpeechPlayer*)player {
   NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
   [defaultCenter removeObserver:self];
diff --git a/ios/web/navigation/navigation_item_impl.mm b/ios/web/navigation/navigation_item_impl.mm
index e273e3da..73a7256 100644
--- a/ios/web/navigation/navigation_item_impl.mm
+++ b/ios/web/navigation/navigation_item_impl.mm
@@ -66,7 +66,7 @@
       ssl_(item.ssl_),
       timestamp_(item.timestamp_),
       user_agent_type_(item.user_agent_type_),
-      http_request_headers_([item.http_request_headers_ copy]),
+      http_request_headers_([item.http_request_headers_ mutableCopy]),
       serialized_state_object_([item.serialized_state_object_ copy]),
       is_created_from_push_state_(item.is_created_from_push_state_),
       has_state_been_replaced_(item.has_state_been_replaced_),
diff --git a/ios/web/navigation/navigation_item_impl_unittest.mm b/ios/web/navigation/navigation_item_impl_unittest.mm
index 401f8cd..7f9964fe 100644
--- a/ios/web/navigation/navigation_item_impl_unittest.mm
+++ b/ios/web/navigation/navigation_item_impl_unittest.mm
@@ -90,6 +90,9 @@
   EXPECT_NSEQ([postData0 dataUsingEncoding:NSUTF8StringEncoding],
               copy.GetPostData());
   EXPECT_NSEQ(state0, copy.GetSerializedStateObject());
+
+  // Ensure that HTTP headers are still mutable after the copying.
+  copy.AddHttpRequestHeaders(@{});
 }
 
 // Tests whether |NavigationItem::AddHttpRequestHeaders()| adds the passed
diff --git a/ios/web/navigation/navigation_manager_impl.mm b/ios/web/navigation/navigation_manager_impl.mm
index 53c7124..8b0eafa7 100644
--- a/ios/web/navigation/navigation_manager_impl.mm
+++ b/ios/web/navigation/navigation_manager_impl.mm
@@ -40,8 +40,8 @@
   is_renderer_initiated = other.is_renderer_initiated;
   transition_type = other.transition_type;
   user_agent_override_option = other.user_agent_override_option;
-  extra_headers.reset([other.extra_headers copy]);
-  post_data.reset([other.post_data copy]);
+  extra_headers = [other.extra_headers copy];
+  post_data = [other.post_data copy];
 
   return *this;
 }
diff --git a/ios/web/navigation/navigation_manager_impl_unittest.mm b/ios/web/navigation/navigation_manager_impl_unittest.mm
index c7d39974..a13af61b 100644
--- a/ios/web/navigation/navigation_manager_impl_unittest.mm
+++ b/ios/web/navigation/navigation_manager_impl_unittest.mm
@@ -2121,8 +2121,8 @@
 TEST_P(NavigationManagerTest, LoadURLWithParamsWithExtraHeadersAndPostData) {
   NavigationManager::WebLoadParams params(GURL("http://www.url.com/0"));
   params.transition_type = ui::PAGE_TRANSITION_TYPED;
-  params.extra_headers.reset(@{@"Content-Type" : @"text/plain"});
-  params.post_data.reset([NSData data]);
+  params.extra_headers = @{@"Content-Type" : @"text/plain"};
+  params.post_data = [NSData data];
 
   EXPECT_CALL(navigation_manager_delegate(), RecordPageStateInNavigationItem())
       .Times(1);
diff --git a/ios/web/public/navigation_manager.h b/ios/web/public/navigation_manager.h
index 73dc66f0..af301944 100644
--- a/ios/web/public/navigation_manager.h
+++ b/ios/web/public/navigation_manager.h
@@ -7,7 +7,6 @@
 
 #include <stddef.h>
 
-#import "base/mac/scoped_nsobject.h"
 #include "ios/web/public/browser_url_rewriter.h"
 #include "ios/web/public/navigation_item_list.h"
 #include "ios/web/public/referrer.h"
@@ -65,11 +64,11 @@
     bool is_renderer_initiated;
 
     // Any extra HTTP headers to add to the load.
-    base::scoped_nsobject<NSDictionary> extra_headers;
+    NSDictionary* extra_headers;
 
     // Any post data to send with the load. When setting this, you should
     // generally set a Content-Type header as well.
-    base::scoped_nsobject<NSData> post_data;
+    NSData* post_data;
 
     // Create a new WebLoadParams with the given URL and defaults for all other
     // parameters.
diff --git a/ios/web/public/test/web_js_test.h b/ios/web/public/test/web_js_test.h
index 72e023a..481c320 100644
--- a/ios/web/public/test/web_js_test.h
+++ b/ios/web/public/test/web_js_test.h
@@ -8,7 +8,6 @@
 #import <Foundation/Foundation.h>
 
 #import "base/mac/bundle_locations.h"
-#import "base/mac/scoped_nsobject.h"
 #import "testing/gtest_mac.h"
 
 namespace web {
@@ -53,7 +52,7 @@
   // Injects JavaScript at |java_script_paths_|.
   void Inject();
 
-  base::scoped_nsobject<NSArray> java_script_paths_;
+  NSArray* java_script_paths_;
 };
 
 template <class WebTestT>
@@ -61,7 +60,7 @@
   // Main web injection should have occurred.
   ASSERT_NSEQ(@"object", WebTestT::ExecuteJavaScript(@"typeof __gCrWeb"));
 
-  for (NSString* java_script_path in java_script_paths_.get()) {
+  for (NSString* java_script_path in java_script_paths_) {
     NSString* path =
         [base::mac::FrameworkBundle() pathForResource:java_script_path
                                                ofType:@"js"];
@@ -76,8 +75,8 @@
 id WebJsTest<WebTestT>::ExecuteJavaScriptWithFormat(NSString* format, ...) {
   va_list args;
   va_start(args, format);
-  base::scoped_nsobject<NSString> java_script(
-      [[NSString alloc] initWithFormat:format arguments:args]);
+  NSString* java_script =
+      [[NSString alloc] initWithFormat:format arguments:args];
   va_end(args);
 
   return WebTestT::ExecuteJavaScript(java_script);
diff --git a/ios/web/public/web_state/context_menu_params.h b/ios/web/public/web_state/context_menu_params.h
index 925fa300..79e6e59 100644
--- a/ios/web/public/web_state/context_menu_params.h
+++ b/ios/web/public/web_state/context_menu_params.h
@@ -6,7 +6,6 @@
 
 #import <UIKit/UIKit.h>
 
-#import "base/mac/scoped_nsobject.h"
 #include "base/strings/string16.h"
 #include "ios/web/public/referrer.h"
 #include "url/gurl.h"
@@ -21,7 +20,7 @@
   ~ContextMenuParams();
 
   // The title of the menu.
-  base::scoped_nsobject<NSString> menu_title;
+  NSString* menu_title;
 
   // The URL of the link that encloses the node the context menu was invoked on.
   GURL link_url;
@@ -34,14 +33,14 @@
   web::ReferrerPolicy referrer_policy;
 
   // The view in which to present the menu.
-  base::scoped_nsobject<UIView> view;
+  UIView* view;
 
   // The location in |view| to present the menu.
   CGPoint location;
 
   // The text associated with the link. It is either nil or nonempty (it can not
   // be empty).
-  base::scoped_nsobject<NSString> link_text;
+  NSString* link_text;
 };
 
 }  // namespace web
diff --git a/ios/web/web_state/context_menu_params_utils.mm b/ios/web/web_state/context_menu_params_utils.mm
index 2e3074b7..de1eff3 100644
--- a/ios/web/web_state/context_menu_params_utils.mm
+++ b/ios/web/web_state/context_menu_params_utils.mm
@@ -41,7 +41,7 @@
   if (titleAttribute)
     title = titleAttribute;
   if (title) {
-    params.menu_title.reset([title copy]);
+    params.menu_title = [title copy];
   }
   NSString* referrerPolicy = element[kContextMenuElementReferrerPolicy];
   if (referrerPolicy) {
@@ -50,7 +50,7 @@
   }
   NSString* innerText = element[kContextMenuElementInnerText];
   if ([innerText length] > 0) {
-    params.link_text.reset([innerText copy]);
+    params.link_text = [innerText copy];
   }
   return params;
 }
diff --git a/ios/web/web_state/context_menu_params_utils_unittest.mm b/ios/web/web_state/context_menu_params_utils_unittest.mm
index fefd076..dcfeb95 100644
--- a/ios/web/web_state/context_menu_params_utils_unittest.mm
+++ b/ios/web/web_state/context_menu_params_utils_unittest.mm
@@ -37,13 +37,13 @@
 // Tests the empty contructor.
 TEST_F(ContextMenuParamsUtilsTest, EmptyParams) {
   web::ContextMenuParams params;
-  EXPECT_EQ(params.menu_title.get(), nil);
+  EXPECT_EQ(params.menu_title, nil);
   EXPECT_FALSE(params.link_url.is_valid());
   EXPECT_FALSE(params.src_url.is_valid());
   EXPECT_EQ(params.referrer_policy, web::ReferrerPolicyDefault);
-  EXPECT_EQ(params.view.get(), nil);
+  EXPECT_EQ(params.view, nil);
   EXPECT_TRUE(CGPointEqualToPoint(params.location, CGPointZero));
-  EXPECT_EQ(params.link_text.get(), nil);
+  EXPECT_EQ(params.link_text, nil);
 }
 
 // Tests the the parsing of the element NSDictionary.
@@ -56,14 +56,14 @@
     kContextMenuElementInnerText : @(kLinkText),
   });
 
-  EXPECT_NSEQ(params.menu_title.get(), @(kTitle));
+  EXPECT_NSEQ(params.menu_title, @(kTitle));
   EXPECT_EQ(params.link_url, GURL(kLinkUrl));
   EXPECT_EQ(params.src_url, GURL(kSrcUrl));
-  EXPECT_NSEQ(params.link_text.get(), @(kLinkText));
+  EXPECT_NSEQ(params.link_text, @(kLinkText));
   EXPECT_EQ(params.referrer_policy,
             web::ReferrerPolicyFromString(kReferrerPolicy));
 
-  EXPECT_EQ(params.view.get(), nil);
+  EXPECT_EQ(params.view, nil);
   EXPECT_TRUE(CGPointEqualToPoint(params.location, CGPointZero));
 }
 
@@ -75,7 +75,7 @@
   base::string16 urlText = url_formatter::FormatUrl(GURL(kLinkUrl));
   NSString* title = base::SysUTF16ToNSString(urlText);
 
-  EXPECT_NSEQ(params.menu_title.get(), title);
+  EXPECT_NSEQ(params.menu_title, title);
 }
 
 // Tests title is set to "JavaScript" if there is no title and "href" links to
@@ -84,7 +84,7 @@
   web::ContextMenuParams params = web::ContextMenuParamsFromElementDictionary(@{
     kContextMenuElementHyperlink : @(kJavaScriptLinkUrl),
   });
-  EXPECT_NSEQ(params.menu_title.get(), @"JavaScript");
+  EXPECT_NSEQ(params.menu_title, @"JavaScript");
 }
 
 // Tests title is set to |src_url| if there is no title.
@@ -93,7 +93,7 @@
     kContextMenuElementSource : @(kSrcUrl),
   });
   EXPECT_EQ(params.src_url, GURL(kSrcUrl));
-  EXPECT_NSEQ(params.menu_title.get(), @(kSrcUrl));
+  EXPECT_NSEQ(params.menu_title, @(kSrcUrl));
 }
 
 // Tests title is set to nil if there is no title and src is a data URL.
@@ -102,7 +102,7 @@
     kContextMenuElementSource : @(kDataUrl),
   });
   EXPECT_EQ(params.src_url, GURL(kDataUrl));
-  EXPECT_NSEQ(params.menu_title.get(), nil);
+  EXPECT_NSEQ(params.menu_title, nil);
 }
 
 }  // namespace web
diff --git a/ios/web/web_state/navigation_and_load_callbacks_inttest.mm b/ios/web/web_state/navigation_and_load_callbacks_inttest.mm
index 59cd9a2..a2ff79c6 100644
--- a/ios/web/web_state/navigation_and_load_callbacks_inttest.mm
+++ b/ios/web/web_state/navigation_and_load_callbacks_inttest.mm
@@ -727,8 +727,8 @@
 
   // Load request using POST HTTP method.
   web::NavigationManager::WebLoadParams params(url);
-  params.post_data.reset([@"foo" dataUsingEncoding:NSUTF8StringEncoding]);
-  params.extra_headers.reset(@{@"Content-Type" : @"text/html"});
+  params.post_data = [@"foo" dataUsingEncoding:NSUTF8StringEncoding];
+  params.extra_headers = @{@"Content-Type" : @"text/html"};
   LoadWithParams(params);
   ASSERT_TRUE(WaitForWebViewContainingText(web_state(), kTestPageText));
 }
diff --git a/ios/web/web_state/ui/crw_context_menu_controller.mm b/ios/web/web_state/ui/crw_context_menu_controller.mm
index a41b263..4f95ead5 100644
--- a/ios/web/web_state/ui/crw_context_menu_controller.mm
+++ b/ios/web/web_state/ui/crw_context_menu_controller.mm
@@ -255,7 +255,7 @@
 - (void)showContextMenu {
   web::ContextMenuParams params =
       web::ContextMenuParamsFromElementDictionary(_DOMElementForLastTouch);
-  params.view.reset(_webView);
+  params.view = _webView;
   params.location = _locationForLastTouch;
   [_delegate webView:_webView handleContextMenu:params];
 }
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 77f3962..88a5c1a 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -1870,8 +1870,8 @@
       // the transient item's URL (as on other platforms).
       NavigationManager::WebLoadParams reloadParams(transientItem->GetURL());
       reloadParams.transition_type = ui::PAGE_TRANSITION_RELOAD;
-      reloadParams.extra_headers.reset(
-          [transientItem->GetHttpRequestHeaders() copy]);
+      reloadParams.extra_headers =
+          [transientItem->GetHttpRequestHeaders() copy];
       self.webState->GetNavigationManager()->LoadURLWithParams(reloadParams);
     } else {
       self.currentNavItem->SetTransitionType(
diff --git a/ios/web/web_state/web_state_delegate_bridge_unittest.mm b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
index 79745387..43338bd 100644
--- a/ios/web/web_state/web_state_delegate_bridge_unittest.mm
+++ b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
@@ -110,11 +110,11 @@
 TEST_F(WebStateDelegateBridgeTest, HandleContextMenu) {
   EXPECT_EQ(nil, [delegate_ contextMenuParams]);
   web::ContextMenuParams context_menu_params;
-  context_menu_params.menu_title.reset([@"Menu title" copy]);
+  context_menu_params.menu_title = [@"Menu title" copy];
   context_menu_params.link_url = GURL("http://www.url.com");
   context_menu_params.src_url = GURL("http://www.url.com/image.jpeg");
   context_menu_params.referrer_policy = web::ReferrerPolicyOrigin;
-  context_menu_params.view.reset([[UIView alloc] init]);
+  context_menu_params.view = [[UIView alloc] init];
   context_menu_params.location = CGPointMake(5.0, 5.0);
   bridge_->HandleContextMenu(nullptr, context_menu_params);
   web::ContextMenuParams* result_params = [delegate_ contextMenuParams];
diff --git a/ios/web/web_state/web_state_impl.h b/ios/web/web_state/web_state_impl.h
index ee4e098e..25a5085 100644
--- a/ios/web/web_state/web_state_impl.h
+++ b/ios/web/web_state/web_state_impl.h
@@ -324,7 +324,7 @@
   bool is_being_destroyed_;
 
   // The CRWWebController that backs this object.
-  base::scoped_nsobject<CRWWebController> web_controller_;
+  CRWWebController* web_controller_;
 
   // The NavigationManagerImpl that stores session info for this WebStateImpl.
   std::unique_ptr<NavigationManagerImpl> navigation_manager_;
@@ -373,7 +373,7 @@
 
   // Cached session history when web usage is disabled. It is used to restore
   // history into WKWebView when web usage is re-enabled.
-  base::scoped_nsobject<CRWSessionStorage> cached_session_storage_;
+  CRWSessionStorage* cached_session_storage_;
 
   // Favicons URLs received in OnFaviconUrlUpdated.
   // WebStateObserver:FaviconUrlUpdated must be called for same-document
diff --git a/ios/web/web_state/web_state_impl.mm b/ios/web/web_state/web_state_impl.mm
index 3e1a0f7..58d02ce8 100644
--- a/ios/web/web_state/web_state_impl.mm
+++ b/ios/web/web_state/web_state_impl.mm
@@ -89,7 +89,7 @@
   navigation_manager_->SetBrowserState(params.browser_state);
   // Send creation event and create the web controller.
   GlobalWebStateEventTracker::GetInstance()->OnWebStateCreated(this);
-  web_controller_.reset([[CRWWebController alloc] initWithWebState:this]);
+  web_controller_ = [[CRWWebController alloc] initWithWebState:this];
 
   // Restore session history last because WKBasedNavigationManagerImpl relies on
   // CRWWebController to restore history into the web view.
@@ -170,7 +170,7 @@
 
 void WebStateImpl::SetWebController(CRWWebController* web_controller) {
   [web_controller_ close];
-  web_controller_.reset(web_controller);
+  web_controller_ = web_controller;
 }
 
 void WebStateImpl::OnTitleChanged() {
@@ -550,11 +550,11 @@
   if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
     if (enabled) {
       if (cached_session_storage_) {
-        RestoreSessionStorage(cached_session_storage_.get());
+        RestoreSessionStorage(cached_session_storage_);
       }
-      cached_session_storage_.reset();
+      cached_session_storage_ = nil;
     } else {
-      cached_session_storage_.reset(BuildSessionStorage());
+      cached_session_storage_ = BuildSessionStorage();
     }
   }
 
diff --git a/ios/web_view/internal/cwv_web_view.mm b/ios/web_view/internal/cwv_web_view.mm
index 7d7b2c7..85ac1593 100644
--- a/ios/web_view/internal/cwv_web_view.mm
+++ b/ios/web_view/internal/cwv_web_view.mm
@@ -183,8 +183,8 @@
 
   web::NavigationManager::WebLoadParams params(net::GURLWithNSURL(request.URL));
   params.transition_type = ui::PAGE_TRANSITION_TYPED;
-  params.extra_headers.reset([request.allHTTPHeaderFields copy]);
-  params.post_data.reset([request.HTTPBody copy]);
+  params.extra_headers = [request.allHTTPHeaderFields copy];
+  params.post_data = [request.HTTPBody copy];
   _webState->GetNavigationManager()->LoadURLWithParams(params);
   [self updateCurrentURLs];
 }
diff --git a/media/cdm/ppapi/ppapi_cdm_adapter.gni b/media/cdm/ppapi/ppapi_cdm_adapter.gni
index 002c9801..63e0ddec 100644
--- a/media/cdm/ppapi/ppapi_cdm_adapter.gni
+++ b/media/cdm/ppapi/ppapi_cdm_adapter.gni
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/compiler/compiler.gni")
+
 # This template defines a CDM adapter target. Just use this as you would a
 # normal target and everything should work correctly.
 template("ppapi_cdm_adapter") {
@@ -61,6 +63,11 @@
     if (is_linux) {
       # CDM adapter depends on a CDM in component and non-component builds.
       configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
+
+      if (use_lld) {
+        # TODO(crbug.com/795158) LLD warns about libwidevinecdm.so
+        configs -= [ "//build/config/compiler:default_fatal_linker_warnings" ]
+      }
     }
 
     # TODO(jschuh) crbug.com/167187
diff --git a/media/gpu/windows/d3d11_h264_accelerator.cc b/media/gpu/windows/d3d11_h264_accelerator.cc
index 6211104..e0d97acf 100644
--- a/media/gpu/windows/d3d11_h264_accelerator.cc
+++ b/media/gpu/windows/d3d11_h264_accelerator.cc
@@ -25,19 +25,22 @@
 
 class D3D11H264Picture : public H264Picture {
  public:
-  D3D11H264Picture(D3D11PictureBuffer* picture, size_t input_buffer_id)
-      : picture(picture),
-        level_(picture->level()),
-        input_buffer_id_(input_buffer_id) {}
+  D3D11H264Picture(D3D11PictureBuffer* picture)
+      : picture(picture), level_(picture->level()) {
+    picture->set_in_picture_use(true);
+  }
 
   D3D11PictureBuffer* picture;
   size_t level_;
-  size_t input_buffer_id_;
 
  protected:
   ~D3D11H264Picture() override;
 };
 
+D3D11H264Picture::~D3D11H264Picture() {
+  picture->set_in_picture_use(false);
+}
+
 D3D11H264Accelerator::D3D11H264Accelerator(
     D3D11VideoDecoderClient* client,
     Microsoft::WRL::ComPtr<ID3D11VideoDecoder> video_decoder,
@@ -55,9 +58,7 @@
   if (!picture) {
     return nullptr;
   }
-  picture->set_in_picture_use(true);
-  return base::MakeRefCounted<D3D11H264Picture>(picture,
-                                                client_->input_buffer_id());
+  return base::MakeRefCounted<D3D11H264Picture>(picture);
 }
 
 bool D3D11H264Accelerator::SubmitFrameMetadata(
@@ -80,8 +81,10 @@
       // Hardware is busy.  We should make the call again.
       // TODO(liberato): For now, just busy wait.
       ;
+    } else if (!SUCCEEDED(hr)) {
+      LOG(ERROR) << "DecoderBeginFrame failed";
+      return false;
     } else {
-      CHECK(SUCCEEDED(hr));
       break;
     }
   }
@@ -120,20 +123,27 @@
     i++;
   }
   slice_info_.clear();
-  RetrieveBitstreamBuffer();
-  return true;
+  return RetrieveBitstreamBuffer();
 }
 
-void D3D11H264Accelerator::RetrieveBitstreamBuffer() {
+bool D3D11H264Accelerator::RetrieveBitstreamBuffer() {
+  DCHECK(!bitstream_buffer_bytes_);
+  DCHECK(!bitstream_buffer_size_);
+
   current_offset_ = 0;
   void* buffer;
   UINT buffer_size;
   HRESULT hr = video_context_->GetDecoderBuffer(
       video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_BITSTREAM, &buffer_size,
       &buffer);
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "GetDecoderBuffer (Bitstream) failed";
+    return false;
+  }
   bitstream_buffer_bytes_ = (uint8_t*)buffer;
   bitstream_buffer_size_ = buffer_size;
-  CHECK(SUCCEEDED(hr));
+
+  return true;
 }
 
 bool D3D11H264Accelerator::SubmitSlice(const H264PPS* pps,
@@ -235,12 +245,18 @@
   HRESULT hr = video_context_->GetDecoderBuffer(
       video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS,
       &buffer_size, &buffer);
-  CHECK(SUCCEEDED(hr));
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "ReleaseDecoderBuffer (PictureParams) failed";
+    return false;
+  }
 
   memcpy(buffer, &pic_param, sizeof(pic_param));
   hr = video_context_->ReleaseDecoderBuffer(
       video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS);
-  CHECK(SUCCEEDED(hr));
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "ReleaseDecoderBuffer (PictureParams) failed";
+    return false;
+  }
 
   DXVA_Qmatrix_H264 iq_matrix_buf = {};
 
@@ -269,11 +285,18 @@
       video_decoder_.Get(),
       D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX, &buffer_size,
       &buffer);
-  CHECK(SUCCEEDED(hr));
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "GetDecoderBuffer (QuantMatrix) failed";
+    return false;
+  }
   memcpy(buffer, &iq_matrix_buf, sizeof(iq_matrix_buf));
   hr = video_context_->ReleaseDecoderBuffer(
       video_decoder_.Get(),
       D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX);
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "ReleaseDecoderBuffer (QuantMatrix) failed";
+    return false;
+  }
 
   // Ideally all slices in a frame are put in the same bitstream buffer.
   // However the bitstream buffer may not fit all the data, so split on the
@@ -287,8 +310,14 @@
   while (remaining_bitstream > 0) {
     if (bitstream_buffer_size_ < remaining_bitstream &&
         slice_info_.size() > 0) {
-      SubmitSliceData();
-      RetrieveBitstreamBuffer();
+      if (!SubmitSliceData()) {
+        LOG(ERROR) << "SubmitSliceData failed";
+        return false;
+      }
+      if (!RetrieveBitstreamBuffer()) {
+        LOG(ERROR) << "RetrieveBitstreamBuffer failed";
+        return false;
+      }
     }
 
     size_t bytes_to_copy = remaining_bitstream;
@@ -331,21 +360,37 @@
   return true;
 }
 
-void D3D11H264Accelerator::SubmitSliceData() {
+bool D3D11H264Accelerator::SubmitSliceData() {
   CHECK(slice_info_.size() > 0);
   UINT buffer_size;
   void* buffer;
+
+  // TODO(liberato): Should we release the other buffers on failure?
+
   HRESULT hr = video_context_->GetDecoderBuffer(
       video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL,
       &buffer_size, &buffer);
-  CHECK(SUCCEEDED(hr));
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "GetDecoderBuffer (SliceControl) failed";
+    return false;
+  }
+
   CHECK_LE(sizeof(slice_info_[0]) * slice_info_.size(), buffer_size);
   memcpy(buffer, &slice_info_[0], sizeof(slice_info_[0]) * slice_info_.size());
   hr = video_context_->ReleaseDecoderBuffer(
       video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL);
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "ReleaseDecoderBuffer (SliceControl) failed";
+    return false;
+  }
 
   hr = video_context_->ReleaseDecoderBuffer(
       video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_BITSTREAM);
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "ReleaseDecoderBuffer (BitStream) failed";
+    return false;
+  }
+
   D3D11_VIDEO_DECODER_BUFFER_DESC buffers[4] = {};
   buffers[0].BufferType = D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS;
   buffers[0].DataOffset = 0;
@@ -366,39 +411,48 @@
   slice_info_.clear();
   bitstream_buffer_bytes_ = nullptr;
   bitstream_buffer_size_ = 0;
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "SubmitDecoderBuffers failed";
+    return false;
+  }
+
+  return true;
 }
 
 bool D3D11H264Accelerator::SubmitDecode(const scoped_refptr<H264Picture>& pic) {
-  SubmitSliceData();
+  if (!SubmitSliceData()) {
+    LOG(ERROR) << "SubmitSliceData failed";
+    return false;
+  }
 
   HRESULT hr = video_context_->DecoderEndFrame(video_decoder_.Get());
-  CHECK(SUCCEEDED(hr));
+  if (!SUCCEEDED(hr)) {
+    LOG(ERROR) << "DecoderEndFrame failed";
+    return false;
+  }
 
   return true;
 }
 
 void D3D11H264Accelerator::Reset() {
-  if (bitstream_buffer_bytes_) {
-    HRESULT hr = video_context_->ReleaseDecoderBuffer(
-        video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_BITSTREAM);
+  if (!bitstream_buffer_bytes_)
+    return;
 
-    bitstream_buffer_bytes_ = nullptr;
-    bitstream_buffer_size_ = 0;
-    current_offset_ = 0;
-    CHECK(SUCCEEDED(hr));
-  }
+  HRESULT hr = video_context_->ReleaseDecoderBuffer(
+      video_decoder_.Get(), D3D11_VIDEO_DECODER_BUFFER_BITSTREAM);
+
+  bitstream_buffer_bytes_ = nullptr;
+  bitstream_buffer_size_ = 0;
+  current_offset_ = 0;
+  CHECK(SUCCEEDED(hr));
 }
 
 bool D3D11H264Accelerator::OutputPicture(
     const scoped_refptr<H264Picture>& pic) {
   scoped_refptr<D3D11H264Picture> our_pic(
       static_cast<D3D11H264Picture*>(pic.get()));
-  client_->OutputResult(our_pic->picture, our_pic->input_buffer_id_);
+  client_->OutputResult(our_pic->picture);
   return true;
 }
 
-D3D11H264Picture::~D3D11H264Picture() {
-  picture->set_in_picture_use(false);
-}
-
 }  // namespace media
diff --git a/media/gpu/windows/d3d11_h264_accelerator.h b/media/gpu/windows/d3d11_h264_accelerator.h
index b5e664d..173ff43 100644
--- a/media/gpu/windows/d3d11_h264_accelerator.h
+++ b/media/gpu/windows/d3d11_h264_accelerator.h
@@ -28,10 +28,8 @@
 
 class D3D11VideoDecoderClient {
  public:
-  virtual size_t input_buffer_id() const = 0;
   virtual D3D11PictureBuffer* GetPicture() = 0;
-  virtual void OutputResult(D3D11PictureBuffer* picture,
-                            size_t input_buffer_id) = 0;
+  virtual void OutputResult(D3D11PictureBuffer* picture) = 0;
 };
 
 class D3D11H264Accelerator : public H264Decoder::H264Accelerator {
@@ -64,8 +62,8 @@
   bool OutputPicture(const scoped_refptr<H264Picture>& pic) override;
 
  private:
-  void SubmitSliceData();
-  void RetrieveBitstreamBuffer();
+  bool SubmitSliceData();
+  bool RetrieveBitstreamBuffer();
 
   D3D11VideoDecoderClient* client_;
 
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index 696dda0..5245254 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -11,6 +11,7 @@
 #include "media/base/video_codecs.h"
 #include "media/base/video_decoder_config.h"
 #include "media/base/video_frame.h"
+#include "media/gpu/windows/d3d11_video_decoder_impl.h"
 
 namespace {
 
@@ -46,17 +47,13 @@
 
 D3D11VideoDecoder::D3D11VideoDecoder(
     scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
-    base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb,
-    deprecated::OutputWithReleaseMailboxCB output_cb)
+    base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb)
     : impl_task_runner_(std::move(gpu_task_runner)), weak_factory_(this) {
   // We create |impl_| on the wrong thread, but we never use it here.
   // Note that the output callback will hop to our thread, post the video
   // frame, and along with a callback that will hop back to the impl thread
   // when it's released.
-  impl_ = base::MakeUnique<D3D11VideoDecoderImpl>(
-      get_stub_cb, media::BindToCurrentLoop(base::Bind(
-                       &D3D11VideoDecoder::OutputWithThreadHoppingRelease,
-                       weak_factory_.GetWeakPtr(), std::move(output_cb))));
+  impl_ = base::MakeUnique<D3D11VideoDecoderImpl>(get_stub_cb);
   impl_weak_ = impl_->GetWeakPtr();
 }
 
@@ -124,23 +121,4 @@
   return impl_->GetMaxDecodeRequests();
 }
 
-void D3D11VideoDecoder::OutputWithThreadHoppingRelease(
-    deprecated::OutputWithReleaseMailboxCB output_cb,
-    deprecated::ReleaseMailboxCB impl_thread_cb,
-    const scoped_refptr<VideoFrame>& video_frame) {
-  // Called on our thread to output a video frame.  Modify the release cb so
-  // that it jumps back to the impl thread.
-  output_cb.Run(
-      base::BindOnce(&D3D11VideoDecoder::OnMailboxReleased,
-                     weak_factory_.GetWeakPtr(), std::move(impl_thread_cb)),
-      video_frame);
-}
-
-void D3D11VideoDecoder::OnMailboxReleased(
-    deprecated::ReleaseMailboxCB impl_thread_cb,
-    const gpu::SyncToken& token) {
-  impl_task_runner_->PostTask(FROM_HERE,
-                              base::BindOnce(std::move(impl_thread_cb), token));
-}
-
 }  // namespace media
diff --git a/media/gpu/windows/d3d11_video_decoder.h b/media/gpu/windows/d3d11_video_decoder.h
index 6a68a09..1b6cbdb 100644
--- a/media/gpu/windows/d3d11_video_decoder.h
+++ b/media/gpu/windows/d3d11_video_decoder.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_GPU_D3D11_VIDEO_DECODER_H_
 #define MEDIA_GPU_D3D11_VIDEO_DECODER_H_
 
-#include <d3d11.h>
-
 #include <string>
 
 #include "base/memory/ptr_util.h"
@@ -16,11 +14,11 @@
 #include "gpu/ipc/service/command_buffer_stub.h"
 #include "media/base/video_decoder.h"
 #include "media/gpu/media_gpu_export.h"
-#include "media/gpu/windows/d3d11_video_decoder_impl.h"
-#include "media/gpu/windows/output_with_release_mailbox_cb.h"
 
 namespace media {
 
+class D3D11VideoDecoderImpl;
+
 // Thread-hopping implementation of D3D11VideoDecoder.  It's meant to run on
 // a random thread, and hop to the gpu main thread.  It does this so that it
 // can use the D3D context etc.  What should really happen is that we should
@@ -31,8 +29,7 @@
  public:
   D3D11VideoDecoder(
       scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
-      base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb,
-      deprecated::OutputWithReleaseMailboxCB output_cb);
+      base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb);
   ~D3D11VideoDecoder() override;
 
   // VideoDecoder implementation:
@@ -50,17 +47,6 @@
   int GetMaxDecodeRequests() const override;
 
  private:
-  // Call |output_cb| with a release cb that will hop back to the impl thread
-  // to run |impl_thread_cb| when |video_frame| is released.
-  void OutputWithThreadHoppingRelease(
-      deprecated::OutputWithReleaseMailboxCB output_cb,
-      deprecated::ReleaseMailboxCB impl_thread_cb,
-      const scoped_refptr<VideoFrame>& video_frame);
-
-  // ReleaseCB that's run on our thread, but posts it to the impl thread.
-  void OnMailboxReleased(deprecated::ReleaseMailboxCB impl_thread_cb,
-                         const gpu::SyncToken& token);
-
   // The implementation, which we trampoline to the impl thread.
   // This must be freed on the impl thread.
   std::unique_ptr<D3D11VideoDecoderImpl> impl_;
@@ -71,8 +57,6 @@
   // Task runner for |impl_|.  This must be the GPU main thread.
   scoped_refptr<base::SequencedTaskRunner> impl_task_runner_;
 
-  deprecated::OutputWithReleaseMailboxCB output_cb_;
-
   base::WeakPtrFactory<D3D11VideoDecoder> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(D3D11VideoDecoder);
diff --git a/media/gpu/windows/d3d11_video_decoder_impl.cc b/media/gpu/windows/d3d11_video_decoder_impl.cc
index d06a4b7..7f027069 100644
--- a/media/gpu/windows/d3d11_video_decoder_impl.cc
+++ b/media/gpu/windows/d3d11_video_decoder_impl.cc
@@ -9,6 +9,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "gpu/command_buffer/service/mailbox_manager.h"
 #include "gpu/command_buffer/service/texture_manager.h"
+#include "media/base/bind_to_current_loop.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/video_decoder_config.h"
 #include "media/base/video_frame.h"
@@ -27,11 +28,8 @@
 }  // namespace
 
 D3D11VideoDecoderImpl::D3D11VideoDecoderImpl(
-    base::Callback<gpu::CommandBufferStub*()> get_stub_cb,
-    deprecated::OutputWithReleaseMailboxCB output_cb)
-    : get_stub_cb_(get_stub_cb),
-      output_cb_(std::move(output_cb)),
-      weak_factory_(this) {}
+    base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb)
+    : get_stub_cb_(get_stub_cb), weak_factory_(this) {}
 
 D3D11VideoDecoderImpl::~D3D11VideoDecoderImpl() {
   // TODO(liberato): be sure to clear |picture_buffers_| on the main thread.
@@ -47,7 +45,7 @@
                                        bool low_delay,
                                        CdmContext* cdm_context,
                                        const InitCB& init_cb,
-                                       const OutputCB& /* output_cb */) {
+                                       const OutputCB& output_cb) {
   stub_ = get_stub_cb_.Run();
   if (!MakeContextCurrent(stub_)) {
     init_cb.Run(false);
@@ -57,6 +55,8 @@
   // stub_->AddDestructionObserver(this);
   decoder_helper_ = GLES2DecoderHelper::Create(stub_->decoder());
 
+  output_cb_ = output_cb;
+
   // The device is threadsafe.  Does GetImmediateContext create a new object?
   // If so, then we can just use it on the MCVD thread.  All we need to do is
   // to share the Texture object, somehow, which is likely trivial.  We'd have
@@ -361,14 +361,7 @@
   return nullptr;
 }
 
-size_t D3D11VideoDecoderImpl::input_buffer_id() const {
-  // NOTE: nobody uses this for anything.  it just gets returned to us with
-  // OutputResult.  It can be removed once we replace the VDA.
-  return 0;
-}
-
-void D3D11VideoDecoderImpl::OutputResult(D3D11PictureBuffer* buffer,
-                                         size_t input_buffer_id) {
+void D3D11VideoDecoderImpl::OutputResult(D3D11PictureBuffer* buffer) {
   buffer->set_in_client_use(true);
 
   // Note: The pixel format doesn't matter.
@@ -380,12 +373,10 @@
       VideoFrame::ReleaseMailboxCB(), buffer->picture_buffer().size(),
       visible_rect, natural_size, timestamp);
 
-  // TODO(liberato): The VideoFrame release callback will not be called after
-  // MojoVideoDecoderService is destructed.  See VideoFrameFactoryImpl.
-  // NOTE: i think that we drop the picture buffers, which might work okay.
-  output_cb_.Run(base::Bind(&D3D11VideoDecoderImpl::OnMailboxReleased,
-                            weak_factory_.GetWeakPtr(), buffer),
-                 frame);
+  frame->SetReleaseMailboxCB(media::BindToCurrentLoop(
+      base::BindOnce(&D3D11VideoDecoderImpl::OnMailboxReleased,
+                     weak_factory_.GetWeakPtr(), buffer)));
+  output_cb_.Run(frame);
 }
 
 void D3D11VideoDecoderImpl::OnMailboxReleased(
diff --git a/media/gpu/windows/d3d11_video_decoder_impl.h b/media/gpu/windows/d3d11_video_decoder_impl.h
index ed47044..45fe5fe0 100644
--- a/media/gpu/windows/d3d11_video_decoder_impl.h
+++ b/media/gpu/windows/d3d11_video_decoder_impl.h
@@ -27,8 +27,8 @@
 class MEDIA_GPU_EXPORT D3D11VideoDecoderImpl : public VideoDecoder,
                                                public D3D11VideoDecoderClient {
  public:
-  D3D11VideoDecoderImpl(base::Callback<gpu::CommandBufferStub*()> get_stub_cb,
-                        deprecated::OutputWithReleaseMailboxCB output_cb);
+  D3D11VideoDecoderImpl(
+      base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb);
   ~D3D11VideoDecoderImpl() override;
 
   // VideoDecoder implementation:
@@ -47,9 +47,7 @@
 
   // D3D11VideoDecoderClient implementation.
   D3D11PictureBuffer* GetPicture() override;
-  void OutputResult(D3D11PictureBuffer* buffer,
-                    size_t input_buffer_id) override;
-  size_t input_buffer_id() const override;
+  void OutputResult(D3D11PictureBuffer* buffer) override;
 
   // Return a weak ptr, since D3D11VideoDecoder constructs callbacks for us.
   base::WeakPtr<D3D11VideoDecoderImpl> GetWeakPtr();
@@ -61,7 +59,7 @@
   void OnMailboxReleased(D3D11PictureBuffer* buffer,
                          const gpu::SyncToken& sync_token);
 
-  base::Callback<gpu::CommandBufferStub*()> get_stub_cb_;
+  base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb_;
   gpu::CommandBufferStub* stub_ = nullptr;
   // A helper for creating textures. Only valid while |stub_| is valid.
   std::unique_ptr<GLES2DecoderHelper> decoder_helper_;
@@ -84,7 +82,7 @@
 
   std::vector<std::unique_ptr<D3D11PictureBuffer>> picture_buffers_;
 
-  deprecated::OutputWithReleaseMailboxCB output_cb_;
+  VideoDecoder::OutputCB output_cb_;
 
   base::WeakPtrFactory<D3D11VideoDecoderImpl> weak_factory_;
 
diff --git a/media/mojo/services/gpu_mojo_media_client.cc b/media/mojo/services/gpu_mojo_media_client.cc
index 224f3026..d41c1dc 100644
--- a/media/mojo/services/gpu_mojo_media_client.cc
+++ b/media/mojo/services/gpu_mojo_media_client.cc
@@ -30,9 +30,10 @@
 #include "services/service_manager/public/cpp/connect.h"
 #endif  // defined(OS_ANDROID)
 
-#if defined(OS_WIN)
+// OS_WIN guards are needed for cross-compiling on linux.
+#if defined(OS_WIN) && BUILDFLAG(ENABLE_D3D11_VIDEO_DECODER)
 #include "media/gpu/windows/d3d11_video_decoder.h"
-#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(ENABLE_D3D11_VIDEO_DECODER)
 
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
 #include "media/cdm/cdm_proxy.h"
@@ -64,7 +65,8 @@
 }
 #endif  // defined(OS_ANDROID)
 
-#if defined(OS_ANDROID) || BUILDFLAG(ENABLE_D3D11_VIDEO_DECODER)
+#if defined(OS_ANDROID) || \
+    (defined(OS_WIN) && BUILDFLAG(ENABLE_D3D11_VIDEO_DECODER))
 gpu::CommandBufferStub* GetCommandBufferStub(
     base::WeakPtr<MediaGpuChannelManager> media_gpu_channel_manager,
     base::UnguessableToken channel_token,
@@ -123,7 +125,7 @@
       android_overlay_factory_cb_, std::move(request_overlay_info_cb),
       base::MakeUnique<VideoFrameFactoryImpl>(gpu_task_runner_,
                                               std::move(get_stub_cb)));
-#elif BUILDFLAG(ENABLE_D3D11_VIDEO_DECODER)
+#elif defined(OS_WIN) && BUILDFLAG(ENABLE_D3D11_VIDEO_DECODER)
   return base::MakeUnique<D3D11VideoDecoder>(
       gpu_task_runner_,
       base::BindRepeating(&GetCommandBufferStub, media_gpu_channel_manager_,
diff --git a/mojo/public/cpp/system/file_data_pipe_producer.cc b/mojo/public/cpp/system/file_data_pipe_producer.cc
index 7dfdf256..020b3db4 100644
--- a/mojo/public/cpp/system/file_data_pipe_producer.cc
+++ b/mojo/public/cpp/system/file_data_pipe_producer.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <limits>
 #include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/callback.h"
@@ -60,12 +61,14 @@
       ScopedDataPipeProducerHandle producer_handle,
       scoped_refptr<base::SequencedTaskRunner> file_task_runner,
       CompletionCallback callback,
-      scoped_refptr<base::SequencedTaskRunner> callback_task_runner)
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+      std::unique_ptr<Observer> observer)
       : base::RefCountedDeleteOnSequence<FileSequenceState>(file_task_runner),
         file_task_runner_(std::move(file_task_runner)),
         callback_task_runner_(std::move(callback_task_runner)),
         producer_handle_(std::move(producer_handle)),
-        callback_(std::move(callback)) {}
+        callback_(std::move(callback)),
+        observer_(std::move(observer)) {}
 
   void Cancel() {
     base::AutoLock lock(lock_);
@@ -165,8 +168,14 @@
       if (read_size < 0) {
         read_error = base::File::GetLastFileError();
         DCHECK_NE(base::File::FILE_OK, read_error);
+        if (observer_)
+          observer_->OnBytesRead(pipe_buffer, 0u, read_error);
       } else {
         read_error = base::File::FILE_OK;
+        if (observer_) {
+          observer_->OnBytesRead(pipe_buffer, static_cast<size_t>(read_size),
+                                 base::File::FILE_OK);
+        }
       }
       producer_handle_->EndWriteData(
           read_size >= 0 ? static_cast<uint32_t>(read_size) : 0);
@@ -195,6 +204,10 @@
   }
 
   void Finish(MojoResult result) {
+    if (observer_) {
+      observer_->OnDoneReading();
+      observer_ = nullptr;
+    }
     watcher_.reset();
     callback_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback_),
@@ -215,13 +228,17 @@
   // Guards |is_cancelled_|.
   base::Lock lock_;
   bool is_cancelled_ = false;
+  std::unique_ptr<Observer> observer_;
 
   DISALLOW_COPY_AND_ASSIGN(FileSequenceState);
 };
 
 FileDataPipeProducer::FileDataPipeProducer(
-    ScopedDataPipeProducerHandle producer)
-    : producer_(std::move(producer)), weak_factory_(this) {}
+    ScopedDataPipeProducerHandle producer,
+    std::unique_ptr<Observer> observer)
+    : producer_(std::move(producer)),
+      observer_(std::move(observer)),
+      weak_factory_(this) {}
 
 FileDataPipeProducer::~FileDataPipeProducer() {
   if (file_sequence_state_)
@@ -255,7 +272,7 @@
       std::move(producer_), file_task_runner,
       base::BindOnce(&FileDataPipeProducer::OnWriteComplete,
                      weak_factory_.GetWeakPtr(), std::move(callback)),
-      base::SequencedTaskRunnerHandle::Get());
+      base::SequencedTaskRunnerHandle::Get(), std::move(observer_));
 }
 
 void FileDataPipeProducer::OnWriteComplete(
diff --git a/mojo/public/cpp/system/file_data_pipe_producer.h b/mojo/public/cpp/system/file_data_pipe_producer.h
index a8e4393..c3479f7 100644
--- a/mojo/public/cpp/system/file_data_pipe_producer.h
+++ b/mojo/public/cpp/system/file_data_pipe_producer.h
@@ -5,6 +5,8 @@
 #ifndef MOJO_PUBLIC_CPP_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_
 #define MOJO_PUBLIC_CPP_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_
 
+#include <memory>
+
 #include "base/callback_forward.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
@@ -29,8 +31,32 @@
  public:
   using CompletionCallback = base::OnceCallback<void(MojoResult result)>;
 
+  // Interface definition of an optional object that may be supplied to the
+  // FileDataPipeProducer so that the data being read from the consumer can be
+  // observed.
+  class Observer {
+   public:
+    virtual ~Observer() {}
+
+    // Called once per read attempt. |data| contains the read data (if any).
+    // |num_bytes_read| is the number of read bytes, 0 indicates EOF. Both
+    // parameters may only be used when |read_result| is base::File::FILE_OK.
+    // Can be called on any sequence.
+    virtual void OnBytesRead(const void* data,
+                             size_t num_bytes_read,
+                             base::File::Error read_result) = 0;
+
+    // Called when the FileDataPipeProducer has finished reading all data. Will
+    // be called even if there was an error opening the file or reading the
+    // data. Can be called on any sequence.
+    virtual void OnDoneReading() = 0;
+  };
+
   // Constructs a new FileDataPipeProducer which will write data to |producer|.
-  explicit FileDataPipeProducer(ScopedDataPipeProducerHandle producer);
+  // Caller may supply an optional |observer| if observation of the read file
+  // data is desired.
+  FileDataPipeProducer(ScopedDataPipeProducerHandle producer,
+                       std::unique_ptr<Observer> observer);
   ~FileDataPipeProducer();
 
   // Attempts to eventually write all of |file|'s contents to the pipe. Invokes
@@ -77,6 +103,7 @@
 
   ScopedDataPipeProducerHandle producer_;
   scoped_refptr<FileSequenceState> file_sequence_state_;
+  std::unique_ptr<Observer> observer_;
   base::WeakPtrFactory<FileDataPipeProducer> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(FileDataPipeProducer);
diff --git a/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc b/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc
index 0361542a..bd96c3f 100644
--- a/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc
+++ b/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc
@@ -138,6 +138,40 @@
   DISALLOW_COPY_AND_ASSIGN(FileDataPipeProducerTest);
 };
 
+struct DataPipeObserverData {
+  int num_read_errors = 0;
+  size_t bytes_read = 0;
+  int done_called = 0;
+};
+
+class TestObserver : public FileDataPipeProducer::Observer {
+ public:
+  explicit TestObserver(DataPipeObserverData* observer_data)
+      : observer_data_(observer_data) {}
+
+  void OnBytesRead(const void* data,
+                   size_t num_bytes_read,
+                   base::File::Error read_result) override {
+    base::AutoLock auto_lock(lock_);
+    if (read_result == base::File::FILE_OK)
+      observer_data_->bytes_read += num_bytes_read;
+    else
+      observer_data_->num_read_errors++;
+  }
+
+  void OnDoneReading() override {
+    base::AutoLock auto_lock(lock_);
+    observer_data_->done_called++;
+  }
+
+ private:
+  DataPipeObserverData* observer_data_;
+  // Observer may be called on any sequence.
+  base::Lock lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
 TEST_F(FileDataPipeProducerTest, WriteFromFile) {
   const std::string kTestStringFragment = "Hello, world!";
   constexpr size_t kNumRepetitions = 1000;
@@ -153,12 +187,18 @@
                         loop.QuitClosure());
 
   base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  DataPipeObserverData observer_data;
+  auto observer = std::make_unique<TestObserver>(&observer_data);
   WriteFromFileThenCloseWriter(
-      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle)),
+      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+                                             std::move(observer)),
       std::move(file));
   loop.Run();
 
   EXPECT_EQ(test_string, reader.data());
+  EXPECT_EQ(0, observer_data.num_read_errors);
+  EXPECT_EQ(test_string.size(), observer_data.bytes_read);
+  EXPECT_EQ(1, observer_data.done_called);
 }
 
 TEST_F(FileDataPipeProducerTest, WriteFromFilePartial) {
@@ -172,12 +212,18 @@
                         loop.QuitClosure());
 
   base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  DataPipeObserverData observer_data;
+  auto observer = std::make_unique<TestObserver>(&observer_data);
   WriteFromFileThenCloseWriter(
-      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle)),
+      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+                                             std::move(observer)),
       std::move(file), kBytesToWrite);
   loop.Run();
 
   EXPECT_EQ(kTestString.substr(0, kBytesToWrite), reader.data());
+  EXPECT_EQ(0, observer_data.num_read_errors);
+  EXPECT_EQ(kBytesToWrite, observer_data.bytes_read);
+  EXPECT_EQ(1, observer_data.done_called);
 }
 
 TEST_F(FileDataPipeProducerTest, WriteFromInvalidFile) {
@@ -186,16 +232,22 @@
 
   base::RunLoop loop;
   DataPipe pipe(kBytesToWrite);
+  DataPipeObserverData observer_data;
+  auto observer = std::make_unique<TestObserver>(&observer_data);
   DataPipeReader reader(std::move(pipe.consumer_handle), kBytesToWrite,
                         loop.QuitClosure());
 
   base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
   WriteFromFileThenCloseWriter(
-      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle)),
+      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+                                             std::move(observer)),
       std::move(file), kBytesToWrite);
   loop.Run();
 
   EXPECT_EQ(0UL, reader.data().size());
+  EXPECT_EQ(0, observer_data.num_read_errors);
+  EXPECT_EQ(0UL, observer_data.bytes_read);
+  EXPECT_EQ(1, observer_data.done_called);
 }
 
 TEST_F(FileDataPipeProducerTest, WriteFromPath) {
@@ -212,12 +264,18 @@
   DataPipeReader reader(std::move(pipe.consumer_handle), 16,
                         loop.QuitClosure());
 
+  DataPipeObserverData observer_data;
+  auto observer = std::make_unique<TestObserver>(&observer_data);
   WriteFromPathThenCloseWriter(
-      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle)),
+      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+                                             std::move(observer)),
       path);
   loop.Run();
 
   EXPECT_EQ(test_string, reader.data());
+  EXPECT_EQ(0, observer_data.num_read_errors);
+  EXPECT_EQ(test_string.size(), observer_data.bytes_read);
+  EXPECT_EQ(1, observer_data.done_called);
 }
 
 TEST_F(FileDataPipeProducerTest, TinyFile) {
@@ -227,12 +285,18 @@
   DataPipe pipe(16);
   DataPipeReader reader(std::move(pipe.consumer_handle), 16,
                         loop.QuitClosure());
+  DataPipeObserverData observer_data;
+  auto observer = std::make_unique<TestObserver>(&observer_data);
   WriteFromPathThenCloseWriter(
-      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle)),
+      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+                                             std::move(observer)),
       path);
   loop.Run();
 
   EXPECT_EQ(kTestString, reader.data());
+  EXPECT_EQ(0, observer_data.num_read_errors);
+  EXPECT_EQ(kTestString.size(), observer_data.bytes_read);
+  EXPECT_EQ(1, observer_data.done_called);
 }
 
 TEST_F(FileDataPipeProducerTest, HugeFile) {
@@ -254,12 +318,18 @@
   DataPipeReader reader(std::move(pipe.consumer_handle), kDataPipeSize,
                         loop.QuitClosure());
 
+  DataPipeObserverData observer_data;
+  auto observer = std::make_unique<TestObserver>(&observer_data);
   WriteFromPathThenCloseWriter(
-      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle)),
+      std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+                                             std::move(observer)),
       path);
   loop.Run();
 
   EXPECT_EQ(test_string, reader.data());
+  EXPECT_EQ(0, observer_data.num_read_errors);
+  EXPECT_EQ(kHugeFileSize, observer_data.bytes_read);
+  EXPECT_EQ(1, observer_data.done_called);
 }
 
 }  // namespace
diff --git a/net/base/hex_utils.cc b/net/base/hex_utils.cc
index 62cdb637..f6efcbe 100644
--- a/net/base/hex_utils.cc
+++ b/net/base/hex_utils.cc
@@ -16,7 +16,7 @@
 std::string HexDecode(base::StringPiece input) {
   std::vector<uint8_t> output;
   std::string result;
-  if (base::HexStringToBytes(input.as_string(), &output))
+  if (base::HexStringToBytes(input, &output))
     result.assign(reinterpret_cast<const char*>(&output[0]), output.size());
   return result;
 }
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 8b7e5b4b..38c6ea2a3 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -729,6 +729,9 @@
 // SpdyStream layer.
 NET_ERROR(SPDY_RST_STREAM_NO_ERROR_RECEIVED, -372)
 
+// The pushed stream claimed by the request is no longer available.
+NET_ERROR(SPDY_PUSHED_STREAM_NOT_AVAILABLE, -373)
+
 // The cache does not have the requested entry.
 NET_ERROR(CACHE_MISS, -400)
 
diff --git a/net/base/proxy_delegate.h b/net/base/proxy_delegate.h
index 8767728..356717b3 100644
--- a/net/base/proxy_delegate.h
+++ b/net/base/proxy_delegate.h
@@ -15,12 +15,8 @@
 
 namespace net {
 
-class HttpRequestHeaders;
-class HttpResponseHeaders;
-class HostPortPair;
 class ProxyInfo;
 class ProxyServer;
-class ProxyService;
 
 // Delegate for setting up a connection.
 class NET_EXPORT ProxyDelegate {
@@ -48,25 +44,9 @@
   virtual void OnFallback(const ProxyServer& bad_proxy,
                           int net_error) = 0;
 
-  // Called immediately before a proxy tunnel request is sent.
-  // Provides the embedder an opportunity to add extra request headers.
-  virtual void OnBeforeTunnelRequest(const HostPortPair& proxy_server,
-                                     HttpRequestHeaders* extra_headers) = 0;
-
-  // Called when the connect attempt to a CONNECT proxy has completed.
-  virtual void OnTunnelConnectCompleted(const HostPortPair& endpoint,
-                                        const HostPortPair& proxy_server,
-                                        int net_error) = 0;
-
-  // Called after the response headers for the tunnel request are received.
-  virtual void OnTunnelHeadersReceived(
-      const HostPortPair& origin,
-      const HostPortPair& proxy_server,
-      const HttpResponseHeaders& response_headers) = 0;
-
   // Returns true if |proxy_server| is a trusted SPDY/HTTP2 proxy that is
   // allowed to push cross-origin resources.
-  virtual bool IsTrustedSpdyProxy(const net::ProxyServer& proxy_server) = 0;
+  virtual bool IsTrustedSpdyProxy(const ProxyServer& proxy_server) = 0;
 
   // Notifies the ProxyDelegate that |alternative_proxy_server| is broken.
   virtual void OnAlternativeProxyBroken(
diff --git a/net/base/test_proxy_delegate.cc b/net/base/test_proxy_delegate.cc
index cb69981..66e98cb 100644
--- a/net/base/test_proxy_delegate.cc
+++ b/net/base/test_proxy_delegate.cc
@@ -4,8 +4,6 @@
 
 #include "net/base/test_proxy_delegate.h"
 
-#include "net/http/http_request_headers.h"
-#include "net/http/http_response_headers.h"
 #include "net/proxy/proxy_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -15,28 +13,6 @@
 
 TestProxyDelegate::~TestProxyDelegate() = default;
 
-void TestProxyDelegate::VerifyOnTunnelRequestCompleted(
-    const std::string& endpoint,
-    const std::string& proxy_server) const {
-  EXPECT_TRUE(on_tunnel_request_completed_called_);
-  EXPECT_TRUE(HostPortPair::FromString(endpoint).Equals(
-      on_tunnel_request_completed_endpoint_));
-  EXPECT_TRUE(HostPortPair::FromString(proxy_server)
-                  .Equals(on_tunnel_request_completed_proxy_server_));
-}
-
-void TestProxyDelegate::VerifyOnTunnelHeadersReceived(
-    const std::string& origin,
-    const std::string& proxy_server,
-    const std::string& status_line) const {
-  EXPECT_TRUE(on_tunnel_headers_received_called_);
-  EXPECT_TRUE(HostPortPair::FromString(origin).Equals(
-      on_tunnel_headers_received_origin_));
-  EXPECT_TRUE(HostPortPair::FromString(proxy_server)
-                  .Equals(on_tunnel_headers_received_proxy_server_));
-  EXPECT_EQ(status_line, on_tunnel_headers_received_status_line_);
-}
-
 void TestProxyDelegate::OnResolveProxy(
     const GURL& url,
     const std::string& method,
@@ -45,38 +21,10 @@
   result->SetAlternativeProxy(alternative_proxy_server_);
 }
 
-void TestProxyDelegate::OnTunnelConnectCompleted(
-    const HostPortPair& endpoint,
-    const HostPortPair& proxy_server,
-    int net_error) {
-  on_tunnel_request_completed_called_ = true;
-  on_tunnel_request_completed_endpoint_ = endpoint;
-  on_tunnel_request_completed_proxy_server_ = proxy_server;
-}
-
 void TestProxyDelegate::OnFallback(const ProxyServer& bad_proxy,
                                    int net_error) {}
 
-void TestProxyDelegate::OnBeforeTunnelRequest(
-    const HostPortPair& proxy_server,
-    HttpRequestHeaders* extra_headers) {
-  on_before_tunnel_request_called_ = true;
-  if (extra_headers)
-    extra_headers->SetHeader("Foo", proxy_server.ToString());
-}
-
-void TestProxyDelegate::OnTunnelHeadersReceived(
-    const HostPortPair& origin,
-    const HostPortPair& proxy_server,
-    const HttpResponseHeaders& response_headers) {
-  on_tunnel_headers_received_called_ = true;
-  on_tunnel_headers_received_origin_ = origin;
-  on_tunnel_headers_received_proxy_server_ = proxy_server;
-  on_tunnel_headers_received_status_line_ = response_headers.GetStatusLine();
-}
-
-bool TestProxyDelegate::IsTrustedSpdyProxy(
-    const net::ProxyServer& proxy_server) {
+bool TestProxyDelegate::IsTrustedSpdyProxy(const ProxyServer& proxy_server) {
   return proxy_server.is_valid() && trusted_spdy_proxy_ == proxy_server;
 }
 
diff --git a/net/base/test_proxy_delegate.h b/net/base/test_proxy_delegate.h
index 3056e923..073e4da 100644
--- a/net/base/test_proxy_delegate.h
+++ b/net/base/test_proxy_delegate.h
@@ -7,7 +7,6 @@
 
 #include <string>
 
-#include "net/base/host_port_pair.h"
 #include "net/base/proxy_delegate.h"
 #include "net/proxy/proxy_server.h"
 
@@ -15,8 +14,6 @@
 
 namespace net {
 
-class HttpRequestHeaders;
-class HttpResponseHeaders;
 class ProxyInfo;
 
 class TestProxyDelegate : public ProxyDelegate {
@@ -24,45 +21,17 @@
   TestProxyDelegate();
   ~TestProxyDelegate() override;
 
-  bool on_before_tunnel_request_called() const {
-    return on_before_tunnel_request_called_;
-  }
-
-  bool on_tunnel_request_completed_called() const {
-    return on_tunnel_request_completed_called_;
-  }
-
-  bool on_tunnel_headers_received_called() const {
-    return on_tunnel_headers_received_called_;
-  }
-
-  void set_trusted_spdy_proxy(const net::ProxyServer& proxy_server) {
+  void set_trusted_spdy_proxy(const ProxyServer& proxy_server) {
     trusted_spdy_proxy_ = proxy_server;
   }
 
-  void VerifyOnTunnelRequestCompleted(const std::string& endpoint,
-                                      const std::string& proxy_server) const;
-
-  void VerifyOnTunnelHeadersReceived(const std::string& origin,
-                                     const std::string& proxy_server,
-                                     const std::string& status_line) const;
-
   // ProxyDelegate implementation:
   void OnResolveProxy(const GURL& url,
                       const std::string& method,
                       const ProxyRetryInfoMap& proxy_retry_info,
                       ProxyInfo* result) override;
-  void OnTunnelConnectCompleted(const HostPortPair& endpoint,
-                                const HostPortPair& proxy_server,
-                                int net_error) override;
   void OnFallback(const ProxyServer& bad_proxy, int net_error) override;
-  void OnBeforeTunnelRequest(const HostPortPair& proxy_server,
-                             HttpRequestHeaders* extra_headers) override;
-  void OnTunnelHeadersReceived(
-      const HostPortPair& origin,
-      const HostPortPair& proxy_server,
-      const HttpResponseHeaders& response_headers) override;
-  bool IsTrustedSpdyProxy(const net::ProxyServer& proxy_server) override;
+  bool IsTrustedSpdyProxy(const ProxyServer& proxy_server) override;
   void OnAlternativeProxyBroken(
       const ProxyServer& alternative_proxy_server) override;
 
@@ -75,15 +44,7 @@
   }
 
  private:
-  bool on_before_tunnel_request_called_ = false;
-  bool on_tunnel_request_completed_called_ = false;
-  bool on_tunnel_headers_received_called_ = false;
-  net::ProxyServer trusted_spdy_proxy_;
-  HostPortPair on_tunnel_request_completed_endpoint_;
-  HostPortPair on_tunnel_request_completed_proxy_server_;
-  HostPortPair on_tunnel_headers_received_origin_;
-  HostPortPair on_tunnel_headers_received_proxy_server_;
-  std::string on_tunnel_headers_received_status_line_;
+  ProxyServer trusted_spdy_proxy_;
   ProxyServer alternative_proxy_server_;
 };
 
diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h
index 603fb4cc..6ccd52f3 100644
--- a/net/dns/dns_config_service.h
+++ b/net/dns/dns_config_service.h
@@ -34,7 +34,7 @@
 struct NET_EXPORT_PRIVATE DnsConfig {
   DnsConfig();
   DnsConfig(const DnsConfig& other);
-  virtual ~DnsConfig();
+  ~DnsConfig();
 
   bool Equals(const DnsConfig& d) const;
 
diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc
index aef9b55..ea58dc82 100644
--- a/net/http/http_proxy_client_socket.cc
+++ b/net/http/http_proxy_client_socket.cc
@@ -11,7 +11,6 @@
 #include "net/base/auth.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/io_buffer.h"
-#include "net/base/proxy_delegate.h"
 #include "net/http/http_basic_stream.h"
 #include "net/http/http_network_session.h"
 #include "net/http/http_request_info.h"
@@ -30,12 +29,10 @@
     std::unique_ptr<ClientSocketHandle> transport_socket,
     const std::string& user_agent,
     const HostPortPair& endpoint,
-    const HostPortPair& proxy_server,
     HttpAuthController* http_auth_controller,
     bool tunnel,
     bool using_spdy,
     NextProto negotiated_protocol,
-    ProxyDelegate* proxy_delegate,
     bool is_https_proxy)
     : io_callback_(base::Bind(&HttpProxyClientSocket::OnIOComplete,
                               base::Unretained(this))),
@@ -48,8 +45,6 @@
       negotiated_protocol_(negotiated_protocol),
       is_https_proxy_(is_https_proxy),
       redirect_has_load_timing_info_(false),
-      proxy_server_(proxy_server),
-      proxy_delegate_(proxy_delegate),
       net_log_(transport_->socket()->NetLog()) {
   // Synthesize the bits of a request that we actually use.
   request_.url = GURL("https://" + endpoint.ToString());
@@ -405,10 +400,6 @@
     HttpRequestHeaders authorization_headers;
     if (auth_->HaveAuth())
       auth_->AddAuthorizationHeader(&authorization_headers);
-    if (proxy_delegate_) {
-      proxy_delegate_->OnBeforeTunnelRequest(proxy_server_,
-                                             &authorization_headers);
-    }
     std::string user_agent;
     if (!request_.extra_headers.GetHeader(HttpRequestHeaders::kUserAgent,
                                           &user_agent)) {
@@ -455,13 +446,6 @@
       NetLogEventType::HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
       base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers));
 
-  if (proxy_delegate_) {
-    proxy_delegate_->OnTunnelHeadersReceived(
-        HostPortPair::FromURL(request_.url),
-        proxy_server_,
-        *response_.headers);
-  }
-
   switch (response_.headers->response_code()) {
     case 200:  // OK
       if (http_stream_parser_->IsMoreDataBuffered())
diff --git a/net/http/http_proxy_client_socket.h b/net/http/http_proxy_client_socket.h
index 184773933..8444be2 100644
--- a/net/http/http_proxy_client_socket.h
+++ b/net/http/http_proxy_client_socket.h
@@ -32,7 +32,6 @@
 class HttpStream;
 class HttpStreamParser;
 class IOBuffer;
-class ProxyDelegate;
 
 class NET_EXPORT_PRIVATE HttpProxyClientSocket : public ProxyClientSocket {
  public:
@@ -42,12 +41,10 @@
   HttpProxyClientSocket(std::unique_ptr<ClientSocketHandle> transport_socket,
                         const std::string& user_agent,
                         const HostPortPair& endpoint,
-                        const HostPortPair& proxy_server,
                         HttpAuthController* http_auth_controller,
                         bool tunnel,
                         bool using_spdy,
                         NextProto negotiated_protocol,
-                        ProxyDelegate* proxy_delegate,
                         bool is_https_proxy);
 
   // On destruction Disconnect() is called.
@@ -164,11 +161,6 @@
   bool redirect_has_load_timing_info_;
   LoadTimingInfo redirect_load_timing_info_;
 
-  const HostPortPair proxy_server_;
-
-  // This delegate must outlive this proxy client socket.
-  ProxyDelegate* proxy_delegate_;
-
   const NetLogWithSource net_log_;
 
   DISALLOW_COPY_AND_ASSIGN(HttpProxyClientSocket);
diff --git a/net/http/http_proxy_client_socket_fuzzer.cc b/net/http/http_proxy_client_socket_fuzzer.cc
index c5b7879..ac9702a 100644
--- a/net/http/http_proxy_client_socket_fuzzer.cc
+++ b/net/http/http_proxy_client_socket_fuzzer.cc
@@ -65,9 +65,8 @@
   bool is_https_proxy = data_provider.ConsumeBool();
   net::HttpProxyClientSocket socket(
       std::move(socket_handle), "Bond/007", net::HostPortPair("foo", 80),
-      net::HostPortPair("proxy", 42), auth_controller.get(), true /* tunnel */,
-      false /* using_spdy */, net::kProtoUnknown, nullptr /* proxy_delegate */,
-      is_https_proxy);
+      auth_controller.get(), true /* tunnel */, false /* using_spdy */,
+      net::kProtoUnknown, is_https_proxy);
   int result = socket.Connect(callback.callback());
   result = callback.GetResult(result);
 
diff --git a/net/http/http_proxy_client_socket_pool.cc b/net/http/http_proxy_client_socket_pool.cc
index ab7bbb3..b7769a4 100644
--- a/net/http/http_proxy_client_socket_pool.cc
+++ b/net/http/http_proxy_client_socket_pool.cc
@@ -18,7 +18,6 @@
 #include "base/values.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
-#include "net/base/proxy_delegate.h"
 #include "net/http/http_network_session.h"
 #include "net/http/http_proxy_client_socket_wrapper.h"
 #include "net/log/net_log_source_type.h"
@@ -85,8 +84,7 @@
     HttpAuthHandlerFactory* http_auth_handler_factory,
     SpdySessionPool* spdy_session_pool,
     QuicStreamFactory* quic_stream_factory,
-    bool tunnel,
-    ProxyDelegate* proxy_delegate)
+    bool tunnel)
     : transport_params_(transport_params),
       ssl_params_(ssl_params),
       quic_version_(quic_version),
@@ -96,8 +94,7 @@
       endpoint_(endpoint),
       http_auth_cache_(tunnel ? http_auth_cache : NULL),
       http_auth_handler_factory_(tunnel ? http_auth_handler_factory : NULL),
-      tunnel_(tunnel),
-      proxy_delegate_(proxy_delegate) {
+      tunnel_(tunnel) {
   // If doing a QUIC proxy, |quic_version| must not be QUIC_VERSION_UNSUPPORTED,
   // and |ssl_params| must be valid while |transport_params| is null.
   // Otherwise, |quic_version| must be QUIC_VERSION_UNSUPPORTED, and exactly
@@ -153,7 +150,6 @@
           params->spdy_session_pool(),
           params->quic_stream_factory(),
           params->tunnel(),
-          params->proxy_delegate(),
           this->net_log())) {}
 
 HttpProxyConnectJob::~HttpProxyConnectJob() = default;
diff --git a/net/http/http_proxy_client_socket_pool.h b/net/http/http_proxy_client_socket_pool.h
index 0df5e47f3..c41198b 100644
--- a/net/http/http_proxy_client_socket_pool.h
+++ b/net/http/http_proxy_client_socket_pool.h
@@ -31,7 +31,6 @@
 class HttpProxyClientSocketWrapper;
 class NetLog;
 class NetworkQualityProvider;
-class ProxyDelegate;
 class QuicStreamFactory;
 class SSLClientSocketPool;
 class SSLSocketParams;
@@ -57,8 +56,7 @@
       HttpAuthHandlerFactory* http_auth_handler_factory,
       SpdySessionPool* spdy_session_pool,
       QuicStreamFactory* quic_stream_factory,
-      bool tunnel,
-      ProxyDelegate* proxy_delegate);
+      bool tunnel);
 
   const scoped_refptr<TransportSocketParams>& transport_params() const {
     return transport_params_;
@@ -82,10 +80,6 @@
   const HostResolver::RequestInfo& destination() const;
   bool tunnel() const { return tunnel_; }
 
-  ProxyDelegate* proxy_delegate() const {
-    return proxy_delegate_;
-  }
-
  private:
   friend class base::RefCounted<HttpProxySocketParams>;
   ~HttpProxySocketParams();
@@ -100,7 +94,6 @@
   HttpAuthCache* const http_auth_cache_;
   HttpAuthHandlerFactory* const http_auth_handler_factory_;
   const bool tunnel_;
-  ProxyDelegate* proxy_delegate_;
 
   DISALLOW_COPY_AND_ASSIGN(HttpProxySocketParams);
 };
diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_unittest.cc
index 2cf01c15..bf74077d 100644
--- a/net/http/http_proxy_client_socket_pool_unittest.cc
+++ b/net/http/http_proxy_client_socket_pool_unittest.cc
@@ -18,9 +18,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/histogram_tester.h"
 #include "net/base/net_errors.h"
-#include "net/base/proxy_delegate.h"
 #include "net/base/test_completion_callback.h"
-#include "net/base/test_proxy_delegate.h"
 #include "net/http/http_network_session.h"
 #include "net/http/http_proxy_client_socket.h"
 #include "net/http/http_response_headers.h"
@@ -169,26 +167,21 @@
 
   // Returns the a correctly constructed HttpProxyParms
   // for the HTTP or HTTPS proxy.
-  scoped_refptr<HttpProxySocketParams> CreateParams(
-      bool tunnel,
-      ProxyDelegate* proxy_delegate) {
-    return scoped_refptr<HttpProxySocketParams>(new HttpProxySocketParams(
+  scoped_refptr<HttpProxySocketParams> CreateParams(bool tunnel) {
+    return base::MakeRefCounted<HttpProxySocketParams>(
         CreateHttpProxyParams(), CreateHttpsProxyParams(),
         QUIC_VERSION_UNSUPPORTED, std::string(),
         HostPortPair("www.google.com", tunnel ? 443 : 80),
         session_->http_auth_cache(), session_->http_auth_handler_factory(),
-        session_->spdy_session_pool(), session_->quic_stream_factory(), tunnel,
-        proxy_delegate));
+        session_->spdy_session_pool(), session_->quic_stream_factory(), tunnel);
   }
 
-  scoped_refptr<HttpProxySocketParams> CreateTunnelParams(
-      ProxyDelegate* proxy_delegate) {
-    return CreateParams(true, proxy_delegate);
+  scoped_refptr<HttpProxySocketParams> CreateTunnelParams() {
+    return CreateParams(true);
   }
 
-  scoped_refptr<HttpProxySocketParams> CreateNoTunnelParams(
-      ProxyDelegate* proxy_delegate) {
-    return CreateParams(false, proxy_delegate);
+  scoped_refptr<HttpProxySocketParams> CreateNoTunnelParams() {
+    return CreateParams(false);
   }
 
   MockClientSocketFactory* socket_factory() {
@@ -268,17 +261,13 @@
 TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) {
   Initialize(NULL, 0, NULL, 0, NULL, 0, NULL, 0);
 
-  std::unique_ptr<TestProxyDelegate> proxy_delegate(new TestProxyDelegate());
-  int rv = handle_.Init("a", CreateNoTunnelParams(proxy_delegate.get()), LOW,
+  int rv = handle_.Init("a", CreateNoTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         CompletionCallback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsOk());
   EXPECT_TRUE(handle_.is_initialized());
   ASSERT_TRUE(handle_.socket());
   EXPECT_TRUE(handle_.socket()->IsConnected());
-  EXPECT_FALSE(proxy_delegate->on_before_tunnel_request_called());
-  EXPECT_FALSE(proxy_delegate->on_tunnel_headers_received_called());
-  EXPECT_TRUE(proxy_delegate->on_tunnel_request_completed_called());
 
   bool is_secure_proxy = GetParam() == HTTPS || GetParam() == SPDY;
   histogram_tester().ExpectTotalCount(
@@ -292,7 +281,7 @@
 TEST_P(HttpProxyClientSocketPoolTest, SetSocketRequestPriorityOnInit) {
   Initialize(NULL, 0, NULL, 0, NULL, 0, NULL, 0);
   EXPECT_EQ(
-      OK, handle_.Init("a", CreateNoTunnelParams(NULL), HIGHEST,
+      OK, handle_.Init("a", CreateNoTunnelParams(), HIGHEST,
                        ClientSocketPool::RespectLimits::ENABLED,
                        CompletionCallback(), pool_.get(), NetLogWithSource()));
   EXPECT_EQ(HIGHEST, GetLastTransportRequestPriority());
@@ -332,7 +321,7 @@
              spdy_reads, arraysize(spdy_reads), spdy_writes,
              arraysize(spdy_writes));
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -366,9 +355,7 @@
       "CONNECT www.google.com:443 HTTP/1.1\r\n"
       "Host: www.google.com:443\r\n"
       "Proxy-Connection: keep-alive\r\n"
-      "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n"
-      "Foo: " +
-      proxy_host_port + "\r\n\r\n";
+      "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n";
   MockWrite writes[] = {
     MockWrite(SYNCHRONOUS, 0, request.c_str()),
   };
@@ -380,21 +367,13 @@
              NULL, 0);
   AddAuthToCache();
 
-  std::unique_ptr<TestProxyDelegate> proxy_delegate(new TestProxyDelegate());
-  int rv = handle_.Init("a", CreateTunnelParams(proxy_delegate.get()), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsOk());
   EXPECT_TRUE(handle_.is_initialized());
   ASSERT_TRUE(handle_.socket());
   EXPECT_TRUE(handle_.socket()->IsConnected());
-  proxy_delegate->VerifyOnTunnelHeadersReceived(
-      "www.google.com:443",
-      proxy_host_port.c_str(),
-      "HTTP/1.1 200 Connection Established");
-  proxy_delegate->VerifyOnTunnelRequestCompleted(
-      "www.google.com:443",
-      proxy_host_port.c_str());
 }
 
 TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) {
@@ -405,9 +384,7 @@
       "CONNECT www.google.com:443 HTTP/1.1\r\n"
       "Host: www.google.com:443\r\n"
       "Proxy-Connection: keep-alive\r\n"
-      "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n"
-      "Foo: " +
-      proxy_host_port + "\r\n\r\n";
+      "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n";
   MockWrite writes[] = {
     MockWrite(ASYNC, 0, request.c_str()),
   };
@@ -431,8 +408,7 @@
              arraysize(spdy_writes));
   AddAuthToCache();
 
-  std::unique_ptr<TestProxyDelegate> proxy_delegate(new TestProxyDelegate());
-  int rv = handle_.Init("a", CreateTunnelParams(proxy_delegate.get()), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -443,9 +419,6 @@
   EXPECT_TRUE(handle_.is_initialized());
   ASSERT_TRUE(handle_.socket());
   EXPECT_TRUE(handle_.socket()->IsConnected());
-  proxy_delegate->VerifyOnTunnelRequestCompleted(
-      "www.google.com:443",
-      proxy_host_port.c_str());
 }
 
 // Make sure that HttpProxyConnectJob passes on its priority to its
@@ -470,7 +443,7 @@
 
   EXPECT_EQ(
       ERR_IO_PENDING,
-      handle_.Init("a", CreateTunnelParams(NULL), MEDIUM,
+      handle_.Init("a", CreateTunnelParams(), MEDIUM,
                    ClientSocketPool::RespectLimits::ENABLED,
                    callback_.callback(), pool_.get(), NetLogWithSource()));
   EXPECT_EQ(MEDIUM, GetLastTransportRequestPriority());
@@ -486,7 +459,7 @@
 
   socket_factory()->AddSocketDataProvider(data_.get());
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -519,7 +492,7 @@
   }
   socket_factory()->AddSSLSocketDataProvider(ssl_data_.get());
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -551,7 +524,7 @@
   }
   socket_factory()->AddSSLSocketDataProvider(ssl_data_.get());
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -594,7 +567,7 @@
              arraysize(spdy_writes));
   AddAuthToCache();
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -634,7 +607,7 @@
   Initialize(reads, arraysize(reads), writes, arraysize(writes),
              NULL, 0, NULL, 0);
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -673,7 +646,7 @@
              arraysize(spdy_writes));
   AddAuthToCache();
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -730,7 +703,7 @@
              arraysize(spdy_writes));
   AddAuthToCache();
 
-  int rv = handle_.Init("a", CreateTunnelParams(NULL), LOW,
+  int rv = handle_.Init("a", CreateTunnelParams(), LOW,
                         ClientSocketPool::RespectLimits::ENABLED,
                         callback_.callback(), pool_.get(), NetLogWithSource());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
diff --git a/net/http/http_proxy_client_socket_unittest.cc b/net/http/http_proxy_client_socket_unittest.cc
index d26e900..dd35006c 100644
--- a/net/http/http_proxy_client_socket_unittest.cc
+++ b/net/http/http_proxy_client_socket_unittest.cc
@@ -29,8 +29,7 @@
   // non-owning pointer to it.
   connection->SetSocket(std::unique_ptr<StreamSocket>(tagging_sock));
   HttpProxyClientSocket socket(std::move(connection), "", HostPortPair(),
-                               HostPortPair(), nullptr, false, false,
-                               NextProto(), nullptr, false);
+                               nullptr, false, false, NextProto(), false);
 
   EXPECT_EQ(tagging_sock->tag(), SocketTag());
 #if defined(OS_ANDROID)
diff --git a/net/http/http_proxy_client_socket_wrapper.cc b/net/http/http_proxy_client_socket_wrapper.cc
index f5ffa45..5bbf4df 100644
--- a/net/http/http_proxy_client_socket_wrapper.cc
+++ b/net/http/http_proxy_client_socket_wrapper.cc
@@ -12,7 +12,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/values.h"
-#include "net/base/proxy_delegate.h"
 #include "net/http/http_proxy_client_socket.h"
 #include "net/http/http_response_info.h"
 #include "net/log/net_log_event_type.h"
@@ -49,7 +48,6 @@
     SpdySessionPool* spdy_session_pool,
     QuicStreamFactory* quic_stream_factory,
     bool tunnel,
-    ProxyDelegate* proxy_delegate,
     const NetLogWithSource& net_log)
     : next_state_(STATE_NONE),
       group_name_(group_name),
@@ -67,7 +65,6 @@
       spdy_session_pool_(spdy_session_pool),
       has_restarted_(false),
       tunnel_(tunnel),
-      proxy_delegate_(proxy_delegate),
       using_spdy_(false),
       quic_stream_request_(quic_stream_factory),
       http_auth_controller_(
@@ -188,7 +185,6 @@
     connect_callback_ = callback;
   } else {
     connect_timer_.Stop();
-    NotifyProxyDelegateOfCompletion(rv);
   }
 
   return rv;
@@ -360,7 +356,6 @@
   int rv = DoLoop(result);
   if (rv != ERR_IO_PENDING) {
     connect_timer_.Stop();
-    NotifyProxyDelegateOfCompletion(rv);
     // May delete |this|.
     base::ResetAndReturn(&connect_callback_).Run(rv);
   }
@@ -572,8 +567,7 @@
   // Add a HttpProxy connection on top of the tcp socket.
   transport_socket_.reset(new HttpProxyClientSocket(
       std::move(transport_socket_handle_), user_agent_, endpoint_,
-      GetDestination().host_port_pair(), http_auth_controller_.get(), tunnel_,
-      using_spdy_, negotiated_protocol_, proxy_delegate_,
+      http_auth_controller_.get(), tunnel_, using_spdy_, negotiated_protocol_,
       ssl_params_.get() != nullptr));
   return transport_socket_->Connect(base::Bind(
       &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this)));
@@ -726,14 +720,6 @@
   return result;
 }
 
-void HttpProxyClientSocketWrapper::NotifyProxyDelegateOfCompletion(int result) {
-  if (!proxy_delegate_)
-    return;
-
-  const HostPortPair& proxy_server = GetDestination().host_port_pair();
-  proxy_delegate_->OnTunnelConnectCompleted(endpoint_, proxy_server, result);
-}
-
 void HttpProxyClientSocketWrapper::SetConnectTimer(base::TimeDelta delay) {
   connect_timer_.Stop();
   connect_timer_.Start(FROM_HERE, delay, this,
@@ -757,8 +743,6 @@
     }
   }
 
-  NotifyProxyDelegateOfCompletion(ERR_CONNECTION_TIMED_OUT);
-
   CompletionCallback callback = connect_callback_;
   Disconnect();
   callback.Run(ERR_CONNECTION_TIMED_OUT);
diff --git a/net/http/http_proxy_client_socket_wrapper.h b/net/http/http_proxy_client_socket_wrapper.h
index aa5b230bf..5ee406fa 100644
--- a/net/http/http_proxy_client_socket_wrapper.h
+++ b/net/http/http_proxy_client_socket_wrapper.h
@@ -36,7 +36,6 @@
 class HttpResponseInfo;
 class HttpStream;
 class IOBuffer;
-class ProxyDelegate;
 class SpdySessionPool;
 class SSLClientSocketPool;
 class TransportClientSocketPool;
@@ -73,7 +72,6 @@
       SpdySessionPool* spdy_session_pool,
       QuicStreamFactory* quic_stream_factory,
       bool tunnel,
-      ProxyDelegate* proxy_delegate,
       const NetLogWithSource& net_log);
 
   // On destruction Disconnect() is called.
@@ -172,8 +170,6 @@
   int DoRestartWithAuth();
   int DoRestartWithAuthComplete(int result);
 
-  void NotifyProxyDelegateOfCompletion(int result);
-
   void SetConnectTimer(base::TimeDelta duration);
   void ConnectTimeout();
 
@@ -200,7 +196,6 @@
 
   bool has_restarted_;
   const bool tunnel_;
-  ProxyDelegate* const proxy_delegate_;
 
   bool using_spdy_;
   NextProto negotiated_protocol_;
diff --git a/net/http/http_proxy_client_socket_wrapper_unittest.cc b/net/http/http_proxy_client_socket_wrapper_unittest.cc
index 514a7bb0..b5178f5c 100644
--- a/net/http/http_proxy_client_socket_wrapper_unittest.cc
+++ b/net/http/http_proxy_client_socket_wrapper_unittest.cc
@@ -269,7 +269,7 @@
       /*transport_params=*/nullptr, ssl_params, quic_version_, kUserAgent,
       endpoint_host_port_, &http_auth_cache_, http_auth_handler_factory_.get(),
       /*spdy_session_pool=*/nullptr, quic_stream_factory_.get(),
-      /*tunnel=*/true, /*proxy_delegate=*/nullptr, net_log_));
+      /*tunnel=*/true, net_log_));
 
   TestCompletionCallback callback;
   client_socket_wrapper_->Connect(callback.callback());
diff --git a/net/http/http_server_properties.h b/net/http/http_server_properties.h
index b8efe93..22f0a5d 100644
--- a/net/http/http_server_properties.h
+++ b/net/http/http_server_properties.h
@@ -13,6 +13,7 @@
 #include <tuple>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/containers/mru_cache.h"
 #include "base/macros.h"
 #include "base/time/time.h"
@@ -313,8 +314,10 @@
   HttpServerProperties() {}
   virtual ~HttpServerProperties() {}
 
-  // Deletes all data.
-  virtual void Clear() = 0;
+  // Deletes all data. If |callback| is non-null, flushes data to disk
+  // and invokes the callback asynchronously once changes have been written to
+  // disk.
+  virtual void Clear(base::OnceClosure callback) = 0;
 
   // Returns true if |server| supports a network protocol which honors
   // request prioritization.
diff --git a/net/http/http_server_properties_impl.cc b/net/http/http_server_properties_impl.cc
index a17489c8..ccd60cb 100644
--- a/net/http/http_server_properties_impl.cc
+++ b/net/http/http_server_properties_impl.cc
@@ -177,7 +177,7 @@
   return broken_alternative_services_.recently_broken_alternative_services();
 }
 
-void HttpServerPropertiesImpl::Clear() {
+void HttpServerPropertiesImpl::Clear(base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   spdy_servers_map_.Clear();
   alternative_service_map_.Clear();
@@ -187,6 +187,11 @@
   server_network_stats_map_.Clear();
   quic_server_info_map_.Clear();
   canonical_server_info_map_.clear();
+
+  if (!callback.is_null()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  std::move(callback));
+  }
 }
 
 bool HttpServerPropertiesImpl::SupportsRequestPriority(
diff --git a/net/http/http_server_properties_impl.h b/net/http/http_server_properties_impl.h
index d523f32..7967e86 100644
--- a/net/http/http_server_properties_impl.h
+++ b/net/http/http_server_properties_impl.h
@@ -13,6 +13,7 @@
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
@@ -86,7 +87,7 @@
   // HttpServerProperties methods:
   // -----------------------------
 
-  void Clear() override;
+  void Clear(base::OnceClosure callback) override;
   bool SupportsRequestPriority(const url::SchemeHostPort& server) override;
   bool GetSupportsSpdy(const url::SchemeHostPort& server) override;
   void SetSupportsSpdy(const url::SchemeHostPort& server,
diff --git a/net/http/http_server_properties_impl_unittest.cc b/net/http/http_server_properties_impl_unittest.cc
index 32f80acf..c3b04f60 100644
--- a/net/http/http_server_properties_impl_unittest.cc
+++ b/net/http/http_server_properties_impl_unittest.cc
@@ -280,9 +280,21 @@
   EXPECT_TRUE(impl_.SupportsRequestPriority(spdy_server_google));
   EXPECT_TRUE(impl_.SupportsRequestPriority(spdy_server_mail));
 
-  impl_.Clear();
+  base::RunLoop run_loop;
+  bool callback_invoked_ = false;
+  impl_.Clear(base::BindOnce(
+      [](bool* callback_invoked, base::OnceClosure quit_closure) {
+        *callback_invoked = true;
+        std::move(quit_closure).Run();
+      },
+      &callback_invoked_, run_loop.QuitClosure()));
   EXPECT_FALSE(impl_.SupportsRequestPriority(spdy_server_google));
   EXPECT_FALSE(impl_.SupportsRequestPriority(spdy_server_mail));
+
+  // Callback should be run asynchronously.
+  EXPECT_FALSE(callback_invoked_);
+  run_loop.Run();
+  EXPECT_TRUE(callback_invoked_);
 }
 
 TEST_F(SpdyServerPropertiesTest, MRUOfSpdyServersMap) {
@@ -329,7 +341,7 @@
   EXPECT_EQ(alternative_service,
             alternative_service_info_vector[0].alternative_service());
 
-  impl_.Clear();
+  impl_.Clear(base::OnceClosure());
   EXPECT_FALSE(HasAlternativeService(test_server));
 }
 
@@ -933,7 +945,7 @@
                                                    "bar.c.youtube.com", 1234);
 
   SetAlternativeService(canonical_server, canonical_alternative_service);
-  impl_.Clear();
+  impl_.Clear(base::OnceClosure());
   EXPECT_FALSE(HasAlternativeService(test_server));
 }
 
@@ -1093,7 +1105,7 @@
   EXPECT_TRUE(impl_.GetSupportsQuic(&address));
   EXPECT_EQ(actual_address, address);
 
-  impl_.Clear();
+  impl_.Clear(base::OnceClosure());
 
   EXPECT_FALSE(impl_.GetSupportsQuic(&address));
 }
@@ -1186,7 +1198,7 @@
   // Https server should have nothing set for server network stats.
   EXPECT_EQ(NULL, impl_.GetServerNetworkStats(foo_https_server));
 
-  impl_.Clear();
+  impl_.Clear(base::OnceClosure());
   EXPECT_EQ(NULL, impl_.GetServerNetworkStats(foo_http_server));
   EXPECT_EQ(NULL, impl_.GetServerNetworkStats(foo_https_server));
 }
@@ -1305,7 +1317,7 @@
   EXPECT_EQ(1u, impl_.quic_server_info_map().size());
   EXPECT_EQ(quic_server_info1, *(impl_.GetQuicServerInfo(quic_server_id)));
 
-  impl_.Clear();
+  impl_.Clear(base::OnceClosure());
   EXPECT_EQ(0u, impl_.quic_server_info_map().size());
   EXPECT_EQ(nullptr, impl_.GetQuicServerInfo(quic_server_id));
 }
diff --git a/net/http/http_server_properties_manager.cc b/net/http/http_server_properties_manager.cc
index 4a4dabb..5dc49e2e 100644
--- a/net/http/http_server_properties_manager.cc
+++ b/net/http/http_server_properties_manager.cc
@@ -116,9 +116,9 @@
   DCHECK(pref_delegate_);
   DCHECK(clock_);
 
-  pref_delegate_->StartListeningForUpdates(
-      base::Bind(&HttpServerPropertiesManager::OnHttpServerPropertiesChanged,
-                 base::Unretained(this)));
+  pref_delegate_->StartListeningForUpdates(base::BindRepeating(
+      &HttpServerPropertiesManager::OnHttpServerPropertiesChanged,
+      base::Unretained(this)));
   net_log_.BeginEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_INITIALIZATION);
 
   http_server_properties_impl_.reset(new HttpServerPropertiesImpl(clock_));
@@ -127,7 +127,7 @@
 HttpServerPropertiesManager::~HttpServerPropertiesManager() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Flush settings on destruction.
-  UpdatePrefsFromCache();
+  UpdatePrefsFromCache(base::OnceClosure());
 }
 
 // static
@@ -141,11 +141,11 @@
     http_server_properties_dict->SetInteger(kVersionKey, version_number);
 }
 
-void HttpServerPropertiesManager::Clear() {
+void HttpServerPropertiesManager::Clear(base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  http_server_properties_impl_->Clear();
-  UpdatePrefsFromCache();
+  http_server_properties_impl_->Clear(base::OnceClosure());
+  UpdatePrefsFromCache(std::move(callback));
 }
 
 bool HttpServerPropertiesManager::SupportsRequestPriority(
@@ -978,15 +978,17 @@
     return;
 
   network_prefs_update_timer_.Start(
-      FROM_HERE, kUpdatePrefsDelay, this,
-      &HttpServerPropertiesManager::UpdatePrefsFromCache);
+      FROM_HERE, kUpdatePrefsDelay,
+      base::Bind(&HttpServerPropertiesManager::UpdatePrefsFromCache,
+                 base::Unretained(this), base::Passed(base::OnceClosure())));
 
   // TODO(rtenneti): Delete the following histogram after collecting some data.
   UMA_HISTOGRAM_ENUMERATION("Net.HttpServerProperties.UpdatePrefs", location,
                             HttpServerPropertiesManager::NUM_LOCATIONS);
 }
 
-void HttpServerPropertiesManager::UpdatePrefsFromCache() {
+void HttpServerPropertiesManager::UpdatePrefsFromCache(
+    base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   typedef base::MRUCache<url::SchemeHostPort, ServerPref> ServerPrefMap;
@@ -1114,7 +1116,8 @@
       &http_server_properties_dict);
 
   setting_prefs_ = true;
-  pref_delegate_->SetServerProperties(http_server_properties_dict);
+  pref_delegate_->SetServerProperties(http_server_properties_dict,
+                                      std::move(callback));
   setting_prefs_ = false;
 
   net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_PREFS,
diff --git a/net/http/http_server_properties_manager.h b/net/http/http_server_properties_manager.h
index c1ee36a..5f346262 100644
--- a/net/http/http_server_properties_manager.h
+++ b/net/http/http_server_properties_manager.h
@@ -11,6 +11,7 @@
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
@@ -51,13 +52,17 @@
     // Returns nullptr if the pref system has no data for the server properties.
     virtual const base::DictionaryValue* GetServerProperties() const = 0;
 
-    // Sets the server properties to the given value.
-    virtual void SetServerProperties(const base::DictionaryValue& value) = 0;
+    // Sets the server properties to the given value. If |callback| is
+    // non-empty, flushes data to persistent storage and invokes |callback|
+    // asynchronously when complete.
+    virtual void SetServerProperties(const base::DictionaryValue& value,
+                                     base::OnceClosure callback) = 0;
 
     // Starts listening for external storage changes. There will only be one
     // callback active at a time. The first time the |callback| is invoked is
     // expected to mean the initial pref store values have been loaded.
-    virtual void StartListeningForUpdates(const base::Closure& callback) = 0;
+    virtual void StartListeningForUpdates(
+        const base::RepeatingClosure& callback) = 0;
   };
 
   // Create an instance of the HttpServerPropertiesManager.
@@ -83,7 +88,7 @@
   // HttpServerProperties methods:
   // ----------------------------------
 
-  void Clear() override;
+  void Clear(base::OnceClosure callback) override;
   bool SupportsRequestPriority(const url::SchemeHostPort& server) override;
   bool GetSupportsSpdy(const url::SchemeHostPort& server) override;
   void SetSupportsSpdy(const url::SchemeHostPort& server,
@@ -182,8 +187,9 @@
   void ScheduleUpdatePrefs(Location location);
 
   // Update prefs::kHttpServerProperties in preferences with the cached data
-  // from |http_server_properties_impl_|.
-  void UpdatePrefsFromCache();
+  // from |http_server_properties_impl_|. Invokes |callback| when changes have
+  // been committed, if non-null.
+  void UpdatePrefsFromCache(base::OnceClosure callback);
 
  private:
   FRIEND_TEST_ALL_PREFIXES(HttpServerPropertiesManagerTest,
diff --git a/net/http/http_server_properties_manager_unittest.cc b/net/http/http_server_properties_manager_unittest.cc
index ecdb1c3..20e835cf0 100644
--- a/net/http/http_server_properties_manager_unittest.cc
+++ b/net/http/http_server_properties_manager_unittest.cc
@@ -46,7 +46,8 @@
   const base::DictionaryValue* GetServerProperties() const override {
     return &prefs_;
   }
-  void SetServerProperties(const base::DictionaryValue& value) override {
+  void SetServerProperties(const base::DictionaryValue& value,
+                           base::OnceClosure callback) override {
     prefs_.Clear();
     prefs_.MergeDictionary(&value);
     ++num_pref_updates_;
@@ -54,6 +55,7 @@
       prefs_changed_callback_.Run();
     if (!extra_prefs_changed_callback_.is_null())
       extra_prefs_changed_callback_.Run();
+    set_properties_callback_ = std::move(callback);
   }
   void StartListeningForUpdates(const base::Closure& callback) override {
     CHECK(prefs_changed_callback_.is_null());
@@ -80,12 +82,20 @@
     extra_prefs_changed_callback_ = callback;
   }
 
+  // Returns the base::OnceCallback, if any, passed to the last call to
+  // SetServerProperties().
+  base::OnceClosure GetSetPropertiesCallback() {
+    return std::move(set_properties_callback_);
+  }
+
  private:
   base::DictionaryValue prefs_;
   base::Closure prefs_changed_callback_;
   base::Closure extra_prefs_changed_callback_;
   int num_pref_updates_ = 0;
 
+  base::OnceClosure set_properties_callback_;
+
   DISALLOW_COPY_AND_ASSIGN(MockPrefDelegate);
 };
 
@@ -807,8 +817,14 @@
 
   // Clear http server data, which should instantly update prefs.
   EXPECT_EQ(0, pref_delegate_->GetAndClearNumPrefUpdates());
-  http_server_props_manager_->Clear();
+  bool callback_invoked_ = false;
+  http_server_props_manager_->Clear(
+      base::BindOnce([](bool* callback_invoked) { *callback_invoked = true; },
+                     &callback_invoked_));
   EXPECT_EQ(1, pref_delegate_->GetAndClearNumPrefUpdates());
+  EXPECT_FALSE(callback_invoked_);
+  std::move(pref_delegate_->GetSetPropertiesCallback()).Run();
+  EXPECT_TRUE(callback_invoked_);
 
   EXPECT_FALSE(http_server_props_manager_->IsAlternativeServiceBroken(
       broken_alternative_service));
diff --git a/net/http/http_stream_factory_impl_job.cc b/net/http/http_stream_factory_impl_job.cc
index 2820679..1057041 100644
--- a/net/http/http_stream_factory_impl_job.cc
+++ b/net/http/http_stream_factory_impl_job.cc
@@ -201,6 +201,7 @@
       was_alpn_negotiated_(false),
       negotiated_protocol_(kProtoUnknown),
       num_streams_(0),
+      pushed_stream_id_(kNoPushedStreamFound),
       spdy_session_direct_(
           !(proxy_info.is_https() && origin_url_.SchemeIs(url::kHttpScheme))),
       spdy_session_key_(using_quic_
@@ -931,10 +932,9 @@
   // connection this request can pool to.  If so, then go straight to using
   // that.
   if (CanUseExistingSpdySession()) {
-    // TODO(bnc): Use outparam.  https://crbug.com/776415.
-    SpdyStreamId unused;
-    session_->spdy_session_pool()->push_promise_index()->FindSession(
-        spdy_session_key_, origin_url_, &existing_spdy_session_, &unused);
+    session_->spdy_session_pool()->push_promise_index()->ClaimPushedStream(
+        spdy_session_key_, origin_url_, &existing_spdy_session_,
+        &pushed_stream_id_);
     if (!existing_spdy_session_) {
       existing_spdy_session_ =
           session_->spdy_session_pool()->FindAvailableSession(
@@ -1172,8 +1172,8 @@
 
   bool use_relative_url =
       direct || request_info_.url.SchemeIs(url::kHttpsScheme);
-  stream_ = std::make_unique<SpdyHttpStream>(session, use_relative_url,
-                                             net_log_.source());
+  stream_ = std::make_unique<SpdyHttpStream>(
+      session, pushed_stream_id_, use_relative_url, net_log_.source());
   return OK;
 }
 
@@ -1215,10 +1215,9 @@
   // It is possible that a pushed stream has been opened by a server since last
   // time Job checked above.
   if (!existing_spdy_session_) {
-    // TODO(bnc): Use outparam.  https://crbug.com/776415.
-    SpdyStreamId unused;
-    session_->spdy_session_pool()->push_promise_index()->FindSession(
-        spdy_session_key_, origin_url_, &existing_spdy_session_, &unused);
+    session_->spdy_session_pool()->push_promise_index()->ClaimPushedStream(
+        spdy_session_key_, origin_url_, &existing_spdy_session_,
+        &pushed_stream_id_);
     // It is also possible that an HTTP/2 connection has been established since
     // last time Job checked above.
     if (!existing_spdy_session_) {
diff --git a/net/http/http_stream_factory_impl_job.h b/net/http/http_stream_factory_impl_job.h
index 2a4885e..e02ca44 100644
--- a/net/http/http_stream_factory_impl_job.h
+++ b/net/http/http_stream_factory_impl_job.h
@@ -500,6 +500,12 @@
   // Initialized when we have an existing SpdySession.
   base::WeakPtr<SpdySession> existing_spdy_session_;
 
+  // Once Job claims a pushed stream on a SpdySession, |pushed_stream_id_| is
+  // the ID of the claimed stream, and |existing_spdy_session_| points to that
+  // SpdySession.  Otherwise |pushed_stream_id_| is set to kNoPushedStreamFound
+  // (but |existing_spdy_session_| can still be non-null).
+  SpdyStreamId pushed_stream_id_;
+
   // True if not connecting to an Https proxy for an Http url.
   const bool spdy_session_direct_;
 
diff --git a/net/nqe/observation_buffer_unittest.cc b/net/nqe/observation_buffer_unittest.cc
index 1f15b79..187e1c5 100644
--- a/net/nqe/observation_buffer_unittest.cc
+++ b/net/nqe/observation_buffer_unittest.cc
@@ -45,10 +45,6 @@
   }
 }
 
-// Test disabled on OS_WIN to avoid linking errors when calling
-// SetTickClockForTesting.
-// TODO(tbansal): crbug.com/651963. Pass the clock through NQE's constructor.
-#if !defined(OS_WIN)
 // Verify that the percentiles are monotonically non-decreasing when a weight is
 // applied.
 TEST(NetworkQualityObservationBufferTest, GetPercentileWithWeights) {
@@ -90,7 +86,6 @@
   }
   EXPECT_LT(result_lowest, result_highest);
 }
-#endif
 
 // Verifies that the percentiles are correctly computed. All observations have
 // the same timestamp.
diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc
index 58a4440f..65ed64e 100644
--- a/net/proxy/proxy_service_unittest.cc
+++ b/net/proxy/proxy_service_unittest.cc
@@ -214,16 +214,7 @@
     return proxy_retry_info_;
   }
 
-  void OnTunnelConnectCompleted(const HostPortPair& endpoint,
-                                const HostPortPair& proxy_server,
-                                int net_error) override {}
   void OnFallback(const ProxyServer& bad_proxy, int net_error) override {}
-  void OnBeforeTunnelRequest(const HostPortPair& proxy_server,
-                             HttpRequestHeaders* extra_headers) override {}
-  void OnTunnelHeadersReceived(
-      const HostPortPair& origin,
-      const HostPortPair& proxy_server,
-      const HttpResponseHeaders& response_headers) override {}
   bool IsTrustedSpdyProxy(const net::ProxyServer& proxy_server) override {
     return true;
   }
@@ -249,20 +240,11 @@
                       const std::string& method,
                       const ProxyRetryInfoMap& proxy_retry_info,
                       ProxyInfo* result) override {}
-  void OnTunnelConnectCompleted(const HostPortPair& endpoint,
-                                const HostPortPair& proxy_server,
-                                int net_error) override {}
   void OnFallback(const ProxyServer& bad_proxy, int net_error) override {
     proxy_server_ = bad_proxy;
     proxy_fallback_net_error_ = net_error;
     on_proxy_fallback_called_ = true;
   }
-  void OnBeforeTunnelRequest(const HostPortPair& proxy_server,
-                             HttpRequestHeaders* extra_headers) override {}
-  void OnTunnelHeadersReceived(
-      const HostPortPair& origin,
-      const HostPortPair& proxy_server,
-      const HttpResponseHeaders& response_headers) override {}
   bool IsTrustedSpdyProxy(const net::ProxyServer& proxy_server) override {
     return true;
   }
diff --git a/net/socket/client_socket_pool_manager.cc b/net/socket/client_socket_pool_manager.cc
index ffec16f..85d614e3 100644
--- a/net/socket/client_socket_pool_manager.cc
+++ b/net/socket/client_socket_pool_manager.cc
@@ -177,8 +177,7 @@
           proxy_tcp_params, ssl_params, QUIC_VERSION_UNSUPPORTED, user_agent,
           origin_host_port, session->http_auth_cache(),
           session->http_auth_handler_factory(), session->spdy_session_pool(),
-          session->quic_stream_factory(), force_tunnel || using_ssl,
-          session->context().proxy_delegate);
+          session->quic_stream_factory(), force_tunnel || using_ssl);
     } else {
       DCHECK(proxy_info.is_socks());
       char socks_version;
diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc
index 2715d394..f102be2e 100644
--- a/net/socket/ssl_client_socket_pool_unittest.cc
+++ b/net/socket/ssl_client_socket_pool_unittest.cc
@@ -123,8 +123,7 @@
                                       session_->http_auth_handler_factory(),
                                       session_->spdy_session_pool(),
                                       session_->quic_stream_factory(),
-                                      true,
-                                      NULL)),
+                                      true)),
         http_proxy_socket_pool_(kMaxSockets,
                                 kMaxSocketsPerGroup,
                                 &transport_socket_pool_,
diff --git a/net/spdy/chromium/http2_push_promise_index.cc b/net/spdy/chromium/http2_push_promise_index.cc
index 48362a4..467207f8 100644
--- a/net/spdy/chromium/http2_push_promise_index.cc
+++ b/net/spdy/chromium/http2_push_promise_index.cc
@@ -84,10 +84,11 @@
   return it->stream_id;
 }
 
-void Http2PushPromiseIndex::FindSession(const SpdySessionKey& key,
-                                        const GURL& url,
-                                        base::WeakPtr<SpdySession>* session,
-                                        SpdyStreamId* stream_id) const {
+void Http2PushPromiseIndex::ClaimPushedStream(
+    const SpdySessionKey& key,
+    const GURL& url,
+    base::WeakPtr<SpdySession>* session,
+    SpdyStreamId* stream_id) {
   DCHECK(!url.is_empty());
 
   *session = nullptr;
@@ -106,6 +107,7 @@
       *session = it->delegate->GetWeakPtrToSession();
       *stream_id = it->stream_id;
       it->delegate->OnPushedStreamClaimed(it->url, it->stream_id);
+      unclaimed_pushed_streams_.erase(it);
       return;
     }
     ++it;
diff --git a/net/spdy/chromium/http2_push_promise_index.h b/net/spdy/chromium/http2_push_promise_index.h
index fd283bc..fe70c189 100644
--- a/net/spdy/chromium/http2_push_promise_index.h
+++ b/net/spdy/chromium/http2_push_promise_index.h
@@ -25,7 +25,7 @@
 
 }  // namespace test
 
-// Value returned by FindSession() and FindStream() if no stream is found.
+// Value returned by ClaimPushedStream() and FindStream() if no stream is found.
 const SpdyStreamId kNoPushedStreamFound = 0;
 
 // This class manages unclaimed pushed streams (push promises) from the receipt
@@ -88,13 +88,14 @@
 
   // If there exists a session compatible with |key| that has an unclaimed push
   // stream for |url|, then sets |*session| and |*stream| to one such session
-  // and stream.  Makes no guarantee on which (session, stream_id) pair it
-  // returns if there are multiple matches.  Sets |*session| to nullptr and
-  // |*stream| to kNoPushedStreamFound if no such session exists.
-  void FindSession(const SpdySessionKey& key,
-                   const GURL& url,
-                   base::WeakPtr<SpdySession>* session,
-                   SpdyStreamId* stream_id) const;
+  // and stream, and removes entry from index.  Makes no guarantee on which
+  // (session, stream_id) pair is claimed if there are multiple matches.  Sets
+  // |*session| to nullptr and |*stream| to kNoPushedStreamFound if no such
+  // session exists.
+  void ClaimPushedStream(const SpdySessionKey& key,
+                         const GURL& url,
+                         base::WeakPtr<SpdySession>* session,
+                         SpdyStreamId* stream_id);
 
   // Return the estimate of dynamically allocated memory in bytes.
   size_t EstimateMemoryUsage() const;
diff --git a/net/spdy/chromium/http2_push_promise_index_test.cc b/net/spdy/chromium/http2_push_promise_index_test.cc
index 47caeda..21e8d6b9 100644
--- a/net/spdy/chromium/http2_push_promise_index_test.cc
+++ b/net/spdy/chromium/http2_push_promise_index_test.cc
@@ -217,24 +217,24 @@
   EXPECT_EQ(kNoPushedStreamFound, index_.FindStream(url2_, &delegate2));
 }
 
-// If |index_| is empty, then FindSession() should set its |stream_id| outparam
-// to kNoPushedStreamFound for any values of inparams.
+// If |index_| is empty, then ClaimPushedStream() should set its |stream_id|
+// outparam to kNoPushedStreamFound for any values of inparams.
 TEST_F(Http2PushPromiseIndexTest, Empty) {
   base::WeakPtr<SpdySession> session;
   SpdyStreamId stream_id = 2;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
   stream_id = 2;
-  index_.FindSession(key1_, url2_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url2_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
   stream_id = 2;
-  index_.FindSession(key1_, url2_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url2_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
   stream_id = 2;
-  index_.FindSession(key2_, url2_, &session, &stream_id);
+  index_.ClaimPushedStream(key2_, url2_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 }
 
@@ -246,52 +246,48 @@
   TestDelegate delegate1(key1_);
   EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url1_, 2, &delegate1));
 
-  // Retrieve first entry by its URL, no entry found for |url2_|.
+  // No entry found for |url2_|.
   base::WeakPtr<SpdySession> session;
-  SpdyStreamId stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
-  EXPECT_EQ(2u, stream_id);
-
-  stream_id = 2;
-  index_.FindSession(key1_, url2_, &session, &stream_id);
+  SpdyStreamId stream_id = 2;
+  index_.ClaimPushedStream(key1_, url2_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
-  // Register second entry.
+  // Claim first entry.
+  stream_id = kNoPushedStreamFound;
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
+  EXPECT_EQ(2u, stream_id);
+
+  // ClaimPushedStream() unregistered first entry, cannot claim it again.
+  stream_id = 2;
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
+  EXPECT_EQ(kNoPushedStreamFound, stream_id);
+
+  // Register two entries.  Second entry uses same key.
+  EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url1_, 2, &delegate1));
   TestDelegate delegate2(key1_);
   EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url2_, 4, &delegate2));
 
   // Retrieve each entry by their respective URLs.
   stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
   EXPECT_EQ(2u, stream_id);
 
   stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url2_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url2_, &session, &stream_id);
   EXPECT_EQ(4u, stream_id);
 
-  // Unregister first entry.
-  EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate1));
-
-  // No entry found for |url1_|, retrieve second entry by its URL.
+  // ClaimPushedStream() calls unregistered both entries,
+  // cannot claim them again.
   stream_id = 2;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
-  EXPECT_EQ(kNoPushedStreamFound, stream_id);
-
-  stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url2_, &session, &stream_id);
-  EXPECT_EQ(4u, stream_id);
-
-  // Unregister second entry.
-  EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url2_, 4, &delegate2));
-
-  // No entries found for either URL.
-  stream_id = 2;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
   stream_id = 2;
-  index_.FindSession(key1_, url2_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url2_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
+
+  EXPECT_FALSE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate1));
+  EXPECT_FALSE(index_.UnregisterUnclaimedPushedStream(url2_, 4, &delegate2));
 }
 
 // Create two entries with delegates that validate different SpdySessionKeys.
@@ -302,52 +298,48 @@
   TestDelegate delegate1(key1_);
   EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url1_, 2, &delegate1));
 
-  // Retrieve first entry by its SpdySessionKey, no entry found for |key2_|.
+  // No entry found for |key2_|.
   base::WeakPtr<SpdySession> session;
-  SpdyStreamId stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
-  EXPECT_EQ(2u, stream_id);
-
-  stream_id = 2;
-  index_.FindSession(key2_, url1_, &session, &stream_id);
+  SpdyStreamId stream_id = 2;
+  index_.ClaimPushedStream(key2_, url1_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
-  // Register second entry.
+  // Claim first entry.
+  stream_id = kNoPushedStreamFound;
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
+  EXPECT_EQ(2u, stream_id);
+
+  // ClaimPushedStream() unregistered first entry, cannot claim it again.
+  stream_id = 2;
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
+  EXPECT_EQ(kNoPushedStreamFound, stream_id);
+
+  // Register two entries.  Second entry uses same URL.
+  EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url1_, 2, &delegate1));
   TestDelegate delegate2(key2_);
   EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url1_, 4, &delegate2));
 
   // Retrieve each entry by their respective SpdySessionKeys.
   stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
   EXPECT_EQ(2u, stream_id);
 
   stream_id = kNoPushedStreamFound;
-  index_.FindSession(key2_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key2_, url1_, &session, &stream_id);
   EXPECT_EQ(4u, stream_id);
 
-  // Unregister first entry.
-  EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate1));
-
-  // No entry found for |key1_|, retrieve second entry by its SpdySessionKey.
+  // ClaimPushedStream() calls unregistered both entries,
+  // cannot claim them again.
   stream_id = 2;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
-  EXPECT_EQ(kNoPushedStreamFound, stream_id);
-
-  stream_id = kNoPushedStreamFound;
-  index_.FindSession(key2_, url1_, &session, &stream_id);
-  EXPECT_EQ(4u, stream_id);
-
-  // Unregister second entry.
-  EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url1_, 4, &delegate2));
-
-  // No entries found for either SpdySessionKeys.
-  stream_id = 2;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
   stream_id = 2;
-  index_.FindSession(key2_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key2_, url1_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
+
+  EXPECT_FALSE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate1));
+  EXPECT_FALSE(index_.UnregisterUnclaimedPushedStream(url1_, 4, &delegate2));
 }
 
 TEST_F(Http2PushPromiseIndexTest, MultipleMatchingStreams) {
@@ -358,34 +350,33 @@
   EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url1_, 2, &delegate1));
   EXPECT_TRUE(index_.RegisterUnclaimedPushedStream(url1_, 4, &delegate2));
 
-  // Test that FindSession() returns one of the two entries.  FindSession()
-  // makes no guarantee about which entry it returns if there are multiple
-  // matches.
+  // Test that ClaimPushedStream() returns one of the two entries.
+  // ClaimPushedStream() makes no guarantee about which entry it returns if
+  // there are multiple matches.
   base::WeakPtr<SpdySession> session;
-  SpdyStreamId stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
-  EXPECT_NE(kNoPushedStreamFound, stream_id);
+  SpdyStreamId stream_id1 = kNoPushedStreamFound;
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id1);
+  EXPECT_NE(kNoPushedStreamFound, stream_id1);
 
-  // Unregister the first entry.
-  EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate1));
+  // First call to ClaimPushedStream() unregistered one of the entries.
+  // Second call to ClaimPushedStream() must return the other entry.
+  SpdyStreamId stream_id2 = kNoPushedStreamFound;
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id2);
+  EXPECT_NE(kNoPushedStreamFound, stream_id2);
+  EXPECT_NE(stream_id1, stream_id2);
 
-  // Test that the second entry can still be retrieved.
-  stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
-  EXPECT_EQ(4u, stream_id);
+  // Two calls to ClaimPushedStream() unregistered both entries.
+  SpdyStreamId stream_id3 = 2;
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id3);
+  EXPECT_EQ(kNoPushedStreamFound, stream_id3);
 
-  // Unregister the second entry.
-  EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url1_, 4, &delegate2));
-
-  // Test that no entry is found.
-  stream_id = 2;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
-  EXPECT_EQ(kNoPushedStreamFound, stream_id);
+  EXPECT_FALSE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate1));
+  EXPECT_FALSE(index_.UnregisterUnclaimedPushedStream(url1_, 4, &delegate2));
 }
 
-// Test that Delegate::ValidatePushedStream() is called by FindSession(), and if
-// it returns true, then Delegate::OnPushedStreamClaimed() is called with the
-// appropriate arguments.
+// Test that Delegate::ValidatePushedStream() is called by ClaimPushedStream(),
+// and if it returns true, then Delegate::OnPushedStreamClaimed() is called with
+// the appropriate arguments.
 TEST_F(Http2PushPromiseIndexTest, MatchCallsOnPushedStreamClaimed) {
   MockDelegate delegate;
   EXPECT_CALL(delegate, ValidatePushedStream(key1_)).WillOnce(Return(true));
@@ -395,14 +386,16 @@
 
   base::WeakPtr<SpdySession> session;
   SpdyStreamId stream_id = kNoPushedStreamFound;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
   EXPECT_EQ(2u, stream_id);
 
-  EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate));
+  // ClaimPushedStream() unregistered the entry.
+  EXPECT_FALSE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate));
 };
 
-// Test that Delegate::ValidatePushedStream() is called by FindSession(), and if
-// it returns false, then Delegate::OnPushedStreamClaimed() is not called.
+// Test that Delegate::ValidatePushedStream() is called by ClaimPushedStream(),
+// and if it returns false, then Delegate::OnPushedStreamClaimed() is not
+// called.
 TEST_F(Http2PushPromiseIndexTest, MismatchDoesNotCallOnPushedStreamClaimed) {
   MockDelegate delegate;
   EXPECT_CALL(delegate, ValidatePushedStream(key1_)).WillOnce(Return(false));
@@ -412,7 +405,7 @@
 
   base::WeakPtr<SpdySession> session;
   SpdyStreamId stream_id = 2;
-  index_.FindSession(key1_, url1_, &session, &stream_id);
+  index_.ClaimPushedStream(key1_, url1_, &session, &stream_id);
   EXPECT_EQ(kNoPushedStreamFound, stream_id);
 
   EXPECT_TRUE(index_.UnregisterUnclaimedPushedStream(url1_, 2, &delegate));
diff --git a/net/spdy/chromium/spdy_http_stream.cc b/net/spdy/chromium/spdy_http_stream.cc
index 17a12834..aa35adc 100644
--- a/net/spdy/chromium/spdy_http_stream.cc
+++ b/net/spdy/chromium/spdy_http_stream.cc
@@ -33,11 +33,13 @@
 const size_t SpdyHttpStream::kRequestBodyBufferSize = 1 << 14;  // 16KB
 
 SpdyHttpStream::SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session,
+                               SpdyStreamId pushed_stream_id,
                                bool direct,
                                NetLogSource source_dependency)
     : MultiplexedHttpStream(
           std::make_unique<MultiplexedSessionHandle>(spdy_session)),
       spdy_session_(spdy_session),
+      pushed_stream_id_(pushed_stream_id),
       is_reused_(spdy_session_->IsReused()),
       source_dependency_(source_dependency),
       stream_(nullptr),
@@ -79,8 +81,9 @@
   // TODO(bnc): Remove this condition once pushed headers are properly
   // validated.  https://crbug.com/554220.
   if (request_info_->method == "GET") {
-    int error = spdy_session_->GetPushStream(request_info_->url, priority,
-                                             &stream_, stream_net_log);
+    int error =
+        spdy_session_->GetPushedStream(request_info_->url, pushed_stream_id_,
+                                       priority, &stream_, stream_net_log);
     if (error != OK)
       return error;
 
diff --git a/net/spdy/chromium/spdy_http_stream.h b/net/spdy/chromium/spdy_http_stream.h
index fabdbaa..7dac145 100644
--- a/net/spdy/chromium/spdy_http_stream.h
+++ b/net/spdy/chromium/spdy_http_stream.h
@@ -36,6 +36,7 @@
   static const size_t kRequestBodyBufferSize;
   // |spdy_session| must not be NULL.
   SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session,
+                 SpdyStreamId pushed_stream_id,
                  bool direct,
                  NetLogSource source_dependency);
   ~SpdyHttpStream() override;
@@ -133,6 +134,12 @@
   bool ShouldWaitForMoreBufferedData() const;
 
   const base::WeakPtr<SpdySession> spdy_session_;
+
+  // The ID of the pushed stream if one is claimed by this request.
+  // In this case, the request fails if it cannot use that pushed stream.
+  // Otherwise set to kNoPushedStreamFound.
+  const SpdyStreamId pushed_stream_id_;
+
   bool is_reused_;
   SpdyStreamRequest stream_request_;
   const NetLogSource source_dependency_;
diff --git a/net/spdy/chromium/spdy_http_stream_unittest.cc b/net/spdy/chromium/spdy_http_stream_unittest.cc
index d6d2778..5f3d5ddcd 100644
--- a/net/spdy/chromium/spdy_http_stream_unittest.cc
+++ b/net/spdy/chromium/spdy_http_stream_unittest.cc
@@ -197,8 +197,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  auto http_stream =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
   // Make sure getting load timing information the stream early does not crash.
   LoadTimingInfo load_timing_info;
   EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info));
@@ -254,8 +254,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  auto http_stream =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
 
   ASSERT_THAT(http_stream->InitializeStream(request.get(), DEFAULT_PRIORITY,
                                             net_log, CompletionCallback()),
@@ -316,8 +316,8 @@
   HttpResponseInfo response1;
   HttpRequestHeaders headers1;
   NetLogWithSource net_log;
-  auto http_stream1 =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream1 = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
 
   HttpRequestInfo request2;
   request2.method = "GET";
@@ -325,8 +325,8 @@
   TestCompletionCallback callback2;
   HttpResponseInfo response2;
   HttpRequestHeaders headers2;
-  auto http_stream2 =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream2 = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
 
   // First write.
   ASSERT_THAT(http_stream1->InitializeStream(&request1, DEFAULT_PRIORITY,
@@ -423,7 +423,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  SpdyHttpStream http_stream(session_, true, net_log.source());
+  SpdyHttpStream http_stream(session_, kNoPushedStreamFound, true,
+                             net_log.source());
   ASSERT_THAT(http_stream.InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                            CompletionCallback()),
               IsOk());
@@ -478,7 +479,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  SpdyHttpStream http_stream(session_, true, net_log.source());
+  SpdyHttpStream http_stream(session_, kNoPushedStreamFound, true,
+                             net_log.source());
   ASSERT_THAT(http_stream.InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                            CompletionCallback()),
               IsOk());
@@ -532,7 +534,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  SpdyHttpStream http_stream(session_, true, net_log.source());
+  SpdyHttpStream http_stream(session_, kNoPushedStreamFound, true,
+                             net_log.source());
   ASSERT_THAT(http_stream.InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                            CompletionCallback()),
               IsOk());
@@ -600,8 +603,8 @@
   upload_stream.AppendData(kUploadData, kUploadDataSize, false);
 
   NetLogWithSource net_log;
-  auto http_stream =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
   ASSERT_THAT(http_stream->InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                             CompletionCallback()),
               IsOk());
@@ -695,8 +698,8 @@
   upload_stream.AppendData(kUploadData, kUploadDataSize, false);
 
   NetLogWithSource net_log;
-  auto http_stream =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
   ASSERT_THAT(http_stream->InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                             CompletionCallback()),
               IsOk());
@@ -779,8 +782,8 @@
   upload_stream.AppendData("", 0, true);
 
   NetLogWithSource net_log;
-  auto http_stream =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
   ASSERT_THAT(http_stream->InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                             CompletionCallback()),
               IsOk());
@@ -839,8 +842,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  auto http_stream =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
   ASSERT_THAT(http_stream->InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                             CompletionCallback()),
               IsOk());
@@ -892,8 +895,8 @@
               IsOk());
 
   NetLogWithSource net_log;
-  auto http_stream =
-      std::make_unique<SpdyHttpStream>(session_, true, net_log.source());
+  auto http_stream = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
   ASSERT_THAT(http_stream->InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                             CompletionCallback()),
               IsOk());
@@ -997,7 +1000,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  SpdyHttpStream http_stream(session_, true, net_log.source());
+  SpdyHttpStream http_stream(session_, kNoPushedStreamFound, true,
+                             net_log.source());
   ASSERT_THAT(http_stream.InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                            CompletionCallback()),
               IsOk());
@@ -1050,7 +1054,8 @@
   HttpResponseInfo response;
   HttpRequestHeaders headers;
   NetLogWithSource net_log;
-  SpdyHttpStream http_stream(session_, true, net_log.source());
+  SpdyHttpStream http_stream(session_, kNoPushedStreamFound, true,
+                             net_log.source());
   ASSERT_THAT(http_stream.InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                            CompletionCallback()),
               IsOk());
@@ -1092,7 +1097,8 @@
   upload_stream.AppendData("", 0, true);
 
   NetLogWithSource net_log;
-  SpdyHttpStream http_stream(session_, true, net_log.source());
+  SpdyHttpStream http_stream(session_, kNoPushedStreamFound, true,
+                             net_log.source());
   ASSERT_THAT(http_stream.InitializeStream(&request, DEFAULT_PRIORITY, net_log,
                                            CompletionCallback()),
               IsOk());
@@ -1115,6 +1121,80 @@
   EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
 }
 
+// A SpdyHttpStream should use a matching pushed stream, even if it is
+// constructed with |pushed_stream_id == kNoPushedStreamFound|.
+TEST_F(SpdyHttpStreamTest, UsePushedStreamEvenWithKNoPushedStreamFound) {
+  SpdySerializedFrame req(
+      spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
+  SpdySerializedFrame priority(
+      spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true));
+  MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(priority, 2)};
+  SpdySerializedFrame push(spdy_util_.ConstructSpdyPush(
+      nullptr, 0, 2, 1, "https://www.example.org/foo.dat"));
+  SpdySerializedFrame resp(spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
+  MockRead reads[] = {CreateMockRead(push, 1), CreateMockRead(resp, 3),
+                      MockRead(ASYNC, 0, 4)};
+  InitSession(reads, arraysize(reads), writes, arraysize(writes));
+
+  // Create and initialize first stream.
+  NetLogWithSource net_log;
+  auto stream1 = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound, true, net_log.source());
+
+  HttpRequestInfo request1;
+  request1.method = "GET";
+  request1.url = url_;
+  int rv = stream1->InitializeStream(&request1, DEFAULT_PRIORITY, net_log,
+                                     CompletionCallback());
+  EXPECT_THAT(rv, IsOk());
+
+  // Send request.
+  HttpResponseInfo response1;
+  TestCompletionCallback callback1;
+  rv = stream1->SendRequest(HttpRequestHeaders(), &response1,
+                            callback1.callback());
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+  rv = callback1.WaitForResult();
+  EXPECT_THAT(rv, IsOk());
+
+  // Create and initialize second stream.
+  // This request must bind to the pushed stream, even though SpdyHttpStream
+  // constructor is called with kNoPushedStreamFound.
+  auto stream2 = std::make_unique<SpdyHttpStream>(
+      session_, kNoPushedStreamFound /* pushed_stream_id */, true,
+      net_log.source());
+
+  HttpRequestInfo request2;
+  request2.method = "GET";
+  request2.url = GURL("https://www.example.org/foo.dat");
+  rv = stream2->InitializeStream(&request2, DEFAULT_PRIORITY, net_log,
+                                 CompletionCallback());
+  EXPECT_THAT(rv, IsOk());
+
+  // Send request.  This should not send out any frames on the wire, because the
+  // request is served by a pushed stream.
+  HttpResponseInfo response2;
+  TestCompletionCallback callback2;
+  rv = stream2->SendRequest(HttpRequestHeaders(), &response2,
+                            callback2.callback());
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+  rv = callback2.WaitForResult();
+  EXPECT_THAT(rv, IsOk());
+
+  // Read response headers for the second request.
+  rv = stream2->ReadResponseHeaders(callback2.callback());
+  EXPECT_THAT(rv, IsOk());
+
+  // Read response headers for the first request.
+  rv = stream1->ReadResponseHeaders(callback1.callback());
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+  rv = callback1.WaitForResult();
+  EXPECT_THAT(rv, IsOk());
+
+  // Read EOF.
+  base::RunLoop().RunUntilIdle();
+}
+
 // TODO(willchan): Write a longer test for SpdyStream that exercises all
 // methods.
 
diff --git a/net/spdy/chromium/spdy_network_transaction_unittest.cc b/net/spdy/chromium/spdy_network_transaction_unittest.cc
index a70b626..edcde944 100644
--- a/net/spdy/chromium/spdy_network_transaction_unittest.cc
+++ b/net/spdy/chromium/spdy_network_transaction_unittest.cc
@@ -3030,6 +3030,63 @@
   EXPECT_TRUE(data.AllWriteDataConsumed());
 }
 
+TEST_F(SpdyNetworkTransactionTest, ServerCancelsPush) {
+  SpdySerializedFrame req(
+      spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
+  SpdySerializedFrame priority(
+      spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true));
+  MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(priority, 3)};
+
+  SpdySerializedFrame reply(spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
+  SpdySerializedFrame push(spdy_util_.ConstructSpdyPush(
+      nullptr, 0, 2, 1, GetDefaultUrlWithPath("/foo.dat").c_str()));
+  SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true));
+  SpdySerializedFrame rst(
+      spdy_util_.ConstructSpdyRstStream(2, ERROR_CODE_INTERNAL_ERROR));
+  MockRead reads[] = {CreateMockRead(reply, 1), CreateMockRead(push, 2),
+                      CreateMockRead(body, 4), CreateMockRead(rst, 5),
+                      MockRead(ASYNC, 0, 6)};
+
+  SequencedSocketData data(reads, arraysize(reads), writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  // First request to open up connection.
+  HttpNetworkTransaction* trans1 = helper.trans();
+  TestCompletionCallback callback1;
+  int rv = trans1->Start(&request_, callback1.callback(), log_);
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Read until response body arrives.  PUSH_PROMISE comes earlier.
+  rv = callback1.WaitForResult();
+  EXPECT_THAT(rv, IsOk());
+  HttpResponseInfo response = *trans1->GetResponseInfo();
+  EXPECT_TRUE(response.headers);
+  EXPECT_EQ("HTTP/1.1 200", response.headers->GetStatusLine());
+  SpdyString result;
+  ReadResult(trans1, &result);
+  EXPECT_EQ("hello!", result);
+
+  // Create request matching pushed stream.
+  HttpRequestInfo request = CreateGetPushRequest();
+  HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session());
+  TestCompletionCallback callback2;
+  rv = trans2.Start(&request, callback2.callback(), log_);
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Read RST_STREAM from server closing pushed stream.
+  rv = callback2.WaitForResult();
+  EXPECT_THAT(rv, IsError(ERR_SPDY_PUSHED_STREAM_NOT_AVAILABLE));
+
+  // Read EOF.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(data.AllReadDataConsumed());
+  EXPECT_TRUE(data.AllWriteDataConsumed());
+}
+
 // Regression test for https://crbug.com/727653.
 TEST_F(SpdyNetworkTransactionTest, RejectServerPushWithNoMethod) {
   SpdySerializedFrame req(
diff --git a/net/spdy/chromium/spdy_session.cc b/net/spdy/chromium/spdy_session.cc
index fac017b..0043b4cb 100644
--- a/net/spdy/chromium/spdy_session.cc
+++ b/net/spdy/chromium/spdy_session.cc
@@ -817,20 +817,45 @@
   net_log_.EndEvent(NetLogEventType::HTTP2_SESSION);
 }
 
-int SpdySession::GetPushStream(const GURL& url,
-                               RequestPriority priority,
-                               SpdyStream** stream,
-                               const NetLogWithSource& stream_net_log) {
+int SpdySession::GetPushedStream(const GURL& url,
+                                 SpdyStreamId pushed_stream_id,
+                                 RequestPriority priority,
+                                 SpdyStream** stream,
+                                 const NetLogWithSource& stream_net_log) {
   CHECK(!in_io_loop_);
 
+  *stream = nullptr;
+
   if (availability_state_ == STATE_DRAINING) {
-    *stream = nullptr;
     return ERR_CONNECTION_CLOSED;
   }
 
-  *stream = GetActivePushStream(url);
-  if (!*stream)
-    return OK;
+  if (pushed_stream_id == kNoPushedStreamFound) {
+    // Even if no pushed stream has been claimed earlier, there could still be
+    // one corresponding the request, if the scheme is http (those pushes are
+    // not considered by Http2PushPromiseIndex::ClaimPushedStream()), or if the
+    // pushed stream was opened in the meanwhile.
+    pushed_stream_id = pool_->push_promise_index()->FindStream(url, this);
+    if (pushed_stream_id == kNoPushedStreamFound)
+      return OK;
+
+    LogPushStreamClaimed(url, pushed_stream_id);
+    const bool success =
+        pool_->push_promise_index()->UnregisterUnclaimedPushedStream(
+            url, pushed_stream_id, this);
+    DCHECK(success);
+  }
+
+  DCHECK_GT(pushed_stream_id, kNoPushedStreamFound);
+
+  ActiveStreamMap::iterator active_it = active_streams_.find(pushed_stream_id);
+  if (active_it == active_streams_.end()) {
+    // A previously claimed pushed stream might not be available, for example,
+    // if the server has reset it in the meanwhile.
+    return ERR_SPDY_PUSHED_STREAM_NOT_AVAILABLE;
+  }
+
+  *stream = active_it->second;
 
   DCHECK_LT(streams_pushed_and_claimed_count_, streams_pushed_count_);
   streams_pushed_and_claimed_count_++;
@@ -2374,28 +2399,6 @@
   }
 }
 
-SpdyStream* SpdySession::GetActivePushStream(const GURL& url) {
-  const SpdyStreamId stream_id =
-      pool_->push_promise_index()->FindStream(url, this);
-  if (stream_id == kNoPushedStreamFound)
-    return nullptr;
-
-  const bool result =
-      pool_->push_promise_index()->UnregisterUnclaimedPushedStream(
-          url, stream_id, this);
-  DCHECK(result);
-
-  ActiveStreamMap::iterator active_it = active_streams_.find(stream_id);
-  if (active_it == active_streams_.end()) {
-    NOTREACHED();
-    return nullptr;
-  }
-
-  LogPushStreamClaimed(url, stream_id);
-
-  return active_it->second;
-}
-
 void SpdySession::RecordPingRTTHistogram(base::TimeDelta duration) {
   UMA_HISTOGRAM_CUSTOM_TIMES("Net.SpdyPing.RTT", duration,
                              base::TimeDelta::FromMilliseconds(1),
diff --git a/net/spdy/chromium/spdy_session.h b/net/spdy/chromium/spdy_session.h
index 101ba98..17cf4e0d 100644
--- a/net/spdy/chromium/spdy_session.h
+++ b/net/spdy/chromium/spdy_session.h
@@ -284,12 +284,22 @@
   // reset).  Returns an error (not ERR_IO_PENDING) otherwise, and
   // resets |spdy_stream|.
   //
+  // If |pushed_stream_id != kNoPushedStreamFound|, then the pushed stream with
+  // pushed_stream_id is used.  An error is returned if that stream is not
+  // available.
+  //
+  // If |pushed_stream_id == kNoPushedStreamFound|, then any matching pushed
+  // stream that has not been claimed by another request can be used.  This can
+  // happen, for example, with http scheme pushed streams, or if the pushed
+  // stream was received from the server in the meanwhile.
+  //
   // If a stream was found and the stream is still open, the priority
   // of that stream is updated to match |priority|.
-  int GetPushStream(const GURL& url,
-                    RequestPriority priority,
-                    SpdyStream** spdy_stream,
-                    const NetLogWithSource& stream_net_log);
+  int GetPushedStream(const GURL& url,
+                      SpdyStreamId pushed_stream_id,
+                      RequestPriority priority,
+                      SpdyStream** spdy_stream,
+                      const NetLogWithSource& stream_net_log);
 
   // Called when the pushed stream should be cancelled. If the pushed stream is
   // not claimed and active, sends RST to the server to cancel the stream.
@@ -695,11 +705,6 @@
   // that |stream| may hold the last reference to the session.
   void DeleteStream(std::unique_ptr<SpdyStream> stream, int status);
 
-  // Check if we have a pending pushed-stream for this url
-  // Returns the stream if found (and returns it from the pending
-  // list). Returns NULL otherwise.
-  SpdyStream* GetActivePushStream(const GURL& url);
-
   void RecordPingRTTHistogram(base::TimeDelta duration);
   void RecordHistograms();
   void RecordProtocolErrorHistogram(SpdyProtocolErrorDetails details);
diff --git a/net/spdy/chromium/spdy_session_key.cc b/net/spdy/chromium/spdy_session_key.cc
index 6395af71..be206e5 100644
--- a/net/spdy/chromium/spdy_session_key.cc
+++ b/net/spdy/chromium/spdy_session_key.cc
@@ -12,8 +12,7 @@
 
 namespace net {
 
-SpdySessionKey::SpdySessionKey() : privacy_mode_(PRIVACY_MODE_DISABLED) {
-}
+SpdySessionKey::SpdySessionKey() = default;
 
 SpdySessionKey::SpdySessionKey(const HostPortPair& host_port_pair,
                                const ProxyServer& proxy_server,
@@ -25,15 +24,6 @@
       << ", privacy=" << privacy_mode;
 }
 
-SpdySessionKey::SpdySessionKey(const HostPortProxyPair& host_port_proxy_pair,
-                               PrivacyMode privacy_mode)
-    : host_port_proxy_pair_(host_port_proxy_pair),
-      privacy_mode_(privacy_mode) {
-  DVLOG(1) << "SpdySessionKey(hppp=" << host_port_proxy_pair.first.ToString()
-      << "," << host_port_proxy_pair.second.ToURI()
-      << ", privacy=" << privacy_mode;
-}
-
 SpdySessionKey::SpdySessionKey(const SpdySessionKey& other) = default;
 
 SpdySessionKey::~SpdySessionKey() = default;
diff --git a/net/spdy/chromium/spdy_session_key.h b/net/spdy/chromium/spdy_session_key.h
index 61d78194..ef5ac19 100644
--- a/net/spdy/chromium/spdy_session_key.h
+++ b/net/spdy/chromium/spdy_session_key.h
@@ -19,10 +19,6 @@
                  const ProxyServer& proxy_server,
                  PrivacyMode privacy_mode);
 
-  // Temporary hack for implicit copy constructor
-  SpdySessionKey(const HostPortProxyPair& host_port_proxy_pair,
-                 PrivacyMode privacy_mode);
-
   SpdySessionKey(const SpdySessionKey& other);
 
   ~SpdySessionKey();
@@ -55,7 +51,7 @@
  private:
   HostPortProxyPair host_port_proxy_pair_;
   // If enabled, then session cannot be tracked by the server.
-  PrivacyMode privacy_mode_;
+  PrivacyMode privacy_mode_ = PRIVACY_MODE_DISABLED;
 };
 
 }  // namespace net
diff --git a/net/spdy/chromium/spdy_session_pool.cc b/net/spdy/chromium/spdy_session_pool.cc
index e9536fa..312e581a 100644
--- a/net/spdy/chromium/spdy_session_pool.cc
+++ b/net/spdy/chromium/spdy_session_pool.cc
@@ -364,7 +364,8 @@
           direct || request->url().SchemeIs(url::kHttpsScheme);
       request->OnStreamReadyOnPooledConnection(
           used_ssl_config, used_proxy_info,
-          std::make_unique<SpdyHttpStream>(spdy_session, use_relative_url,
+          std::make_unique<SpdyHttpStream>(spdy_session, kNoPushedStreamFound,
+                                           use_relative_url,
                                            source_dependency));
     }
   }
diff --git a/net/spdy/chromium/spdy_session_unittest.cc b/net/spdy/chromium/spdy_session_unittest.cc
index 3e5da06..8adf0cb 100644
--- a/net/spdy/chromium/spdy_session_unittest.cc
+++ b/net/spdy/chromium/spdy_session_unittest.cc
@@ -1667,8 +1667,8 @@
   EXPECT_EQ(0, session_unacked_recv_window_bytes());
 
   SpdyStream* spdy_stream2;
-  rv = session_->GetPushStream(pushed_url, MEDIUM, &spdy_stream2,
-                               NetLogWithSource());
+  rv = session_->GetPushedStream(pushed_url, kNoPushedStreamFound, MEDIUM,
+                                 &spdy_stream2, NetLogWithSource());
   ASSERT_THAT(rv, IsOk());
   ASSERT_TRUE(spdy_stream2);
 
@@ -5569,8 +5569,8 @@
   EXPECT_EQ(0u, num_active_pushed_streams());
 
   SpdyStream* pushed_stream;
-  int rv = session_->GetPushStream(GURL(kPushedUrl), IDLE, &pushed_stream,
-                                   NetLogWithSource());
+  int rv = session_->GetPushedStream(GURL(kPushedUrl), kNoPushedStreamFound,
+                                     IDLE, &pushed_stream, NetLogWithSource());
   ASSERT_THAT(rv, IsOk());
   ASSERT_TRUE(pushed_stream);
   test::StreamDelegateCloseOnHeaders delegate2(pushed_stream->GetWeakPtr());
@@ -5592,6 +5592,113 @@
   EXPECT_TRUE(data.AllReadDataConsumed());
 }
 
+// Test GetPushedStream() behavior with |pushed_stream_id| arguments different
+// from kNoPushedStreamFound.
+TEST_F(SpdySessionTest, GetPushedStream) {
+  const char kPushedUrl[] = "https://www.example.org/a.dat";
+  SpdyHeaderBlock push_headers;
+  push_headers[":method"] = "GET";
+  spdy_util_.AddUrlToHeaderBlock(kPushedUrl, &push_headers);
+  SpdySerializedFrame push_promise(
+      spdy_util_.ConstructInitialSpdyPushFrame(std::move(push_headers), 2, 1));
+  SpdySerializedFrame headers_frame(
+      spdy_util_.ConstructSpdyPushHeaders(2, nullptr, 0));
+  MockRead reads[] = {
+      MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(push_promise, 2),
+      MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(headers_frame, 5),
+      MockRead(ASYNC, ERR_IO_PENDING, 7), MockRead(ASYNC, 0, 8)};
+
+  SpdySerializedFrame req(
+      spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
+  SpdySerializedFrame priority(
+      spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true));
+  SpdySerializedFrame rst(
+      spdy_util_.ConstructSpdyRstStream(2, ERROR_CODE_CANCEL));
+  MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(priority, 3),
+                        CreateMockWrite(rst, 6)};
+
+  SequencedSocketData data(reads, arraysize(reads), writes, arraysize(writes));
+  session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+  AddSSLSocketData();
+
+  CreateNetworkSession();
+  CreateSpdySession();
+
+  base::WeakPtr<SpdyStream> spdy_stream1 =
+      CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
+                                test_url_, LOWEST, NetLogWithSource());
+  ASSERT_TRUE(spdy_stream1);
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+  test::StreamDelegateDoNothing delegate1(spdy_stream1);
+  spdy_stream1->SetDelegate(&delegate1);
+
+  EXPECT_EQ(0u, num_active_streams());
+  EXPECT_EQ(1u, num_created_streams());
+  EXPECT_EQ(0u, num_pushed_streams());
+  EXPECT_EQ(0u, num_active_pushed_streams());
+
+  SpdyHeaderBlock headers(spdy_util_.ConstructGetHeaderBlock(kDefaultUrl));
+  spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
+
+  // Activate first request.
+  EXPECT_EQ(0u, delegate1.stream_id());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, delegate1.stream_id());
+  EXPECT_EQ(1u, num_active_streams());
+  EXPECT_EQ(0u, num_created_streams());
+  EXPECT_EQ(0u, num_pushed_streams());
+  EXPECT_EQ(0u, num_active_pushed_streams());
+
+  // No streams are pushed yet, therefore GetPushedStream() should return an
+  // error.
+  SpdyStream* pushed_stream;
+  int rv = session_->GetPushedStream(GURL(kPushedUrl), 2 /* pushed_stream_id */,
+                                     IDLE, &pushed_stream, NetLogWithSource());
+  EXPECT_THAT(rv, IsError(ERR_SPDY_PUSHED_STREAM_NOT_AVAILABLE));
+
+  // Read PUSH_PROMISE.
+  data.Resume();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, num_active_streams());
+  EXPECT_EQ(0u, num_created_streams());
+  EXPECT_EQ(1u, num_pushed_streams());
+  EXPECT_EQ(0u, num_active_pushed_streams());
+
+  // GetPushedStream() should return an error if there does not exist a pushed
+  // stream with ID |pushed_stream_id|.
+  rv = session_->GetPushedStream(GURL(kPushedUrl), 4 /* pushed_stream_id */,
+                                 IDLE, &pushed_stream, NetLogWithSource());
+  EXPECT_THAT(rv, IsError(ERR_SPDY_PUSHED_STREAM_NOT_AVAILABLE));
+
+  // GetPushedStream() should return OK and return the pushed stream in
+  // |pushed_stream| outparam if |pushed_stream_id| matches.
+  rv = session_->GetPushedStream(GURL(kPushedUrl), 2 /* pushed_stream_id */,
+                                 IDLE, &pushed_stream, NetLogWithSource());
+  EXPECT_THAT(rv, IsOk());
+  ASSERT_TRUE(pushed_stream);
+  test::StreamDelegateCloseOnHeaders delegate2(pushed_stream->GetWeakPtr());
+  pushed_stream->SetDelegate(&delegate2);
+
+  // Upon reading pushed headers, delegate closes the stream.
+  data.Resume();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, num_active_streams());
+  EXPECT_EQ(0u, num_created_streams());
+  EXPECT_EQ(0u, num_pushed_streams());
+  EXPECT_EQ(0u, num_active_pushed_streams());
+
+  // Read EOF.
+  data.Resume();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(delegate1.StreamIsClosed());
+  EXPECT_TRUE(delegate2.StreamIsClosed());
+
+  EXPECT_TRUE(data.AllWriteDataConsumed());
+  EXPECT_TRUE(data.AllReadDataConsumed());
+}
+
 TEST_F(SpdySessionTest, RejectInvalidUnknownFrames) {
   session_deps_.host_resolver->set_synchronous_mode(true);
 
diff --git a/net/spdy/chromium/spdy_stream_unittest.cc b/net/spdy/chromium/spdy_stream_unittest.cc
index 8f546f81..cf6482d8 100644
--- a/net/spdy/chromium/spdy_stream_unittest.cc
+++ b/net/spdy/chromium/spdy_stream_unittest.cc
@@ -344,8 +344,8 @@
   data.RunUntilPaused();
 
   SpdyStream* push_stream;
-  EXPECT_THAT(session->GetPushStream(GURL(kPushUrl), IDLE, &push_stream,
-                                     NetLogWithSource()),
+  EXPECT_THAT(session->GetPushedStream(GURL(kPushUrl), kNoPushedStreamFound,
+                                       IDLE, &push_stream, NetLogWithSource()),
               IsOk());
   ASSERT_TRUE(push_stream);
   EXPECT_EQ(kPushUrl, push_stream->GetUrlFromHeaders().spec());
@@ -657,8 +657,8 @@
   data.RunUntilPaused();
 
   SpdyStream* push_stream;
-  EXPECT_THAT(session->GetPushStream(GURL(kPushUrl), IDLE, &push_stream,
-                                     NetLogWithSource()),
+  EXPECT_THAT(session->GetPushedStream(GURL(kPushUrl), kNoPushedStreamFound,
+                                       IDLE, &push_stream, NetLogWithSource()),
               IsOk());
   EXPECT_FALSE(push_stream);
 
diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc
index 4171bbc..75a4cf8 100644
--- a/net/test/spawned_test_server/base_test_server.cc
+++ b/net/test/spawned_test_server/base_test_server.cc
@@ -508,6 +508,12 @@
     arguments->Set("no-anonymous-ftp-user", std::make_unique<base::Value>());
   }
 
+  if (redirect_connect_to_localhost_) {
+    DCHECK_EQ(TYPE_BASIC_AUTH_PROXY, type_);
+    arguments->Set("redirect-connect-to-localhost",
+                   std::make_unique<base::Value>());
+  }
+
   if (UsingSSL(type_)) {
     // Check the certificate arguments of the HTTPS server.
     base::FilePath certificate_path(certificates_dir_);
diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h
index a3592ac4..c279b6c 100644
--- a/net/test/spawned_test_server/base_test_server.h
+++ b/net/test/spawned_test_server/base_test_server.h
@@ -370,6 +370,11 @@
     no_anonymous_ftp_user_ = no_anonymous_ftp_user;
   }
 
+  // Redirect proxied CONNECT requests to localhost.
+  void set_redirect_connect_to_localhost(bool redirect_connect_to_localhost) {
+    redirect_connect_to_localhost_ = redirect_connect_to_localhost;
+  }
+
   // Marks the root certificate of an HTTPS test server as trusted for
   // the duration of tests.
   bool LoadTestRootCert() const WARN_UNUSED_RESULT;
@@ -454,6 +459,9 @@
   // Disable creation of anonymous FTP user?
   bool no_anonymous_ftp_user_ = false;
 
+  // Redirect proxied CONNECT requests to localhost?
+  bool redirect_connect_to_localhost_ = false;
+
   std::unique_ptr<ScopedPortException> allowed_port_;
 
   DISALLOW_COPY_AND_ASSIGN(BaseTestServer);
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index 634b41e..0576ac6 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -1765,6 +1765,7 @@
   """
 
   _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
+  redirect_connect_to_localhost = False;
 
   def parse_request(self):
     """Overrides parse_request to check credential."""
@@ -1850,6 +1851,9 @@
       self.send_response(400)
       self.end_headers()
 
+    if BasicAuthProxyRequestHandler.redirect_connect_to_localhost:
+      host = "127.0.0.1"
+
     try:
       sock = socket.create_connection((host, port))
       self.send_response(200, 'Connection established')
@@ -2108,6 +2112,8 @@
       print 'Echo UDP server started on port %d...' % server.server_port
       server_data['port'] = server.server_port
     elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
+      BasicAuthProxyRequestHandler.redirect_connect_to_localhost = \
+          self.options.redirect_connect_to_localhost
       server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
       print 'BasicAuthProxy server started on port %d...' % server.server_port
       server_data['port'] = server.server_port
@@ -2323,6 +2329,12 @@
                                   action='store_true')
     self.option_parser.add_option('--token-binding-params', action='append',
                                   default=[], type='int')
+    self.option_parser.add_option('--redirect-connect-to-localhost',
+                                  dest='redirect_connect_to_localhost',
+                                  default=False, action='store_true',
+                                  help='If set, the Proxy server will connect '
+                                  'to localhost instead of the requested URL '
+                                  'on CONNECT requests')
 
 
 if __name__ == '__main__':
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
index 27891f9..d869f83 100644
--- a/net/websockets/websocket_end_to_end_test.cc
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -227,16 +227,7 @@
     resolved_proxy_info_.proxy_info = *result;
   }
 
-  void OnTunnelConnectCompleted(const HostPortPair& endpoint,
-                                const HostPortPair& proxy_server,
-                                int net_error) override {}
   void OnFallback(const ProxyServer& bad_proxy, int net_error) override {}
-  void OnBeforeTunnelRequest(const HostPortPair& proxy_server,
-                             HttpRequestHeaders* extra_headers) override {}
-  void OnTunnelHeadersReceived(
-      const HostPortPair& origin,
-      const HostPortPair& proxy_server,
-      const HttpResponseHeaders& response_headers) override {}
   bool IsTrustedSpdyProxy(const net::ProxyServer& proxy_server) override {
     return true;
   }
diff --git a/sandbox/linux/tests/unit_tests.cc b/sandbox/linux/tests/unit_tests.cc
index 1b28aa2..515d0ea 100644
--- a/sandbox/linux/tests/unit_tests.cc
+++ b/sandbox/linux/tests/unit_tests.cc
@@ -135,16 +135,7 @@
   // We need to fork(), so we can't be multi-threaded, as threads could hold
   // locks.
   int num_threads = CountThreads();
-#if !defined(THREAD_SANITIZER)
   const int kNumExpectedThreads = 1;
-#else
-  // Under TSAN, there is a special helper thread. It should be completely
-  // invisible to our testing, so we ignore it. It should be ok to fork()
-  // with this thread. It's currently buggy, but it's the best we can do until
-  // there is a way to delay the start of the thread
-  // (https://code.google.com/p/thread-sanitizer/issues/detail?id=19).
-  const int kNumExpectedThreads = 2;
-#endif
 
   // The kernel is at liberty to wake a thread id futex before updating /proc.
   // If another test running in the same process has stopped a thread, it may
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
new file mode 100644
index 0000000..ac126434
--- /dev/null
+++ b/services/network/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+static_library("network_service") {
+  sources = [
+    "cookie_manager.cc",
+    "cookie_manager.h",
+  ]
+
+  deps = [
+    "//base",
+    "//mojo/public/cpp/bindings",
+    "//net",
+    "//services/network/public/interfaces",
+    "//url",
+  ]
+}
+
+source_set("tests") {
+  testonly = true
+
+  sources = [
+    "cookie_manager_unittest.cc",
+  ]
+
+  deps = [
+    "//base",
+    "//mojo/public/cpp/bindings",
+    "//net",
+    "//net:test_support",
+    "//services/network:network_service",
+    "//services/network/public/interfaces",
+    "//testing/gtest",
+  ]
+}
diff --git a/content/network/cookie_manager.cc b/services/network/cookie_manager.cc
similarity index 86%
rename from content/network/cookie_manager.cc
rename to services/network/cookie_manager.cc
index 3214409..0012abb 100644
--- a/content/network/cookie_manager.cc
+++ b/services/network/cookie_manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/network/cookie_manager.h"
+#include "services/network/cookie_manager.h"
 
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/cookies/canonical_cookie.h"
@@ -10,7 +10,7 @@
 #include "net/cookies/cookie_options.h"
 #include "url/gurl.h"
 
-namespace content {
+namespace network {
 
 namespace {
 
@@ -195,8 +195,9 @@
 
   cookie_store_->DeleteAllCreatedBetweenWithPredicateAsync(
       start_time, end_time,
-      base::Bind(&PredicateWrapper::Predicate,
-                 std::make_unique<PredicateWrapper>(std::move(filter))),
+      base::BindRepeating(
+          &PredicateWrapper::Predicate,
+          std::make_unique<PredicateWrapper>(std::move(filter))),
       std::move(callback));
 }
 
@@ -211,17 +212,18 @@
 
   notification_registration->subscription = cookie_store_->AddCallbackForCookie(
       url, name,
-      base::Bind(&CookieManager::CookieChanged,
-                 // base::Unretained is safe as destruction of the
-                 // CookieManager will also destroy the
-                 // notifications_registered list (which this object will be
-                 // inserted into, below), which will destroy the
-                 // CookieChangedSubscription, unregistering the callback.
-                 base::Unretained(this),
-                 // base::Unretained is safe as destruction of the
-                 // NotificationRegistration will also destroy the
-                 // CookieChangedSubscription, unregistering the callback.
-                 base::Unretained(notification_registration.get())));
+      base::BindRepeating(
+          &CookieManager::CookieChanged,
+          // base::Unretained is safe as destruction of the
+          // CookieManager will also destroy the
+          // notifications_registered list (which this object will be
+          // inserted into, below), which will destroy the
+          // CookieChangedSubscription, unregistering the callback.
+          base::Unretained(this),
+          // base::Unretained is safe as destruction of the
+          // NotificationRegistration will also destroy the
+          // CookieChangedSubscription, unregistering the callback.
+          base::Unretained(notification_registration.get())));
 
   notification_registration->notification_pointer.set_connection_error_handler(
       base::BindOnce(&CookieManager::NotificationPipeBroken,
@@ -247,18 +249,18 @@
       std::move(notification_pointer);
 
   notification_registration->subscription =
-      cookie_store_->AddCallbackForAllChanges(
-          base::Bind(&CookieManager::CookieChanged,
-                     // base::Unretained is safe as destruction of the
-                     // CookieManager will also destroy the
-                     // notifications_registered list (which this object will be
-                     // inserted into, below), which will destroy the
-                     // CookieChangedSubscription, unregistering the callback.
-                     base::Unretained(this),
-                     // base::Unretained is safe as destruction of the
-                     // NotificationRegistration will also destroy the
-                     // CookieChangedSubscription, unregistering the callback.
-                     base::Unretained(notification_registration.get())));
+      cookie_store_->AddCallbackForAllChanges(base::BindRepeating(
+          &CookieManager::CookieChanged,
+          // base::Unretained is safe as destruction of the
+          // CookieManager will also destroy the
+          // notifications_registered list (which this object will be
+          // inserted into, below), which will destroy the
+          // CookieChangedSubscription, unregistering the callback.
+          base::Unretained(this),
+          // base::Unretained is safe as destruction of the
+          // NotificationRegistration will also destroy the
+          // CookieChangedSubscription, unregistering the callback.
+          base::Unretained(notification_registration.get())));
 
   notification_registration->notification_pointer.set_connection_error_handler(
       base::BindOnce(&CookieManager::NotificationPipeBroken,
@@ -303,4 +305,4 @@
   AddRequest(std::move(new_interface));
 }
 
-}  // namespace content
+}  // namespace network
diff --git a/content/network/cookie_manager.h b/services/network/cookie_manager.h
similarity index 92%
rename from content/network/cookie_manager.h
rename to services/network/cookie_manager.h
index c623d8e..f2c0139 100644
--- a/content/network/cookie_manager.h
+++ b/services/network/cookie_manager.h
@@ -2,29 +2,28 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_NETWORK_COOKIE_MANAGER_H_
-#define CONTENT_NETWORK_COOKIE_MANAGER_H_
+#ifndef SERVICES_NETWORK_COOKIE_MANAGER_H_
+#define SERVICES_NETWORK_COOKIE_MANAGER_H_
 
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "base/macros.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "net/cookies/cookie_store.h"
 #include "services/network/public/interfaces/cookie_manager.mojom.h"
 
 class GURL;
 
-namespace content {
+namespace network {
 
 // Wrap a cookie store in an implementation of the mojo cookie interface.
 
 // This is an IO thread object; all methods on this object must be called on
 // the IO thread.  Note that this does not restrict the locations from which
 // mojo messages may be sent to the object.
-class CONTENT_EXPORT CookieManager : public network::mojom::CookieManager {
+class CookieManager : public network::mojom::CookieManager {
  public:
   // Construct a CookieService that can serve mojo requests for the underlying
   // cookie store.  |*cookie_store| must outlive this object.
@@ -94,6 +93,6 @@
   DISALLOW_COPY_AND_ASSIGN(CookieManager);
 };
 
-}  // namespace content
+}  // namespace network
 
-#endif  // CONTENT_NETWORK_COOKIE_MANAGER_H_
+#endif  // SERVICES_NETWORK_COOKIE_MANAGER_H_
diff --git a/content/network/cookie_manager_unittest.cc b/services/network/cookie_manager_unittest.cc
similarity index 99%
rename from content/network/cookie_manager_unittest.cc
rename to services/network/cookie_manager_unittest.cc
index b20d180..3f4d1664 100644
--- a/content/network/cookie_manager_unittest.cc
+++ b/services/network/cookie_manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/network/cookie_manager.h"
+#include "services/network/cookie_manager.h"
 
 #include <algorithm>
 
@@ -35,7 +35,7 @@
 //        sort cookie list responses from the network::mojom::CookieManager.
 //      * CompareCookiesByValue: As above, but only by value.
 
-namespace content {
+namespace network {
 
 // Wraps a network::mojom::CookieManager in synchronous, blocking calls to make
 // it easier to test.
@@ -199,7 +199,7 @@
 
   base::MessageLoopForIO message_loop_;
   net::CookieMonster cookie_monster_;
-  std::unique_ptr<content::CookieManager> cookie_service_;
+  std::unique_ptr<CookieManager> cookie_service_;
   network::mojom::CookieManagerPtr cookie_service_ptr_;
   std::unique_ptr<SynchronousCookieManager> service_wrapper_;
 
@@ -1799,4 +1799,4 @@
   EXPECT_EQ(1u, service()->GetClientsBoundForTesting());
 }
 
-}  // namespace content
+}  // namespace network
diff --git a/services/network/public/cpp/proxy_resolving_client_socket.cc b/services/network/public/cpp/proxy_resolving_client_socket.cc
index 3646fdf..2e95253 100644
--- a/services/network/public/cpp/proxy_resolving_client_socket.cc
+++ b/services/network/public/cpp/proxy_resolving_client_socket.cc
@@ -164,163 +164,6 @@
   return net::ERR_IO_PENDING;
 }
 
-void ProxyResolvingClientSocket::ConnectToProxy(int net_error) {
-  pac_request_ = NULL;
-
-  DCHECK_NE(net_error, net::ERR_IO_PENDING);
-  if (net_error == net::OK) {
-    // Removes unsupported proxies from the list. Currently, this removes
-    // just the SCHEME_QUIC proxy, which doesn't yet support tunneling.
-    // TODO(xunjieli): Allow QUIC proxy once it supports tunneling.
-    proxy_info_.RemoveProxiesWithoutScheme(
-        net::ProxyServer::SCHEME_DIRECT | net::ProxyServer::SCHEME_HTTP |
-        net::ProxyServer::SCHEME_HTTPS | net::ProxyServer::SCHEME_SOCKS4 |
-        net::ProxyServer::SCHEME_SOCKS5);
-
-    if (proxy_info_.is_empty()) {
-      // No proxies/direct to choose from. This happens when we don't support
-      // any of the proxies in the returned list.
-      net_error = net::ERR_NO_SUPPORTED_PROXIES;
-    }
-  }
-
-  // TODO(xunjieli): This results in retrying "Direct" twice, because of
-  // ReconsiderProxyAfterError() path. https://crbug.com/793076.
-  // Try falling back to a direct connection if we have not tried that before.
-  if (net_error != net::OK) {
-    if (!tried_direct_connect_fallback_) {
-      tried_direct_connect_fallback_ = true;
-      proxy_info_.UseDirect();
-    } else {
-      CloseTransportSocket();
-      base::ResetAndReturn(&user_connect_callback_).Run(net_error);
-      return;
-    }
-  }
-
-  transport_.reset(new net::ClientSocketHandle);
-  // Now that the proxy is resolved, issue a socket connect.
-  net::HostPortPair host_port_pair = net::HostPortPair::FromURL(url_);
-  net_error = net::InitSocketHandleForRawConnect(
-      host_port_pair, network_session_.get(), proxy_info_, ssl_config_,
-      ssl_config_, net::PRIVACY_MODE_DISABLED, net_log_, transport_.get(),
-      base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxyDone,
-                          base::Unretained(this)));
-  if (net_error != net::ERR_IO_PENDING) {
-    // Since this method is always called asynchronously. it is OK to call
-    // ConnectToProxyDone synchronously.
-    ConnectToProxyDone(net_error);
-  }
-}
-
-void ProxyResolvingClientSocket::ConnectToProxyDone(int net_error) {
-  if (net_error != net::OK) {
-    // If the connection fails, try another proxy.
-    net_error = ReconsiderProxyAfterError(net_error);
-    // ReconsiderProxyAfterError either returns an error (in which case it is
-    // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering
-    // another proxy.
-    DCHECK_NE(net_error, net::OK);
-    if (net_error == net::ERR_IO_PENDING) {
-      // Proxy reconsideration pending. Return.
-      return;
-    }
-    CloseTransportSocket();
-  } else {
-    network_session_->proxy_service()->ReportSuccess(proxy_info_, NULL);
-  }
-  base::ResetAndReturn(&user_connect_callback_).Run(net_error);
-}
-
-// TODO(xunjieli): This following method is out of sync with
-// HttpStreamFactoryImpl::JobController. The logic should be refactored into a
-// common place.
-// This method reconsiders the proxy on certain errors. If it does
-// reconsider a proxy it always returns ERR_IO_PENDING and posts a call to
-// ConnectToProxy with the result of the reconsideration.
-int ProxyResolvingClientSocket::ReconsiderProxyAfterError(int error) {
-  DCHECK(!pac_request_);
-  DCHECK_NE(error, net::OK);
-  DCHECK_NE(error, net::ERR_IO_PENDING);
-  // A failure to resolve the hostname or any error related to establishing a
-  // TCP connection could be grounds for trying a new proxy configuration.
-  //
-  // Why do this when a hostname cannot be resolved?  Some URLs only make sense
-  // to proxy servers.  The hostname in those URLs might fail to resolve if we
-  // are still using a non-proxy config.  We need to check if a proxy config
-  // now exists that corresponds to a proxy server that could load the URL.
-  //
-  switch (error) {
-    case net::ERR_PROXY_CONNECTION_FAILED:
-    case net::ERR_NAME_NOT_RESOLVED:
-    case net::ERR_INTERNET_DISCONNECTED:
-    case net::ERR_ADDRESS_UNREACHABLE:
-    case net::ERR_CONNECTION_CLOSED:
-    case net::ERR_CONNECTION_RESET:
-    case net::ERR_CONNECTION_REFUSED:
-    case net::ERR_CONNECTION_ABORTED:
-    case net::ERR_TIMED_OUT:
-    case net::ERR_TUNNEL_CONNECTION_FAILED:
-    case net::ERR_SOCKS_CONNECTION_FAILED:
-      break;
-    case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
-      // Remap the SOCKS-specific "host unreachable" error to a more
-      // generic error code (this way consumers like the link doctor
-      // know to substitute their error page).
-      //
-      // Note that if the host resolving was done by the SOCSK5 proxy, we can't
-      // differentiate between a proxy-side "host not found" versus a proxy-side
-      // "address unreachable" error, and will report both of these failures as
-      // ERR_ADDRESS_UNREACHABLE.
-      return net::ERR_ADDRESS_UNREACHABLE;
-    case net::ERR_PROXY_AUTH_REQUESTED: {
-      net::ProxyClientSocket* proxy_socket =
-          static_cast<net::ProxyClientSocket*>(transport_->socket());
-
-      if (proxy_socket->GetAuthController()->HaveAuth()) {
-        return proxy_socket->RestartWithAuth(
-            base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxyDone,
-                                base::Unretained(this)));
-      }
-      return error;
-    }
-    default:
-      return error;
-  }
-
-  if (proxy_info_.is_https() && ssl_config_.send_client_cert) {
-    network_session_->ssl_client_auth_cache()->Remove(
-        proxy_info_.proxy_server().host_port_pair());
-  }
-
-  int rv = network_session_->proxy_service()->ReconsiderProxyAfterError(
-      url_, std::string(), error, &proxy_info_,
-      base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxy,
-                          base::Unretained(this)),
-      &pac_request_, NULL, net_log_);
-  if (rv == net::OK || rv == net::ERR_IO_PENDING) {
-    CloseTransportSocket();
-  } else {
-    // If ReconsiderProxyAfterError() failed synchronously, it means
-    // there was nothing left to fall-back to, so fail the transaction
-    // with the last connection error we got.
-    rv = error;
-  }
-
-  // We either have new proxy info or there was an error in falling back.
-  // In both cases we want to post ConnectToProxy (in the error case
-  // we might still want to fall back a direct connection).
-  if (rv != net::ERR_IO_PENDING) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(&ProxyResolvingClientSocket::ConnectToProxy,
-                                  weak_factory_.GetWeakPtr(), rv));
-    // Since we potentially have another try to go (trying the direct connect)
-    // set the return code code to ERR_IO_PENDING.
-    rv = net::ERR_IO_PENDING;
-  }
-  return rv;
-}
-
 void ProxyResolvingClientSocket::Disconnect() {
   CloseTransportSocket();
   if (pac_request_) {
@@ -417,10 +260,167 @@
   NOTIMPLEMENTED();
 }
 
+void ProxyResolvingClientSocket::ConnectToProxy(int net_error) {
+  pac_request_ = NULL;
+
+  DCHECK_NE(net_error, net::ERR_IO_PENDING);
+  if (net_error == net::OK) {
+    // Removes unsupported proxies from the list. Currently, this removes
+    // just the SCHEME_QUIC proxy, which doesn't yet support tunneling.
+    // TODO(xunjieli): Allow QUIC proxy once it supports tunneling.
+    proxy_info_.RemoveProxiesWithoutScheme(
+        net::ProxyServer::SCHEME_DIRECT | net::ProxyServer::SCHEME_HTTP |
+        net::ProxyServer::SCHEME_HTTPS | net::ProxyServer::SCHEME_SOCKS4 |
+        net::ProxyServer::SCHEME_SOCKS5);
+
+    if (proxy_info_.is_empty()) {
+      // No proxies/direct to choose from. This happens when we don't support
+      // any of the proxies in the returned list.
+      net_error = net::ERR_NO_SUPPORTED_PROXIES;
+    }
+  }
+
+  // TODO(xunjieli): This results in retrying "Direct" twice, because of
+  // ReconsiderProxyAfterError() path. https://crbug.com/793076.
+  // Try falling back to a direct connection if we have not tried that before.
+  if (net_error != net::OK) {
+    if (!tried_direct_connect_fallback_) {
+      tried_direct_connect_fallback_ = true;
+      proxy_info_.UseDirect();
+    } else {
+      CloseTransportSocket();
+      base::ResetAndReturn(&user_connect_callback_).Run(net_error);
+      return;
+    }
+  }
+
+  transport_.reset(new net::ClientSocketHandle);
+  // Now that the proxy is resolved, issue a socket connect.
+  net::HostPortPair host_port_pair = net::HostPortPair::FromURL(url_);
+  net_error = net::InitSocketHandleForRawConnect(
+      host_port_pair, network_session_.get(), proxy_info_, ssl_config_,
+      ssl_config_, net::PRIVACY_MODE_DISABLED, net_log_, transport_.get(),
+      base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxyDone,
+                          base::Unretained(this)));
+  if (net_error != net::ERR_IO_PENDING) {
+    // Since this method is always called asynchronously. it is OK to call
+    // ConnectToProxyDone synchronously.
+    ConnectToProxyDone(net_error);
+  }
+}
+
+void ProxyResolvingClientSocket::ConnectToProxyDone(int net_error) {
+  if (net_error != net::OK) {
+    // If the connection fails, try another proxy.
+    net_error = ReconsiderProxyAfterError(net_error);
+    // ReconsiderProxyAfterError either returns an error (in which case it is
+    // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering
+    // another proxy.
+    DCHECK_NE(net_error, net::OK);
+    if (net_error == net::ERR_IO_PENDING) {
+      // Proxy reconsideration pending. Return.
+      return;
+    }
+    CloseTransportSocket();
+  } else {
+    network_session_->proxy_service()->ReportSuccess(proxy_info_, NULL);
+  }
+  base::ResetAndReturn(&user_connect_callback_).Run(net_error);
+}
+
 void ProxyResolvingClientSocket::CloseTransportSocket() {
   if (transport_.get() && transport_->socket())
     transport_->socket()->Disconnect();
   transport_.reset();
 }
 
+// TODO(xunjieli): This following method is out of sync with
+// HttpStreamFactoryImpl::JobController. The logic should be refactored into a
+// common place.
+// This method reconsiders the proxy on certain errors. If it does
+// reconsider a proxy it always returns ERR_IO_PENDING and posts a call to
+// ConnectToProxy with the result of the reconsideration.
+int ProxyResolvingClientSocket::ReconsiderProxyAfterError(int error) {
+  DCHECK(!pac_request_);
+  DCHECK_NE(error, net::OK);
+  DCHECK_NE(error, net::ERR_IO_PENDING);
+  // A failure to resolve the hostname or any error related to establishing a
+  // TCP connection could be grounds for trying a new proxy configuration.
+  //
+  // Why do this when a hostname cannot be resolved?  Some URLs only make sense
+  // to proxy servers.  The hostname in those URLs might fail to resolve if we
+  // are still using a non-proxy config.  We need to check if a proxy config
+  // now exists that corresponds to a proxy server that could load the URL.
+  //
+  switch (error) {
+    case net::ERR_PROXY_CONNECTION_FAILED:
+    case net::ERR_NAME_NOT_RESOLVED:
+    case net::ERR_INTERNET_DISCONNECTED:
+    case net::ERR_ADDRESS_UNREACHABLE:
+    case net::ERR_CONNECTION_CLOSED:
+    case net::ERR_CONNECTION_RESET:
+    case net::ERR_CONNECTION_REFUSED:
+    case net::ERR_CONNECTION_ABORTED:
+    case net::ERR_TIMED_OUT:
+    case net::ERR_TUNNEL_CONNECTION_FAILED:
+    case net::ERR_SOCKS_CONNECTION_FAILED:
+      break;
+    case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
+      // Remap the SOCKS-specific "host unreachable" error to a more
+      // generic error code (this way consumers like the link doctor
+      // know to substitute their error page).
+      //
+      // Note that if the host resolving was done by the SOCSK5 proxy, we can't
+      // differentiate between a proxy-side "host not found" versus a proxy-side
+      // "address unreachable" error, and will report both of these failures as
+      // ERR_ADDRESS_UNREACHABLE.
+      return net::ERR_ADDRESS_UNREACHABLE;
+    case net::ERR_PROXY_AUTH_REQUESTED: {
+      net::ProxyClientSocket* proxy_socket =
+          static_cast<net::ProxyClientSocket*>(transport_->socket());
+
+      if (proxy_socket->GetAuthController()->HaveAuth()) {
+        return proxy_socket->RestartWithAuth(
+            base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxyDone,
+                                base::Unretained(this)));
+      }
+      return error;
+    }
+    default:
+      return error;
+  }
+
+  if (proxy_info_.is_https() && ssl_config_.send_client_cert) {
+    network_session_->ssl_client_auth_cache()->Remove(
+        proxy_info_.proxy_server().host_port_pair());
+  }
+
+  int rv = network_session_->proxy_service()->ReconsiderProxyAfterError(
+      url_, std::string(), error, &proxy_info_,
+      base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxy,
+                          base::Unretained(this)),
+      &pac_request_, NULL, net_log_);
+  if (rv == net::OK || rv == net::ERR_IO_PENDING) {
+    CloseTransportSocket();
+  } else {
+    // If ReconsiderProxyAfterError() failed synchronously, it means
+    // there was nothing left to fall-back to, so fail the transaction
+    // with the last connection error we got.
+    rv = error;
+  }
+
+  // We either have new proxy info or there was an error in falling back.
+  // In both cases we want to post ConnectToProxy (in the error case
+  // we might still want to fall back a direct connection).
+  if (rv != net::ERR_IO_PENDING) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(&ProxyResolvingClientSocket::ConnectToProxy,
+                                  weak_factory_.GetWeakPtr(), rv));
+    // Since we potentially have another try to go (trying the direct connect)
+    // set the return code code to ERR_IO_PENDING.
+    rv = net::ERR_IO_PENDING;
+  }
+  return rv;
+}
+
 }  // namespace network
diff --git a/services/service_manager/embedder/embedded_service_info.h b/services/service_manager/embedder/embedded_service_info.h
index 8a45b244..92880df8 100644
--- a/services/service_manager/embedder/embedded_service_info.h
+++ b/services/service_manager/embedder/embedded_service_info.h
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
+#include "base/optional.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/platform_thread.h"
 #include "services/service_manager/embedder/service_manager_embedder_export.h"
@@ -50,6 +51,12 @@
   // If the service uses its own thread, this determines the priority of the
   // thread.
   base::ThreadPriority thread_priority = base::ThreadPriority::NORMAL;
+
+  // If set, serves as a hint to the embedding environment that instances of
+  // this service should share a process with similar instances of any other
+  // services that are registered with the same group name. Choice of group
+  // names is arbitrary.
+  base::Optional<std::string> process_group;
 };
 
 }  // namespace service_manager
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 574bf65..2061fae 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -1728,7 +1728,8 @@
               "device_type": "sprout"
             }
           ],
-          "expiration": 14400
+          "expiration": 14400,
+          "hard_timeout": 960
         },
         "test": "gpu_unittests"
       },
@@ -4563,7 +4564,8 @@
             {
               "device_type": "coho"
             }
-          ]
+          ],
+          "hard_timeout": 960
         },
         "test": "gpu_unittests"
       },
@@ -5548,7 +5550,8 @@
             {
               "device_type": "gce_x86"
             }
-          ]
+          ],
+          "hard_timeout": 960
         },
         "test": "gpu_unittests"
       },
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index bbcdd90..793a006 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -1592,6 +1592,7 @@
               "device_type": "hammerhead"
             }
           ],
+          "hard_timeout": 960,
           "output_links": [
             {
               "link": [
@@ -3269,6 +3270,7 @@
               "device_type": "hammerhead"
             }
           ],
+          "hard_timeout": 960,
           "output_links": [
             {
               "link": [
@@ -4887,7 +4889,7 @@
             }
           ],
           "expiration": 10800,
-          "hard_timeout": 450,
+          "hard_timeout": 960,
           "output_links": [
             {
               "link": [
@@ -7858,7 +7860,7 @@
             }
           ],
           "expiration": 10800,
-          "hard_timeout": 450,
+          "hard_timeout": 960,
           "output_links": [
             {
               "link": [
@@ -12624,7 +12626,7 @@
             }
           ],
           "expiration": 10800,
-          "hard_timeout": 450,
+          "hard_timeout": 960,
           "output_links": [
             {
               "link": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 0b04d48..3abde81 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -4392,7 +4392,6 @@
           "--mus"
         ],
         "name": "ash_unittests-mus",
-        "override_isolate_target": "ash_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -4442,7 +4441,6 @@
           "--mus"
         ],
         "name": "mus_content_browsertests",
-        "override_isolate_target": "content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "shards": 2
@@ -4479,6 +4477,16 @@
         "test": "keyboard_unittests"
       },
       {
+        "args": [
+          "--mus"
+        ],
+        "name": "mus_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "unit_tests"
+      },
+      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index 5814644..129cf61 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -463,6 +463,53 @@
             }
           ]
         },
+        "test": "mojo_common_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1"
+            }
+          ]
+        },
+        "test": "mojo_public_bindings_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1"
+            }
+          ]
+        },
+        "test": "mojo_public_system_unittests"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.service_manager_unittests.filter"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1"
+            }
+          ]
+        },
+        "test": "service_manager_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1"
+            }
+          ]
+        },
         "test": "skia_unittests"
       },
       {
@@ -475,6 +522,20 @@
           ]
         },
         "test": "sql_unittests"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.ui_base_unittests.filter"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1"
+            }
+          ]
+        },
+        "test": "ui_base_unittests"
       }
     ]
   },
diff --git a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
index ff64ac4..ae9b5e6e 100644
--- a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
@@ -22,9 +22,6 @@
 -ChromeSecurityExploitBrowserTest.CreateFilesystemURLInExtensionOrigin
 -ChromeSitePerProcessTest.LaunchExternalProtocolFromSubframe
 -ContentFaviconDriverTest.ReloadBypassingCache
--ContentVerifierTest.DotSlashPaths
--ContentVerifierTest.FailOnDone
--ContentVerifierTest.FailOnRead
 -CredentialManagerBrowserTest.CreatePublicKeyCredentialAlgorithmNotSupported
 -CredentialManagerBrowserTest.CreatePublicKeyCredentialNotImplemented
 -CredentialManagerBrowserTest.MojoConnectionRecreatedAfterNavigation
@@ -639,10 +636,8 @@
 # Switch test from using a custom net::URLRequestFileJob to using a test
 # URLLoaderFactory via SetNetworkFactoryForTesting.
 -ErrorPageOfflineTestWithAllowDinosaurFalse.CheckEasterEggIsDisabled
--DNSErrorPageTest.Empty404
--DNSErrorPageTest.SniffSmallHttpErrorResponseAsDownload
+-ErrorPageSniffTest.SniffSmallHttpErrorResponseAsDownload
 -DNSErrorPageTest.StaleCacheStatus
--DNSErrorPageTest.DNSError_DoClickLink
 
 # Switch test from using a net::URLRequestFileJob that adds load timing to
 # to using a test URLLoaderFactory via SetNetworkFactoryForTesting.
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 0fef513..5e04423 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1913,30 +1913,6 @@
       },
     },
   },
-  'gpu_unittests': {
-    'modifications': {
-      'KitKat Tablet Tester': {
-        'swarming': {
-          'hard_timeout': 450,
-        },
-      },
-      'Lollipop Tablet Tester': {
-        'swarming': {
-          'hard_timeout': 450,
-        },
-      },
-      'Marshmallow Phone Tester (rel)': {
-        'swarming': {
-          'hard_timeout': 960,
-        },
-      },
-      'Marshmallow Tablet Tester': {
-        'swarming': {
-          'hard_timeout': 450,
-        },
-      },
-    },
-  },
   'headless_browsertests': {
     'remove_from': [
       'Linux Tests (dbg)(1)(32)',
@@ -2451,7 +2427,6 @@
       # chromium.linux
       'Cast Audio Linux',
       'Cast Linux',
-      'Fuchsia x64',
       # chromium.memory
       'Linux ASan LSan Tests (1)',
       'Linux Chromium OS ASan LSan Tests (1)',
@@ -2496,7 +2471,6 @@
       # chromium.linux
       'Cast Audio Linux',
       'Cast Linux',
-      'Fuchsia x64',
       # chromium.memory
       'Linux ASan LSan Tests (1)',
       'Linux Chromium OS ASan LSan Tests (1)',
@@ -2542,7 +2516,6 @@
       # chromium.linux
       'Cast Audio Linux',
       'Cast Linux',
-      'Fuchsia x64',
       # chromium.memory
       'Linux ASan LSan Tests (1)',
       'Linux Chromium OS ASan LSan Tests (1)',
@@ -3039,7 +3012,6 @@
       # chromium.linux
       'Cast Audio Linux',
       'Cast Linux',
-      'Fuchsia x64',
       'Linux Tests (dbg)(1)(32)',
       # On chromium.mac, unclear why these aren't run.
       'Mac10.10 Tests',
@@ -3596,10 +3568,6 @@
     },
   },
   'ui_base_unittests': {
-    'remove_from': [
-      # chromium.linux
-      'Fuchsia x64',
-    ],
     'modifications': {
       'KitKat Tablet Tester': {
         'swarming': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index e55c16d..040055f 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -381,7 +381,11 @@
     'crypto_unittests': {},
     'google_apis_unittests': {},
     'gpu_ipc_service_unittests': {},
-    'gpu_unittests': {},
+    'gpu_unittests': {
+      'android_swarming': {
+        'hard_timeout': 960,
+      },
+    },
     'ipc_tests': {},
     'jingle_unittests': {},
     'media_blink_unittests': {},
@@ -1121,7 +1125,6 @@
       'args': [
         '--mus',
       ],
-      'override_isolate_target': 'ash_unittests',
       'test': 'ash_unittests',
     },
     'mus_browser_tests': {
@@ -1139,12 +1142,17 @@
       'args': [
         '--mus',
       ],
-      'override_isolate_target': 'content_browsertests',
       'swarming': {
         'shards': 2,
       },
       'test': 'content_browsertests',
     },
+    'mus_unit_tests': {
+      'args': [
+        '--mus',
+      ],
+      'test': 'unit_tests',
+    },
   },
 
   'network_service_gtests': {
@@ -2224,7 +2232,7 @@
     'viz_fyi_gtests',
   ],
 
-  'mojo_chromiumos_gtests': [
+  'mojo_chromiumos_fyi_gtests': [
     'aura_non_clang_gtests',
     'mash_chromium_gtests',
     'mojo_chromiumos_specific_gtests',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 53499ac3..801e2767 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1123,7 +1123,7 @@
           'mash:all',
         ],
         'test_suites': {
-          'gtest_tests': 'mojo_chromiumos_gtests',
+          'gtest_tests': 'mojo_chromiumos_fyi_gtests',
           'isolated_scripts': 'mojo_chromiumos_isolated_scripts',
         },
       },
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index b9eb7e7..0f83eee5 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -739,7 +739,7 @@
 
 crbug.com/767269 [ Win ] inspector-protocol/layout-fonts/cjk-ideograph-fallback-by-lang.js [ Pass Failure ]
 
-crbug.com/788110 [ Linux ] inspector-protocol/layout-fonts/unicode-range-combining-chars-fallback.js [ Pass Failure ]
+crbug.com/788110 [ Linux Win10 ] inspector-protocol/layout-fonts/unicode-range-combining-chars-fallback.js [ Pass Failure ]
 
 # Run these tests with under virtual/scalefactor... only.
 crbug.com/567837 fast/hidpi/static [ Skip ]
@@ -1414,7 +1414,7 @@
 
 crbug.com/665577 virtual/threaded/fast/scroll-behavior/overflow-scroll-root-frame-animates.html [ Pass Timeout ]
 crbug.com/665577 virtual/threaded/fast/scroll-behavior/smooth-scroll/mousewheel-scroll.html [ Pass Failure ]
-crbug.com/665577 [ Linux Win ] virtual/threaded/fast/scroll-behavior/smooth-scroll/mousewheel-scroll-interrupted.html [ Pass Failure ]
+crbug.com/665577 [ Linux Win ] virtual/threaded/fast/scroll-behavior/smooth-scroll/mousewheel-scroll-interrupted.html [ Pass Failure Timeout ]
 crbug.com/665577 [ Linux Win ] virtual/threaded/fast/scroll-behavior/smooth-scroll/track-scroll.html [ Pass Failure ]
 
 crbug.com/599670 [ Win ] http/tests/devtools/resource-parameters-ipv6.js [ Timeout Pass ]
@@ -1626,21 +1626,8 @@
 crbug.com/501659 http/tests/security/xss-DENIED-xml-external-entity.xhtml [ Failure ]
 crbug.com/501659 fast/css/stylesheet-candidate-nodes-crash.xhtml [ Failure ]
 
-# Mac10.10-specific failures that still need triaging.
-# Form controls need rebaseline because of the default font change.
-# If you see wider INPUT elements or narrower TEXTAREA elements, you may do just
-# rebaseline. See crbug.com/508768#c6
-crbug.com/509025 [ Mac10.10 ] fast/css/css2-system-fonts.html [ Failure ]
-crbug.com/509025 [ Mac10.10 ] fast/forms/select/hidden-listbox.html [ Failure ]
-crbug.com/509025 [ Mac10.10 ] fast/forms/textarea/textarea-newline.html [ Failure ]
-
 crbug.com/545140 [ Mac10.10 Mac10.11 Retina Mac10.12 ] fast/encoding/denormalised-voiced-japanese-chars.html [ Failure ]
 
-crbug.com/509025 [ Mac10.10 ] fast/events/context-no-deselect.html [ Failure ]
-crbug.com/509025 [ Mac10.10 ] virtual/rootlayerscrolls/scrollbars/rtl/overflow-scroll-rtl.html [ Failure ]
-crbug.com/509025 [ Mac10.10 ] virtual/rootlayerscrolls/scrollbars/short-scrollbar.html [ Failure ]
-crbug.com/509025 [ Mac10.10 ] virtual/mouseevent_fractional/fast/events/context-no-deselect.html [ Failure ]
-
 crbug.com/621772 fast/forms/color/color-suggestion-picker-with-scrollbar-appearance.html [ Pass Failure ]
 crbug.com/652995 fast/forms/text/input-text-scroll-left-on-blur.html [ Pass Failure ]
 
@@ -2477,8 +2464,6 @@
 crbug.com/701047 [ Mac10.12 ] editing/style/block-style-003.html [ Failure ]
 crbug.com/701047 [ Mac10.12 ] editing/style/block-styles-007.html [ Failure ]
 
-crbug.com/735245 http/tests/devtools/application-panel/storage-view-reports-quota.js [ Pass Timeout Failure ]
-
 # [css-grid]
 crbug.com/659610 fast/css-grid-layout/grid-baseline.html [ Failure ]
 crbug.com/659610 fast/css-grid-layout/grid-baseline-margins.html [ Failure ]
@@ -3009,7 +2994,7 @@
 crbug.com/736255 [ Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-baseline-multi-item-vert-001b.html [ Skip ]
 crbug.com/736732 virtual/enable_wasm/external/wpt/wasm/wasm_local_iframe_test.html [ Timeout Pass ]
 
-crbug.com/798116 [ Linux Win7 ] fast/forms/select/listbox-in-multi-column.html [ Failure Pass ]
+crbug.com/798116 [ Win7 ] fast/forms/select/listbox-in-multi-column.html [ Skip ]
 
 # These imported tests exercise tens of thousands of code points and generate large results. Marked slow, but still timeout.
 crbug.com/736056 [ Mac ] external/wpt/encoding/legacy-mb-japanese [ Timeout Pass ]
@@ -3183,7 +3168,7 @@
 crbug.com/755405 [ Android ] editing/selection/previous-line-position.html [ Failure ]
 crbug.com/755405 [ Android ] editing/style/block-styles-007.html [ Failure ]
 crbug.com/755405 [ Android ] fast/backgrounds/background-clip-text.html [ Failure ]
-crbug.com/755405 [ Android ] fast/css/child-style-can-override-visited-style.html [ Failure ]
+crbug.com/755405 [ Android ] fast/css/child-style-can-override-visited-style.html [ Failure Timeout ]
 crbug.com/755405 [ Android ] fast/css/clip-zooming.html [ Failure ]
 crbug.com/755405 [ Android ] fast/css/compare-content-style.html [ Failure ]
 crbug.com/755405 [ Android ] fast/css/first-letter-hover.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
index 02c36ec2..baa92a1 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -4855,6 +4855,12 @@
      {}
     ]
    ],
+   "uievents/mouse/layout_change_should_fire_mouseover-manual.html": [
+    [
+     "/uievents/mouse/layout_change_should_fire_mouseover-manual.html",
+     {}
+    ]
+   ],
    "uievents/mouse/mouseevent_move_button-manual.html": [
     [
      "/uievents/mouse/mouseevent_move_button-manual.html",
@@ -77693,6 +77699,18 @@
      {}
     ]
    ],
+   "css/selectors/any-link-dynamic-001.html": [
+    [
+     "/css/selectors/any-link-dynamic-001.html",
+     [
+      [
+       "/css/selectors/any-link-dynamic-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/selectors/focus-within-001.html": [
     [
      "/css/selectors/focus-within-001.html",
@@ -110415,11 +110433,6 @@
      {}
     ]
    ],
-   "css/css-multicol/multicol-gap-animation-003-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "css/css-multicol/multicol-gap-fraction-001-ref.xht": [
     [
      {}
@@ -121860,6 +121873,11 @@
      {}
     ]
    ],
+   "css/selectors/any-link-dynamic-001-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/selectors/attribute-selectors/attribute-case/resources/semantics-quirks.html": [
     [
      {}
@@ -178306,6 +178324,12 @@
      {}
     ]
    ],
+   "hr-time/performance-tojson.html": [
+    [
+     "/hr-time/performance-tojson.html",
+     {}
+    ]
+   ],
    "hr-time/test_cross_frame_start.html": [
     [
      "/hr-time/test_cross_frame_start.html",
@@ -190154,6 +190178,12 @@
      {}
     ]
    ],
+   "longtask-timing/longtask-tojson.html": [
+    [
+     "/longtask-timing/longtask-tojson.html",
+     {}
+    ]
+   ],
    "longtask-timing/shared-renderer/longtask-in-new-window.html": [
     [
      "/longtask-timing/shared-renderer/longtask-in-new-window.html",
@@ -201734,6 +201764,12 @@
      {}
     ]
    ],
+   "performance-timeline/performanceentry-tojson.html": [
+    [
+     "/performance-timeline/performanceentry-tojson.html",
+     {}
+    ]
+   ],
    "performance-timeline/po-callback-mutate.any.js": [
     [
      "/performance-timeline/po-callback-mutate.any.html",
@@ -210304,6 +210340,12 @@
      {}
     ]
    ],
+   "resource-timing/resource-timing-tojson.html": [
+    [
+     "/resource-timing/resource-timing-tojson.html",
+     {}
+    ]
+   ],
    "resource-timing/resource-timing.html": [
     [
      "/resource-timing/resource-timing.html",
@@ -268425,10 +268467,6 @@
    "5e83250f2f2ff49682eae68056330aa3b4c673c7",
    "testharness"
   ],
-  "css/css-multicol/multicol-gap-animation-003-expected.txt": [
-   "697b921d83c68c5ec34ad0fef1de9bcf20555024",
-   "support"
-  ],
   "css/css-multicol/multicol-gap-animation-003.html": [
    "6d5c81b2277c34bac89e8cde247dd9aabbdd505f",
    "testharness"
@@ -292773,6 +292811,14 @@
    "b45da5560d0eb821b5552f1b67fab5cead4508b1",
    "support"
   ],
+  "css/selectors/any-link-dynamic-001-ref.html": [
+   "342d89969da0ca3782efacee0ecbf882c718d6c6",
+   "support"
+  ],
+  "css/selectors/any-link-dynamic-001.html": [
+   "c33602e863151182d29afd88a8878374bb118b69",
+   "reftest"
+  ],
   "css/selectors/attribute-selectors/attribute-case/cssom.html": [
    "d5e05750213fbf7959125d64e9ea1a26948ba91d",
    "testharness"
@@ -303413,6 +303459,10 @@
    "4aef47650d5cbc750393c3ac9423dbff24a15917",
    "testharness"
   ],
+  "hr-time/performance-tojson.html": [
+   "2d45889944dab7b0489a03a649a70e1177bca428",
+   "testharness"
+  ],
   "hr-time/resources/now_frame.html": [
    "031edb78f5ab21f51005fdb30a99a605669254ce",
    "support"
@@ -323033,6 +323083,10 @@
    "e9c7f9671fba6eba939a3241bbddffb2a6c2bb13",
    "testharness"
   ],
+  "longtask-timing/longtask-tojson.html": [
+   "c80d01bb7d3825dbdad09137b47ab4e5327f7fbf",
+   "testharness"
+  ],
   "longtask-timing/resources/makelongtask.js": [
    "64401c9b936a3a1bb43744d821258d43628819ca",
    "support"
@@ -332381,6 +332435,10 @@
    "30e6893af2cda301efb45fa7cfe16cec04701445",
    "testharness"
   ],
+  "performance-timeline/performanceentry-tojson.html": [
+   "bc8a6f3fb13af9df11781a21b96f342e7d7ddf4e",
+   "testharness"
+  ],
   "performance-timeline/performanceobservers.js": [
    "0faeecf506da5b8a5c722a1ce8c7b5854227bca0",
    "support"
@@ -340885,6 +340943,10 @@
    "b15a57e2eee61f2748bf758e6f3e06e84234f1e3",
    "support"
   ],
+  "resource-timing/resource-timing-tojson.html": [
+   "643ce44bcf4a279853f07bde1abb376314e0b3bf",
+   "testharness"
+  ],
   "resource-timing/resource-timing.html": [
    "eb3e3064aa55df9ce9a0679a51d0b01d2fb63422",
    "testharness"
@@ -346429,6 +346491,10 @@
    "6b1d7bce96ca023959d5248aa8af0aa83c6d3aa5",
    "testharness"
   ],
+  "uievents/mouse/layout_change_should_fire_mouseover-manual.html": [
+   "4e96209d99278b974347c6bd636454b0e7daf3c4",
+   "manual"
+  ],
   "uievents/mouse/mouseevent_move_button-manual.html": [
    "9cc673035fef3c2e8677e8d6679babfe8a1af854",
    "manual"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/2dcontext/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/2dcontext/OWNERS
index 14ca7d4e..3b83b67 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/2dcontext/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/2dcontext/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: paint-dev@chromium.org
 # COMPONENT: Blink>Canvas
+# WPT-NOTIFY: true
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/background-fetch/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/background-fetch/OWNERS
index f4dc67b..6b3427b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/background-fetch/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/background-fetch/OWNERS
@@ -1 +1,5 @@
 file://third_party/WebKit/Source/modules/background_fetch/OWNERS
+
+# TEAM: platform-capabilities@chromium.org
+# COMPONENT: Blink>BackgroundFetch
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms/OWNERS
index 2c3d32a..e090a5a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: paint-dev@chromium.org
 # COMPONENT: Blink>Transforms
+# WPT-NOTIFY: true
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/geometry/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/geometry/OWNERS
index f850950..b788e32 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/geometry/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/geometry/OWNERS
@@ -1,5 +1,6 @@
 # TEAM: paint-dev@chromium.org
 # COMPONENT: Blink>Geometry
+# WPT-NOTIFY: true
 xlai@chromium.org
 jinho.bang@samsung.com
 hs1217.lee@samsung.com
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/selectors/any-link-dynamic-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/selectors/any-link-dynamic-001-ref.html
new file mode 100644
index 0000000..b540742
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/selectors/any-link-dynamic-001-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS test reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<style>
+  span { color: green; }
+</style>
+<body>
+  <a></a><span>This should be green</span>
+</body>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/selectors/any-link-dynamic-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/selectors/any-link-dynamic-001.html
new file mode 100644
index 0000000..e84989f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/selectors/any-link-dynamic-001.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS test: Handling of dynamic changes to :any-link selectors</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="match" href="any-link-dynamic-001-ref.html">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#the-any-link-pseudo">
+<style>
+  span { color: green; }
+  :any-link + span { color: red; }
+</style>
+<body onload="window.oldColor = getComputedStyle(document.querySelector('span')).color;
+              document.querySelector('a').removeAttribute('href');">
+  <a href=""></a><span>This should be green</span>
+</body>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/OWNERS
index 72726ec..96cb6024 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/OWNERS
@@ -1 +1,3 @@
+# COMPONENT: Internals>Media>Encrypted
+# WPT-NOTIFY: true
 jrummell@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html
new file mode 100644
index 0000000..038e6f6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Adding cues to a disabled text track</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var cueDuration = 0.1;
+    var video = document.createElement("video");
+    var track = video.addTextTrack("subtitles");
+    track.mode = "disabled";
+
+    for (var i = 0; i < 10; ++i) {
+        var start = i * cueDuration;
+        var end = start + cueDuration;
+        track.addCue(new VTTCue(start, end, "Test Cue " + i));
+    }
+
+    // Waiting for 2 cue durations to elapse.
+    video.ontimeupdate = t.step_func(function(event) {
+        if (event.target.currentTime < (2 * cueDuration))
+            return;
+
+        // End test after at least 2 cueDurations to make sure the test
+        // would have gone through the period where the first 2 cues would
+        // have been rendered if the track was not disabled.
+        t.done();
+    });
+
+    video.src = getVideoURI("/media/test");
+    video.play();
+});
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html
new file mode 100644
index 0000000..d517b9d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Disabling a track</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track kind="subtitles" src="resources/captions.vtt"/>
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    video.textTracks[0].mode = "disabled";
+
+    // Waiting for the duration of the first cue to elapse.
+    video.ontimeupdate = t.step_func(function (event) {
+        if (event.target.currentTime < 1)
+            return;
+
+        // End test after the duration of the first cue to make sure
+        // the test would have gone through the period where this cue
+        // would have been rendered if the track was not disabled.
+        t.done();
+    });
+
+    video.src = getVideoURI("/media/test");
+    video.play();
+});
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/media/track/track-element-dom-change-crash.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html
similarity index 63%
rename from third_party/WebKit/LayoutTests/media/track/track-element-dom-change-crash.html
rename to third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html
index 277525ec..ff447f3 100644
--- a/third_party/WebKit/LayoutTests/media/track/track-element-dom-change-crash.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<title>Tests that the browser handles simple DOM mutations properly.</title>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
+<title>Simple DOM mutations with track element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 <script>
 test(function() {
     var video = document.createElement("video");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html
new file mode 100644
index 0000000..ffc8ec0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/settings.vtt" default>
+    <script>
+    async_test(function(t) {
+        var cues = null;
+        var testTrack = document.querySelector("track");
+        var stage = 0;
+        var timer = null;
+        function step_onLoad() {
+            switch (stage) {
+                case 0:
+                    cues = testTrack.track.cues;
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
+                    assert_equals(cues.length, 4, "Number of cues after first loading of the track");
+                    ++stage;
+                    testTrack.src = "resources/non-existing-file.vtt"; // this should fail
+                    break;
+                case 1:
+                case 3:
+                case 5:
+                    assert_unreached("'error' event did not fire, stage = " + stage);
+                    break;
+                case 2:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after loading of the second track");
+                    assert_equals(cues.length, 4, "Number of cues after loading of the second track");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    ++stage;
+                    testTrack.src = ""; // this should fail
+                    // CuesList will be cleared in the next tick. Spec claims that this should happen immediately,
+                    // but all implementations are doing this asynchronously.
+                    assert_equals(cues.length, 4, "Number of cues immediately after 'src' mutation with the empty URL");
+                    // This should raise onError event. If no, we'll know about this after some time.
+                    timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
+                    break;
+                case 4:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after loading of the second track");
+                    assert_equals(cues.length, 4, "Number of cues after loading of the second track");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    ++stage;
+                    testTrack.removeAttribute('src');
+                    // This should raise onError event, so we'll wait for it for some time
+                    timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
+                    break;
+                default:
+                    assert_unreached("unexpected stage number = " + stage);
+                    break;
+            }
+        }
+
+        function step_onError() {
+            switch (stage) {
+                case 0:
+                case 2:
+                case 4:
+                    assert_unreached("'error' event fired, stage = " + stage);
+                    break;
+                case 1:
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 0, "Number of cues after trying to load non-existing url");
+                    assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after trying to load non-existing url");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    break;
+                case 3:
+                    clearTimeout(timer);
+                    assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after setting an empty URL");
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 0, "Number of cues with an empty URL set");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    break;
+                case 5:
+                    clearTimeout(timer);
+                    assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after removing 'src' attr");
+                    assert_equals(cues.length, 0, "Number of cues after removing 'src' attr");
+                    t.done();
+                    break;
+                default:
+                    assert_unreached("unexpected stage number = " + stage);
+                    break;
+            }
+        }
+
+        testTrack.onload = t.step_func(step_onLoad);
+        testTrack.onerror = t.step_func(step_onError);
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html
new file mode 100644
index 0000000..34a53d15
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/settings.vtt" default>
+    <script>
+    async_test(function(t) {
+        var cues = null;
+        var testTrack = document.querySelector("track");
+        var stage = 0;
+        function step_onLoad() {
+            switch (stage) {
+                case 0:
+                    cues = testTrack.track.cues;
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
+                    assert_equals(cues.length, 4, "Number of cues after first loading of the track");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    ++stage;
+                    testTrack.src = "resources/entities.vtt";
+                    // CuesList will be cleared in a microtask. Spec claims that this should happen immediately,
+                    // but all known implementations are doing this asynchronously.
+                    assert_equals(cues.length, 4, "Number of cues immediately after 'src' mutation with the new URL");
+                    break;
+                case 1:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED), "readyState after loading of the second track";
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 7, "Number of cues after loading of the second track");
+                    assert_equals(cues[cues.length-1].text, 'This & is parsed to the same as &amp;.', "Last cue content check");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    break;
+                case 2:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after after loading of the first track again");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 4, "Number of cues after loading of the first track");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    // This should not raise onLoad or onError event, so we'll wait for it for some time
+                    t.step_timeout(t.step_func_done(function() {
+                        assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after changing 'src' to the same value");
+                        assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                        assert_equals(cues.length, 4, "Number of cues after changing 'src' to the same value");
+                    }, 100));
+                    break;
+                case 3:
+                    assert_unreached("'load' event should not fire, stage = " + stage);
+                    break;
+            }
+        }
+
+        testTrack.onload = t.step_func(step_onLoad);
+        testTrack.onerror = t.unreached_func("'error' event should not fire");
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/imagebitmap-renderingcontext/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/imagebitmap-renderingcontext/OWNERS
index 14ca7d4e..3b83b67 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/imagebitmap-renderingcontext/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/imagebitmap-renderingcontext/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: paint-dev@chromium.org
 # COMPONENT: Blink>Canvas
+# WPT-NOTIFY: true
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/images/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/images/OWNERS
index 990a4f1..6640d0e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/images/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/images/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: paint-dev@chromium.org
 # COMPONENT: Blink>Image
+# WPT-NOTIFY: true
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/notifications/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/notifications/OWNERS
index 1c13657..b47764966 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/notifications/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/notifications/OWNERS
@@ -1 +1,5 @@
+file://third_party/WebKit/Source/modules/notifications/OWNERS
+
 # TEAM: platform-capabilities@chromium.org
+# COMPONENT: UI>Notifications
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/OWNERS
index 14ca7d4e..3b83b67 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: paint-dev@chromium.org
 # COMPONENT: Blink>Canvas
+# WPT-NOTIFY: true
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/svg/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/svg/OWNERS
index 9e4d106..cf0ed9aa 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/svg/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/svg/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: paint-dev@chromium.org
 # COMPONENT: Blink>SVG
+# WPT-NOTIFY: true
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota-expected.txt
index c4b54ea..61726d2f4 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota-expected.txt
@@ -2,11 +2,11 @@
 
 Tree element found: true
 Clear storage view is visible: true
-0 B used out of -- storage quota
+-- B used out of -- storage quota
 Usage breakdown:
 
 Running: Now with data
-9.5 MB used out of -- storage quota
+-- KB used out of -- storage quota
 Usage breakdown:
-IndexedDB: 9.5 MB
+IndexedDB: --.- KB
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota.js b/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota.js
index ce21f4787..d333a7a 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/application-panel/storage-view-reports-quota.js
@@ -15,7 +15,7 @@
 
   async function writeArray() {
     var array = [];
-    for (var i = 0; i < 5000000; i++)
+    for (var i = 0; i < 20000; i++)
       array.push(i % 10);
     var mainFrameId = TestRunner.resourceTreeModel.mainFrame.id;
     await new Promise(resolve => ApplicationTestRunner.createDatabase(mainFrameId, 'Database1', resolve));
@@ -38,7 +38,8 @@
     });
     // Quota will vary between setups, rather strip it altogether
     var clean = view._quotaRow.innerHTML.replace(/\&nbsp;/g, ' ');
-    var quotaStripped = clean.replace(/(.*) \d+ .?B([^\d]*)/, '$1 --$2');
+    // Clean usage value because it's platform-dependent.
+    var quotaStripped = clean.replace(/[\d.]+ (K?B used out of) \d+ .?B([^\d]*)/, '-- $1 --$2');
     TestRunner.addResult(quotaStripped);
 
     TestRunner.addResult('Usage breakdown:');
@@ -48,8 +49,11 @@
       for (var j = 0; j < children.length; j++) {
         if (children[j].classList.contains('usage-breakdown-legend-title'))
           typeUsage = children[j].textContent + typeUsage;
-        if (children[j].classList.contains('usage-breakdown-legend-value'))
-          typeUsage = typeUsage + children[j].textContent;
+        if (children[j].classList.contains('usage-breakdown-legend-value')) {
+          // Clean usage value because it's platform-dependent.
+          var cleanedValue = children[j].textContent.replace(/\d+.\d\sKB/, '--.- KB');
+          typeUsage = typeUsage + cleanedValue;
+        }
       }
       TestRunner.addResult(typeUsage);
     }
@@ -71,7 +75,7 @@
   TestRunner.markStep('Now with data');
 
   await writeArray();
-  await dumpWhenMatches(clearStorageView, usage => usage > 5000000);
+  await dumpWhenMatches(clearStorageView, usage => usage > 20000);
 
   TestRunner.completeTest();
 })();
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/extensions/extensions-api-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/extensions/extensions-api-expected.txt
index 0cf8c01..a39ed5b3 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/extensions/extensions-api-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/extensions/extensions-api-expected.txt
@@ -1,5 +1,5 @@
-CONSOLE WARNING: line 31: network.onFinished is deprecated. Use network.onRequestFinished instead
-CONSOLE WARNING: line 31: webInspector.resources is deprecated. Use webInspector.network instead
+CONSOLE WARNING: line 32: network.onFinished is deprecated. Use network.onRequestFinished instead
+CONSOLE WARNING: line 32: webInspector.resources is deprecated. Use webInspector.network instead
 Tests public interface of WebInspector Extensions API
 
 Started extension.
diff --git a/third_party/WebKit/LayoutTests/media/track/captions-webvtt/tc022-entities.vtt b/third_party/WebKit/LayoutTests/media/track/captions-webvtt/tc022-entities.vtt
deleted file mode 100644
index a8817954..0000000
--- a/third_party/WebKit/LayoutTests/media/track/captions-webvtt/tc022-entities.vtt
+++ /dev/null
@@ -1,30 +0,0 @@
-WEBVTT
-Cue content with escape characters for &, <, >, LRM, RLM and non-breaking space.
-
-1
-00:00:00.000 --> 00:00:30.500 align:start position:20%
-This cue has an ampersand &amp; character.
-
-2
-00:00:31.000 --> 00:01:00.500 align:start position:20%
-This cue has a less than &lt; character.
-
-3
-00:01:01.000 --> 00:02:00.500 align:start position:20%
-This cue has a greater than &gt; character.
-
-4
-00:02:01.000 --> 00:02:30.500 align:start position:20%
-This cue has a Left-to-Right Mark &lrm;.
-
-5
-00:02:31.000 --> 00:03:00.500 align:start position:20%
-This cue has a Right-to-Left Mark &rlm;.
-
-6
-00:03:01.000 --> 00:03:30.500 align:start position:20%
-This cue has a non-breaking space &nbsp;.
-
-7
-00:03:31.000 --> 00:04:00.500
-This & is parsed to the same as &amp;.
diff --git a/third_party/WebKit/LayoutTests/media/track/track-disabled-addcue.html b/third_party/WebKit/LayoutTests/media/track/track-disabled-addcue.html
deleted file mode 100644
index 147eced..0000000
--- a/third_party/WebKit/LayoutTests/media/track/track-disabled-addcue.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<title>Test adding cues to a disabled text track. </title>
-<script src="../media-file.js"></script>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script>
-async_test(function(t) {
-    var cueDuration = 0.1;
-    var video = document.createElement("video");
-    var track = video.addTextTrack("subtitles");
-    track.mode = "disabled";
-
-    for (var i = 0; i < 10; ++i) {
-        var start = i * cueDuration;
-        var end = start + cueDuration;
-        track.addCue(new VTTCue(start, end, "Test Cue " + i));
-    }
-
-    // Waiting for 2 cue durations to elapse.
-    video.ontimeupdate = t.step_func(function(e) {
-        if (e.target.currentTime < (2 * cueDuration))
-            return;
-
-        // End test after at least 2 cueDurations to make sure the test
-        // doesn't crash during the period the first 2 cues would have been
-        // rendered if the track was not disabled.
-        // 2 cue durations have elapsed.
-        t.done();
-    });
-
-    video.src = findMediaFile("video", "../content/test");
-    video.play();
-});
-</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/media/track/track-disabled.html b/third_party/WebKit/LayoutTests/media/track/track-disabled.html
deleted file mode 100644
index 5b44aa8..0000000
--- a/third_party/WebKit/LayoutTests/media/track/track-disabled.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<title>Test disabling a track.</title>
-<video>
-    <track kind="subtitles" src="captions-webvtt/captions.vtt"/>
-</video>
-<script src="../media-file.js"></script>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script>
-async_test(function(t) {
-    var video = document.querySelector("video");
-    video.textTracks[0].mode = "disabled";
-
-    // Waiting for the duration of the first cue to elapse.
-    video.ontimeupdate = t.step_func(function (e) {
-         if (e.target.currentTime < 1)
-              return;
-
-          // End test after the duration of the first cue to make sure
-          // the test doesn't crash during the period this cue would
-          // have been rendered if the track was not disabled.
-          // The duration of the first cue has elapsed.
-          t.done();
-    });
-
-    video.src = findMediaFile("video", "../content/test");
-    video.play();
-});
-</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/media/track/track-element-src-change-error-handling.html b/third_party/WebKit/LayoutTests/media/track/track-element-src-change-error-handling.html
deleted file mode 100644
index cce06bd0..0000000
--- a/third_party/WebKit/LayoutTests/media/track/track-element-src-change-error-handling.html
+++ /dev/null
@@ -1,93 +0,0 @@
-
-<!DOCTYPE html>
-<title>HTMLTrackElement 'src' attribute mutations</title>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<video>
-    <track src="captions-webvtt/tc013-settings.vtt" default>
-</video>
-<script>
-async_test(function(t) {
-    var cues = null;
-    var testTrack = document.querySelector("track");
-    var stage = 0;
-    var timer = null;
-    function step_onLoad() {
-        switch (stage) {
-            case 0:
-                cues = testTrack.track.cues;
-                assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
-                assert_equals(cues.length, 4, "Number of cues after first loading of the track");
-                ++stage;
-                testTrack.src = "captions-webvtt/non-existing-file.vtt"; // this should fail
-                break;
-            case 1:
-            case 3:
-            case 5:
-                assert_unreached("'error' event did not fire, stage = " + stage);
-                break;
-            case 2:
-                assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after loading of the second track");
-                assert_equals(cues.length, 4, "Number of cues after loading of the second track");
-                assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
-                ++stage;
-                testTrack.src = ""; // this should fail
-                // CuesList will be cleared in the next tick. Spec claims that this should happen immediately,
-                // but all implementations are doing this asynchronously.
-                assert_equals(cues.length, 4, "Number of cues immediately after 'src' mutation with the empty URL");
-                // This should raise onError event. If no, we'll know about this after some time.
-                timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
-                break;
-            case 4:
-                assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after loading of the second track");
-                assert_equals(cues.length, 4, "Number of cues after loading of the second track");
-                assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
-                ++stage;
-                testTrack.removeAttribute('src');
-                // This should raise onError event, so we'll wait for it for some time
-                timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
-                break;
-            default:
-                assert_unreached("unexpected stage number = " + stage);
-                break;
-        }
-    }
-
-    function step_onError() {
-        switch (stage) {
-            case 0:
-            case 2:
-            case 4:
-                assert_unreached("'error' event fired, stage = " + stage);
-                break;
-            case 1:
-                assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
-                assert_equals(cues.length, 0, "Number of cues after trying to load non-existing url");
-                assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after trying to load non-existing url");
-                ++stage;
-                testTrack.src = "captions-webvtt/tc013-settings.vtt";
-                break;
-            case 3:
-                clearTimeout(timer);
-                assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after setting an empty URL");
-                assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
-                assert_equals(cues.length, 0, "Number of cues with an empty URL set");
-                ++stage;
-                testTrack.src = "captions-webvtt/tc013-settings.vtt";
-                break;
-            case 5:
-                clearTimeout(timer);
-                assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after removing 'src' attr");
-                assert_equals(cues.length, 0, "Number of cues after removing 'src' attr");
-                t.done();
-                break;
-            default:
-                assert_unreached("unexpected stage number = " + stage);
-                break;
-        }
-    }
-
-    testTrack.onload = t.step_func(step_onLoad);
-    testTrack.onerror = t.step_func(step_onError);
-});
-</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/media/track/track-element-src-change.html b/third_party/WebKit/LayoutTests/media/track/track-element-src-change.html
deleted file mode 100644
index 4a09f81..0000000
--- a/third_party/WebKit/LayoutTests/media/track/track-element-src-change.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<title>HTMLTrackElement 'src' attribute mutations</title>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<video>
-    <track src="captions-webvtt/tc013-settings.vtt" default>
-</video>
-<script>
-async_test(function(t) {
-    var cues = null;
-    var testTrack = document.querySelector("track");
-    var stage = 0;
-    function step_onLoad() {
-        switch (stage) {
-            case 0:
-                cues = testTrack.track.cues;
-                assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
-                assert_equals(cues.length, 4, "Number of cues after first loading of the track");
-                assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
-                ++stage;
-                testTrack.src = "captions-webvtt/tc022-entities.vtt";
-                // CuesList will be cleared in a microtask. Spec claims that this should happen immediately,
-                // but all known implementations are doing this asynchronously.
-                assert_equals(cues.length, 4, "Number of cues immediately after 'src' mutation with the new URL");
-                break;
-            case 1:
-                assert_equals(testTrack.readyState, HTMLTrackElement.LOADED), "readyState after loading of the second track";
-                assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
-                assert_equals(cues.length, 7, "Number of cues after loading of the second track");
-                assert_equals(cues[cues.length-1].text, 'This & is parsed to the same as &amp;.', "Last cue content check");
-                ++stage;
-                testTrack.src = "captions-webvtt/tc013-settings.vtt";
-                break;
-            case 2:
-                assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after after loading of the first track again");
-                assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
-                assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
-                assert_equals(cues.length, 4, "Number of cues after loading of the first track");
-                ++stage;
-                testTrack.src = "captions-webvtt/tc013-settings.vtt";
-                // This should not raise onLoad or onError event, so we'll wait for it for some time
-                t.step_timeout(t.step_func_done(function() {
-                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after changing 'src' to the same value");
-                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
-                    assert_equals(cues.length, 4, "Number of cues after changing 'src' to the same value");
-                }, 100));
-                break;
-            case 3:
-                assert_unreached("'load' event should not fire, stage = " + stage);
-                break;
-        }
-    }
-
-    testTrack.onload = t.step_func(step_onLoad);
-    testTrack.onerror = t.unreached_func("'error' event should not fire");
-});
-</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/paint/markers/first-letter.html b/third_party/WebKit/LayoutTests/paint/markers/first-letter.html
new file mode 100644
index 0000000..86f4228
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/markers/first-letter.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<style>
+div:first-letter {
+  color: red;
+}
+</style>
+
+<div id="div">None Composition Spelling TextMatch</div>
+
+<script>
+function addCompositionMarker(elem, start, end) {
+    const range = document.createRange();
+    const textNode = elem.firstChild;
+    range.setStart(textNode, start);
+    range.setEnd(textNode, end);
+    if (typeof internals !== 'undefined')
+        internals.addCompositionMarker(range, 'orange', 'thin', 'lightBlue');
+};
+
+function addSpellingMarker(elem, start, end) {
+    const range = document.createRange();
+    const textNode = elem.firstChild;
+    range.setStart(textNode, start);
+    range.setEnd(textNode, end);
+    if (typeof internals !== 'undefined')
+        internals.setMarker(document, range, 'spelling');
+};
+
+function addTextMatchMarker(elem, start, end) {
+    const range = document.createRange();
+    const textNode = elem.firstChild;
+    range.setStart(textNode, start);
+    range.setEnd(textNode, end);
+    if (typeof internals !== 'undefined') {
+        internals.addTextMatchMarker(range, 'kActive');
+        internals.setMarkedTextMatchesAreHighlighted(document, true);
+    }
+};
+
+onload = runAfterLayoutAndPaint(function() {
+    addCompositionMarker(div, 5, 16);
+    addSpellingMarker(div, 17, 25);
+    addTextMatchMarker(div, 26, 35);
+}, true);
+</script>
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/markers/first-letter-expected.png b/third_party/WebKit/LayoutTests/platform/linux/paint/markers/first-letter-expected.png
new file mode 100644
index 0000000..687a4d0f88
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/paint/markers/first-letter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/markers/first-letter-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/markers/first-letter-expected.txt
new file mode 100644
index 0000000..53221d4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/paint/markers/first-letter-expected.txt
@@ -0,0 +1,11 @@
+layer at (0,0) size 800x600
+  LayoutView at (0,0) size 800x600
+layer at (0,0) size 800x36
+  LayoutBlockFlow {HTML} at (0,0) size 800x36
+    LayoutBlockFlow {BODY} at (8,8) size 784x20
+      LayoutBlockFlow {DIV} at (0,0) size 784x20
+        LayoutInline {<pseudo:first-letter>} at (0,0) size 12x19 [color=#FF0000]
+          LayoutTextFragment (anonymous) at (0,0) size 12x19
+            text run at (0,0) width 12: "N"
+        LayoutTextFragment {#text} at (12,0) size 236x19
+          text run at (12,0) width 236: "one Composition Spelling TextMatch"
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/spv175/paint/markers/first-letter-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/spv175/paint/markers/first-letter-expected.png
new file mode 100644
index 0000000..687a4d0f88
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/spv175/paint/markers/first-letter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/spv175/paint/markers/first-letter-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/spv175/paint/markers/first-letter-expected.txt
new file mode 100644
index 0000000..53221d4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/spv175/paint/markers/first-letter-expected.txt
@@ -0,0 +1,11 @@
+layer at (0,0) size 800x600
+  LayoutView at (0,0) size 800x600
+layer at (0,0) size 800x36
+  LayoutBlockFlow {HTML} at (0,0) size 800x36
+    LayoutBlockFlow {BODY} at (8,8) size 784x20
+      LayoutBlockFlow {DIV} at (0,0) size 784x20
+        LayoutInline {<pseudo:first-letter>} at (0,0) size 12x19 [color=#FF0000]
+          LayoutTextFragment (anonymous) at (0,0) size 12x19
+            text run at (0,0) width 12: "N"
+        LayoutTextFragment {#text} at (12,0) size 236x19
+          text run at (12,0) width 236: "one Composition Spelling TextMatch"
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.png
index 62e128e..7305d55 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.txt
index c2b1ef0..bb54cbd 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/events/context-no-deselect-expected.txt
@@ -5,7 +5,6 @@
     LayoutBlockFlow {BODY} at (8,8) size 784x584
       LayoutTextControl {INPUT} at (0,0) size 131x19 [bgcolor=#FFFFFF] [border: (2px inset #EEEEEE)]
       LayoutText {#text} at (0,0) size 0x0
-      LayoutText {#text} at (0,0) size 0x0
 layer at (11,11) size 125x13
   LayoutBlockFlow {DIV} at (3,3) size 125x13
     LayoutText {#text} at (0,0) size 89x13
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/forms/select/hidden-listbox-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/forms/select/hidden-listbox-expected.txt
index 8453bf45..5169a1ca 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/forms/select/hidden-listbox-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/forms/select/hidden-listbox-expected.txt
@@ -7,8 +7,8 @@
         text run at (0,0) width 523: "This tests that the whole listbox control is hidden when visibility is set to hidden. "
       LayoutBR {BR} at (522,14) size 1x0
       LayoutText {#text} at (0,0) size 0x0
-hidden layer at (8,26) size 179x59 clip at (9,27) size 166x57 scrollHeight 56
-  LayoutListBox {SELECT} at (0,18.25) size 178.84x58.75 [bgcolor=#FFFFFF] [border: (1px solid #999999)]
-    LayoutBlockFlow {OPTION} at (1,1) size 165.84x14.19
-      LayoutText {#text} at (2,0) size 162x13
-        text run at (2,0) width 162: "This text should not be visible"
+hidden layer at (8,26) size 166x59 clip at (9,27) size 153x57
+  LayoutListBox {SELECT} at (0,18) size 165.67x58.75 [bgcolor=#FFFFFF] [border: (1px solid #999999)]
+    LayoutBlockFlow {OPTION} at (1,1) size 152.67x14.19
+      LayoutText {#text} at (2,0) size 149x13
+        text run at (2,0) width 149: "This text should not be visible"
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/mouseevent_fractional/fast/events/context-no-deselect-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/mouseevent_fractional/fast/events/context-no-deselect-expected.png
new file mode 100644
index 0000000..7305d55
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/mouseevent_fractional/fast/events/context-no-deselect-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/mouseevent_fractional/fast/events/context-no-deselect-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/mouseevent_fractional/fast/events/context-no-deselect-expected.txt
new file mode 100644
index 0000000..bb54cbd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/mouseevent_fractional/fast/events/context-no-deselect-expected.txt
@@ -0,0 +1,13 @@
+layer at (0,0) size 800x600
+  LayoutView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  LayoutBlockFlow {HTML} at (0,0) size 800x600
+    LayoutBlockFlow {BODY} at (8,8) size 784x584
+      LayoutTextControl {INPUT} at (0,0) size 131x19 [bgcolor=#FFFFFF] [border: (2px inset #EEEEEE)]
+      LayoutText {#text} at (0,0) size 0x0
+layer at (11,11) size 125x13
+  LayoutBlockFlow {DIV} at (3,3) size 125x13
+    LayoutText {#text} at (0,0) size 89x13
+      text run at (0,0) width 89: "some sample text"
+selection start: position 5 of child 0 {#text} of child 0 {DIV} of {#document-fragment} of child 1 {INPUT} of body
+selection end:   position 15 of child 0 {#text} of child 0 {DIV} of {#document-fragment} of child 1 {INPUT} of body
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/markers/first-letter-expected.png b/third_party/WebKit/LayoutTests/platform/mac/paint/markers/first-letter-expected.png
new file mode 100644
index 0000000..c21e7bbd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/paint/markers/first-letter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/markers/first-letter-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/paint/markers/first-letter-expected.txt
new file mode 100644
index 0000000..7711ef15
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/paint/markers/first-letter-expected.txt
@@ -0,0 +1,11 @@
+layer at (0,0) size 800x600
+  LayoutView at (0,0) size 800x600
+layer at (0,0) size 800x34
+  LayoutBlockFlow {HTML} at (0,0) size 800x34
+    LayoutBlockFlow {BODY} at (8,8) size 784x18
+      LayoutBlockFlow {DIV} at (0,0) size 784x18
+        LayoutInline {<pseudo:first-letter>} at (0,0) size 12x18 [color=#FF0000]
+          LayoutTextFragment (anonymous) at (0,0) size 12x18
+            text run at (0,0) width 12: "N"
+        LayoutTextFragment {#text} at (11,0) size 241x18
+          text run at (11,0) width 241: "one Composition Spelling TextMatch"
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/markers/first-letter-expected.png b/third_party/WebKit/LayoutTests/platform/win/paint/markers/first-letter-expected.png
new file mode 100644
index 0000000..4c05ea8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win/paint/markers/first-letter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/markers/first-letter-expected.txt b/third_party/WebKit/LayoutTests/platform/win/paint/markers/first-letter-expected.txt
new file mode 100644
index 0000000..32f1681
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win/paint/markers/first-letter-expected.txt
@@ -0,0 +1,11 @@
+layer at (0,0) size 800x600
+  LayoutView at (0,0) size 800x600
+layer at (0,0) size 800x36
+  LayoutBlockFlow {HTML} at (0,0) size 800x36
+    LayoutBlockFlow {BODY} at (8,8) size 784x20
+      LayoutBlockFlow {DIV} at (0,0) size 784x20
+        LayoutInline {<pseudo:first-letter>} at (0,0) size 12x19 [color=#FF0000]
+          LayoutTextFragment (anonymous) at (0,0) size 12x19
+            text run at (0,0) width 12: "N"
+        LayoutTextFragment {#text} at (12,0) size 223x19
+          text run at (12,0) width 223: "one Composition Spelling TextMatch"
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html b/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
index 3920f6a..d9d1bf0 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
@@ -65,10 +65,6 @@
   test(() => {
     assert_equals(matrixComponent.is2D, params.is2D);
   }, "is2D value is correct for " + params.cssText);
-
-  test(() => {
-    assert_equals(matrixComponent.toString(), params.cssText);
-  }, "toString is correct for " + params.cssText);
 }
 
 test(() => {
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssPerspective.html b/third_party/WebKit/LayoutTests/typedcssom/cssPerspective.html
index d7c8a0ea..8ed43658 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssPerspective.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssPerspective.html
@@ -7,12 +7,6 @@
 let EPSILON = 1e-6;
 
 test(() => {
-  let perspective = new CSSPerspective(new CSSUnitValue(10, 'px'));
-
-  assert_equals(perspective.toString(), 'perspective(10px)');
-}, "toString should return perspective(<CSSNumericValue.cssString()>)");
-
-test(() => {
   let transformValue = new CSSTransformValue([new CSSPerspective(CSS.em(10))]);
   assert_throws(new TypeError(), () => {
     transformValue.toMatrix();
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html b/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html
index 8c4ed25..bac1c6b50 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html
@@ -111,20 +111,6 @@
   });
 }, "Invalid arguments to constructor should throw");
 
-for (let params of testParams) {
-  test(() => {
-    assert_equals(params.input.toString(), params.cssText);
-  }, "toString value is correct for " + params.cssText);
-}
-
-test(() => {
-  let rotation = new CSSRotation(1, 2, 3, CSS.deg(10));
-  assert_equals(rotation.toString(), 'rotate3d(1, 2, 3, 10deg)');
-  rotation.is2D = true;
-  assert_true(rotation.is2D);
-  assert_equals(rotation.toString(), 'rotate(10deg)');
-}, "x, y, and z components are not included in toString when is2D is true");
-
 test(() => {
   // Obtained by doing the following in a console:
   // $0.style.transform = 'rotate3d(1, 2, 3, 10rad)';
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
index d3f6d34..6c88a98 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
@@ -50,12 +50,6 @@
   }
 ];
 
-for (let params of testParams) {
-  test(() => {
-    assert_equals(params.input.toString(), params.cssText);
-  }, "toString is correct for " + params.cssText);
-}
-
 test(() => {
   assert_throws(new TypeError(), () => { new CSSScale(); });
   assert_throws(new TypeError(), () => { new CSSScale(1); });
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssSkew.html b/third_party/WebKit/LayoutTests/typedcssom/cssSkew.html
deleted file mode 100644
index d224db4..0000000
--- a/third_party/WebKit/LayoutTests/typedcssom/cssSkew.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources/comparisons.js"></script>
-
-<script>
-var EPSILON = 1e-6; // float epsilon
-
-function tanUnitValue(unitValue) {
-  if (unitValue.unit == 'deg') {
-    var radians = unitValue.value * Math.PI / 180;
-    return Math.tan(radians);
-  }
-  if (unitValue.unit = 'rad') {
-    return Math.tan(unitValue.value);
-  }
-  // We've only used degrees and radians in this test.
-  throw Error('Test bug: helper function tanUnitValue needs updating.');
-}
-
-var testParams = [
-  {
-    input: new CSSSkew(new CSSUnitValue(0, 'deg'), new CSSUnitValue(0, 'deg')),
-    ax: new CSSUnitValue(0, 'deg'),
-    ay: new CSSUnitValue(0, 'deg'),
-    cssText: "skew(0deg, 0deg)"
-  },
-  {
-    input: new CSSSkew(new CSSUnitValue(1, 'deg'), new CSSUnitValue(2, 'deg')),
-    ax: new CSSUnitValue(1, 'deg'),
-    ay: new CSSUnitValue(2, 'deg'),
-    cssText: "skew(1deg, 2deg)"},
-  {
-    input: new CSSSkew(new CSSUnitValue(-2, 'deg'), new CSSUnitValue(-4, 'deg')),
-    ax: new CSSUnitValue(-2, 'deg'),
-    ay: new CSSUnitValue(-4, 'deg'),
-    cssText: "skew(-2deg, -4deg)"
-  },
-  {
-    input: new CSSSkew(
-        new CSSUnitValue(3.4, 'deg'), new CSSUnitValue(2.7, 'deg')),
-    ax: new CSSUnitValue(3.4, 'deg'),
-    ay: new CSSUnitValue(2.7, 'deg'),
-    cssText: "skew(3.4deg, 2.7deg)"
-  },
-  {
-    input: new CSSSkew(new CSSUnitValue(1, 'rad'), new CSSUnitValue(0, 'deg')),
-    ax: new CSSUnitValue(1, 'rad'),
-    ay: new CSSUnitValue(0, 'deg'),
-    cssText: "skew(1rad, 0deg)"
-  },
-  {
-    input: new CSSSkew(new CSSUnitValue(0, 'deg'), new CSSUnitValue(1, 'rad')),
-    ax: new CSSUnitValue(0, 'deg'),
-    ay: new CSSUnitValue(1, 'rad'),
-    cssText: "skew(0deg, 1rad)"
-  }
-];
-
-for (let params of testParams) {
-  test(() => {
-    assert_equals(params.input.toString(), params.cssText);
-  }, "toString is correct for " + params.cssText);
-}
-
-</script>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssTranslation.html b/third_party/WebKit/LayoutTests/typedcssom/cssTranslation.html
index 9eb5720..1260d3c7 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssTranslation.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssTranslation.html
@@ -47,12 +47,6 @@
   },
 ];
 
-for (let params of testParams) {
-  test(() => {
-    assert_equals(params.input.toString(), params.cssText);
-  }, "toString value is correct for " + params.cssText);
-}
-
 test(() => {
   assert_throws(new TypeError(), () => {
     new CSSTranslation(CSS.px(0), CSS.px(0), CSS.percent(10));
diff --git a/third_party/WebKit/LayoutTests/typedcssom/stylevalue-serialization/cssTransformValue.html b/third_party/WebKit/LayoutTests/typedcssom/stylevalue-serialization/cssTransformValue.html
new file mode 100644
index 0000000..d75a3b4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/typedcssom/stylevalue-serialization/cssTransformValue.html
@@ -0,0 +1,115 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>IDL-constructed CSSTransformValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#positionvalue-serialization">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gTestCases = [
+  {
+    value: new CSSTranslation(CSS.percent(1), CSS.px(1)),
+    cssText: 'translate(1%, 1px)',
+    desc: 'CSSTranslation with 2 arguments'
+  },
+  {
+    value: new CSSTranslation(CSS.px(1), CSS.percent(2), CSS.px(3)),
+    cssText: 'translate3d(1px, 2%, 3px)',
+    desc: 'CSSTranslation with 3 arguments'
+  },
+  {
+    value: new CSSScale(CSS.number(2), CSS.number(3)),
+    cssText: 'scale(2, 3)',
+    desc: 'CSSScale with 2 arguments'
+  },
+  {
+    value: new CSSScale(CSS.number(1), CSS.number(2), CSS.number(3)),
+    cssText: 'scale3d(1, 2, 3)',
+    desc: 'CSSScale with 3 arguments'
+  },
+  {
+    value: new CSSRotation(CSS.deg(90)),
+    cssText: 'rotate(90deg)',
+    desc: 'CSSRotation with 1 argument'
+  },
+  {
+    value: new CSSRotation(CSS.number(1), CSS.number(2), CSS.number(3), CSS.deg(90)),
+    cssText: 'rotate3d(1, 2, 3, 90deg)',
+    desc: 'CSSRotation with 4 arguments'
+  },
+  {
+    value: new CSSSkew(CSS.deg(90), CSS.deg(45)),
+    cssText: 'skew(90deg, 45deg)',
+    desc: 'CSSSkew'
+  },
+  {
+    value: new CSSPerspective(CSS.px(1)),
+    cssText: 'perspective(1px)',
+    desc: 'CSSPerspective'
+  },
+  {
+    value: new CSSTransformValue([new CSSPerspective(CSS.px(1))]),
+    cssText: 'perspective(1px)',
+    desc: 'CSSTransformValue with a single transform'
+  },
+  {
+    value: new CSSTransformValue([
+      new CSSTranslation(CSS.px(1), CSS.px(0)),
+      new CSSRotation(CSS.deg(90)),
+      new CSSPerspective(CSS.px(1)),
+      new CSSSkew(CSS.deg(90), CSS.deg(45)),
+      new CSSScale(CSS.number(1), CSS.number(2), CSS.number(3)),
+    ]),
+    cssText: 'translate(1px, 0px) rotate(90deg) perspective(1px) skew(90deg, 45deg) scale3d(1, 2, 3)',
+    desc: 'CSSTransformValue with multiple transforms'
+  },
+  {
+    value: new CSSTransformValue([
+      new CSSTranslation(new CSSMathSum(CSS.px(1), CSS.em(1)), CSS.px(0)),
+      new CSSRotation(new CSSMathSum(CSS.deg(90), CSS.turn(1))),
+      new CSSPerspective(new CSSMathSum(CSS.px(1), CSS.em(1))),
+      new CSSSkew(new CSSMathProduct(CSS.deg(90), 2), new CSSMathProduct(CSS.turn(1), 2)),
+      new CSSScale(
+        new CSSMathProduct(CSS.number(1), CSS.number(2)),
+        new CSSMathSum(CSS.number(1), CSS.number(1)),
+        new CSSMathProduct(CSS.number(3))
+      ),
+    ]),
+    cssText: 'translate(calc(1px + 1em), 0px) rotate(calc(90deg + 1turn)) perspective(calc(1px + 1em)) skew(calc(90deg * 2), calc(1turn * 2)) scale3d(calc(1 * 2), calc(1 + 1), calc(3))',
+    desc: 'CSSTransformValue containing CSSMathValues'
+  },
+  {
+    value: new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6])),
+    cssText: 'matrix(1, 2, 3, 4, 5, 6)',
+    desc: 'CSSMatrixComponent with 6 elements'
+  },
+  {
+    value: new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])),
+    cssText: 'matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)',
+    desc: 'CSSMatrixComponent with 16 elements'
+  },
+];
+
+for (const {value, cssText, desc} of gTestCases) {
+  test(() => {
+    assert_equals(value.toString(), cssText);
+  }, desc + ' serializes correctly');
+}
+
+test(() => {
+  let result = new CSSTransformValue([
+    new CSSTranslation(CSS.px(1), CSS.px(2), CSS.px(3)),
+    new CSSRotation(1, 2, 3, CSS.deg(90)),
+    new CSSScale(1, 2, 3),
+  ]);
+
+  for (const transform of result) {
+    transform.is2D = true;
+  }
+
+  assert_equals(result.toString(), 'translate(1px, 2px) rotate(90deg) scale(1, 2)');
+}, 'CSSTransformValue with updated is2D serializes as 2D transforms');
+
+</script>
diff --git a/third_party/WebKit/Source/DEPS b/third_party/WebKit/Source/DEPS
index 07607a1..8cf10da 100644
--- a/third_party/WebKit/Source/DEPS
+++ b/third_party/WebKit/Source/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
     "+base/callback.h",
     "+base/callback_forward.h",
+    "+base/containers/span.h",
     "+base/debug",
     "+base/gtest_prod_util.h",
     "+base/location.h",
diff --git a/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValue.h b/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValue.h
index ada23e1..6761d245 100644
--- a/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValue.h
+++ b/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValue.h
@@ -33,6 +33,7 @@
 
 #include <memory>
 
+#include "base/containers/span.h"
 #include "bindings/core/v8/NativeValueTraits.h"
 #include "bindings/core/v8/ScriptValue.h"
 #include "bindings/core/v8/serialization/Transferables.h"
@@ -147,8 +148,8 @@
 
   String ToWireString() const;
 
-  StringView GetWireData() const {
-    return StringView(data_buffer_.get(), data_buffer_size_);
+  base::span<const uint8_t> GetWireData() const {
+    return {data_buffer_.get(), data_buffer_size_};
   }
 
   // Deserializes the value (in the current context). Returns a null value in
diff --git a/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueTest.cpp b/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueTest.cpp
index d0f4874d..d16867d 100644
--- a/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueTest.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueTest.cpp
@@ -26,13 +26,12 @@
           scope.GetIsolate(), v8OriginalTrue,
           SerializedScriptValue::SerializeOptions(), ASSERT_NO_EXCEPTION);
 
-  StringView wire_data = sourceSerializedScriptValue->GetWireData();
-  DCHECK(wire_data.Is8Bit());
+  base::span<const uint8_t> wire_data =
+      sourceSerializedScriptValue->GetWireData();
 
   scoped_refptr<SerializedScriptValue> serializedScriptValue =
       SerializedScriptValue::Create(
-          reinterpret_cast<const char*>(wire_data.Characters8()),
-          wire_data.length());
+          reinterpret_cast<const char*>(wire_data.data()), wire_data.length());
   v8::Local<v8::Value> deserialized =
       serializedScriptValue->Deserialize(scope.GetIsolate());
   EXPECT_TRUE(deserialized->IsTrue());
diff --git a/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModulesTest.cpp b/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModulesTest.cpp
index b86c6069..05134bc 100644
--- a/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModulesTest.cpp
+++ b/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModulesTest.cpp
@@ -148,10 +148,9 @@
   scoped_refptr<SerializedScriptValue> serialized_value =
       SerializedScriptValue::Serialize(isolate, value, options,
                                        non_throwable_exception_state);
-  StringView ssv_wire_data = serialized_value->GetWireData();
-  DCHECK(ssv_wire_data.Is8Bit());
+  base::span<const uint8_t> ssv_wire_data = serialized_value->GetWireData();
   DCHECK(wire_bytes->IsEmpty());
-  wire_bytes->Append(ssv_wire_data.Characters8(), ssv_wire_data.length());
+  wire_bytes->Append(ssv_wire_data.data(), ssv_wire_data.length());
 
   // Sanity check that the serialization header has not changed, as the tests
   // that use this method rely on the header format.
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSRotation.cpp b/third_party/WebKit/Source/core/css/cssom/CSSRotation.cpp
index 4ecbe724..03ab7c9 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSRotation.cpp
+++ b/third_party/WebKit/Source/core/css/cssom/CSSRotation.cpp
@@ -165,8 +165,8 @@
   DCHECK(x_->to(CSSPrimitiveValue::UnitType::kNumber));
   DCHECK(y_->to(CSSPrimitiveValue::UnitType::kNumber));
   DCHECK(z_->to(CSSPrimitiveValue::UnitType::kNumber));
+  DCHECK(angle_->to(CSSPrimitiveValue::UnitType::kRadians));
 
-  CSSUnitValue* angle = ToCSSUnitValue(angle_);
   CSSFunctionValue* result =
       CSSFunctionValue::Create(is2D() ? CSSValueRotate : CSSValueRotate3d);
   if (!is2D()) {
@@ -180,8 +180,12 @@
     result->Append(*y);
     result->Append(*z);
   }
-  result->Append(
-      *CSSPrimitiveValue::Create(angle->value(), angle->GetInternalUnit()));
+
+  const CSSValue* angle = angle_->ToCSSValue();
+  if (!angle)
+    return nullptr;
+
+  result->Append(*angle);
   return result;
 }
 
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSSkew.cpp b/third_party/WebKit/Source/core/css/cssom/CSSSkew.cpp
index d539457e..52463f44 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSSkew.cpp
+++ b/third_party/WebKit/Source/core/css/cssom/CSSSkew.cpp
@@ -88,15 +88,14 @@
 }
 
 const CSSFunctionValue* CSSSkew::ToCSSValue() const {
-  // TDOO(meade): Handle calc angles here.
-  CSSUnitValue* ax = ToCSSUnitValue(ax_);
-  CSSUnitValue* ay = ToCSSUnitValue(ay_);
+  const CSSValue* ax = ax_->ToCSSValue();
+  const CSSValue* ay = ay_->ToCSSValue();
+  if (!ax || !ay)
+    return nullptr;
 
   CSSFunctionValue* result = CSSFunctionValue::Create(CSSValueSkew);
-  result->Append(
-      *CSSPrimitiveValue::Create(ax->value(), ax->GetInternalUnit()));
-  result->Append(
-      *CSSPrimitiveValue::Create(ay->value(), ay->GetInternalUnit()));
+  result->Append(*ax);
+  result->Append(*ay);
   return result;
 }
 
diff --git a/third_party/WebKit/Source/core/dom/Node.cpp b/third_party/WebKit/Source/core/dom/Node.cpp
index f16af36..458af6e4 100644
--- a/third_party/WebKit/Source/core/dom/Node.cpp
+++ b/third_party/WebKit/Source/core/dom/Node.cpp
@@ -2226,8 +2226,16 @@
 
   if (IsDisabledFormControl(this) && event.IsMouseEvent() &&
       !RuntimeEnabledFeatures::SendMouseEventsDisabledFormControlsEnabled()) {
-    UseCounter::Count(GetDocument(),
-                      WebFeature::kDispatchMouseEventOnDisabledFormControl);
+    if (HasEventListeners(event.type())) {
+      UseCounter::Count(GetDocument(),
+                        WebFeature::kDispatchMouseEventOnDisabledFormControl);
+      if (event.type() == EventTypeNames::mousedown ||
+          event.type() == EventTypeNames::mouseup) {
+        UseCounter::Count(
+            GetDocument(),
+            WebFeature::kDispatchMouseUpDownEventOnDisabledFormControl);
+      }
+    }
     return;
   }
 
diff --git a/third_party/WebKit/Source/core/dom/events/EventPath.cpp b/third_party/WebKit/Source/core/dom/events/EventPath.cpp
index 601c648..315c468 100644
--- a/third_party/WebKit/Source/core/dom/events/EventPath.cpp
+++ b/third_party/WebKit/Source/core/dom/events/EventPath.cpp
@@ -380,6 +380,15 @@
   return false;
 }
 
+bool EventPath::HasEventListenersInPath(const AtomicString& event_type) const {
+  for (const auto& context : node_event_contexts_) {
+    const Node* target_node = context.GetNode();
+    if (target_node && target_node->HasEventListeners(event_type))
+      return true;
+  }
+  return false;
+}
+
 NodeEventContext& EventPath::TopNodeEventContext() {
   DCHECK(!IsEmpty());
   return Last();
diff --git a/third_party/WebKit/Source/core/dom/events/EventPath.h b/third_party/WebKit/Source/core/dom/events/EventPath.h
index 2ca9e3e..e43d69b55 100644
--- a/third_party/WebKit/Source/core/dom/events/EventPath.h
+++ b/third_party/WebKit/Source/core/dom/events/EventPath.h
@@ -80,6 +80,7 @@
   void AdjustForTouchEvent(const TouchEvent&);
 
   bool DisabledFormControlExistsInPath() const;
+  bool HasEventListenersInPath(const AtomicString& event_type) const;
 
   NodeEventContext& TopNodeEventContext();
 
diff --git a/third_party/WebKit/Source/core/events/MouseEvent.cpp b/third_party/WebKit/Source/core/events/MouseEvent.cpp
index cbf2d02e7..54a89e32 100644
--- a/third_party/WebKit/Source/core/events/MouseEvent.cpp
+++ b/third_party/WebKit/Source/core/events/MouseEvent.cpp
@@ -27,6 +27,7 @@
 #include "core/frame/LocalDOMWindow.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/LocalFrameView.h"
+#include "core/frame/UseCounter.h"
 #include "core/input/InputDeviceCapabilities.h"
 #include "core/layout/LayoutObject.h"
 #include "core/layout/LayoutView.h"
@@ -447,8 +448,19 @@
     return dispatcher.Dispatch();
 
   if (!send_to_disabled_form_controls &&
-      IsDisabledFormControl(&dispatcher.GetNode()))
+      IsDisabledFormControl(&dispatcher.GetNode())) {
+    if (GetEventPath().HasEventListenersInPath(type())) {
+      UseCounter::Count(dispatcher.GetNode().GetDocument(),
+                        WebFeature::kDispatchMouseEventOnDisabledFormControl);
+      if (type() == EventTypeNames::mousedown ||
+          type() == EventTypeNames::mouseup) {
+        UseCounter::Count(
+            dispatcher.GetNode().GetDocument(),
+            WebFeature::kDispatchMouseUpDownEventOnDisabledFormControl);
+      }
+    }
     return DispatchEventResult::kCanceledBeforeDispatch;
+  }
 
   if (type().IsEmpty())
     return DispatchEventResult::kNotCanceled;  // Shouldn't happen.
diff --git a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp
index 5b1707f4..95709ba2 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.cpp
@@ -588,19 +588,6 @@
   return Response::OK();
 }
 
-Response InspectorPageAgent::navigate(const String& url,
-                                      Maybe<String> referrer,
-                                      Maybe<String> transitionType,
-                                      String* out_frame_id,
-                                      Maybe<String>* out_loader_id,
-                                      Maybe<String>* errorText) {
-  LocalFrame* frame = inspected_frames_->Root();
-  *out_frame_id = IdentifiersFactory::FrameId(frame);
-  DocumentLoader* loader = frame->Loader().GetDocumentLoader();
-  *out_loader_id = IdentifiersFactory::LoaderId(loader);
-  return Response::OK();
-}
-
 Response InspectorPageAgent::stopLoading() {
   return Response::OK();
 }
diff --git a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h
index d8aae489..e2f813b 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h
+++ b/third_party/WebKit/Source/core/inspector/InspectorPageAgent.h
@@ -118,12 +118,6 @@
   protocol::Response setLifecycleEventsEnabled(bool) override;
   protocol::Response reload(Maybe<bool> bypass_cache,
                             Maybe<String> script_to_evaluate_on_load) override;
-  protocol::Response navigate(const String& url,
-                              Maybe<String> referrer,
-                              Maybe<String> transitionType,
-                              String* frame_id,
-                              Maybe<String>* loader_id,
-                              Maybe<String>* errorText) override;
   protocol::Response stopLoading() override;
   protocol::Response setAdBlockingEnabled(bool) override;
   protocol::Response getResourceTree(
diff --git a/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json b/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
index 810fd76f..a41535d 100644
--- a/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
+++ b/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
@@ -80,7 +80,7 @@
             {
                 "domain": "Page",
                 "exclude": ["getNavigationHistory", "navigateToHistoryEntry", "captureScreenshot", "screencastFrameAck", "handleJavaScriptDialog", "setColorPickerEnabled",
-                            "getAppManifest", "requestAppBanner", "setControlNavigations", "processNavigation", "printToPDF", "bringToFront", "setDownloadBehavior"],
+                            "getAppManifest", "requestAppBanner", "setControlNavigations", "processNavigation", "printToPDF", "bringToFront", "setDownloadBehavior", "navigate"],
                 "async": ["getResourceContent", "searchInResource"],
                 "exclude_events": ["screencastFrame", "screencastVisibilityChanged", "colorPicked", "interstitialShown", "interstitialHidden", "javascriptDialogOpening", "javascriptDialogClosed", "navigationRequested"]
             },
diff --git a/third_party/WebKit/Source/core/messaging/BlinkCloneableMessageStructTraits.h b/third_party/WebKit/Source/core/messaging/BlinkCloneableMessageStructTraits.h
index 1a3b6ff..6abf59f9 100644
--- a/third_party/WebKit/Source/core/messaging/BlinkCloneableMessageStructTraits.h
+++ b/third_party/WebKit/Source/core/messaging/BlinkCloneableMessageStructTraits.h
@@ -18,10 +18,7 @@
                     blink::BlinkCloneableMessage> {
   static base::span<const uint8_t> encoded_message(
       blink::BlinkCloneableMessage& input) {
-    StringView wire_data = input.message->GetWireData();
-    return base::make_span(
-        reinterpret_cast<const uint8_t*>(wire_data.Characters8()),
-        wire_data.length());
+    return input.message->GetWireData();
   }
 
   static Vector<blink::mojom::blink::SerializedBlobPtr> blobs(
diff --git a/third_party/WebKit/Source/core/paint/InlineTextBoxPainter.cpp b/third_party/WebKit/Source/core/paint/InlineTextBoxPainter.cpp
index c504070..d24007e 100644
--- a/third_party/WebKit/Source/core/paint/InlineTextBoxPainter.cpp
+++ b/third_party/WebKit/Source/core/paint/InlineTextBoxPainter.cpp
@@ -39,12 +39,19 @@
 std::pair<unsigned, unsigned> GetTextMatchMarkerPaintOffsets(
     const DocumentMarker& marker,
     const InlineTextBox& text_box) {
+  // text_box.Start() returns an offset relative to the start of the layout
+  // object. We add the LineLayoutItem's TextStartOffset() to get a DOM offset
+  // (which is what DocumentMarker uses). This is necessary to get proper
+  // behavior with the :first-letter psuedo element.
+  const unsigned text_box_start =
+      text_box.Start() + text_box.GetLineLayoutItem().TextStartOffset();
+
   DCHECK_EQ(DocumentMarker::kTextMatch, marker.GetType());
-  const unsigned start_offset = marker.StartOffset() > text_box.Start()
-                                    ? marker.StartOffset() - text_box.Start()
+  const unsigned start_offset = marker.StartOffset() > text_box_start
+                                    ? marker.StartOffset() - text_box_start
                                     : 0U;
   const unsigned end_offset =
-      std::min(marker.EndOffset() - text_box.Start(), text_box.Len());
+      std::min(marker.EndOffset() - text_box_start, text_box.Len());
   return std::make_pair(start_offset, end_offset);
 }
 
@@ -440,10 +447,17 @@
   DCHECK(inline_text_box_.Truncation() != kCFullTruncation);
   DCHECK(inline_text_box_.Len());
 
+  // inline_text_box_.Start() returns an offset relative to the start of the
+  // layout object. We add the LineLayoutItem's TextStartOffset() to get a DOM
+  // offset (which is what DocumentMarker uses). This is necessary to get proper
+  // behavior with the :first-letter psuedo element.
+  const unsigned inline_text_box_start =
+      inline_text_box_.Start() +
+      inline_text_box_.GetLineLayoutItem().TextStartOffset();
+
   // Start painting at the beginning of the text or the specified underline
   // start offset, whichever is greater.
-  unsigned paint_start =
-      std::max(inline_text_box_.Start(), marker.StartOffset());
+  unsigned paint_start = std::max(inline_text_box_start, marker.StartOffset());
   // Cap the maximum paint start to the last character in the text box.
   paint_start = std::min(paint_start, inline_text_box_.end());
 
@@ -455,8 +469,8 @@
 
   // paint_start and paint_end are currently relative to the start of the text
   // node. Subtract to make them relative to the start of the InlineTextBox.
-  paint_start -= inline_text_box_.Start();
-  paint_end -= inline_text_box_.Start();
+  paint_start -= inline_text_box_start;
+  paint_end -= inline_text_box_start;
 
   return ApplyTruncationToPaintOffsets({paint_start, paint_end});
 }
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerPainter.cpp b/third_party/WebKit/Source/core/paint/PaintLayerPainter.cpp
index a02910c..cc01d0d 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerPainter.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerPainter.cpp
@@ -747,10 +747,10 @@
   }
 }
 
-static void ForAllFragments(
-    GraphicsContext& context,
-    const PaintLayerFragments& fragments,
-    const std::function<void(const PaintLayerFragment&)> function) {
+template <typename Function>
+static void ForAllFragments(GraphicsContext& context,
+                            const PaintLayerFragments& fragments,
+                            const Function& function) {
   for (size_t i = 0; i < fragments.size(); ++i) {
     Optional<ScopedDisplayItemFragment> scoped_display_item_fragment;
     if (i)
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
index 98aaf67..a6ab157 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
@@ -1652,6 +1652,8 @@
   if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
     return;
 
+  // TODO(crbug.com/797779): Implement fragments across frame boundaries.
+
   const auto* enclosing_pagination_layer =
       context_.painting_layer->EnclosingPaginationLayer();
   if (!enclosing_pagination_layer)
@@ -1679,8 +1681,7 @@
       first_fragment.SetPaginationOffset(
           ToLayoutPoint(iterator.PaginationOffset()));
     }
-  } else {
-    DCHECK(parent_composited_layer);
+  } else if (parent_composited_layer) {
     // All objects under the composited layer use the same pagination offset.
     first_fragment.SetPaginationOffset(
         parent_composited_layer->GetLayoutObject()
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp
index f3458eb..29739cc 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp
@@ -3735,6 +3735,36 @@
   // TODO(crbug.com/797779): Add code to verify fragments under the iframe.
 }
 
+TEST_P(PaintPropertyTreeBuilderTest, CompositedMulticolFrameUnderMulticol) {
+  // TODO(crbug.com/796768): Currently this test crashes for SPv2 when mapping
+  // layer clip rects from one fragment to another. May need to adjust fragment
+  // clip hierarchy to fix the crash.
+  if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
+    return;
+
+  SetBodyInnerHTML(R"HTML(
+    <style>body { margin: 0 }</style>
+    <div style='columns: 3; column-gap: 0; column-fill: auto;
+        width: 300px; height: 200px'>
+      <div style='height: 300px'></div>
+      <iframe id='iframe' style='will-change: transform;
+          width: 90px; height: 300px; border: none; background: green'></iframe>
+    </div>
+  )HTML");
+  SetChildFrameHTML(R"HTML(
+    <style>body { margin: 0 }</style>
+    <div style='columns: 2; column-gap: 0; column-fill: auto;
+        width: 80px; height: 100px'>
+      <div id="multicolContent" style='height: 200px; background: blue'></div>
+    </div>
+  )HTML");
+
+  // This should not crash on duplicated subsequences in the iframe.
+  GetDocument().View()->UpdateAllLifecyclePhases();
+
+  // TODO(crbug.com/797779): Add code to verify fragments under the iframe.
+}
+
 TEST_P(PaintPropertyTreeBuilderTest,
        FragmentedBecomesUnfragmentedClearPaginationOffset) {
   SetBodyInnerHTML(R"HTML(
diff --git a/third_party/WebKit/Source/core/testing/Internals.cpp b/third_party/WebKit/Source/core/testing/Internals.cpp
index 8e3e29f..aef84a1 100644
--- a/third_party/WebKit/Source/core/testing/Internals.cpp
+++ b/third_party/WebKit/Source/core/testing/Internals.cpp
@@ -2860,12 +2860,11 @@
 
 DOMArrayBuffer* Internals::serializeObject(
     scoped_refptr<SerializedScriptValue> value) const {
-  StringView view = value->GetWireData();
-  DCHECK(view.Is8Bit());
+  base::span<const uint8_t> span = value->GetWireData();
   DOMArrayBuffer* buffer =
-      DOMArrayBuffer::CreateUninitializedOrNull(view.length(), sizeof(LChar));
+      DOMArrayBuffer::CreateUninitializedOrNull(span.length(), sizeof(uint8_t));
   if (buffer)
-    memcpy(buffer->Data(), view.Characters8(), view.length());
+    memcpy(buffer->Data(), span.data(), span.length());
   return buffer;
 }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/Tests.js b/third_party/WebKit/Source/devtools/front_end/Tests.js
index 89c80ee..fd97c75 100644
--- a/third_party/WebKit/Source/devtools/front_end/Tests.js
+++ b/third_party/WebKit/Source/devtools/front_end/Tests.js
@@ -902,11 +902,22 @@
   };
 
   TestSuite.prototype.testWindowInitializedOnNavigateBack = function() {
+    var test = this;
+    test.takeControl();
     var messages = ConsoleModel.consoleModel.messages();
-    this.assertEquals(1, messages.length);
-    var text = messages[0].messageText;
-    if (text.indexOf('Uncaught') !== -1)
-      this.fail(text);
+    if (messages.length === 1) {
+      checkMessages();
+    } else {
+      ConsoleModel.consoleModel.addEventListener(
+          ConsoleModel.ConsoleModel.Events.MessageAdded, checkMessages.bind(this), this);
+    }
+
+    function checkMessages() {
+      var messages = ConsoleModel.consoleModel.messages();
+      test.assertEquals(1, messages.length);
+      test.assertTrue(messages[0].messageText.indexOf('Uncaught') === -1);
+      test.releaseControl();
+    }
   };
 
   TestSuite.prototype.testConsoleContextNames = function() {
diff --git a/third_party/WebKit/Source/devtools/front_end/extensions/ExtensionAPI.js b/third_party/WebKit/Source/devtools/front_end/extensions/ExtensionAPI.js
index 945f961..693f9e2 100644
--- a/third_party/WebKit/Source/devtools/front_end/extensions/ExtensionAPI.js
+++ b/third_party/WebKit/Source/devtools/front_end/extensions/ExtensionAPI.js
@@ -94,6 +94,11 @@
  * @suppressGlobalPropertiesCheck
  */
 function injectedExtensionAPI(extensionInfo, inspectedTabId, themeName, testHook, injectedScriptId) {
+  var chrome = window.chrome || {};
+  var devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
+  if (devtools_descriptor)
+    return;
+
   var apiPrivate = {};
 
   defineCommonExtensionSymbols(apiPrivate);
@@ -757,13 +762,7 @@
   var extensionServer = new ExtensionServerClient();
   var coreAPI = new InspectorExtensionAPI();
 
-  var chrome = window.chrome || {};
-  // Override chrome.devtools as a workaround for a error-throwing getter being exposed
-  // in extension pages loaded into a non-extension process (only happens for remote client
-  // extensions)
-  var devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
-  if (!devtools_descriptor || devtools_descriptor.get)
-    Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
+  Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
 
   // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
   chrome.devtools.inspectedWindow = {};
diff --git a/third_party/WebKit/Source/devtools/front_end/network/RequestHeadersView.js b/third_party/WebKit/Source/devtools/front_end/network/RequestHeadersView.js
index a55bf58c..781f171 100644
--- a/third_party/WebKit/Source/devtools/front_end/network/RequestHeadersView.js
+++ b/third_party/WebKit/Source/devtools/front_end/network/RequestHeadersView.js
@@ -109,7 +109,8 @@
    */
   _formatHeader(name, value) {
     var fragment = createDocumentFragment();
-    fragment.createChild('div', 'header-name').textContent = name + ':';
+    fragment.createChild('div', 'header-name').textContent = name + ': ';
+    fragment.createChild('span', 'header-separator');
     fragment.createChild('div', 'header-value source-code').textContent = value;
 
     return fragment;
@@ -253,9 +254,10 @@
     for (var i = 0; i < params.length; ++i) {
       var paramNameValue = createDocumentFragment();
       if (params[i].name !== '') {
-        var name = this._formatParameter(params[i].name + ':', 'header-name', this._decodeRequestParameters);
+        var name = this._formatParameter(params[i].name + ': ', 'header-name', this._decodeRequestParameters);
         var value = this._formatParameter(params[i].value, 'header-value source-code', this._decodeRequestParameters);
         paramNameValue.appendChild(name);
+        paramNameValue.createChild('span', 'header-separator');
         paramNameValue.appendChild(value);
       } else {
         paramNameValue.appendChild(
@@ -373,7 +375,8 @@
 
     if (this._request.statusCode) {
       var statusCodeFragment = createDocumentFragment();
-      statusCodeFragment.createChild('div', 'header-name').textContent = Common.UIString('Status Code') + ':';
+      statusCodeFragment.createChild('div', 'header-name').textContent = Common.UIString('Status Code') + ': ';
+      statusCodeFragment.createChild('span', 'header-separator');
 
       var statusCodeImage = statusCodeFragment.createChild('label', 'resource-status-image', 'dt-icon-label');
       statusCodeImage.title = this._request.statusCode + ' ' + this._request.statusText;
diff --git a/third_party/WebKit/Source/devtools/front_end/network/requestHeadersTree.css b/third_party/WebKit/Source/devtools/front_end/network/requestHeadersTree.css
index 2b2239b..5d3ae946 100644
--- a/third_party/WebKit/Source/devtools/front_end/network/requestHeadersTree.css
+++ b/third_party/WebKit/Source/devtools/front_end/network/requestHeadersTree.css
@@ -64,12 +64,16 @@
 .tree-outline .header-name {
     color: rgb(33%, 33%, 33%);
     display: inline-block;
-    margin-right: 0.5em;
+    margin-right: 0.25em;
     font-weight: bold;
     vertical-align: top;
     white-space: pre-wrap;
 }
 
+.tree-outline .header-separator {
+    user-select: none;
+}
+
 .tree-outline .header-value {
     display: inline;
     margin-right: 1em;
diff --git a/third_party/WebKit/Source/devtools/front_end/persistence/NetworkPersistenceManager.js b/third_party/WebKit/Source/devtools/front_end/persistence/NetworkPersistenceManager.js
index 1247aa2..97f7e428 100644
--- a/third_party/WebKit/Source/devtools/front_end/persistence/NetworkPersistenceManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/persistence/NetworkPersistenceManager.js
@@ -351,7 +351,7 @@
    * @param {!Workspace.UISourceCode} uiSourceCode
    */
   _networkUISourceCodeRemoved(uiSourceCode) {
-    if (uiSourceCode.project().type() === Workspace.projectTypes.Network)
+    if (uiSourceCode.project().type() !== Workspace.projectTypes.Network)
       return;
     this._unbind(uiSourceCode);
     this._networkUISourceCodeForEncodedPath.delete(this._encodedPathFromUrl(uiSourceCode.url()));
diff --git a/third_party/WebKit/Source/modules/indexeddb/IDBTestHelper.cpp b/third_party/WebKit/Source/modules/indexeddb/IDBTestHelper.cpp
index e3bb3ca1..e4c055a 100644
--- a/third_party/WebKit/Source/modules/indexeddb/IDBTestHelper.cpp
+++ b/third_party/WebKit/Source/modules/indexeddb/IDBTestHelper.cpp
@@ -21,13 +21,11 @@
   scoped_refptr<SerializedScriptValue> null_ssv =
       SerializedScriptValue::NullValue();
 
-  StringView ssv_wire_bytes = null_ssv->GetWireData();
-  DCHECK(ssv_wire_bytes.Is8Bit());
+  base::span<const uint8_t> ssv_wire_bytes = null_ssv->GetWireData();
 
   scoped_refptr<SharedBuffer> idb_value_buffer = SharedBuffer::Create();
-  idb_value_buffer->Append(
-      reinterpret_cast<const char*>(ssv_wire_bytes.Characters8()),
-      ssv_wire_bytes.length());
+  idb_value_buffer->Append(reinterpret_cast<const char*>(ssv_wire_bytes.data()),
+                           ssv_wire_bytes.length());
   return IDBValue::Create(std::move(idb_value_buffer),
                           Vector<scoped_refptr<BlobDataHandle>>(),
                           Vector<WebBlobInfo>(), IDBKey::CreateNumber(42.0),
diff --git a/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.cpp b/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.cpp
index 47b222b..6fa01de 100644
--- a/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.cpp
+++ b/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.cpp
@@ -123,7 +123,6 @@
 #endif  // DCHECK_IS_ON()
 
   wire_data_ = serialized_value_->GetWireData();
-  DCHECK(wire_data_.Is8Bit());
   for (const auto& kvp : serialized_value_->BlobDataHandles())
     blob_handles_.push_back(std::move(kvp.value));
 }
@@ -147,7 +146,7 @@
   //                         Blob::Create to avoid a buffer copy.
   std::unique_ptr<BlobData> wrapper_blob_data = BlobData::Create();
   wrapper_blob_data->SetContentType(String(kWrapMimeType));
-  wrapper_blob_data->AppendBytes(wire_data_.Characters8(), wire_data_size);
+  wrapper_blob_data->AppendBytes(wire_data_.data(), wire_data_size);
   scoped_refptr<BlobDataHandle> wrapper_handle =
       BlobDataHandle::Create(std::move(wrapper_blob_data), wire_data_size);
   blob_info_.emplace_back(wrapper_handle->Uuid(), wrapper_handle->GetType(),
@@ -162,9 +161,10 @@
   IDBValueWrapper::WriteVarInt(serialized_value_->BlobDataHandles().size(),
                                wire_data_buffer_);
 
-  wire_data_ = StringView(wire_data_buffer_.data(), wire_data_buffer_.size());
+  wire_data_ = base::make_span(
+      reinterpret_cast<const uint8_t*>(wire_data_buffer_.data()),
+      wire_data_buffer_.size());
   DCHECK(!wire_data_buffer_.IsEmpty());
-  DCHECK(wire_data_.Is8Bit());
   return true;
 }
 
@@ -177,16 +177,15 @@
 
   if (wire_data_buffer_.IsEmpty()) {
     // The wire bytes are coming directly from the SSV's GetWireData() call.
-    DCHECK_EQ(wire_data_.Characters8(),
-              serialized_value_->GetWireData().Characters8());
+    DCHECK_EQ(wire_data_.data(), serialized_value_->GetWireData().data());
     DCHECK_EQ(wire_data_.length(), serialized_value_->GetWireData().length());
-    return SharedBuffer::Create(wire_data_.Characters8(),
+    return SharedBuffer::Create(wire_data_.data(),
                                 static_cast<size_t>(wire_data_.length()));
   }
 
   // The wire bytes are coming from wire_data_buffer_, so we can avoid a copy.
   DCHECK_EQ(wire_data_buffer_.data(),
-            reinterpret_cast<const char*>(wire_data_.Characters8()));
+            reinterpret_cast<const char*>(wire_data_.data()));
   DCHECK_EQ(wire_data_buffer_.size(), wire_data_.length());
   return SharedBuffer::AdoptVector(wire_data_buffer_);
 }
diff --git a/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h b/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h
index e0aa0785..0e6b29af 100644
--- a/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h
+++ b/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h
@@ -185,7 +185,7 @@
   Vector<char> wire_data_buffer_;
 
   // Points into SerializedScriptValue's data buffer, or into wire_data_buffer_.
-  StringView wire_data_;
+  base::span<const uint8_t> wire_data_;
 
   size_t original_data_length_ = 0;
 
diff --git a/third_party/WebKit/Source/modules/notifications/NotificationData.cpp b/third_party/WebKit/Source/modules/notifications/NotificationData.cpp
index cd3200cb..b701d5f 100644
--- a/third_party/WebKit/Source/modules/notifications/NotificationData.cpp
+++ b/third_party/WebKit/Source/modules/notifications/NotificationData.cpp
@@ -95,13 +95,7 @@
     if (exception_state.HadException())
       return WebNotificationData();
 
-    StringView ssv_wire_data = serialized_script_value->GetWireData();
-    DCHECK(ssv_wire_data.Is8Bit());
-    Vector<char> serialized_data;
-    serialized_data.ReserveInitialCapacity(ssv_wire_data.length());
-    serialized_data.Append(ssv_wire_data.Characters8(), ssv_wire_data.length());
-
-    web_data.data = serialized_data;
+    web_data.data = WebVector<char>(serialized_script_value->GetWireData());
   }
 
   Vector<WebNotificationAction> actions;
diff --git a/third_party/WebKit/Source/platform/heap/HeapTest.cpp b/third_party/WebKit/Source/platform/heap/HeapTest.cpp
index 1a43500..9b870d5 100644
--- a/third_party/WebKit/Source/platform/heap/HeapTest.cpp
+++ b/third_party/WebKit/Source/platform/heap/HeapTest.cpp
@@ -42,6 +42,7 @@
 #include "platform/heap/HeapTestUtilities.h"
 #include "platform/heap/SafePoint.h"
 #include "platform/heap/SelfKeepAlive.h"
+#include "platform/heap/StackFrameDepth.h"
 #include "platform/heap/ThreadState.h"
 #include "platform/heap/Visitor.h"
 #include "platform/testing/UnitTestHelpers.h"
@@ -6253,6 +6254,20 @@
   EXPECT_EQ(kGrowsTowardsLower, StackGrowthDirection());
 }
 
+TEST(HeapTest, StackFrameDepthDisabledByDefault) {
+  StackFrameDepth depth;
+  // Only allow recursion after explicitly enabling the stack limit.
+  EXPECT_FALSE(depth.IsSafeToRecurse());
+}
+
+TEST(HeapTest, StackFrameDepthEnable) {
+  StackFrameDepth depth;
+  StackFrameDepthScope scope(&depth);
+  // The scope may fail to enable recursion when the stack is close to the
+  // limit. In all other cases we should be able to safely recurse.
+  EXPECT_TRUE(depth.IsSafeToRecurse() || !depth.IsEnabled());
+}
+
 class TestMixinAllocationA : public GarbageCollected<TestMixinAllocationA>,
                              public GarbageCollectedMixin {
   USING_GARBAGE_COLLECTED_MIXIN(TestMixinAllocationA);
diff --git a/third_party/WebKit/Source/platform/heap/StackFrameDepth.h b/third_party/WebKit/Source/platform/heap/StackFrameDepth.h
index 47ed658..73c8fb9 100644
--- a/third_party/WebKit/Source/platform/heap/StackFrameDepth.h
+++ b/third_party/WebKit/Source/platform/heap/StackFrameDepth.h
@@ -77,7 +77,7 @@
   // The stack pointer is assumed to grow towards lower addresses;
   // |kMinimumStackLimit| then being the limit that a stack
   // pointer will always exceed.
-  static const uintptr_t kMinimumStackLimit = ~0ul;
+  static const uintptr_t kMinimumStackLimit = ~uintptr_t{0};
 
   static uintptr_t GetFallbackStackLimit();
 
diff --git a/third_party/WebKit/Source/platform/runtime_enabled_features.json5 b/third_party/WebKit/Source/platform/runtime_enabled_features.json5
index 714cda7f..68e6f5e 100644
--- a/third_party/WebKit/Source/platform/runtime_enabled_features.json5
+++ b/third_party/WebKit/Source/platform/runtime_enabled_features.json5
@@ -273,7 +273,7 @@
     },
     {
       name: "CSSScrollSnapPoints",
-      status: "test",
+      status: "experimental",
     },
     {
       name: "CSSSnapSize",
diff --git a/third_party/WebKit/Tools/Scripts/merge-layout-test-results b/third_party/WebKit/Tools/Scripts/merge-layout-test-results
index fda1453e..1b0d6b0 100755
--- a/third_party/WebKit/Tools/Scripts/merge-layout-test-results
+++ b/third_party/WebKit/Tools/Scripts/merge-layout-test-results
@@ -101,7 +101,12 @@
         help='Directories to merge the results from.')
 
     # Swarming Isolated Merge Script API
-    # script.py --build-properties /s/build.json --output-json /tmp/output.json  shard0/output.json shard1/output.json
+    # script.py \
+    #     --build-properties /s/build.json \
+    #     --output-json /tmp/output.json \
+    #     --task-output-dir /path/to/task/output/dir \
+    #     shard0/output.json \
+    #     shard1/output.json
     parser.add_argument(
         '-o', '--output-json',
         help='(Swarming Isolated Merge Script API) Output JSON file to create.')
@@ -109,6 +114,9 @@
         '--build-properties',
         help='(Swarming Isolated Merge Script API) Build property JSON file provided by recipes.')
     parser.add_argument(
+        '--task-output-dir',
+        help='(Swarming Isolated Merge Script API) Directory containing all swarming task results.')
+    parser.add_argument(
         '--results-json-override-with-build-property',
         nargs=2, metavar=('RESULT_JSON_KEY', 'BUILD_PROPERTY_KEY'), default=[],
         action='append',
diff --git a/third_party/WebKit/public/platform/web_feature.mojom b/third_party/WebKit/public/platform/web_feature.mojom
index f109f87e..fb53e9261 100644
--- a/third_party/WebKit/public/platform/web_feature.mojom
+++ b/third_party/WebKit/public/platform/web_feature.mojom
@@ -1827,6 +1827,7 @@
   kCSSSelectorWebkitTextfieldDecorationContainer = 2318,
   kCSSSelectorWebkitUnknownPseudo = 2319,
   kFilterAsContainingBlockMayChangeOutput = 2320,
+  kDispatchMouseUpDownEventOnDisabledFormControl = 2321,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/tools/binary_size/libsupersize/archive.py b/tools/binary_size/libsupersize/archive.py
index a39e13a..105ccaa 100644
--- a/tools/binary_size/libsupersize/archive.py
+++ b/tools/binary_size/libsupersize/archive.py
@@ -72,6 +72,9 @@
     elif full_name.startswith('rel.'):
       symbol.flags |= models.FLAG_REL
       symbol.full_name = full_name[4:]
+    elif full_name.startswith('hot.'):
+      symbol.flags |= models.FLAG_HOT
+      symbol.full_name = full_name[4:]
 
 
 def _NormalizeNames(raw_symbols):
diff --git a/tools/binary_size/libsupersize/function_signature.py b/tools/binary_size/libsupersize/function_signature.py
index fbd1e67..4fb954c 100644
--- a/tools/binary_size/libsupersize/function_signature.py
+++ b/tools/binary_size/libsupersize/function_signature.py
@@ -77,9 +77,11 @@
     space_idx = paren_idx - 6
   while True:
     space_idx = _FindLastCharOutsideOfBrackets(name, ' ', space_idx)
-    # Special case: "operator new", and "operator<< <template>".
+    # Special cases: "operator new", "operator< <templ>", "operator<< <tmpl>".
+    # No space is added for operator>><tmpl>.
     if -1 == space_idx or (
         -1 == name.find('operator', space_idx - 8, space_idx) and
+        -1 == name.find('operator<', space_idx - 9, space_idx) and
         -1 == name.find('operator<<', space_idx - 10, space_idx)):
       break
     space_idx -= 8
@@ -93,13 +95,10 @@
     if last_right_idx == -1:
       return name
     left_idx = _FindLastCharOutsideOfBrackets(name, '<', last_right_idx + 1)
-    if left_idx == -1:
-      return name
-    # Special case: std::operator<< <
-    if left_idx > 0 and name[left_idx - 1] == ' ':
-      left_idx -= 1
-    name = name[:left_idx] + name[last_right_idx + 1:]
-    last_right_idx = left_idx
+    if left_idx != -1:
+      # Leave in empty <>s to denote that it's a template.
+      name = name[:left_idx + 1] + name[last_right_idx:]
+      last_right_idx = left_idx
 
 
 def _NormalizeTopLevelGccLambda(name, left_paren_idx):
diff --git a/tools/binary_size/libsupersize/function_signature_test.py b/tools/binary_size/libsupersize/function_signature_test.py
index b830366b..65e755bfb 100755
--- a/tools/binary_size/libsupersize/function_signature_test.py
+++ b/tools/binary_size/libsupersize/function_signature_test.py
@@ -16,7 +16,7 @@
     def check(ret_part, name_part, params_part, after_part='',
               name_without_templates=None):
       if name_without_templates is None:
-        name_without_templates = re.sub(r'<.*?>', '', name_part) + after_part
+        name_without_templates = re.sub(r'<.*?>', '<>', name_part) + after_part
 
       signature = ''.join((name_part, params_part, after_part))
       got_full_name, got_template_name, got_name = (
@@ -60,7 +60,22 @@
     check('std::basic_ostream<char, std::char_traits<char> >& ',
           'std::operator<< <std::char_traits<char> >',
           '(std::basic_ostream<char, std::char_traits<char> >&, char)',
-          name_without_templates='std::operator<<')
+          name_without_templates='std::operator<< <>')
+    check('',
+          'std::basic_istream<char, std::char_traits<char> >'
+          '::operator>>',
+          '(unsigned int&)',
+          name_without_templates='std::basic_istream<>::operator>>')
+    check('',
+          'std::operator><std::allocator<char> >', '()',
+          name_without_templates='std::operator><>')
+    check('',
+          'std::operator>><std::allocator<char> >',
+          '(std::basic_istream<char, std::char_traits<char> >&)',
+          name_without_templates='std::operator>><>')
+    check('',
+          'std::basic_istream<char>::operator>', '(unsigned int&)',
+          name_without_templates='std::basic_istream<>::operator>')
     check('v8::internal::SlotCallbackResult ',
           'v8::internal::UpdateTypedSlotHelper::UpdateCodeTarget'
           '<v8::PointerUpdateJobTraits<(v8::Direction)1>::Foo(v8::Heap*, '
@@ -71,7 +86,7 @@
           '{lambda(v8::SlotType)#2}::operator()(v8::SlotType) const::'
           '{lambda(v8::Object**)#1})',
           name_without_templates=(
-              'v8::internal::UpdateTypedSlotHelper::UpdateCodeTarget'))
+              'v8::internal::UpdateTypedSlotHelper::UpdateCodeTarget<>'))
     check('',
           'WTF::StringAppend<WTF::String, WTF::String>::operator WTF::String',
           '()',
@@ -86,7 +101,7 @@
 
     # Test with multiple template args.
     check('int ', 'Foo<int()>::bar<a<b> >', '()',
-          name_without_templates='Foo::bar')
+          name_without_templates='Foo<>::bar<>')
 
     # SkArithmeticImageFilter.cpp has class within function body. e.g.:
     #   ArithmeticFP::onCreateGLSLInstance() looks like:
@@ -125,7 +140,7 @@
     check('', 'blink::CSSValueKeywordsHash::findValueImpl', '(char const*)',
           '::value_word_list')
     check('', 'foo::Bar<Z<Y> >::foo<bar>', '(abc)', '::var<baz>',
-          name_without_templates='foo::Bar::foo::var')
+          name_without_templates='foo::Bar<>::foo<>::var<>')
 
 if __name__ == '__main__':
   logging.basicConfig(level=logging.DEBUG,
diff --git a/tools/binary_size/libsupersize/models.py b/tools/binary_size/libsupersize/models.py
index 72abfdf..0dc9aba8 100644
--- a/tools/binary_size/libsupersize/models.py
+++ b/tools/binary_size/libsupersize/models.py
@@ -89,6 +89,7 @@
 FLAG_REL_LOCAL = 16
 FLAG_GENERATED_SOURCE = 32
 FLAG_CLONE = 64
+FLAG_HOT = 128
 
 
 DIFF_STATUS_UNCHANGED = 0
@@ -247,6 +248,8 @@
       parts.append('gen')
     if flags & FLAG_CLONE:
       parts.append('clone')
+    if flags & FLAG_HOT:
+      parts.append('hot')
     return '{%s}' % ','.join(parts)
 
   def IsBss(self):
diff --git a/tools/binary_size/libsupersize/testdata/test.map b/tools/binary_size/libsupersize/testdata/test.map
index ba91d9e..e1a6a12b 100644
--- a/tools/binary_size/libsupersize/testdata/test.map
+++ b/tools/binary_size/libsupersize/testdata/test.map
@@ -92,7 +92,7 @@
  .text._ZN5blink23ContiguousContainerBase11shrinkToFitEv
                 0x002a0000       0x10 obj/third_party/WebKit.a(PaintChunker.o)
                 0x002a0001                blink::ContiguousContainerBase::shrinkToFit()
- .text._ZN5blink23ContiguousContainerBase11shrinkToFitEv2
+ .text.hot._ZN5blink23ContiguousContainerBase11shrinkToFitEv2
                 0x002a0010       0xc obj/third_party/WebKit.a(PaintChunker.o)
                 0x002a0011                blink::ContiguousContainerBase::shrinkToFit() [clone .part.1234] [clone .isra.2]
  .text._ZN5blink23ContiguousContainerBaseC2EOS0_
diff --git a/tools/chrome_proxy/webdriver/lite_page.py b/tools/chrome_proxy/webdriver/lite_page.py
index b0d1d54..a9d83e2a 100644
--- a/tools/chrome_proxy/webdriver/lite_page.py
+++ b/tools/chrome_proxy/webdriver/lite_page.py
@@ -147,7 +147,7 @@
   # Android because it depends on window size of the browser.
   @AndroidOnly
   @ChromeVersionBeforeM(65)
-  def testLitePageBTF(self):
+  def testLitePageBTFOldFlags(self):
     # If it was attempted to run with another experiment, skip this test.
     if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
         in common.ParseFlags().browser_args):
@@ -192,11 +192,64 @@
         self.assertHasChromeProxyViaHeader(response)
         self.assertIn(response.status, [200, 204])
 
+  # Checks that a Lite Page does not have an error when scrolling to the bottom
+  # of the page and is able to load all resources. This test is only run on
+  # Android because it depends on window size of the browser.
+  @AndroidOnly
+  @ChromeVersionEqualOrAfterM(65)
+  def testLitePageBTFWithoutFallback(self):
+    # If it was attempted to run with another experiment, skip this test.
+    if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
+        in common.ParseFlags().browser_args):
+      self.skipTest('This test cannot be run with other experiments.')
+    with TestDriver() as test_driver:
+      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
+      # Need to force 2G speed to get lite-page response.
+      test_driver.AddChromeArg('--force-effective-connection-type=2G')
+
+      # Need to force lite page so target page doesn't fallback to Lo-Fi
+      # Set exp=alt1 to force Lite-page response.
+      test_driver.AddChromeArg('--data-reduction-proxy-experiment=alt1')
+
+      # This page is long and has many media resources.
+      test_driver.LoadURL('http://check.googlezip.net/metrics/index.html')
+
+      # Verify that a Lite Page response for the main frame was seen.
+      lite_page_responses = 0
+      for response in test_driver.GetHTTPResponses():
+        # Skip CSI requests when validating Lite Page headers. CSI requests
+        # aren't expected to have LoFi headers.
+        if '/csi?' in response.url:
+          continue
+        if response.url.startswith('data:'):
+          continue
+        if (self.checkLitePageResponse(response)):
+          lite_page_responses = lite_page_responses + 1
+      self.assertEqual(1, lite_page_responses)
+
+      # Scroll to the bottom of the window and ensure scrollHeight increases.
+      original_scroll_height = test_driver.ExecuteJavascriptStatement(
+        'document.body.scrollHeight')
+      test_driver.ExecuteJavascriptStatement(
+        'window.scrollTo(0,Math.max(document.body.scrollHeight));')
+      # Give some time for loading after scrolling.
+      time.sleep(2)
+      new_scroll_height = test_driver.ExecuteJavascriptStatement(
+        'document.body.scrollHeight')
+      self.assertGreater(new_scroll_height, original_scroll_height)
+
+      # Make sure there were more requests that were proxied.
+      responses = test_driver.GetHTTPResponses(override_has_logs=True)
+      self.assertNotEqual(0, len(responses))
+      for response in responses:
+        self.assertHasChromeProxyViaHeader(response)
+        self.assertIn(response.status, [200, 204])
+
   # Checks that a Nano Lite Page does not have an error when scrolling to the
   # bottom of the page and is able to load all resources. This test is only run
   # on Android because it depends on window size of the browser.
   @AndroidOnly
-  @ChromeVersionEqualOrAfterM(61)
+  @ChromeVersionEqualOrAfterM(65)
   def testLitePageBTFNano(self):
     # If it was attempted to run with another experiment, skip this test.
     if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
@@ -316,7 +369,6 @@
 
       self.assertEqual(0, lite_page_responses)
       self.assertNotEqual(0, lofi_resource)
-      self.assertNotEqual(0, lofi_resource)
 
   # Checks that the server provides a preview (either Lite Page or fallback
   # to LoFi) for a 2G connection.
diff --git a/tools/chrome_proxy/webdriver/lofi.py b/tools/chrome_proxy/webdriver/lofi.py
index 59caa20..87ea292 100644
--- a/tools/chrome_proxy/webdriver/lofi.py
+++ b/tools/chrome_proxy/webdriver/lofi.py
@@ -14,7 +14,7 @@
   #  The test page is uncacheable otherwise a cached page may be served that
   #  doesn't have the correct via headers.
   @ChromeVersionBeforeM(65)
-  def testLoFi(self):
+  def testLoFiOldFlags(self):
     with TestDriver() as test_driver:
       test_driver.AddChromeArg('--enable-spdy-proxy-auth')
       test_driver.AddChromeArg('--enable-features='
@@ -42,15 +42,16 @@
       histogram = test_driver.GetHistogram('Previews.InfoBarAction.LoFi', 5)
       self.assertEqual(1, histogram['count'])
 
-  #  Checks that the compressed image is below a certain threshold.
-  #  The test page is uncacheable otherwise a cached page may be served that
-  #  doesn't have the correct via headers.
-  @ChromeVersionEqualOrAfterM(65)
-  def testLoFiWithServerPreviewsFlag(self):
+  # Checks that LoFi images are served when LoFi slow connections are used and
+  # the network quality estimator returns Slow2G.
+  @ChromeVersionBeforeM(65)
+  def testLoFiSlowConnectionOldFlags(self):
     with TestDriver() as test_driver:
       test_driver.AddChromeArg('--enable-spdy-proxy-auth')
       test_driver.AddChromeArg('--enable-features='
                                'DataReductionProxyDecidesTransform')
+      test_driver.AddChromeArg('--data-reduction-proxy-lo-fi=slow-connections-'
+                               'only')
       # Disable server experiments such as tamper detection.
       test_driver.AddChromeArg('--data-reduction-proxy-server-experiments-'
                                'disabled')
@@ -74,16 +75,18 @@
       # Verify that Lo-Fi responses were seen.
       self.assertNotEqual(0, lofi_responses)
 
+      # Verify Lo-Fi previews info bar recorded
+      histogram = test_driver.GetHistogram('Previews.InfoBarAction.LoFi', 5)
+      self.assertEqual(1, histogram['count'])
+
   # Checks that LoFi images are served when LoFi slow connections are used and
   # the network quality estimator returns Slow2G.
-  @ChromeVersionBeforeM(65)
-  def testLoFiSlowConnection(self):
+  @ChromeVersionEqualOrAfterM(65)
+  def testLoFiOnSlowConnection(self):
     with TestDriver() as test_driver:
       test_driver.AddChromeArg('--enable-spdy-proxy-auth')
       test_driver.AddChromeArg('--enable-features='
                                'DataReductionProxyDecidesTransform')
-      test_driver.AddChromeArg('--data-reduction-proxy-lo-fi=slow-connections-'
-                               'only')
       # Disable server experiments such as tamper detection.
       test_driver.AddChromeArg('--data-reduction-proxy-server-experiments-'
                                'disabled')
@@ -113,7 +116,8 @@
 
   # Checks that LoFi images are NOT served when the network quality estimator
   # returns fast connection type.
-  def testLoFiFastConnection(self):
+  @ChromeVersionBeforeM(65)
+  def testLoFiFastConnectionOldFlags(self):
     with TestDriver() as test_driver:
       test_driver.AddChromeArg('--enable-spdy-proxy-auth')
       test_driver.AddChromeArg('--enable-features='
@@ -151,6 +155,45 @@
       histogram = test_driver.GetHistogram('Previews.InfoBarAction.LoFi', 5)
       self.assertEqual(histogram, {})
 
+  # Checks that LoFi images are NOT served when the network quality estimator
+  # returns fast connection.
+  @ChromeVersionEqualOrAfterM(65)
+  def testLoFiFastConnection(self):
+    with TestDriver() as test_driver:
+      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
+      test_driver.AddChromeArg('--enable-features='
+                               'DataReductionProxyDecidesTransform')
+      # Disable server experiments such as tamper detection.
+      test_driver.AddChromeArg('--data-reduction-proxy-server-experiments-'
+                               'disabled')
+      test_driver.AddChromeArg('--force-fieldtrial-params='
+                               'NetworkQualityEstimator.Enabled:'
+                               'force_effective_connection_type/4G')
+      test_driver.AddChromeArg('--force-fieldtrials=NetworkQualityEstimator/'
+                               'Enabled')
+
+      test_driver.LoadURL('http://check.googlezip.net/static/index.html')
+
+      lofi_responses = 0
+      for response in test_driver.GetHTTPResponses():
+        if response.url.endswith('html'):
+          # Main resource should accept transforms but not be transformed.
+          self.assertEqual('lite-page',
+            response.request_headers['chrome-proxy-accept-transform'])
+          self.assertNotIn('chrome-proxy-content-transform',
+            response.response_headers)
+          if 'chrome-proxy' in response.response_headers:
+            self.assertNotIn('page-policies',
+                             response.response_headers['chrome-proxy'])
+        else:
+          # No subresources should accept transforms.
+          self.assertNotIn('chrome-proxy-accept-transform',
+            response.request_headers)
+
+      # Verify no Lo-Fi previews info bar recorded
+      histogram = test_driver.GetHistogram('Previews.InfoBarAction.LoFi', 5)
+      self.assertEqual(histogram, {})
+
   # Checks that LoFi images are not served, but the if-heavy CPAT header is
   # added when LoFi slow connections are used and the network quality estimator
   # returns 4G.
@@ -201,7 +244,7 @@
   # load should not pick the Lo-Fi placeholder from cache and original image
   # should be loaded.
   @ChromeVersionBeforeM(65)
-  def testLoFiCacheBypass(self):
+  def testLoFiCacheBypassOldFlags(self):
     # If it was attempted to run with another experiment, skip this test.
     if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
         in common.ParseFlags().browser_args):
@@ -282,7 +325,7 @@
   # load should not pick the Lo-Fi placeholder from cache and original image
   # should be loaded.
   @ChromeVersionEqualOrAfterM(65)
-  def testLoFiCacheBypassWithServerPreviewsFlag(self):
+  def testLoFiCacheBypass(self):
     # If it was attempted to run with another experiment, skip this test.
     if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
         in common.ParseFlags().browser_args):
@@ -362,7 +405,7 @@
   # directive is provided when LoFi is always-on without Lite Pages enabled.
   @ChromeVersionEqualOrAfterM(61)
   @ChromeVersionBeforeM(65)
-  def testLoFiForcedExperiment(self):
+  def testLoFiForcedExperimentOldFlags(self):
     # If it was attempted to run with another experiment, skip this test.
     if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
         in common.ParseFlags().browser_args):
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index 78542c9..b6c0a3cf 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 = '318667'
+CLANG_REVISION = '321529'
 
 use_head_revision = bool(os.environ.get('LLVM_FORCE_HEAD_REVISION', '0')
                          in ('1', 'YES'))
@@ -890,6 +890,11 @@
       args.force_local_build):
     AddSvnToPathOnWin()
 
+  if use_head_revision:
+    # TODO(hans): Trunk was updated; remove after the next roll.
+    global VERSION
+    VERSION = '7.0.0'
+
   global CLANG_REVISION, PACKAGE_VERSION
   if args.print_revision:
     if use_head_revision or args.llvm_force_head_revision:
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 6362cc2..748c729 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1333,6 +1333,26 @@
   <int value="4" label="Account check failed"/>
 </enum>
 
+<enum name="ArcBootContinueCodeInstallationResult">
+  <int value="0" label="Success"/>
+  <int value="1" label="Host side code is not ready"/>
+  <int value="2" label="Can not install host code"/>
+</enum>
+
+<enum name="ArcCodeRelocationResult">
+  <int value="0" label="Success"/>
+  <int value="1" label="Error bootlockboxd not ready"/>
+  <int value="2" label="Error unable to relocate"/>
+  <int value="3" label="Error unable to sign"/>
+</enum>
+
+<enum name="ArcCodeVerificationResult">
+  <int value="0" label="Success"/>
+  <int value="1" label="Error bootlockboxd not ready"/>
+  <int value="2" label="The first boot after OTA or OOBE boot"/>
+  <int value="3" label="Invalid code"/>
+</enum>
+
 <enum name="ArcContainerLifetimeEvent">
   <int value="0" label="Starting"/>
   <int value="1" label="Failed to start"/>
@@ -17333,6 +17353,7 @@
   <int value="2318" label="CSSSelectorWebkitTextfieldDecorationContainer"/>
   <int value="2319" label="CSSSelectorWebkitUnknownPseudo"/>
   <int value="2320" label="FilterAsContainingBlockMayChangeOutput"/>
+  <int value="2321" label="DispatchMouseUpDownEventOnDisabledFormControl"/>
 </enum>
 
 <enum name="FeedbackSource">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 712a99e..36e7a5e 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -2504,6 +2504,55 @@
   </summary>
 </histogram>
 
+<histogram name="Arc.BootContinueCodeInstallationResult"
+    enum="ArcBootContinueCodeInstallationResult">
+  <owner>elijahtaylor@google.com</owner>
+  <owner>xzhou@google.com</owner>
+  <summary>Code installation result for ARC boot continue.</summary>
+</histogram>
+
+<histogram name="Arc.CodeIntegrityCheckingTotalTime" units="ms">
+  <owner>elijahtaylor@google.com</owner>
+  <owner>xzhou@google.com</owner>
+  <summary>
+    Total time needed to check the integrity of host generated code. If
+    signature checking fails, it also includes the time to regenerate and sign
+    the code.
+  </summary>
+</histogram>
+
+<histogram name="Arc.CodeRelocationResult" enum="ArcCodeRelocationResult">
+  <owner>elijahtaylor@google.com</owner>
+  <owner>xzhou@google.com</owner>
+  <summary>Host code relocation result.</summary>
+</histogram>
+
+<histogram name="Arc.CodeRelocationTime" units="ms">
+  <owner>elijahtaylor@google.com</owner>
+  <owner>xzhou@google.com</owner>
+  <summary>Time needed to relocate boot*.art files.</summary>
+</histogram>
+
+<histogram name="Arc.CodeSigningTime" units="ms">
+  <owner>elijahtaylor@google.com</owner>
+  <owner>xzhou@google.com</owner>
+  <summary>Time needed to sign boot*.art files.</summary>
+</histogram>
+
+<histogram name="Arc.CodeVerificationResult" enum="ArcCodeVerificationResult">
+  <owner>elijahtaylor@google.com</owner>
+  <owner>xzhou@google.com</owner>
+  <summary>
+    Code verification result for host generated code for boot for login screen.
+  </summary>
+</histogram>
+
+<histogram name="Arc.CodeVerificationTime" units="ms">
+  <owner>elijahtaylor@google.com</owner>
+  <owner>xzhou@google.com</owner>
+  <summary>Time needed to verify host generated code.</summary>
+</histogram>
+
 <histogram name="Arc.ComplianceReportSinceUpdateNotificationTime" units="ms">
   <owner>alexchau@google.com</owner>
   <owner>emaxx@google.com</owner>
@@ -36796,6 +36845,9 @@
 
 <histogram name="Memory.Experimental.Browser.PrivateMemoryFootprint.MacOS"
     units="MB">
+  <obsolete>
+    Deprecated 12/2017. Replaced by Memory.Browser.PrivateMemoryFootprint.
+  </obsolete>
   <owner>erikchen@chromium.org</owner>
   <summary>
     A rough estimate of the private memory footprint of the browser process.
@@ -36882,6 +36934,9 @@
 
 <histogram name="Memory.Experimental.Extension.PrivateMemoryFootprint.MacOS"
     units="MB">
+  <obsolete>
+    Deprecated 12/2017. Replaced by Memory.Extension.PrivateMemoryFootprint.
+  </obsolete>
   <owner>erikchen@chromium.org</owner>
   <summary>
     A rough estimate of the private memory footprint of an extension process.
@@ -36910,6 +36965,9 @@
 
 <histogram name="Memory.Experimental.Gpu.PrivateMemoryFootprint.MacOS"
     units="MB">
+  <obsolete>
+    Deprecated 12/2017. Replaced by Memory.Gpu.PrivateMemoryFootprint.
+  </obsolete>
   <owner>erikchen@chromium.org</owner>
   <summary>
     A rough estimate of the private memory footprint of the GPU process.
@@ -37002,6 +37060,9 @@
 
 <histogram name="Memory.Experimental.Renderer.PrivateMemoryFootprint.MacOS"
     units="MB">
+  <obsolete>
+    Deprecated 12/2017. Replaced by Memory.Renderer.PrivateMemoryFootprint.
+  </obsolete>
   <owner>erikchen@chromium.org</owner>
   <summary>
     A rough estimate of the private memory footprint of a renderer process.
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index ae40c92..907d53f 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -516,6 +516,18 @@
     Metrics related to a scroll action caused by the generated ScrollUpdate
     gesture event.
   </summary>
+  <metric name="IsMainThread">
+    <summary>
+      Whether the event is handled on the main thread or not.
+    </summary>
+  </metric>
+  <metric name="TimeToHandled">
+    <summary>
+      The time in microseconds between initial creation of a touch event and the
+      generated ScrollUpdate gesture event is handled on main/impl thread. If no
+      swap was induced by the ScrollUpdate gesture event, no recording is made.
+    </summary>
+  </metric>
   <metric name="TimeToScrollUpdateSwapBegin">
     <summary>
       The time in microseconds between the initial creation of a touch event and
@@ -532,6 +544,19 @@
     Metrics related to first scroll action caused by the generated ScrollUpdate
     gesture event.
   </summary>
+  <metric name="IsMainThread">
+    <summary>
+      Whether the event is handled on the main thread or not.
+    </summary>
+  </metric>
+  <metric name="TimeToHandled">
+    <summary>
+      The time in microseconds between initial creation of a touch event and the
+      first generated ScrollUpdate gesture event in a given scroll gesture event
+      sequence is handled on main/impl thread. If no swap was induced by the
+      ScrollUpdate gesture event, no recording is made.
+    </summary>
+  </metric>
   <metric name="TimeToScrollUpdateSwapBegin">
     <summary>
       The time in microseconds between initial creation of a touch event and the
@@ -550,6 +575,19 @@
     Metrics related to a scroll action caused by the generated ScrollUpdate
     gesture event.
   </summary>
+  <metric name="IsMainThread">
+    <summary>
+      Whether the event is handled on the main thread or not.
+    </summary>
+  </metric>
+  <metric name="TimeToHandled">
+    <summary>
+      The time in microseconds between initial creation of a wheel event and the
+      generated ScrollUpdate gesture event is handled on main/impl thread. If no
+      swap was induced by the ScrollUpdate gesture event, no recording is made.
+      The first GSU of every scrolling sequence is excluded from this metric.
+    </summary>
+  </metric>
   <metric name="TimeToScrollUpdateSwapBegin">
     <summary>
       The time in microseconds between the initial creation of a wheel event and
@@ -566,6 +604,19 @@
     Metrics related to first scroll action caused by the generated ScrollUpdate
     gesture event.
   </summary>
+  <metric name="IsMainThread">
+    <summary>
+      Whether the event is handled on the main thread or not.
+    </summary>
+  </metric>
+  <metric name="TimeToHandled">
+    <summary>
+      The time in microseconds between initial creation of a wheel event and the
+      first generated ScrollUpdate gesture event in a given scroll gesture event
+      sequence is handled on main/impl thread. If no swap was induced by the
+      ScrollBegin gesture event, no recording is made.
+    </summary>
+  </metric>
   <metric name="TimeToScrollUpdateSwapBegin">
     <summary>
       The time in microseconds between the initial creation of a wheel event and
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index bd19245..570bfd9 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -1,4 +1,6 @@
 # Test Expectation file for telemetry tests.
+# Instructions of how to use this file:
+# https://chromium.googlesource.com/chromium/src/+/master/docs/speed/perf_bot_sheriffing.md#Disabling-Telemetry-Tests
 
 # tags: All Android_Svelte Android_Webview Android_but_not_webview Mac Win Linux
 # tags: ChromeOS Android Desktop Mobile Nexus_5 Nexus_5X Nexus_6 Nexus_6P
@@ -192,6 +194,8 @@
 crbug.com/738854 [ Nexus_5X ] system_health.common_mobile/load:tools:drive [ Skip ]
 crbug.com/738854 [ Android_Webview ] system_health.common_mobile/load:tools:drive [ Skip ]
 crbug.com/797261 [ Android_Webview ] system_health.common_mobile/load:games:spychase [ Skip ]
+crbug.com/798536 [ Android ] system_health.common_mobile/background:news:nytimes [ Skip ]
+crbug.com/798536 [ Android ] system_health.common_mobile/load:games:spychase [ Skip ]
 
 # Benchmark: system_health.memory_desktop
 crbug.com/728576 [ Mac ] system_health.memory_desktop/browse:news:cnn [ Skip ]
diff --git a/tools/perf/upload_perf_results.py b/tools/perf/upload_perf_results.py
index 48a3da71..05b11a2 100755
--- a/tools/perf/upload_perf_results.py
+++ b/tools/perf/upload_perf_results.py
@@ -45,6 +45,7 @@
   parser.add_argument('-o', '--output-json', required=True)
   parser.add_argument('--build-properties', help=argparse.SUPPRESS)
   parser.add_argument('--summary-json', help=argparse.SUPPRESS)
+  parser.add_argument('--task-output-dir', help=argparse.SUPPRESS)
   parser.add_argument('--verbose', help=argparse.SUPPRESS)
   parser.add_argument('jsons_to_merge', nargs='*')
 
diff --git a/ui/aura/mus/window_tree_host_mus.cc b/ui/aura/mus/window_tree_host_mus.cc
index 6d3a3f2..a146cfc 100644
--- a/ui/aura/mus/window_tree_host_mus.cc
+++ b/ui/aura/mus/window_tree_host_mus.cc
@@ -15,6 +15,7 @@
 #include "ui/aura/window_event_dispatcher.h"
 #include "ui/aura/window_tree_host_observer.h"
 #include "ui/base/class_property.h"
+#include "ui/base/ui_base_switches_util.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/events/event.h"
@@ -56,7 +57,11 @@
   // seems them at the time the window is created.
   for (auto& pair : init_params.properties)
     window_mus->SetPropertyFromServer(pair.first, &pair.second);
-  CreateCompositor(viz::FrameSinkId());
+  // If window-server is hosting viz, then use the FrameSinkId from the server.
+  // In other cases, let a valid FrameSinkId be selected by
+  // context_factory_private().
+  CreateCompositor(switches::IsMusHostingViz() ? window_mus->GetFrameSinkId()
+                                               : viz::FrameSinkId());
   if (!init_params.uses_real_accelerated_widget) {
     gfx::AcceleratedWidget accelerated_widget;
 // We need accelerated widget numbers to be different for each window and
diff --git a/ui/aura/window.cc b/ui/aura/window.cc
index 40f6fe9..063a286 100644
--- a/ui/aura/window.cc
+++ b/ui/aura/window.cc
@@ -1091,6 +1091,12 @@
 }
 
 viz::FrameSinkId Window::GetFrameSinkId() const {
+  if (IsRootWindow()) {
+    DCHECK(host_);
+    auto* compositor = host_->compositor();
+    DCHECK(compositor);
+    return compositor->frame_sink_id();
+  }
   return port_->GetFrameSinkId();
 }
 
diff --git a/ui/aura/window_unittest.cc b/ui/aura/window_unittest.cc
index 1560854..c9aec7a3 100644
--- a/ui/aura/window_unittest.cc
+++ b/ui/aura/window_unittest.cc
@@ -3181,6 +3181,12 @@
   animator->RemoveObserver(&observer);
 }
 
+TEST_P(WindowTest, RootWindowUsesCompositorFrameSinkId) {
+  EXPECT_EQ(host()->compositor()->frame_sink_id(),
+            root_window()->GetFrameSinkId());
+  EXPECT_TRUE(root_window()->GetFrameSinkId().is_valid());
+}
+
 TEST_P(WindowTest, LocalSurfaceIdChanges) {
   Window window(nullptr);
   window.Init(ui::LAYER_NOT_DRAWN);
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 44c2225..c0160ff3 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -219,7 +219,7 @@
       ]
     }
   }
-  if (is_android || is_linux) {
+  if (is_android || is_linux || is_fuchsia) {
     sources += [
       "gl_implementation_osmesa.cc",
       "gl_implementation_osmesa.h",
diff --git a/ui/gl/gl_implementation_osmesa.cc b/ui/gl/gl_implementation_osmesa.cc
index 4a8bf6f..14fa648 100644
--- a/ui/gl/gl_implementation_osmesa.cc
+++ b/ui/gl/gl_implementation_osmesa.cc
@@ -8,6 +8,7 @@
 #include "base/logging.h"
 #include "base/native_library.h"
 #include "base/path_service.h"
+#include "build/build_config.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_gl_api_implementation.h"
 #include "ui/gl/gl_implementation.h"
@@ -17,10 +18,20 @@
 
 bool InitializeStaticGLBindingsOSMesaGL() {
   base::FilePath module_path;
+
+#if !defined(OS_FUCHSIA)
+  // On all platforms except Fuchsia libosmesa.so is expected to be in the same
+  // directory as the chrome binary. Pass full path to dlopen() to ensure we
+  // load the right version.
+  //
+  // On Fuchsia libraries are normally in the lib directory. The loader service
+  // will load libosmesa.so that belongs to the chrome package when dlopen() is
+  // called with relative path.
   if (!PathService::Get(base::DIR_MODULE, &module_path)) {
     LOG(ERROR) << "PathService::Get failed.";
     return false;
   }
+#endif  // !defined(OS_FUCHSIA)
 
   base::FilePath library_path = module_path.Append("libosmesa.so");
   base::NativeLibrary library = LoadLibraryAndPrintError(library_path);
diff --git a/ui/latency/latency_tracker.cc b/ui/latency/latency_tracker.cc
index 3516fb54..45251bb 100644
--- a/ui/latency/latency_tracker.cc
+++ b/ui/latency/latency_tracker.cc
@@ -108,11 +108,15 @@
 
 void LatencyTracker::ReportUkmScrollLatency(
     const InputMetricEvent& metric_event,
-    const std::string& metric_name,
     const LatencyInfo::LatencyComponent& start_component,
-    const LatencyInfo::LatencyComponent& end_component,
+    const LatencyInfo::LatencyComponent&
+        time_to_scroll_update_swap_begin_component,
+    const LatencyInfo::LatencyComponent& time_to_handled_component,
+    bool is_main_thread,
     const ukm::SourceId ukm_source_id) {
-  CONFIRM_EVENT_TIMES_EXIST(start_component, end_component)
+  CONFIRM_EVENT_TIMES_EXIST(start_component,
+                            time_to_scroll_update_swap_begin_component)
+  CONFIRM_EVENT_TIMES_EXIST(start_component, time_to_handled_component)
 
   // Only report a subset of this metric as the volume is too high.
   if (metric_sampling_ &&
@@ -141,10 +145,17 @@
   std::unique_ptr<ukm::UkmEntryBuilder> builder =
       ukm_recorder->GetEntryBuilder(ukm_source_id, event_name.c_str());
   builder->AddMetric(
-      metric_name.c_str(),
-      std::max(static_cast<int64_t>(0), (end_component.last_event_time -
-                                         start_component.first_event_time)
-                                            .InMicroseconds()));
+      "TimeToScrollUpdateSwapBegin",
+      std::max(static_cast<int64_t>(0),
+               (time_to_scroll_update_swap_begin_component.last_event_time -
+                start_component.first_event_time)
+                   .InMicroseconds()));
+  builder->AddMetric("TimeToHandled",
+                     std::max(static_cast<int64_t>(0),
+                              (time_to_handled_component.last_event_time -
+                               start_component.first_event_time)
+                                  .InMicroseconds()));
+  builder->AddMetric("IsMainThread", is_main_thread);
 }
 
 void LatencyTracker::ComputeEndToEndLatencyHistograms(
@@ -181,12 +192,6 @@
                                   ".TimeToScrollUpdateSwapBegin2",
                               original_component, gpu_swap_begin_component);
 
-    ReportUkmScrollLatency(input_modality == "Touch"
-                               ? InputMetricEvent::SCROLL_BEGIN_TOUCH
-                               : InputMetricEvent::SCROLL_BEGIN_WHEEL,
-                           "TimeToScrollUpdateSwapBegin", original_component,
-                           gpu_swap_begin_component, latency.ukm_source_id());
-
   } else if (latency.FindLatency(
                  ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
                  &original_component)) {
@@ -209,11 +214,6 @@
                                   ".TimeToScrollUpdateSwapBegin2",
                               original_component, gpu_swap_begin_component);
 
-    ReportUkmScrollLatency(input_modality == "Touch"
-                               ? InputMetricEvent::SCROLL_UPDATE_TOUCH
-                               : InputMetricEvent::SCROLL_UPDATE_WHEEL,
-                           "TimeToScrollUpdateSwapBegin", original_component,
-                           gpu_swap_begin_component, latency.ukm_source_id());
   } else if (latency.FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0,
                                  &original_component)) {
     if (input_modality == "KeyPress") {
@@ -239,6 +239,23 @@
         &rendering_scheduled_component);
     DCHECK_AND_RETURN_ON_FAIL(found_component);
   }
+  if (input_modality == "Touch" || input_modality == "Wheel") {
+    InputMetricEvent input_metric_event;
+    if (scroll_name == "ScrollBegin") {
+      input_metric_event = input_modality == "Touch"
+                               ? InputMetricEvent::SCROLL_BEGIN_TOUCH
+                               : InputMetricEvent::SCROLL_BEGIN_WHEEL;
+    } else {
+      DCHECK_EQ(scroll_name, "ScrollUpdate");
+      input_metric_event = input_modality == "Touch"
+                               ? InputMetricEvent::SCROLL_UPDATE_TOUCH
+                               : InputMetricEvent::SCROLL_UPDATE_WHEEL;
+    }
+    ReportUkmScrollLatency(
+        input_metric_event, original_component, gpu_swap_begin_component,
+        rendering_scheduled_component, rendering_scheduled_on_main,
+        latency.ukm_source_id());
+  }
 
   const std::string thread_name = rendering_scheduled_on_main ? "Main" : "Impl";
 
diff --git a/ui/latency/latency_tracker.h b/ui/latency/latency_tracker.h
index 36577ae7..f0180ebc4 100644
--- a/ui/latency/latency_tracker.h
+++ b/ui/latency/latency_tracker.h
@@ -46,9 +46,11 @@
 
   void ReportUkmScrollLatency(
       const InputMetricEvent& metric_event,
-      const std::string& metric_name,
       const LatencyInfo::LatencyComponent& start_component,
-      const LatencyInfo::LatencyComponent& end_component,
+      const LatencyInfo::LatencyComponent&
+          time_to_scroll_update_swap_begin_component,
+      const LatencyInfo::LatencyComponent& time_to_handled_component,
+      bool is_main_thread,
       const ukm::SourceId ukm_source_id);
 
   void ComputeEndToEndLatencyHistograms(
diff --git a/ui/ozone/common/gl_ozone_osmesa.cc b/ui/ozone/common/gl_ozone_osmesa.cc
index af19a96..395995d 100644
--- a/ui/ozone/common/gl_ozone_osmesa.cc
+++ b/ui/ozone/common/gl_ozone_osmesa.cc
@@ -28,14 +28,7 @@
 
 bool GLOzoneOSMesa::InitializeStaticGLBindings(
     gl::GLImplementation implementation) {
-#if defined(OS_FUCHSIA)
-  // TODO(fuchsia): Enable this once there's EGL available, see
-  // https://crbug.com/750943.
-  NOTIMPLEMENTED();
-  return false;
-#else
   return gl::InitializeStaticGLBindingsOSMesaGL();
-#endif
 }
 
 void GLOzoneOSMesa::InitializeDebugGLBindings() {
diff --git a/ui/ozone/platform/headless/headless_surface_factory.cc b/ui/ozone/platform/headless/headless_surface_factory.cc
index 3c1b9b0..6e64f51 100644
--- a/ui/ozone/platform/headless/headless_surface_factory.cc
+++ b/ui/ozone/platform/headless/headless_surface_factory.cc
@@ -130,9 +130,7 @@
 HeadlessSurfaceFactory::HeadlessSurfaceFactory(base::FilePath base_path)
     : base_path_(base_path) {
   CheckBasePath();
-#if !defined(OS_FUCHSIA)
   osmesa_implementation_ = std::make_unique<GLOzoneOSMesaHeadless>(this);
-#endif
 }
 
 HeadlessSurfaceFactory::~HeadlessSurfaceFactory() = default;
@@ -148,11 +146,7 @@
 
 std::vector<gl::GLImplementation>
 HeadlessSurfaceFactory::GetAllowedGLImplementations() {
-#if defined(OS_FUCHSIA)
-  return std::vector<gl::GLImplementation>{gl::kGLImplementationStubGL};
-#else
   return std::vector<gl::GLImplementation>{gl::kGLImplementationOSMesaGL};
-#endif
 }
 
 GLOzone* HeadlessSurfaceFactory::GetGLOzone(
diff --git a/ui/ozone/platform/wayland/wayland_pointer_unittest.cc b/ui/ozone/platform/wayland/wayland_pointer_unittest.cc
index 239d7a4..a342306 100644
--- a/ui/ozone/platform/wayland/wayland_pointer_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_pointer_unittest.cc
@@ -43,7 +43,7 @@
 
 TEST_P(WaylandPointerTest, Leave) {
   MockPlatformWindowDelegate other_delegate;
-  WaylandWindow other_window(&other_delegate, &connection,
+  WaylandWindow other_window(&other_delegate, connection.get(),
                              gfx::Rect(0, 0, 10, 10));
   gfx::AcceleratedWidget other_widget = gfx::kNullAcceleratedWidget;
   EXPECT_CALL(other_delegate, OnAcceleratedWidgetAvailable(_, _))
diff --git a/ui/ozone/platform/wayland/wayland_surface_factory_unittest.cc b/ui/ozone/platform/wayland/wayland_surface_factory_unittest.cc
index 0d48521..73512e2 100644
--- a/ui/ozone/platform/wayland/wayland_surface_factory_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_surface_factory_unittest.cc
@@ -19,7 +19,7 @@
 
 class WaylandSurfaceFactoryTest : public WaylandTest {
  public:
-  WaylandSurfaceFactoryTest() : surface_factory(&connection) {}
+  WaylandSurfaceFactoryTest() : surface_factory(connection.get()) {}
 
   ~WaylandSurfaceFactoryTest() override {}
 
diff --git a/ui/ozone/platform/wayland/wayland_test.cc b/ui/ozone/platform/wayland/wayland_test.cc
index 9036ed1c..60328cb 100644
--- a/ui/ozone/platform/wayland/wayland_test.cc
+++ b/ui/ozone/platform/wayland/wayland_test.cc
@@ -11,17 +11,22 @@
 
 namespace ui {
 
-WaylandTest::WaylandTest()
-    : window(&delegate, &connection, gfx::Rect(0, 0, 800, 600)) {}
+WaylandTest::WaylandTest() {
+  // TODO(tonikitoo): Set the proper KeyboardLayoutEngine instance here,
+  // before the WaylandConnection is instantiated.
+  connection.reset(new WaylandConnection);
+  window = std::make_unique<WaylandWindow>(&delegate, connection.get(),
+                                           gfx::Rect(0, 0, 800, 600));
+}
 
 WaylandTest::~WaylandTest() {}
 
 void WaylandTest::SetUp() {
   ASSERT_TRUE(server.Start(GetParam()));
-  ASSERT_TRUE(connection.Initialize());
+  ASSERT_TRUE(connection->Initialize());
   EXPECT_CALL(delegate, OnAcceleratedWidgetAvailable(_, _))
       .WillOnce(SaveArg<0>(&widget));
-  ASSERT_TRUE(window.Initialize());
+  ASSERT_TRUE(window->Initialize());
   ASSERT_NE(widget, gfx::kNullAcceleratedWidget);
 
   // Wait for the client to flush all pending requests from initialization.
diff --git a/ui/ozone/platform/wayland/wayland_test.h b/ui/ozone/platform/wayland/wayland_test.h
index 918f039..459422d 100644
--- a/ui/ozone/platform/wayland/wayland_test.h
+++ b/ui/ozone/platform/wayland/wayland_test.h
@@ -37,9 +37,9 @@
   wl::FakeServer server;
   wl::MockSurface* surface;
 
-  WaylandConnection connection;
   MockPlatformWindowDelegate delegate;
-  WaylandWindow window;
+  std::unique_ptr<WaylandConnection> connection;
+  std::unique_ptr<WaylandWindow> window;
   gfx::AcceleratedWidget widget = gfx::kNullAcceleratedWidget;
 
  private:
diff --git a/ui/ozone/platform/wayland/wayland_window_unittest.cc b/ui/ozone/platform/wayland/wayland_window_unittest.cc
index 70790d5e..ca2df44 100644
--- a/ui/ozone/platform/wayland/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_window_unittest.cc
@@ -90,7 +90,7 @@
 
 TEST_P(WaylandWindowTest, SetTitle) {
   EXPECT_CALL(*GetXdgSurface(), SetTitle(StrEq("hello")));
-  window.SetTitle(base::ASCIIToUTF16("hello"));
+  window->SetTitle(base::ASCIIToUTF16("hello"));
 }
 
 TEST_P(WaylandWindowTest, MaximizeAndRestore) {
@@ -102,16 +102,16 @@
 
   EXPECT_CALL(*GetXdgSurface(), SetMaximized());
   EXPECT_CALL(*GetXdgSurface(), UnsetMaximized());
-  window.Maximize();
+  window->Maximize();
   SendConfigureEvent(0, 0, serial, &states);
   Sync();
 
-  window.Restore();
+  window->Restore();
 }
 
 TEST_P(WaylandWindowTest, Minimize) {
   EXPECT_CALL(*GetXdgSurface(), SetMinimized());
-  window.Minimize();
+  window->Minimize();
 }
 
 TEST_P(WaylandWindowTest, SetFullscreenAndRestore) {
@@ -122,11 +122,11 @@
 
   EXPECT_CALL(*GetXdgSurface(), SetFullscreen());
   EXPECT_CALL(*GetXdgSurface(), UnsetFullscreen());
-  window.ToggleFullscreen();
+  window->ToggleFullscreen();
   SendConfigureEvent(0, 0, 1, &states);
   Sync();
 
-  window.Restore();
+  window->Restore();
 }
 
 TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) {
@@ -138,31 +138,31 @@
   EXPECT_CALL(*GetXdgSurface(), SetMaximized());
   EXPECT_CALL(*GetXdgSurface(), UnsetMaximized());
 
-  window.Maximize();
+  window->Maximize();
   SetWlArrayWithState(XDG_SURFACE_STATE_MAXIMIZED, &states);
   SendConfigureEvent(0, 0, 2, &states);
   Sync();
 
-  window.ToggleFullscreen();
+  window->ToggleFullscreen();
   SetWlArrayWithState(XDG_SURFACE_STATE_FULLSCREEN, &states);
   SendConfigureEvent(0, 0, 3, &states);
   Sync();
 
-  window.Restore();
+  window->Restore();
 }
 
 TEST_P(WaylandWindowTest, CanDispatchMouseEventDefault) {
-  EXPECT_FALSE(window.CanDispatchEvent(&test_mouse_event));
+  EXPECT_FALSE(window->CanDispatchEvent(&test_mouse_event));
 }
 
 TEST_P(WaylandWindowTest, CanDispatchMouseEventFocus) {
-  window.set_pointer_focus(true);
-  EXPECT_TRUE(window.CanDispatchEvent(&test_mouse_event));
+  window->set_pointer_focus(true);
+  EXPECT_TRUE(window->CanDispatchEvent(&test_mouse_event));
 }
 
 TEST_P(WaylandWindowTest, CanDispatchMouseEventUnfocus) {
-  window.set_pointer_focus(false);
-  EXPECT_FALSE(window.CanDispatchEvent(&test_mouse_event));
+  window->set_pointer_focus(false);
+  EXPECT_FALSE(window->CanDispatchEvent(&test_mouse_event));
 }
 
 ACTION_P(CloneEvent, ptr) {
@@ -172,7 +172,7 @@
 TEST_P(WaylandWindowTest, DispatchEvent) {
   std::unique_ptr<Event> event;
   EXPECT_CALL(delegate, DispatchEvent(_)).WillOnce(CloneEvent(&event));
-  window.DispatchEvent(&test_mouse_event);
+  window->DispatchEvent(&test_mouse_event);
   ASSERT_TRUE(event);
   ASSERT_TRUE(event->IsMouseEvent());
   auto* mouse_event = event->AsMouseEvent();