diff --git a/DEPS b/DEPS
index f6fb4852..32714fc4 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '98d735069d0937f367852ed968a33210ceb527c2',
+  'v8_revision': '2e896f6f6039add01a0d1d5efe6d5efe942c38a1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -60,7 +60,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '2ed3149ac7f92d367b9629da29c45ffc0d6055d3',
+  'swiftshader_revision': '400667e6604eb07e53a2894ede1f492fc3c0b117',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
diff --git a/WATCHLISTS b/WATCHLISTS
index d24d032..8f92d7d 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1716,7 +1716,6 @@
     'blink_frames': ['blink-reviews-frames@chromium.org'],
     'blink_geolocation': ['mcasas+geolocation@chromium.org',
                           'mlamouri+watch-blink@chromium.org',
-                          'mvanouwerkerk+watch@chromium.org',
                           'timvolodine@chromium.org'],
     'blink_heap': ['ager@chromium.org',
                    'haraken@chromium.org',
@@ -1818,8 +1817,7 @@
                     'fs@opera.com',
                     'glenn@chromium.org',
                     'silviapf@chromium.org'],
-    'blink_vibration': ['mlamouri+watch-blink@chromium.org',
-                        'mvanouwerkerk+watch@chromium.org'],
+    'blink_vibration': ['mlamouri+watch-blink@chromium.org'],
     'blink_viewport_interaction': ['kenneth.christiansen@gmail.com'],
     'blink_w3ctests': ['blink-reviews-w3ctests@chromium.org'],
     'blink_web': ['kinuko+watch@chromium.org'],
diff --git a/base/sync_socket.h b/base/sync_socket.h
index fcf41550..53fbeb6 100644
--- a/base/sync_socket.h
+++ b/base/sync_socket.h
@@ -93,6 +93,9 @@
   // processes.
   Handle handle() const { return handle_; }
 
+  // Extracts and takes ownership of the contained handle.
+  Handle Release();
+
  protected:
   Handle handle_;
 
diff --git a/base/sync_socket_nacl.cc b/base/sync_socket_nacl.cc
index 4a02082e..19a20bec 100644
--- a/base/sync_socket_nacl.cc
+++ b/base/sync_socket_nacl.cc
@@ -75,6 +75,12 @@
   return 0;
 }
 
+SyncSocket::Handle SyncSocket::Release() {
+  Handle r = handle_;
+  handle_ = kInvalidHandle;
+  return r;
+}
+
 CancelableSyncSocket::CancelableSyncSocket() {
 }
 
diff --git a/base/sync_socket_posix.cc b/base/sync_socket_posix.cc
index 5d9e25e..da995f4b 100644
--- a/base/sync_socket_posix.cc
+++ b/base/sync_socket_posix.cc
@@ -207,6 +207,12 @@
   return number_chars;
 }
 
+SyncSocket::Handle SyncSocket::Release() {
+  Handle r = handle_;
+  handle_ = kInvalidHandle;
+  return r;
+}
+
 CancelableSyncSocket::CancelableSyncSocket() {}
 CancelableSyncSocket::CancelableSyncSocket(Handle handle)
     : SyncSocket(handle) {
diff --git a/base/sync_socket_win.cc b/base/sync_socket_win.cc
index c101f77a..797f12f 100644
--- a/base/sync_socket_win.cc
+++ b/base/sync_socket_win.cc
@@ -293,6 +293,12 @@
   return available;
 }
 
+SyncSocket::Handle SyncSocket::Release() {
+  Handle r = handle_;
+  handle_ = kInvalidHandle;
+  return r;
+}
+
 CancelableSyncSocket::CancelableSyncSocket()
     : shutdown_event_(base::WaitableEvent::ResetPolicy::MANUAL,
                       base::WaitableEvent::InitialState::NOT_SIGNALED),
diff --git a/base/test/scoped_async_task_scheduler.cc b/base/test/scoped_async_task_scheduler.cc
index 397243e..0b5736a 100644
--- a/base/test/scoped_async_task_scheduler.cc
+++ b/base/test/scoped_async_task_scheduler.cc
@@ -29,6 +29,9 @@
 
 ScopedAsyncTaskScheduler::~ScopedAsyncTaskScheduler() {
   DCHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_);
+  // Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be
+  // skipped, resulting in memory leaks.
+  TaskScheduler::GetInstance()->FlushForTesting();
   TaskScheduler::GetInstance()->Shutdown();
   TaskScheduler::GetInstance()->JoinForTesting();
   TaskScheduler::SetInstance(nullptr);
diff --git a/base/test/scoped_task_environment.cc b/base/test/scoped_task_environment.cc
index 8744b4a..b18bf6a 100644
--- a/base/test/scoped_task_environment.cc
+++ b/base/test/scoped_task_environment.cc
@@ -32,6 +32,9 @@
   RunLoop().RunUntilIdle();
 
   DCHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_);
+  // Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be
+  // skipped, resulting in memory leaks.
+  TaskScheduler::GetInstance()->FlushForTesting();
   TaskScheduler::GetInstance()->Shutdown();
   TaskScheduler::GetInstance()->JoinForTesting();
   TaskScheduler::SetInstance(nullptr);
diff --git a/build/android/adb_install_apk.py b/build/android/adb_install_apk.py
index 7904b41..fd8b8d7 100755
--- a/build/android/adb_install_apk.py
+++ b/build/android/adb_install_apk.py
@@ -113,7 +113,8 @@
         device.Install(apk, reinstall=args.keep_data,
                        allow_downgrade=args.downgrade,
                        timeout=args.timeout)
-    except device_errors.CommandFailedError:
+    except (device_errors.CommandFailedError,
+            device_errors.DeviceUnreachableError):
       logging.exception('Failed to install %s', args.apk_name)
       if blacklist:
         blacklist.Extend([str(device)], reason='install_failure')
@@ -129,4 +130,3 @@
 
 if __name__ == '__main__':
   sys.exit(main())
-
diff --git a/build/android/provision_devices.py b/build/android/provision_devices.py
index 1e8ed92b..ecf22c9 100755
--- a/build/android/provision_devices.py
+++ b/build/android/provision_devices.py
@@ -137,7 +137,8 @@
     if blacklist:
       blacklist.Extend([str(device)], reason='provision_timeout')
 
-  except device_errors.CommandFailedError:
+  except (device_errors.CommandFailedError,
+          device_errors.DeviceUnreachableError):
     logging.exception('Failed to provision device %s. Adding to blacklist.',
                       str(device))
     if blacklist:
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index dca9615d..84a37fc1 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -172,6 +172,8 @@
         logging.exception('gtest shard failed.')
       except device_errors.CommandTimeoutError:
         logging.exception('gtest shard timed out.')
+      except device_errors.DeviceUnreachableError:
+        logging.exception('gtest shard device unreachable.')
       except Exception:
         device.ForceStop(self._package)
         raise
diff --git a/build/android/pylib/local/device/local_device_perf_test_run.py b/build/android/pylib/local/device/local_device_perf_test_run.py
index 7e4538d..2ac8b0f 100644
--- a/build/android/pylib/local/device/local_device_perf_test_run.py
+++ b/build/android/pylib/local/device/local_device_perf_test_run.py
@@ -245,7 +245,8 @@
           result_type = self._RunSingleTest(test)
         except device_errors.CommandTimeoutError:
           result_type = base_test_result.ResultType.TIMEOUT
-        except device_errors.CommandFailedError:
+        except (device_errors.CommandFailedError,
+                device_errors.DeviceUnreachableError):
           logging.exception('Exception when executing %s.', test)
           result_type = base_test_result.ResultType.FAIL
         finally:
diff --git a/build/android/tombstones.py b/build/android/tombstones.py
index 9838acc..dbc6281c 100755
--- a/build/android/tombstones.py
+++ b/build/android/tombstones.py
@@ -49,6 +49,8 @@
                datetime.datetime.fromtimestamp(entry['st_mtime']))
   except device_errors.CommandFailedError:
     logging.exception('Could not retrieve tombstones.')
+  except device_errors.DeviceUnreachableError:
+    logging.exception('Device unreachable retrieving tombstones.')
   except device_errors.CommandTimeoutError:
     logging.exception('Timed out retrieving tombstones.')
 
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index 437fe706..6be5cca 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -117,7 +117,13 @@
   cache.UnrefImage(another_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageLowerQuality) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageLowerQuality DISABLED_GetTaskForImageLowerQuality
+#else
+#define MAYBE_GetTaskForImageLowerQuality GetTaskForImageLowerQuality
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageLowerQuality) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -149,7 +155,14 @@
   cache.UnrefImage(another_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageDifferentImage) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageDifferentImage \
+  DISABLED_GetTaskForImageDifferentImage
+#else
+#define MAYBE_GetTaskForImageDifferentImage GetTaskForImageDifferentImage
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageDifferentImage) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -189,7 +202,13 @@
   cache.UnrefImage(second_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageLargerScale) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageLargerScale DISABLED_GetTaskForImageLargerScale
+#else
+#define MAYBE_GetTaskForImageLargerScale GetTaskForImageLargerScale
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageLargerScale) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -240,7 +259,15 @@
   cache.UnrefImage(third_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageLargerScaleNoReuse) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageLargerScaleNoReuse \
+  DISABLED_GetTaskForImageLargerScaleNoReuse
+#else
+#define MAYBE_GetTaskForImageLargerScaleNoReuse \
+  GetTaskForImageLargerScaleNoReuse
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageLargerScaleNoReuse) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -289,7 +316,13 @@
   cache.UnrefImage(third_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageHigherQuality) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageHigherQuality DISABLED_GetTaskForImageHigherQuality
+#else
+#define MAYBE_GetTaskForImageHigherQuality GetTaskForImageHigherQuality
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageHigherQuality) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -327,7 +360,15 @@
   cache.UnrefImage(second_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageAlreadyDecodedAndLocked) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageAlreadyDecodedAndLocked \
+  DISABLED_GetTaskForImageAlreadyDecodedAndLocked
+#else
+#define MAYBE_GetTaskForImageAlreadyDecodedAndLocked \
+  GetTaskForImageAlreadyDecodedAndLocked
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageAlreadyDecodedAndLocked) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -373,7 +414,15 @@
   cache.UnrefImage(draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageAlreadyDecodedNotLocked) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageAlreadyDecodedNotLocked \
+  DISABLED_GetTaskForImageAlreadyDecodedNotLocked
+#else
+#define MAYBE_GetTaskForImageAlreadyDecodedNotLocked \
+  GetTaskForImageAlreadyDecodedNotLocked
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageAlreadyDecodedNotLocked) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -419,7 +468,14 @@
   cache.UnrefImage(draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageAlreadyUploaded) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageAlreadyUploaded \
+  DISABLED_GetTaskForImageAlreadyUploaded
+#else
+#define MAYBE_GetTaskForImageAlreadyUploaded GetTaskForImageAlreadyUploaded
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetTaskForImageAlreadyUploaded) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -503,7 +559,16 @@
   cache.UnrefImage(draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetTaskForImageCanceledWhileReffedGetsNewTask) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetTaskForImageCanceledWhileReffedGetsNewTask \
+  DISABLED_GetTaskForImageCanceledWhileReffedGetsNewTask
+#else
+#define MAYBE_GetTaskForImageCanceledWhileReffedGetsNewTask \
+  GetTaskForImageCanceledWhileReffedGetsNewTask
+#endif
+TEST(GpuImageDecodeCacheTest,
+     MAYBE_GetTaskForImageCanceledWhileReffedGetsNewTask) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -555,7 +620,15 @@
   cache.UnrefImage(draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, NoTaskForImageAlreadyFailedDecoding) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_NoTaskForImageAlreadyFailedDecoding \
+  DISABLED_NoTaskForImageAlreadyFailedDecoding
+#else
+#define MAYBE_NoTaskForImageAlreadyFailedDecoding \
+  NoTaskForImageAlreadyFailedDecoding
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_NoTaskForImageAlreadyFailedDecoding) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -589,7 +662,13 @@
   cache.UnrefImage(draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetDecodedImageForDraw) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetDecodedImageForDraw DISABLED_GetDecodedImageForDraw
+#else
+#define MAYBE_GetDecodedImageForDraw GetDecodedImageForDraw
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetDecodedImageForDraw) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -694,7 +773,15 @@
   cache.DrawWithImageFinished(draw_image, decoded_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetDecodedImageForDrawLargerScale) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetDecodedImageForDrawLargerScale \
+  DISABLED_GetDecodedImageForDrawLargerScale
+#else
+#define MAYBE_GetDecodedImageForDrawLargerScale \
+  GetDecodedImageForDrawLargerScale
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetDecodedImageForDrawLargerScale) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -753,7 +840,15 @@
   cache.UnrefImage(larger_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetDecodedImageForDrawHigherQuality) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetDecodedImageForDrawHigherQuality \
+  DISABLED_GetDecodedImageForDrawHigherQuality
+#else
+#define MAYBE_GetDecodedImageForDrawHigherQuality \
+  GetDecodedImageForDrawHigherQuality
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetDecodedImageForDrawHigherQuality) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -810,7 +905,14 @@
   cache.UnrefImage(higher_quality_draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetDecodedImageForDrawNegative) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetDecodedImageForDrawNegative \
+  DISABLED_GetDecodedImageForDrawNegative
+#else
+#define MAYBE_GetDecodedImageForDrawNegative GetDecodedImageForDrawNegative
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetDecodedImageForDrawNegative) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -847,7 +949,15 @@
   cache.UnrefImage(draw_image);
 }
 
-TEST(GpuImageDecodeCacheTest, GetLargeScaledDecodedImageForDraw) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetLargeScaledDecodedImageForDraw \
+  DISABLED_GetLargeScaledDecodedImageForDraw
+#else
+#define MAYBE_GetLargeScaledDecodedImageForDraw \
+  GetLargeScaledDecodedImageForDraw
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_GetLargeScaledDecodedImageForDraw) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -886,7 +996,15 @@
   EXPECT_FALSE(cache.DiscardableIsLockedForTesting(draw_image));
 }
 
-TEST(GpuImageDecodeCacheTest, AtRasterUsedDirectlyIfSpaceAllows) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_AtRasterUsedDirectlyIfSpaceAllows \
+  DISABLED_AtRasterUsedDirectlyIfSpaceAllows
+#else
+#define MAYBE_AtRasterUsedDirectlyIfSpaceAllows \
+  AtRasterUsedDirectlyIfSpaceAllows
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_AtRasterUsedDirectlyIfSpaceAllows) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -931,8 +1049,16 @@
   cache.UnrefImage(draw_image);
 }
 
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_GetDecodedImageForDrawAtRasterDecodeMultipleTimes \
+  DISABLED_GetDecodedImageForDrawAtRasterDecodeMultipleTimes
+#else
+#define MAYBE_GetDecodedImageForDrawAtRasterDecodeMultipleTimes \
+  GetDecodedImageForDrawAtRasterDecodeMultipleTimes
+#endif
 TEST(GpuImageDecodeCacheTest,
-     GetDecodedImageForDrawAtRasterDecodeMultipleTimes) {
+     MAYBE_GetDecodedImageForDrawAtRasterDecodeMultipleTimes) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -1091,7 +1217,14 @@
   EXPECT_EQ(0u, cache.GetBytesUsedForTesting());
 }
 
-TEST(GpuImageDecodeCacheTest, ShouldAggressivelyFreeResources) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_ShouldAggressivelyFreeResources \
+  DISABLED_ShouldAggressivelyFreeResources
+#else
+#define MAYBE_ShouldAggressivelyFreeResources ShouldAggressivelyFreeResources
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_ShouldAggressivelyFreeResources) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -1155,7 +1288,15 @@
   }
 }
 
-TEST(GpuImageDecodeCacheTest, OrphanedImagesFreeOnReachingZeroRefs) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_OrphanedImagesFreeOnReachingZeroRefs \
+  DISABLED_OrphanedImagesFreeOnReachingZeroRefs
+#else
+#define MAYBE_OrphanedImagesFreeOnReachingZeroRefs \
+  OrphanedImagesFreeOnReachingZeroRefs
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_OrphanedImagesFreeOnReachingZeroRefs) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -1212,7 +1353,15 @@
             cache.GetDrawImageSizeForTesting(second_draw_image));
 }
 
-TEST(GpuImageDecodeCacheTest, OrphanedZeroRefImagesImmediatelyDeleted) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_OrphanedZeroRefImagesImmediatelyDeleted \
+  DISABLED_OrphanedZeroRefImagesImmediatelyDeleted
+#else
+#define MAYBE_OrphanedZeroRefImagesImmediatelyDeleted \
+  OrphanedZeroRefImagesImmediatelyDeleted
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_OrphanedZeroRefImagesImmediatelyDeleted) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
@@ -1262,7 +1411,13 @@
             cache.GetDrawImageSizeForTesting(second_draw_image));
 }
 
-TEST(GpuImageDecodeCacheTest, QualityCappedAtMedium) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_QualityCappedAtMedium DISABLED_QualityCappedAtMedium
+#else
+#define MAYBE_QualityCappedAtMedium QualityCappedAtMedium
+#endif
+TEST(GpuImageDecodeCacheTest, MAYBE_QualityCappedAtMedium) {
   auto context_provider = TestContextProvider::Create();
   context_provider->BindToCurrentThread();
   TestGpuImageDecodeCache cache(context_provider.get());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
index a4f3297c..6740fdda 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
@@ -10,6 +10,7 @@
 import android.os.Looper;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.browser.banners.AppDetailsDelegate;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
@@ -52,6 +53,14 @@
 public abstract class AppHooks {
     private static AppHooksImpl sInstance;
 
+    /**
+     * Sets a mocked instance for testing.
+     */
+    @VisibleForTesting
+    public static void setInstanceForTesting(AppHooksImpl instance) {
+        sInstance = instance;
+    }
+
     @CalledByNative
     public static AppHooks get() {
         if (sInstance == null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
index a26b021..f8722440 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
@@ -35,7 +35,6 @@
 import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
 import org.chromium.base.VisibleForTesting;
-import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.metrics.RecordHistogram;
@@ -587,7 +586,9 @@
 
     @VisibleForTesting
     void cancelOffTheRecordDownloads() {
-        boolean cancelActualDownload = LibraryLoader.isInitialized()
+        boolean cancelActualDownload =
+                BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
+                        .isStartupSuccessfullyCompleted()
                 && Profile.getLastUsedProfile().hasOffTheRecordProfile();
 
         List<DownloadSharedPreferenceEntry> entries = mDownloadSharedPreferenceHelper.getEntries();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
index 8aaa7bf..90bf8e8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
@@ -80,9 +80,15 @@
     // Maps the notification ids to their corresponding notification managers.
     private static SparseArray<MediaNotificationManager> sManagers;
 
+    // Overrides N detection. The production code will use |null|, which uses the Android version
+    // code. Otherwise, |isRunningN()| will return whatever value is set.
+    @VisibleForTesting
+    static Boolean sOverrideIsRunningNForTesting;
+
     // Maps the notification ids to their corresponding choices of the service, button receiver and
     // group name.
-    private static SparseArray<NotificationOptions> sMapNotificationIdToOptions;
+    @VisibleForTesting
+    static SparseArray<NotificationOptions> sMapNotificationIdToOptions;
 
     static {
         sManagers = new SparseArray<MediaNotificationManager>();
@@ -105,18 +111,23 @@
     private int mNotificationId;
 
     // ListenerService running for the notification. Only non-null when showing.
-    private ListenerService mService;
+    @VisibleForTesting
+    ListenerService mService;
 
     private SparseArray<MediaButtonInfo> mActionToButtonInfo;
 
-    private ChromeNotificationBuilder mNotificationBuilder;
+    @VisibleForTesting
+    ChromeNotificationBuilder mNotificationBuilder;
 
-    private Bitmap mDefaultNotificationLargeIcon;
+    @VisibleForTesting
+    Bitmap mDefaultNotificationLargeIcon;
 
     // |mMediaNotificationInfo| should be not null if and only if the notification is showing.
-    private MediaNotificationInfo mMediaNotificationInfo;
+    @VisibleForTesting
+    MediaNotificationInfo mMediaNotificationInfo;
 
-    private MediaSessionCompat mMediaSession;
+    @VisibleForTesting
+    MediaSessionCompat mMediaSession;
 
     private final MediaSessionCompat.Callback mMediaSessionCallback =
             new MediaSessionCompat.Callback() {
@@ -157,7 +168,8 @@
                 }
             };
 
-    private static class NotificationOptions {
+    @VisibleForTesting
+    static class NotificationOptions {
         public Class<?> serviceClass;
         public Class<?> receiverClass;
         public String groupName;
@@ -175,24 +187,29 @@
      * {@code MediaNotificationListener} callbacks. We have to create a separate derived class for
      * each type of notification since one class corresponds to one instance of the service only.
      */
-    private abstract static class ListenerService extends Service {
-        private static final String ACTION_PLAY =
-                "MediaNotificationManager.ListenerService.PLAY";
-        private static final String ACTION_PAUSE =
-                "MediaNotificationManager.ListenerService.PAUSE";
-        private static final String ACTION_STOP =
-                "MediaNotificationManager.ListenerService.STOP";
-        private static final String ACTION_SWIPE =
-                "MediaNotificationManager.ListenerService.SWIPE";
-        private static final String ACTION_CANCEL =
-                "MediaNotificationManager.ListenerService.CANCEL";
-        private static final String ACTION_PREVIOUS_TRACK =
+    @VisibleForTesting
+    abstract static class ListenerService extends Service {
+        @VisibleForTesting
+        static final String ACTION_PLAY = "MediaNotificationManager.ListenerService.PLAY";
+        @VisibleForTesting
+        static final String ACTION_PAUSE = "MediaNotificationManager.ListenerService.PAUSE";
+        @VisibleForTesting
+        static final String ACTION_STOP = "MediaNotificationManager.ListenerService.STOP";
+        @VisibleForTesting
+        static final String ACTION_SWIPE = "MediaNotificationManager.ListenerService.SWIPE";
+        @VisibleForTesting
+        static final String ACTION_CANCEL = "MediaNotificationManager.ListenerService.CANCEL";
+        @VisibleForTesting
+        static final String ACTION_PREVIOUS_TRACK =
                 "MediaNotificationManager.ListenerService.PREVIOUS_TRACK";
-        private static final String ACTION_NEXT_TRACK =
+        @VisibleForTesting
+        static final String ACTION_NEXT_TRACK =
                 "MediaNotificationManager.ListenerService.NEXT_TRACK";
-        private static final String ACTION_SEEK_FORWARD =
+        @VisibleForTesting
+        static final String ACTION_SEEK_FORWARD =
                 "MediaNotificationManager.ListenerService.SEEK_FORWARD";
-        private static final String ACTION_SEEK_BACKWARD =
+        @VisibleForTesting
+        static final String ACTION_SEEK_BACKWARD =
                 "MediaNotificationmanager.ListenerService.SEEK_BACKWARD";
 
         @Override
@@ -212,7 +229,7 @@
 
         @Override
         public int onStartCommand(Intent intent, int flags, int startId) {
-            if (!processIntent(intent)) stopSelf();
+            if (!processIntent(intent)) stopListenerService();
 
             return START_NOT_STICKY;
         }
@@ -220,7 +237,13 @@
         @Nullable
         protected abstract MediaNotificationManager getManager();
 
-        private boolean processIntent(Intent intent) {
+        @VisibleForTesting
+        void stopListenerService() {
+            stopSelf();
+        }
+
+        @VisibleForTesting
+        boolean processIntent(Intent intent) {
             if (intent == null) return false;
 
             MediaNotificationManager manager = getManager();
@@ -238,14 +261,13 @@
             return true;
         }
 
-        private void processAction(Intent intent, MediaNotificationManager manager) {
+        @VisibleForTesting
+        void processAction(Intent intent, MediaNotificationManager manager) {
             String action = intent.getAction();
 
             // Before Android L, instead of using the MediaSession callback, the system will fire
             // ACTION_MEDIA_BUTTON intents which stores the information about the key event.
             if (Intent.ACTION_MEDIA_BUTTON.equals(action)) {
-                assert Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
-
                 KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
                 if (event == null) return;
                 if (event.getAction() != KeyEvent.ACTION_DOWN) return;
@@ -288,7 +310,7 @@
                     || ACTION_CANCEL.equals(action)) {
                 manager.onStop(
                         MediaNotificationListener.ACTION_SOURCE_MEDIA_NOTIFICATION);
-                stopSelf();
+                stopListenerService();
             } else if (ACTION_PLAY.equals(action)) {
                 manager.onPlay(MediaNotificationListener.ACTION_SOURCE_MEDIA_NOTIFICATION);
             } else if (ACTION_PAUSE.equals(action)) {
@@ -551,7 +573,8 @@
                 && icon.getHeight() >= MINIMAL_MEDIA_IMAGE_SIZE_PX;
     }
 
-    private static MediaNotificationManager getManager(int notificationId) {
+    @VisibleForTesting
+    static MediaNotificationManager getManager(int notificationId) {
         return sManagers.get(notificationId);
     }
 
@@ -561,12 +584,8 @@
     }
 
     @VisibleForTesting
-    static void ensureManagerForTesting(int notificationId) {
-        MediaNotificationManager manager = sManagers.get(notificationId);
-        if (manager == null) {
-            manager = new MediaNotificationManager(notificationId);
-            sManagers.put(notificationId, manager);
-        }
+    static void setManagerForTesting(int notificationId, MediaNotificationManager manager) {
+        sManagers.put(notificationId, manager);
     }
 
     @VisibleForTesting
@@ -588,7 +607,9 @@
     }
 
     private static boolean isRunningN() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+        return (sOverrideIsRunningNForTesting != null)
+                ? sOverrideIsRunningNForTesting
+                : Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
     }
 
     /**
@@ -612,7 +633,8 @@
         }
     }
 
-    private MediaNotificationManager(int notificationId) {
+    @VisibleForTesting
+    MediaNotificationManager(int notificationId) {
         mNotificationId = notificationId;
 
         mActionToButtonInfo = new SparseArray<>();
@@ -647,7 +669,8 @@
      *
      * @param service the service that was started
      */
-    private void onServiceStarted(ListenerService service) {
+    @VisibleForTesting
+    void onServiceStarted(ListenerService service) {
         if (mService == service) return;
 
         mService = service;
@@ -657,30 +680,36 @@
     /**
      * Handles the service destruction destruction.
      */
-    private void onServiceDestroyed() {
+    @VisibleForTesting
+    void onServiceDestroyed() {
         mService = null;
         if (mMediaNotificationInfo != null) clear(mMediaNotificationInfo.id);
     }
 
-    private void onPlay(int actionSource) {
+    @VisibleForTesting
+    void onPlay(int actionSource) {
         if (!mMediaNotificationInfo.isPaused) return;
         mMediaNotificationInfo.listener.onPlay(actionSource);
     }
 
-    private void onPause(int actionSource) {
+    @VisibleForTesting
+    void onPause(int actionSource) {
         if (mMediaNotificationInfo.isPaused) return;
         mMediaNotificationInfo.listener.onPause(actionSource);
     }
 
-    private void onStop(int actionSource) {
+    @VisibleForTesting
+    void onStop(int actionSource) {
         mMediaNotificationInfo.listener.onStop(actionSource);
     }
 
-    private void onMediaSessionAction(int action) {
+    @VisibleForTesting
+    void onMediaSessionAction(int action) {
         mMediaNotificationInfo.listener.onMediaSessionAction(action);
     }
 
-    private void showNotification(MediaNotificationInfo mediaNotificationInfo) {
+    @VisibleForTesting
+    void showNotification(MediaNotificationInfo mediaNotificationInfo) {
         if (mediaNotificationInfo.equals(mMediaNotificationInfo)) return;
         if (mediaNotificationInfo.isPaused && mMediaNotificationInfo != null
                 && mediaNotificationInfo.tabId != mMediaNotificationInfo.tabId) {
@@ -700,8 +729,9 @@
             updateNotificationBuilder();
             AppHooks.get().startForegroundService(createIntent());
         } else {
-            mService.startService(createIntent());
+            getContext().startService(createIntent());
         }
+        // TODO(zqzhang): merge this call to the if statement above?
         updateNotification();
     }
 
@@ -756,11 +786,11 @@
         return metadataBuilder.build();
     }
 
-    private void updateNotification() {
+    @VisibleForTesting
+    void updateNotification() {
         if (mService == null) return;
 
         if (mMediaNotificationInfo == null) return;
-
         updateMediaSession();
         updateNotificationBuilder();
 
@@ -780,11 +810,15 @@
         }
     }
 
-    private void updateNotificationBuilder() {
+    @VisibleForTesting
+    void updateNotificationBuilder() {
         mNotificationBuilder = NotificationBuilderFactory.createChromeNotificationBuilder(
                 true /* preferCompat */, ChannelsInitializer.CHANNEL_ID_MEDIA);
         setMediaStyleLayoutForNotificationBuilder(mNotificationBuilder);
 
+        // TODO(zqzhang): It's weird that setShowWhen() doesn't work on K. Calling setWhen() to
+        // force removing the time.
+        mNotificationBuilder.setShowWhen(false).setWhen(0);
         mNotificationBuilder.setSmallIcon(mMediaNotificationInfo.notificationSmallIcon);
         mNotificationBuilder.setAutoCancel(false);
         mNotificationBuilder.setLocalOnly(true);
@@ -811,7 +845,8 @@
                                                  : NotificationCompat.VISIBILITY_PUBLIC);
     }
 
-    private void updateMediaSession() {
+    @VisibleForTesting
+    void updateMediaSession() {
         if (!mMediaNotificationInfo.supportsPlayPause()) return;
 
         if (mMediaSession == null) mMediaSession = createMediaSession();
@@ -900,6 +935,8 @@
     private void setMediaStyleLayoutForNotificationBuilder(ChromeNotificationBuilder builder) {
         setMediaStyleNotificationText(builder);
         if (!mMediaNotificationInfo.supportsPlayPause()) {
+            // TODO(zqzhang): this should be wrong. On pre-N, the notification will look bad when
+            // the large icon is not set.
             builder.setLargeIcon(null);
         } else if (mMediaNotificationInfo.notificationLargeIcon != null) {
             builder.setLargeIcon(mMediaNotificationInfo.notificationLargeIcon);
@@ -913,9 +950,6 @@
             }
             builder.setLargeIcon(mDefaultNotificationLargeIcon);
         }
-        // TODO(zqzhang): It's weird that setShowWhen() don't work on K. Calling setWhen() to force
-        // removing the time.
-        builder.setShowWhen(false).setWhen(0);
 
         addNotificationButtons(builder);
     }
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 75f33029..e174070 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1644,6 +1644,12 @@
   "junit/src/org/chromium/chrome/browser/media/router/cast/TestUtils.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationButtonComputationTest.java",
+  "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowNotificationManager.java",
+  "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowResources.java",
+  "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerTestBase.java",
+  "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceActionsTest.java",
+  "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java",
+  "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java",
   "junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java",
   "junit/src/org/chromium/chrome/browser/notifications/ChannelsInitializerTest.java",
   "junit/src/org/chromium/chrome/browser/notifications/ChannelsUpdaterTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
index bd1b00b1..02b45c9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
@@ -96,6 +96,16 @@
     }
 
     @Override
+    protected void shutdownService() {
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                DownloadNotificationServiceTest.super.shutdownService();
+            }
+        });
+    }
+
+    @Override
     protected void tearDown() throws Exception {
         SharedPreferences sharedPrefs = ContextUtils.getAppSharedPreferences();
         SharedPreferences.Editor editor = sharedPrefs.edit();
@@ -402,7 +412,14 @@
                 DownloadSharedPreferenceHelper.KEY_PENDING_DOWNLOAD_NOTIFICATIONS, notifications);
         editor.apply();
         startNotificationService();
-        getService().onTaskRemoved(new Intent());
+
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                getService().onTaskRemoved(new Intent());
+            }
+        });
+
         assertTrue(getService().isPaused());
         assertFalse(sharedPrefs.contains(
                 DownloadSharedPreferenceHelper.KEY_PENDING_DOWNLOAD_NOTIFICATIONS));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java
new file mode 100644
index 0000000..e6090bb
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java
@@ -0,0 +1,216 @@
+// 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.media.ui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Notification;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Build;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowNotification;
+
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.content_public.common.MediaMetadata;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+/**
+ * JUnit tests for checking MediaNotificationManager presents correct notification to Android
+ * NotificationManager.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, application = BaseChromiumApplication.class,
+        shadows = MediaNotificationTestShadowResources.class)
+public class MediaNotificationManagerNotificationTest extends MediaNotificationManagerTestBase {
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectMetadata_PreN_NonEmptyArtistAndAlbum() {
+        MediaNotificationManager.sOverrideIsRunningNForTesting = false;
+
+        mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "artist", "album"));
+        mMediaNotificationInfoBuilder.setOrigin("https://example.com/");
+
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        ShadowNotification shadowNotification = Shadows.shadowOf(notification);
+
+        assertEquals("title", shadowNotification.getContentTitle());
+        assertEquals("artist - album", shadowNotification.getContentText());
+        if (hasNApis()) {
+            assertEquals("https://example.com/",
+                    notification.extras.getString(Notification.EXTRA_SUB_TEXT));
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectMetadata_PreN_EmptyArtistAndAlbum() {
+        MediaNotificationManager.sOverrideIsRunningNForTesting = false;
+
+        mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "", ""));
+        mMediaNotificationInfoBuilder.setOrigin("https://example.com/");
+
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        ShadowNotification shadowNotification = Shadows.shadowOf(notification);
+
+        assertEquals(info.metadata.getTitle(), shadowNotification.getContentTitle());
+        assertEquals(info.origin, shadowNotification.getContentText());
+        if (hasNApis()) {
+            assertEquals(null, notification.extras.getString(Notification.EXTRA_SUB_TEXT));
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectMetadata_AtLeastN_EmptyArtistAndAlbum() {
+        MediaNotificationManager.sOverrideIsRunningNForTesting = true;
+
+        mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "", ""));
+        mMediaNotificationInfoBuilder.setOrigin("https://example.com/");
+
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        ShadowNotification shadowNotification = Shadows.shadowOf(notification);
+
+        assertEquals(info.metadata.getTitle(), shadowNotification.getContentTitle());
+        assertEquals("", shadowNotification.getContentText());
+        if (hasNApis()) {
+            assertEquals(info.origin, notification.extras.getString(Notification.EXTRA_SUB_TEXT));
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectLargeIcon_WithLargeIcon() {
+        Bitmap largeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        mMediaNotificationInfoBuilder.setNotificationLargeIcon(largeIcon);
+
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        if (hasNApis()) {
+            assertTrue(largeIcon.sameAs(iconToBitmap(
+                    notification.extras.getParcelable(Notification.EXTRA_LARGE_ICON))));
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectLargeIcon_WithoutLargeIcon_AtLeastN() {
+        mMediaNotificationInfoBuilder.setNotificationLargeIcon(null);
+
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        if (hasNApis()) {
+            assertNull(notification.extras.getParcelable(Notification.EXTRA_LARGE_ICON));
+        }
+        assertNull(getManager().mDefaultNotificationLargeIcon);
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectLargeIcon_WithoutLargeIcon_PreN() {
+        MediaNotificationManager.sOverrideIsRunningNForTesting = false;
+        assertNull(getManager().mDefaultNotificationLargeIcon);
+
+        mMediaNotificationInfoBuilder.setNotificationLargeIcon(null);
+
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        assertNotNull(getManager().mDefaultNotificationLargeIcon);
+        if (hasNApis()) {
+            assertTrue(getManager().mDefaultNotificationLargeIcon.sameAs(iconToBitmap(
+                    notification.extras.getParcelable(Notification.EXTRA_LARGE_ICON))));
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectLargeIcon_DontSupportPlayPause() {
+        Bitmap largeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        mMediaNotificationInfoBuilder.setNotificationLargeIcon(largeIcon).setActions(0);
+
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        if (hasNApis()) {
+            assertNull(notification.extras.getParcelable(Notification.EXTRA_LARGE_ICON));
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectMiscInfo() {
+        mMediaNotificationInfoBuilder.setNotificationSmallIcon(1 /* resId */)
+                .setActions(0)
+                .setContentIntent(new Intent());
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        ShadowNotification shadowNotification = Shadows.shadowOf(notification);
+
+        assertFalse(shadowNotification.isWhenShown());
+        assertFalse(shadowNotification.isOngoing());
+        if (hasNApis()) {
+            assertNotNull(notification.getSmallIcon());
+            assertFalse((notification.flags & Notification.FLAG_AUTO_CANCEL) != 0);
+            assertTrue((notification.flags & Notification.FLAG_LOCAL_ONLY) != 0);
+            assertEquals(NOTIFICATION_GROUP_NAME, notification.getGroup());
+            assertTrue(notification.isGroupSummary());
+            assertNull(notification.deleteIntent);
+            assertNotNull(notification.contentIntent);
+            assertEquals(Notification.VISIBILITY_PRIVATE, notification.visibility);
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectMiscInfo_SupportsSwipeAway() {
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        ShadowNotification shadowNotification = Shadows.shadowOf(notification);
+
+        assertTrue(shadowNotification.isOngoing());
+        if (hasNApis()) {
+            assertNotNull(notification.deleteIntent);
+        }
+    }
+
+    @Test
+    public void updateNotificationBuilderDisplaysCorrectMiscInfo_Private() {
+        mMediaNotificationInfoBuilder.setPrivate(false);
+        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
+        Notification notification = updateNotificationBuilderAndBuild(info);
+
+        if (hasNApis()) {
+            assertEquals(Notification.VISIBILITY_PUBLIC, notification.visibility);
+        }
+    }
+
+    private Notification updateNotificationBuilderAndBuild(MediaNotificationInfo info) {
+        getManager().mMediaNotificationInfo = info;
+
+        // This is the fake implementation to ensure |mMediaSession| is non-null.
+        //
+        // TODO(zqzhang): move around code so that updateNotification() doesn't need a MediaSession.
+        getManager().updateMediaSession();
+        getManager().updateNotificationBuilder();
+
+        return getManager().mNotificationBuilder.build();
+    }
+
+    private boolean hasNApis() {
+        return RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N;
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceActionsTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceActionsTest.java
new file mode 100644
index 0000000..d91e54e
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceActionsTest.java
@@ -0,0 +1,248 @@
+// 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.media.ui;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Intent;
+import android.media.AudioManager;
+import android.view.KeyEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.blink.mojom.MediaSessionAction;
+import org.chromium.chrome.browser.media.ui.MediaNotificationManager.ListenerService;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+/**
+ * JUnit tests for checking {@link MediaNotificationManager.ListenerService} handles intent actionss
+ * correctly.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, application = BaseChromiumApplication.class,
+        shadows = {MediaNotificationTestShadowResources.class,
+                MediaNotificationTestShadowNotificationManager.class})
+public class MediaNotificationManagerServiceActionsTest extends MediaNotificationManagerTestBase {
+    @Test
+    public void testProcessIntentWithNoAction() {
+        setUpServiceAndClearInvocations();
+        doNothing().when(getManager()).onServiceStarted(any(ListenerService.class));
+        assertTrue(mService.processIntent(new Intent()));
+        verify(getManager()).onServiceStarted(mService);
+    }
+
+    @Test
+    public void testProcessIntentWithAction() {
+        setUpService();
+        doNothing().when(getManager()).onServiceStarted(any(ListenerService.class));
+        Intent intentWithAction = new Intent().setAction("foo");
+        assertTrue(mService.processIntent(intentWithAction));
+        verify(mService).processAction(intentWithAction, getManager());
+    }
+
+    @Test
+    public void testProcessMediaButton_Play() {
+        setUpService();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_MEDIA_PLAY), getManager());
+        verify(getManager()).onPlay(MediaNotificationListener.ACTION_SOURCE_MEDIA_SESSION);
+    }
+
+    @Test
+    public void testProcessMediaButton_Pause() {
+        setUpService();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_MEDIA_PAUSE), getManager());
+        verify(getManager()).onPause(MediaNotificationListener.ACTION_SOURCE_MEDIA_SESSION);
+    }
+
+    @Test
+    public void testProcessMediaButton_HeadsetHook() {
+        setUpService();
+
+        mMediaNotificationInfoBuilder.setPaused(false);
+        getManager().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_HEADSETHOOK), getManager());
+        verify(getManager()).onPause(MediaNotificationListener.ACTION_SOURCE_MEDIA_SESSION);
+    }
+
+    @Test
+    public void testProcessMediaButton_PlayPause() {
+        setUpService();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE), getManager());
+        verify(getManager()).onPause(MediaNotificationListener.ACTION_SOURCE_MEDIA_SESSION);
+    }
+
+    @Test
+    public void testProcessMediaButton_Previous() {
+        setUpService();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_MEDIA_PREVIOUS), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.PREVIOUS_TRACK);
+    }
+
+    @Test
+    public void testProcessMediaButton_Next() {
+        setUpService();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_MEDIA_NEXT), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.NEXT_TRACK);
+    }
+
+    @Test
+    public void testProcessMediaButton_Rewind() {
+        setUpService();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.SEEK_FORWARD);
+    }
+
+    @Test
+    public void testProcessMediaButton_Backward() {
+        setUpService();
+
+        mService.processAction(
+                createMediaButtonActionIntent(KeyEvent.KEYCODE_MEDIA_REWIND), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.SEEK_BACKWARD);
+    }
+
+    @Test
+    public void testProcessMediaButtonActionWithNoKeyEvent() {
+        setUpService();
+
+        clearInvocations(getManager());
+        mService.processAction(new Intent(Intent.ACTION_MEDIA_BUTTON), getManager());
+
+        verifyZeroInteractions(getManager());
+    }
+
+    @Test
+    public void testProcessMediaButtonActionWithWrongTypeKeyEvent() {
+        setUpService();
+
+        clearInvocations(getManager());
+        mService.processAction(
+                new Intent(Intent.ACTION_MEDIA_BUTTON)
+                        .putExtra(Intent.EXTRA_KEY_EVENT,
+                                new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)),
+                getManager());
+        mService.processAction(new Intent(Intent.ACTION_MEDIA_BUTTON)
+                                       .putExtra(Intent.EXTRA_KEY_EVENT,
+                                               new KeyEvent(KeyEvent.ACTION_MULTIPLE,
+                                                       KeyEvent.KEYCODE_MEDIA_PLAY)),
+                getManager());
+
+        verifyZeroInteractions(getManager());
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_Stop() {
+        setUpService();
+
+        MediaNotificationManager manager = getManager();
+        ListenerService service = mService;
+
+        mService.processAction(new Intent(ListenerService.ACTION_STOP), getManager());
+        verify(manager).onStop(MediaNotificationListener.ACTION_SOURCE_MEDIA_NOTIFICATION);
+        verify(service).stopListenerService();
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_Swipe() {
+        setUpService();
+
+        MediaNotificationManager manager = getManager();
+        ListenerService service = mService;
+
+        mService.processAction(new Intent(ListenerService.ACTION_SWIPE), getManager());
+        verify(manager).onStop(MediaNotificationListener.ACTION_SOURCE_MEDIA_NOTIFICATION);
+        verify(service).stopListenerService();
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_Cancel() {
+        setUpService();
+
+        MediaNotificationManager manager = getManager();
+        ListenerService service = mService;
+
+        mService.processAction(new Intent(ListenerService.ACTION_CANCEL), getManager());
+        verify(manager).onStop(MediaNotificationListener.ACTION_SOURCE_MEDIA_NOTIFICATION);
+        verify(service).stopListenerService();
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_Play() {
+        setUpService();
+
+        mService.processAction(new Intent(ListenerService.ACTION_PLAY), getManager());
+        verify(getManager()).onPlay(MediaNotificationListener.ACTION_SOURCE_MEDIA_NOTIFICATION);
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_Pause() {
+        setUpService();
+
+        mService.processAction(new Intent(ListenerService.ACTION_PAUSE), getManager());
+        verify(getManager()).onPause(MediaNotificationListener.ACTION_SOURCE_MEDIA_NOTIFICATION);
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_Noisy() {
+        setUpService();
+
+        mService.processAction(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY), getManager());
+        verify(getManager()).onPause(MediaNotificationListener.ACTION_SOURCE_HEADSET_UNPLUG);
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_PreviousTrack() {
+        setUpService();
+
+        mService.processAction(new Intent(ListenerService.ACTION_PREVIOUS_TRACK), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.PREVIOUS_TRACK);
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_NextTrack() {
+        setUpService();
+
+        mService.processAction(new Intent(ListenerService.ACTION_NEXT_TRACK), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.NEXT_TRACK);
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_SeekForward() {
+        setUpService();
+
+        mService.processAction(new Intent(ListenerService.ACTION_SEEK_FORWARD), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.SEEK_FORWARD);
+    }
+
+    @Test
+    public void testProcessNotificationButtonAction_SeekBackward() {
+        setUpService();
+
+        mService.processAction(new Intent(ListenerService.ACTION_SEEK_BACKWARD), getManager());
+        verify(getManager()).onMediaSessionAction(MediaSessionAction.SEEK_BACKWARD);
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java
new file mode 100644
index 0000000..3bef5c5
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java
@@ -0,0 +1,253 @@
+// 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.media.ui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.app.Notification;
+import android.content.Intent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.chrome.browser.media.ui.MediaNotificationManager.ListenerService;
+import org.chromium.content_public.common.MediaMetadata;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * JUnit tests for checking {@link MediaNotificationManager} handles the listener service life cycle
+ * correctly.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, application = BaseChromiumApplication.class,
+        shadows = {MediaNotificationTestShadowResources.class,
+                MediaNotificationTestShadowNotificationManager.class})
+public class MediaNotificationManagerServiceLifecycleTest extends MediaNotificationManagerTestBase {
+    @Test
+    public void testServiceLifeCycle() {
+        ensureMediaNotificationInfo();
+
+        Intent intent = getManager().createIntent();
+
+        assertNull(mService);
+        mMockContext.startService(intent);
+        verify(getManager()).onServiceStarted(mService);
+        assertNotNull(mService);
+        verify(mService).onStartCommand(intent, 0, 0);
+
+        mService.stopListenerService();
+        assertNull(mService);
+    }
+
+    @Test
+    public void testProcessIntentFailureStopsService() throws TimeoutException {
+        MediaNotificationManager manager = getManager();
+        setUpService();
+
+        ListenerService service = mService;
+        doReturn(false).when(mService).processIntent(any(Intent.class));
+        mMockContext.startService(new Intent());
+        verify(service).stopListenerService();
+        assertNull(getManager());
+        verify(manager).onServiceDestroyed();
+    }
+
+    @Test
+    public void testProcessNullIntent() {
+        setUpService();
+        assertFalse(mService.processIntent(null));
+    }
+
+    @Test
+    public void testProcessIntentWhenManagerIsNull() {
+        setUpService();
+        MediaNotificationManager.setManagerForTesting(NOTIFICATION_ID, null);
+        assertFalse(mService.processIntent(new Intent()));
+    }
+
+    @Test
+    public void testProcessIntentWhenNotificationInfoIsNull() {
+        setUpService();
+        getManager().mMediaNotificationInfo = null;
+        assertFalse(mService.processIntent(new Intent()));
+    }
+
+    @Test
+    public void testShowNotificationIsNoOpWhenInfoMatches() {
+        doCallRealMethod().when(getManager()).onServiceStarted(any(ListenerService.class));
+        doNothing().when(getManager()).updateNotification();
+        setUpServiceAndClearInvocations();
+
+        MediaNotificationInfo newInfo = mMediaNotificationInfoBuilder.build();
+        getManager().showNotification(newInfo);
+
+        verify(getManager()).showNotification(newInfo);
+        verifyNoMoreInteractions(getManager());
+        verify(mMockAppHooks, never()).startForegroundService(any(Intent.class));
+        verify(mMockContext, never()).startService(any(Intent.class));
+    }
+
+    @Test
+    public void testShowNotificationIsNoOpWhenInfoIsPausedAndFromAnotherTab() {
+        doCallRealMethod().when(getManager()).onServiceStarted(any(ListenerService.class));
+        doNothing().when(getManager()).updateNotification();
+        mMediaNotificationInfoBuilder.setTabId(0);
+        setUpServiceAndClearInvocations();
+
+        mMediaNotificationInfoBuilder.setTabId(1).setPaused(true);
+        MediaNotificationInfo newInfo = mMediaNotificationInfoBuilder.build();
+        getManager().showNotification(newInfo);
+
+        verify(getManager()).showNotification(newInfo);
+        verifyNoMoreInteractions(getManager());
+        verify(mMockAppHooks, never()).startForegroundService(any(Intent.class));
+        verify(mMockContext, never()).startService(any(Intent.class));
+    }
+
+    @Test
+    public void testShowNotificationWhenServiceNotCreated() {
+        MediaNotificationInfo newInfo = mMediaNotificationInfoBuilder.build();
+        getManager().showNotification(newInfo);
+
+        verify(getManager(), times(1)).updateMediaSession();
+        verify(getManager(), times(1)).updateNotificationBuilder();
+        verify(mMockContext, never()).startService(any(Intent.class));
+        verify(mMockAppHooks, times(1)).startForegroundService(any(Intent.class));
+        verify(getManager(), times(1)).updateNotification();
+    }
+
+    @Test
+    public void testShowNotificationWhenServiceAlreadyCreated() {
+        doCallRealMethod().when(getManager()).onServiceStarted(any(ListenerService.class));
+        doNothing().when(getManager()).updateNotification();
+        setUpServiceAndClearInvocations();
+
+        mMediaNotificationInfoBuilder.setPaused(true);
+        MediaNotificationInfo newInfo = mMediaNotificationInfoBuilder.build();
+        getManager().showNotification(newInfo);
+
+        verify(getManager()).showNotification(newInfo);
+        verify(mMockAppHooks, never()).startForegroundService(any(Intent.class));
+        verify(mMockContext).startService(any(Intent.class));
+    }
+
+    @Test
+    public void testShowNotificationBeforeServiceCreatedUpdatesNotificationInfo() {
+        doCallRealMethod().when(getManager()).onServiceStarted(any(ListenerService.class));
+        doNothing().when(getManager()).updateNotification();
+
+        // The initial call to |showNotification()| should update the notification info and request
+        // to start the service.
+        MediaNotificationInfo oldInfo = mMediaNotificationInfoBuilder.build();
+        getManager().showNotification(oldInfo);
+
+        InOrder order = inOrder(getManager(), mMockAppHooks);
+
+        assertEquals(oldInfo, getManager().mMediaNotificationInfo);
+        order.verify(getManager(), times(1)).updateMediaSession();
+        order.verify(getManager(), times(1)).updateNotificationBuilder();
+        order.verify(mMockAppHooks, times(1)).startForegroundService(any(Intent.class));
+        order.verify(getManager(), times(1)).updateNotification();
+
+        // The second call to |showNotification()| should only update the notification info.
+        mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("new title", "", ""));
+        MediaNotificationInfo newInfo = mMediaNotificationInfoBuilder.build();
+        getManager().showNotification(newInfo);
+
+        assertEquals(newInfo, getManager().mMediaNotificationInfo);
+        order.verify(getManager(), times(1)).updateMediaSession();
+        order.verify(getManager(), times(1)).updateNotificationBuilder();
+        order.verify(mMockAppHooks, times(1)).startForegroundService(any(Intent.class));
+        order.verify(getManager(), times(1)).updateNotification();
+
+        verify(getManager(), never()).onServiceStarted(any(ListenerService.class));
+
+        // Simulate the service has started.
+        mMockContext.startService(getManager().createIntent());
+        order.verify(getManager(), times(1)).onServiceStarted(mService);
+        order.verify(getManager(), times(1)).updateNotification();
+    }
+
+    @Test
+    public void updateNotificationIsNoOpBeforeServiceCreated() {
+        getManager().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build();
+        getManager().updateNotification();
+
+        verify(getManager()).updateNotification();
+        verify(getManager(), never()).updateMediaSession();
+        verify(getManager(), never()).updateNotificationBuilder();
+    }
+
+    @Test
+    public void updateNotificationIsNoOpWhenNotificiationInfoIsNull() {
+        setUpService();
+        getManager().mService = mService;
+        getManager().mMediaNotificationInfo = null;
+        getManager().updateNotification();
+
+        verify(getManager()).updateNotification();
+        verify(getManager(), never()).updateMediaSession();
+        verify(getManager(), never()).updateNotificationBuilder();
+
+        verify(mService, never()).stopForeground(anyBoolean());
+        verify(mService, never()).startForeground(anyInt(), any(Notification.class));
+    }
+
+    @Test
+    public void updateNotificationSetsServiceBackgroundWhenPausedAndSupportsSwipeAway() {
+        mMediaNotificationInfoBuilder.setPaused(true);
+        setUpService();
+        getManager().mService = mService;
+        getManager().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build();
+        getManager().updateNotification();
+
+        verify(mService).stopForeground(false);
+        // One of the invocations comes from |setUpService()|.
+        verify(MediaNotificationTestShadowNotificationManager.sMockObserver, times(2))
+                .notify(eq(NOTIFICATION_ID), any(Notification.class));
+    }
+
+    @Test
+    public void updateNotificationSetsServiceBackgroundWhenPausedButDoesntSupportSwipeAway() {
+        mMediaNotificationInfoBuilder.setPaused(true).setActions(0);
+        setUpService();
+        getManager().mService = mService;
+        getManager().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build();
+        getManager().updateNotification();
+
+        verify(mService).startForeground(eq(NOTIFICATION_ID), any(Notification.class));
+    }
+
+    @Test
+    public void updateNotificationSetsServiceForegroundWhenPlaying() {
+        mMediaNotificationInfoBuilder.setPaused(false);
+        setUpService();
+        getManager().mService = mService;
+        getManager().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build();
+        getManager().updateNotification();
+
+        verify(mService).startForeground(eq(NOTIFICATION_ID), any(Notification.class));
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerTestBase.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerTestBase.java
new file mode 100644
index 0000000..df9dbee
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerTestBase.java
@@ -0,0 +1,193 @@
+// 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.media.ui;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.view.KeyEvent;
+
+import org.junit.Before;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowLog;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.AppHooks;
+import org.chromium.chrome.browser.AppHooksImpl;
+import org.chromium.chrome.browser.media.ui.MediaNotificationManager.ListenerService;
+import org.chromium.content_public.common.MediaMetadata;
+
+/**
+ * Common test fixtures for MediaNotificationManager JUnit tests.
+ */
+public class MediaNotificationManagerTestBase {
+    static final int NOTIFICATION_ID = 0;
+    static final String NOTIFICATION_GROUP_NAME = "group-name";
+    Context mMockContext;
+    MockListenerService mService;
+    MediaNotificationListener mListener;
+    AppHooksImpl mMockAppHooks;
+
+    MediaNotificationInfo.Builder mMediaNotificationInfoBuilder;
+
+    static class MockMediaNotificationManager extends MediaNotificationManager {
+        public MockMediaNotificationManager() {
+            super(NOTIFICATION_ID);
+        }
+    }
+
+    static class MockListenerService extends ListenerService {
+        @Override
+        protected MediaNotificationManager getManager() {
+            return MediaNotificationManager.getManager(NOTIFICATION_ID);
+        }
+
+        @Override
+        public int onStartCommand(Intent intent, int flags, int startId) {
+            return super.onStartCommand(intent, flags, startId);
+        }
+    }
+
+    static class MockMediaButtonReceiver extends MediaButtonReceiver {
+        @Override
+        public Class<?> getServiceClass() {
+            return MockListenerService.class;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        // For checking the notification presented to NotificationManager.
+        assertTrue(RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N);
+
+        ShadowLog.stream = System.out;
+
+        mMockContext = spy(RuntimeEnvironment.application);
+        ContextUtils.initApplicationContextForTests(mMockContext);
+
+        mListener = mock(MediaNotificationListener.class);
+
+        MediaNotificationManager.sMapNotificationIdToOptions.put(NOTIFICATION_ID,
+                new MediaNotificationManager.NotificationOptions(MockListenerService.class,
+                        MockMediaButtonReceiver.class, NOTIFICATION_GROUP_NAME));
+
+        MediaNotificationManager.setManagerForTesting(
+                NOTIFICATION_ID, spy(new MockMediaNotificationManager()));
+
+        mMediaNotificationInfoBuilder =
+                new MediaNotificationInfo.Builder()
+                        .setMetadata(new MediaMetadata("title", "artist", "album"))
+                        .setOrigin("https://example.com")
+                        .setListener(mListener)
+                        .setId(NOTIFICATION_ID);
+
+        doNothing().when(getManager()).onServiceStarted(any(ListenerService.class));
+        // Robolectric does not have "ShadowMediaSession".
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) {
+                MediaSessionCompat mockSession = mock(MediaSessionCompat.class);
+                getManager().mMediaSession = mockSession;
+                doReturn(null).when(mockSession).getSessionToken();
+                return "Created mock media session";
+            }
+        })
+                .when(getManager())
+                .updateMediaSession();
+
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) {
+                Intent intent = (Intent) invocation.getArgument(0);
+                startService(intent);
+                return new ComponentName(mMockContext, MockListenerService.class);
+            }
+        })
+                .when(mMockContext)
+                .startService(any(Intent.class));
+
+        mMockAppHooks = mock(AppHooksImpl.class);
+        AppHooks.setInstanceForTesting(mMockAppHooks);
+
+        // Init the command line to avoid assertion failure in |SysUtils#isLowEndDevice()|.
+        CommandLine.init(null);
+    }
+
+    MediaNotificationManager getManager() {
+        return MediaNotificationManager.getManager(NOTIFICATION_ID);
+    }
+
+    void ensureMediaNotificationInfo() {
+        getManager().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build();
+    }
+
+    void setUpServiceAndClearInvocations() {
+        setUpService();
+        clearInvocations(getManager());
+        clearInvocations(mService);
+        clearInvocations(mMockContext);
+    }
+
+    void setUpService() {
+        ensureMediaNotificationInfo();
+
+        Intent intent = getManager().createIntent();
+        mMockContext.startService(intent);
+    }
+
+    private void startService(Intent intent) {
+        ensureService();
+        mService.onStartCommand(intent, 0, 0);
+    }
+
+    private void ensureService() {
+        if (mService != null) return;
+        mService = spy(new MockListenerService());
+
+        doAnswer(new Answer() {
+            public Object answer(InvocationOnMock invocation) {
+                mService.onDestroy();
+                mService = null;
+                return "service stopped";
+            }
+        })
+                .when(mService)
+                .stopListenerService();
+    }
+
+    Intent createMediaButtonActionIntent(int keyCode) {
+        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+
+        return intent;
+    }
+
+    Bitmap iconToBitmap(Icon icon) {
+        if (icon == null) return null;
+
+        BitmapDrawable drawable = (BitmapDrawable) icon.loadDrawable(mMockContext);
+        assert drawable != null;
+        return drawable.getBitmap();
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowNotificationManager.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowNotificationManager.java
new file mode 100644
index 0000000..d42c637b
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowNotificationManager.java
@@ -0,0 +1,33 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.media.ui;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowNotificationManager;
+
+/**
+ * Dummy Robolectric shadow for Android NotificationManager for MediaNotification tests.
+ */
+@Implements(NotificationManager.class)
+public class MediaNotificationTestShadowNotificationManager extends ShadowNotificationManager {
+    public static final NotificationManager sMockObserver;
+
+    static {
+        sMockObserver = mock(NotificationManager.class);
+    }
+
+    @Implementation
+    @Override
+    public void notify(int id, Notification notification) {
+        sMockObserver.notify(id, notification);
+        super.notify(id, notification);
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowResources.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowResources.java
new file mode 100644
index 0000000..7febe444
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestShadowResources.java
@@ -0,0 +1,39 @@
+// 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.media.ui;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.res.Resources;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowResources;
+
+/**
+ * Dummy Robolectric shadow for Android Resources for MediaNotification tests.
+ */
+@Implements(Resources.class)
+public class MediaNotificationTestShadowResources extends ShadowResources {
+    public static final Resources sResources;
+
+    static {
+        sResources = mock(Resources.class);
+        doReturn("mocked text").when(sResources).getText(anyInt());
+        doReturn("mocked resource name").when(sResources).getResourceName(anyInt());
+    }
+
+    @Implementation
+    public CharSequence getText(int id) {
+        return sResources.getText(id);
+    }
+
+    @Implementation
+    public CharSequence getResourceName(int id) {
+        return sResources.getResourceName(id);
+    }
+}
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 8d60a9f1..0e806fa 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -3128,7 +3128,13 @@
   TestHelper("testFindAPI", "web_view/shim", NO_TEST_SERVER);
 }
 
-IN_PROC_BROWSER_TEST_P(WebViewTest, Shim_TestFindAPI_findupdate) {
+// crbug.com/697171
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_Shim_TestFindAPI_findupdate DISABLED_Shim_TestFindAPI_findupdate
+#else
+#define MAYBE_Shim_TestFindAPI_findupdate Shim_TestFindAPI_findupdate
+#endif
+IN_PROC_BROWSER_TEST_P(WebViewTest, MAYBE_Shim_TestFindAPI_findupdate) {
   TestHelper("testFindAPI_findupdate", "web_view/shim", NO_TEST_SERVER);
 }
 
diff --git a/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc b/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc
index 71f1781..fd2c314b 100644
--- a/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc
+++ b/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc
@@ -155,6 +155,11 @@
   app_manager_->AddObserver(this);
   pref_change_registrar_.reset(new PrefChangeRegistrar());
   pref_change_registrar_->Init(profile_->GetPrefs());
+  // Kiosk app can be started only when policy compliance is reported.
+  pref_change_registrar_->Add(
+      prefs::kArcPolicyComplianceReported,
+      base::Bind(&ArcKioskAppService::PreconditionsChanged,
+                 base::Unretained(this)));
   notification_blocker_.reset(new ArcKioskNotificationBlocker());
   PreconditionsChanged();
 }
@@ -185,9 +190,15 @@
   VLOG_IF(2, app_info_ && app_info_->ready) << "Kiosk app is ready";
   VLOG(2) << "Maintenance session is "
           << (maintenance_session_running_ ? "running" : "not running");
+  VLOG(2) << "Policy compliance is "
+          << (profile_->GetPrefs()->GetBoolean(
+                  prefs::kArcPolicyComplianceReported)
+                  ? "reported"
+                  : "not yet reported");
   VLOG(2) << "Kiosk app with id: " << app_id_ << " is "
           << (app_launcher_ ? "already launched" : "not yet launched");
-  if (app_info_ && app_info_->ready && !maintenance_session_running_) {
+  if (app_info_ && app_info_->ready && !maintenance_session_running_ &&
+      profile_->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported)) {
     if (!app_launcher_) {
       VLOG(2) << "Starting kiosk app";
       app_launcher_ = base::MakeUnique<ArcKioskAppLauncher>(
diff --git a/chrome/browser/chromeos/arc/policy/arc_policy_bridge.cc b/chrome/browser/chromeos/arc/policy/arc_policy_bridge.cc
index f628eb5..e837bbf 100644
--- a/chrome/browser/chromeos/arc/policy/arc_policy_bridge.cc
+++ b/chrome/browser/chromeos/arc/policy/arc_policy_bridge.cc
@@ -311,6 +311,12 @@
   arc_bridge_service()->policy()->RemoveObserver(this);
 }
 
+// static
+void ArcPolicyBridge::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterBooleanPref(prefs::kArcPolicyComplianceReported, false);
+}
+
 void ArcPolicyBridge::OverrideIsManagedForTesting(bool is_managed) {
   is_managed_ = is_managed;
 }
@@ -392,6 +398,8 @@
     std::unique_ptr<base::Value> parsed_json) {
   // Always returns "compliant".
   callback.Run(kPolicyCompliantJson);
+  GetProfile()->GetPrefs()->SetBoolean(prefs::kArcPolicyComplianceReported,
+                                       true);
 
   const base::DictionaryValue* dict = nullptr;
   if (parsed_json->GetAsDictionary(&dict))
diff --git a/chrome/browser/chromeos/arc/policy/arc_policy_bridge.h b/chrome/browser/chromeos/arc/policy/arc_policy_bridge.h
index 51fa6d7..4966c0a 100644
--- a/chrome/browser/chromeos/arc/policy/arc_policy_bridge.h
+++ b/chrome/browser/chromeos/arc/policy/arc_policy_bridge.h
@@ -46,6 +46,8 @@
                   policy::PolicyService* policy_service);
   ~ArcPolicyBridge() override;
 
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
   void OverrideIsManagedForTesting(bool is_managed);
 
   // InstanceHolder<mojom::PolicyInstance>::Observer overrides.
diff --git a/chrome/browser/chromeos/arc/policy/arc_policy_bridge_unittest.cc b/chrome/browser/chromeos/arc/policy/arc_policy_bridge_unittest.cc
index 19ab2ee..d8639db 100644
--- a/chrome/browser/chromeos/arc/policy/arc_policy_bridge_unittest.cc
+++ b/chrome/browser/chromeos/arc/policy/arc_policy_bridge_unittest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/chromeos/arc/policy/arc_policy_bridge.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/arc/arc_bridge_service.h"
@@ -143,8 +144,8 @@
     testing_profile_manager_ = base::MakeUnique<TestingProfileManager>(
         TestingBrowserProcess::GetGlobal());
     ASSERT_TRUE(testing_profile_manager_->SetUp());
-    ASSERT_TRUE(
-        testing_profile_manager_->CreateTestingProfile("user@gmail.com"));
+    profile_ = testing_profile_manager_->CreateTestingProfile("user@gmail.com");
+    ASSERT_TRUE(profile_);
   }
 
  protected:
@@ -152,6 +153,7 @@
   FakePolicyInstance* policy_instance() { return policy_instance_.get(); }
   policy::PolicyMap& policy_map() { return policy_map_; }
   base::RunLoop& run_loop() { return run_loop_; }
+  Profile* profile() { return profile_; }
 
  private:
   safe_json::TestingJsonParser::ScopedFactoryOverride factory_override_;
@@ -159,6 +161,7 @@
   std::unique_ptr<chromeos::ScopedUserManagerEnabler> user_manager_enabler_;
   std::unique_ptr<TestingProfileManager> testing_profile_manager_;
   base::RunLoop run_loop_;
+  TestingProfile* profile_;
 
   std::unique_ptr<ArcBridgeService> bridge_service_;
   std::unique_ptr<ArcPolicyBridge> policy_bridge_;
@@ -365,29 +368,43 @@
 }
 
 TEST_F(ArcPolicyBridgeTest, EmptyReportComplianceTest) {
+  ASSERT_FALSE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
   policy_bridge()->ReportCompliance(
       "{}", PolicyComplianceCallback(run_loop().QuitClosure(),
                                      kPolicyCompliantResponse));
   run_loop().Run();
+  ASSERT_TRUE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
 }
 
 TEST_F(ArcPolicyBridgeTest, ParsableReportComplianceTest) {
+  ASSERT_FALSE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
   policy_bridge()->ReportCompliance(
       "{\"nonComplianceDetails\" : []}",
       PolicyComplianceCallback(run_loop().QuitClosure(),
                                kPolicyCompliantResponse));
   run_loop().Run();
+  ASSERT_TRUE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
 }
 
 TEST_F(ArcPolicyBridgeTest, NonParsableReportComplianceTest) {
+  ASSERT_FALSE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
   policy_bridge()->ReportCompliance(
       "\"nonComplianceDetails\" : [}",
       PolicyComplianceCallback(run_loop().QuitClosure(),
                                kPolicyCompliantResponse));
   run_loop().Run();
+  ASSERT_FALSE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
 }
 
 TEST_F(ArcPolicyBridgeTest, ReportComplianceTest_WithNonCompliantDetails) {
+  ASSERT_FALSE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
   policy_bridge()->ReportCompliance(
       "{\"nonComplianceDetails\" : "
       "[{\"fieldPath\":\"\",\"nonComplianceReason\":0,\"packageName\":\"\","
@@ -395,6 +412,8 @@
       PolicyComplianceCallback(run_loop().QuitClosure(),
                                kPolicyCompliantResponse));
   run_loop().Run();
+  ASSERT_TRUE(
+      profile()->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported));
 }
 
 // This and the following test send the policies through a mojo connection
diff --git a/chrome/browser/predictors/resource_prefetch_common.cc b/chrome/browser/predictors/resource_prefetch_common.cc
index d324969..ebdbea8 100644
--- a/chrome/browser/predictors/resource_prefetch_common.cc
+++ b/chrome/browser/predictors/resource_prefetch_common.cc
@@ -25,6 +25,7 @@
 const char kPrefetchingMode[] = "prefetching";
 const char kEnableUrlLearningParamName[] = "enable-url-learning";
 const char kEnableManifestsParamName[] = "enable-manifests";
+const char kEnableOriginLearningParamName[] = "enable-origin-learning";
 
 const base::Feature kSpeculativeResourcePrefetchingFeature =
     base::Feature(kSpeculativeResourcePrefetchingFeatureName,
@@ -71,6 +72,11 @@
   if (enable_manifests_value == "true")
     config->is_manifests_enabled = true;
 
+  bool enable_origin_learning = base::GetFieldTrialParamValueByFeature(
+                                    kSpeculativeResourcePrefetchingFeature,
+                                    kEnableOriginLearningParamName) == "true";
+  config->is_origin_learning_enabled = enable_origin_learning;
+
   // Ensure that a resource that was only seen once is never prefetched. This
   // prevents the easy mistake of trying to prefetch an ephemeral url.
   DCHECK_GT(config->min_resource_hits_to_trigger_prefetch, 1U);
@@ -146,7 +152,7 @@
       max_prefetches_inflight_per_host_per_navigation(3),
       is_url_learning_enabled(false),
       is_manifests_enabled(false),
-      is_origin_prediction_enabled(false) {}
+      is_origin_learning_enabled(false) {}
 
 ResourcePrefetchPredictorConfig::ResourcePrefetchPredictorConfig(
     const ResourcePrefetchPredictorConfig& other) = default;
diff --git a/chrome/browser/predictors/resource_prefetch_common.h b/chrome/browser/predictors/resource_prefetch_common.h
index e13e6cc8..c430327 100644
--- a/chrome/browser/predictors/resource_prefetch_common.h
+++ b/chrome/browser/predictors/resource_prefetch_common.h
@@ -27,6 +27,7 @@
 extern const char kPrefetchingMode[];
 extern const char kEnableUrlLearningParamName[];
 extern const char kEnableManifestsParamName[];
+extern const char kEnableOriginLearningParamName[];
 extern const base::Feature kSpeculativeResourcePrefetchingFeature;
 
 struct ResourcePrefetchPredictorConfig;
@@ -135,8 +136,8 @@
   bool is_url_learning_enabled;
   // True iff the predictor could use manifests.
   bool is_manifests_enabled;
-  // True iff the origin-based prediction is enabled.
-  bool is_origin_prediction_enabled;
+  // True iff origin-based learning is enabled.
+  bool is_origin_learning_enabled;
 };
 
 }  // namespace predictors
diff --git a/chrome/browser/predictors/resource_prefetch_common_unittest.cc b/chrome/browser/predictors/resource_prefetch_common_unittest.cc
index 56e1203..643d3d0 100644
--- a/chrome/browser/predictors/resource_prefetch_common_unittest.cc
+++ b/chrome/browser/predictors/resource_prefetch_common_unittest.cc
@@ -73,7 +73,7 @@
     EXPECT_FALSE(config.IsSmallDBEnabledForTest());
     EXPECT_FALSE(config.is_url_learning_enabled);
     EXPECT_FALSE(config.is_manifests_enabled);
-    EXPECT_FALSE(config.is_origin_prediction_enabled);
+    EXPECT_FALSE(config.is_origin_learning_enabled);
     EXPECT_GT(config.min_resource_hits_to_trigger_prefetch, 1U);
   }
 
@@ -162,6 +162,19 @@
   EXPECT_TRUE(config.is_manifests_enabled);
 }
 
+TEST_F(ResourcePrefetchCommonTest, EnableOriginLearning) {
+  variations::testing::VariationParamsManager params_manager(
+      "dummy-trial",
+      {{kModeParamName, kLearningMode},
+       {kEnableOriginLearningParamName, "true"}},
+      {kSpeculativeResourcePrefetchingFeatureName});
+
+  ResourcePrefetchPredictorConfig config;
+  EXPECT_TRUE(IsSpeculativeResourcePrefetchingEnabled(profile_.get(), &config));
+  TestIsPrefetchLearning(config);
+  EXPECT_TRUE(config.is_origin_learning_enabled);
+}
+
 // Verifies whether prefetching is disabled according to the network type. But
 // learning should not be disabled by network.
 TEST_F(ResourcePrefetchCommonTest, RespectsNetworkSettings) {
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.cc b/chrome/browser/predictors/resource_prefetch_predictor.cc
index 76719ae..5bc03cb0 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor.cc
@@ -804,7 +804,7 @@
   if (!response.is_no_store)
     page_request_summary.subresource_requests.push_back(response);
 
-  if (config_.is_origin_prediction_enabled)
+  if (config_.is_origin_learning_enabled)
     UpdateOrAddToOrigins(&page_request_summary.origins, response);
 }
 
@@ -813,7 +813,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK_EQ(INITIALIZED, initialization_state_);
 
-  if (!config_.is_origin_prediction_enabled)
+  if (!config_.is_origin_learning_enabled)
     return;
 
   NavigationMap::const_iterator nav_it =
@@ -1228,7 +1228,7 @@
                   config_.max_hosts_to_track, host_table_cache_.get(),
                   summary.initial_url.host(), host_redirect_table_cache_.get());
 
-  if (config_.is_origin_prediction_enabled) {
+  if (config_.is_origin_learning_enabled) {
     LearnOrigins(host, summary.origins, config_.max_hosts_to_track,
                  origin_table_cache_.get());
   }
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
index 71ed0e3..a67e7da 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
@@ -241,7 +241,7 @@
     config.min_resource_confidence_to_trigger_prefetch = 0.5;
     config.is_url_learning_enabled = true;
     config.is_manifests_enabled = true;
-    config.is_origin_prediction_enabled = true;
+    config.is_origin_learning_enabled = true;
 
     config.mode |= ResourcePrefetchPredictorConfig::LEARNING;
     predictor_.reset(new ResourcePrefetchPredictor(config, profile_.get()));
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 623c2af..126c287 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -606,6 +606,7 @@
 
 #if defined(OS_CHROMEOS)
   arc::ArcSessionManager::RegisterProfilePrefs(registry);
+  arc::ArcPolicyBridge::RegisterProfilePrefs(registry);
   chromeos::first_run::RegisterProfilePrefs(registry);
   chromeos::file_system_provider::RegisterProfilePrefs(registry);
   chromeos::KeyPermissions::RegisterProfilePrefs(registry);
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index f661e89..c850042c 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -36,6 +36,9 @@
 // utility methods (IsArcPlayStoreEnabledForProfile() and
 // SetArcPlayStoreEnabledForProfile()) in chrome/browser/chromeos/arc/arc_util.
 const char kArcEnabled[] = "arc.enabled";
+// A preference that indicated whether Android reported it's compliance status
+// with provided policies. This is used only as a signal to start Android kiosk.
+const char kArcPolicyComplianceReported[] = "arc.policy_compliance_reported";
 // A preference that indicates that user accepted PlayStore terms.
 const char kArcTermsAccepted[] = "arc.terms.accepted";
 // A preference to keep user's consent to use location service.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index d26ea9d..8b7f3ed 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -25,6 +25,7 @@
 extern const char kArcBackupRestoreEnabled[];
 extern const char kArcDataRemoveRequested[];
 extern const char kArcEnabled[];
+extern const char kArcPolicyComplianceReported[];
 extern const char kArcTermsAccepted[];
 extern const char kArcLocationServiceEnabled[];
 extern const char kArcPackages[];
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 86b774e33..b0ac5a3 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -216,6 +216,7 @@
     "intent_helper/link_handler_model_impl_unittest.cc",
     "intent_helper/local_activity_resolver_unittest.cc",
     "intent_helper/page_transition_util_unittest.cc",
+    "kiosk/arc_kiosk_bridge_unittest.cc",
   ]
 
   deps = [
@@ -225,6 +226,7 @@
     "//chromeos",
     "//device/bluetooth",
     "//mojo/public/cpp/system:system",
+    "//testing/gmock",
     "//testing/gtest",
     "//ui/aura",
     "//ui/aura:test_support",
diff --git a/components/arc/kiosk/arc_kiosk_bridge_unittest.cc b/components/arc/kiosk/arc_kiosk_bridge_unittest.cc
new file mode 100644
index 0000000..b2243f7c
--- /dev/null
+++ b/components/arc/kiosk/arc_kiosk_bridge_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "components/arc/arc_bridge_service.h"
+#include "components/arc/kiosk/arc_kiosk_bridge.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class MockArcKioskBridgeDelegate : public arc::ArcKioskBridge::Delegate {
+ public:
+  MockArcKioskBridgeDelegate() = default;
+
+  MOCK_METHOD0(OnMaintenanceSessionCreated, void());
+  MOCK_METHOD0(OnMaintenanceSessionFinished, void());
+};
+
+}  // namespace
+
+namespace arc {
+
+class ArcKioskBridgeTest : public testing::Test {
+ public:
+  ArcKioskBridgeTest()
+      : bridge_service_(base::MakeUnique<ArcBridgeService>()),
+        delegate_(base::MakeUnique<MockArcKioskBridgeDelegate>()),
+        kiosk_bridge_(base::MakeUnique<ArcKioskBridge>(bridge_service_.get(),
+                                                       delegate_.get())) {}
+
+ protected:
+  std::unique_ptr<ArcBridgeService> bridge_service_;
+  std::unique_ptr<MockArcKioskBridgeDelegate> delegate_;
+  std::unique_ptr<ArcKioskBridge> kiosk_bridge_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ArcKioskBridgeTest);
+};
+
+TEST_F(ArcKioskBridgeTest, MaintenanceSessionFinished) {
+  EXPECT_CALL(*delegate_, OnMaintenanceSessionCreated()).Times(1);
+  kiosk_bridge_->OnMaintenanceSessionCreated(1);
+  EXPECT_CALL(*delegate_, OnMaintenanceSessionFinished()).Times(1);
+  kiosk_bridge_->OnMaintenanceSessionFinished(1, true);
+}
+
+TEST_F(ArcKioskBridgeTest, MaintenanceSessionNotFinished) {
+  EXPECT_CALL(*delegate_, OnMaintenanceSessionCreated()).Times(1);
+  kiosk_bridge_->OnMaintenanceSessionCreated(1);
+  EXPECT_CALL(*delegate_, OnMaintenanceSessionFinished()).Times(0);
+  kiosk_bridge_->OnMaintenanceSessionFinished(2, true);
+}
+
+}  // namespace arc
diff --git a/components/ntp_snippets/remote/remote_suggestions_scheduler_impl.cc b/components/ntp_snippets/remote/remote_suggestions_scheduler_impl.cc
index 699c3b5a..e83531a6 100644
--- a/components/ntp_snippets/remote/remote_suggestions_scheduler_impl.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_scheduler_impl.cc
@@ -174,8 +174,10 @@
 
   // EulaAcceptedNotifier::Observer implementation.
   void OnEulaAccepted() override {
-    // Emulate a browser foregrounded event.
-    scheduler_->OnBrowserForegrounded();
+    // Emulate a persistent fetch - we really want to fetch, initially!
+    // TODO(jkrcal): Find a cleaner solution. This is somewhat hacky and can
+    // mess up with metrics.
+    scheduler_->OnPersistentSchedulerWakeUp();
   }
 
  private:
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 731f573..ef4dfc9 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1196,14 +1196,10 @@
     "renderer_host/media/video_capture_provider.h",
     "renderer_host/native_web_keyboard_event_aura.cc",
     "renderer_host/native_web_keyboard_event_mac.mm",
-    "renderer_host/offscreen_canvas_compositor_frame_sink.cc",
-    "renderer_host/offscreen_canvas_compositor_frame_sink.h",
     "renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc",
     "renderer_host/offscreen_canvas_compositor_frame_sink_manager.h",
-    "renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc",
-    "renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h",
-    "renderer_host/offscreen_canvas_surface_factory_impl.cc",
-    "renderer_host/offscreen_canvas_surface_factory_impl.h",
+    "renderer_host/offscreen_canvas_provider_impl.cc",
+    "renderer_host/offscreen_canvas_provider_impl.h",
     "renderer_host/offscreen_canvas_surface_impl.cc",
     "renderer_host/offscreen_canvas_surface_impl.h",
     "renderer_host/overscroll_configuration.cc",
diff --git a/content/browser/renderer_host/media/audio_output_delegate_impl.cc b/content/browser/renderer_host/media/audio_output_delegate_impl.cc
index 84aecef..43df0830 100644
--- a/content/browser/renderer_host/media/audio_output_delegate_impl.cc
+++ b/content/browser/renderer_host/media/audio_output_delegate_impl.cc
@@ -171,7 +171,7 @@
 void AudioOutputDelegateImpl::SendCreatedNotification() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   subscriber_->OnStreamCreated(stream_id_, reader_->shared_memory(),
-                               reader_->foreign_socket());
+                               reader_->TakeForeignSocket());
 }
 
 void AudioOutputDelegateImpl::UpdatePlayingState(bool playing) {
diff --git a/content/browser/renderer_host/media/audio_output_delegate_impl_unittest.cc b/content/browser/renderer_host/media/audio_output_delegate_impl_unittest.cc
index 355cdce4..a3d09fb8 100644
--- a/content/browser/renderer_host/media/audio_output_delegate_impl_unittest.cc
+++ b/content/browser/renderer_host/media/audio_output_delegate_impl_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/sync_socket.h"
 #include "content/browser/audio_manager_thread.h"
 #include "content/browser/media/capture/audio_mirroring_manager.h"
 #include "content/public/browser/browser_thread.h"
@@ -71,10 +72,16 @@
 
 class MockEventHandler : public media::AudioOutputDelegate::EventHandler {
  public:
-  MOCK_METHOD3(OnStreamCreated,
-               void(int stream_id,
-                    base::SharedMemory* shared_memory,
-                    base::CancelableSyncSocket* socket));
+  void OnStreamCreated(int stream_id,
+                       base::SharedMemory* shared_memory,
+                       std::unique_ptr<base::CancelableSyncSocket> socket) {
+    EXPECT_EQ(stream_id, kStreamId);
+    EXPECT_NE(shared_memory, nullptr);
+    EXPECT_NE(socket.get(), nullptr);
+    GotOnStreamCreated();
+  }
+
+  MOCK_METHOD0(GotOnStreamCreated, void());
   MOCK_METHOD1(OnStreamError, void(int stream_id));
 };
 
@@ -114,8 +121,7 @@
   void CreateTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -139,8 +145,7 @@
   void PlayTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -166,8 +171,7 @@
   void PauseTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -193,8 +197,7 @@
   void PlayPausePlayTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -222,8 +225,7 @@
   void PlayPlayTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -250,8 +252,7 @@
   void CreateDivertTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -278,8 +279,7 @@
   void CreateDivertPauseTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -309,8 +309,7 @@
   void PlayDivertTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
 
@@ -338,8 +337,7 @@
   void ErrorTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(event_handler_, OnStreamError(kStreamId));
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
@@ -386,8 +384,7 @@
   void PlayAndDestroyTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
     EXPECT_CALL(mirroring_manager_, RemoveDiverter(NotNull()));
@@ -412,8 +409,7 @@
   void ErrorAndDestroyTest(base::Closure done) {
     EXPECT_CALL(media_observer_,
                 OnCreatingAudioStream(kRenderProcessId, kRenderFrameId));
-    EXPECT_CALL(event_handler_,
-                OnStreamCreated(kStreamId, NotNull(), NotNull()));
+    EXPECT_CALL(event_handler_, GotOnStreamCreated());
     EXPECT_CALL(mirroring_manager_,
                 AddDiverter(kRenderProcessId, kRenderFrameId, NotNull()));
     EXPECT_CALL(mirroring_manager_, RemoveDiverter(NotNull()));
diff --git a/content/browser/renderer_host/media/audio_renderer_host.cc b/content/browser/renderer_host/media/audio_renderer_host.cc
index 2c79886d..5579d05 100644
--- a/content/browser/renderer_host/media/audio_renderer_host.cc
+++ b/content/browser/renderer_host/media/audio_renderer_host.cc
@@ -107,7 +107,7 @@
 void AudioRendererHost::OnStreamCreated(
     int stream_id,
     base::SharedMemory* shared_memory,
-    base::CancelableSyncSocket* foreign_socket) {
+    std::unique_ptr<base::CancelableSyncSocket> foreign_socket) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   if (!PeerHandle()) {
diff --git a/content/browser/renderer_host/media/audio_renderer_host.h b/content/browser/renderer_host/media/audio_renderer_host.h
index 5e4ce65c..eb3c90f 100644
--- a/content/browser/renderer_host/media/audio_renderer_host.h
+++ b/content/browser/renderer_host/media/audio_renderer_host.h
@@ -91,9 +91,10 @@
   bool OnMessageReceived(const IPC::Message& message) override;
 
   // AudioOutputDelegate::EventHandler implementation
-  void OnStreamCreated(int stream_id,
-                       base::SharedMemory* shared_memory,
-                       base::CancelableSyncSocket* foreign_socket) override;
+  void OnStreamCreated(
+      int stream_id,
+      base::SharedMemory* shared_memory,
+      std::unique_ptr<base::CancelableSyncSocket> foreign_socket) override;
   void OnStreamError(int stream_id) override;
 
   void OverrideDevicePermissionsForTesting(bool has_access);
diff --git a/content/browser/renderer_host/media/audio_sync_reader.cc b/content/browser/renderer_host/media/audio_sync_reader.cc
index bbefb4bf..be6afd9 100644
--- a/content/browser/renderer_host/media/audio_sync_reader.cc
+++ b/content/browser/renderer_host/media/audio_sync_reader.cc
@@ -5,6 +5,7 @@
 #include "content/browser/renderer_host/media/audio_sync_reader.h"
 
 #include <algorithm>
+#include <limits>
 #include <string>
 #include <utility>
 
@@ -140,6 +141,12 @@
                                               std::move(foreign_socket)));
 }
 
+std::unique_ptr<base::CancelableSyncSocket>
+AudioSyncReader::TakeForeignSocket() {
+  DCHECK(foreign_socket_);
+  return std::move(foreign_socket_);
+}
+
 // media::AudioOutputController::SyncReader implementations.
 void AudioSyncReader::RequestMoreData(base::TimeDelta delay,
                                       base::TimeTicks delay_timestamp,
diff --git a/content/browser/renderer_host/media/audio_sync_reader.h b/content/browser/renderer_host/media/audio_sync_reader.h
index 29d1feb8..9cccff15 100644
--- a/content/browser/renderer_host/media/audio_sync_reader.h
+++ b/content/browser/renderer_host/media/audio_sync_reader.h
@@ -41,9 +41,8 @@
       const media::AudioParameters& params);
 
   base::SharedMemory* shared_memory() const { return shared_memory_.get(); }
-  base::CancelableSyncSocket* foreign_socket() const {
-    return foreign_socket_.get();
-  }
+
+  std::unique_ptr<base::CancelableSyncSocket> TakeForeignSocket();
 
   // media::AudioOutputController::SyncReader implementations.
   void RequestMoreData(base::TimeDelta delay,
diff --git a/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
index 2e3f8649..23f3e82a 100644
--- a/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
+++ b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
@@ -239,9 +239,11 @@
   base::SharedMemory shared_memory;
   ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(100));
 
-  base::CancelableSyncSocket local, remote;
-  ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&local, &remote));
-  event_handler->OnStreamCreated(kStreamId, &shared_memory, &remote);
+  auto local = base::MakeUnique<base::CancelableSyncSocket>();
+  auto remote = base::MakeUnique<base::CancelableSyncSocket>();
+  ASSERT_TRUE(
+      base::CancelableSyncSocket::CreatePair(local.get(), remote.get()));
+  event_handler->OnStreamCreated(kStreamId, &shared_memory, std::move(remote));
 
   base::RunLoop().RunUntilIdle();
   // Make sure we got the callback from creating stream.
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.cc
deleted file mode 100644
index 97f1fcf..0000000
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.h"
-
-#include "base/memory/ptr_util.h"
-#include "cc/surfaces/surface.h"
-#include "cc/surfaces/surface_manager.h"
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-
-namespace content {
-
-OffscreenCanvasCompositorFrameSink::OffscreenCanvasCompositorFrameSink(
-    OffscreenCanvasCompositorFrameSinkProviderImpl* provider,
-    const cc::FrameSinkId& frame_sink_id,
-    cc::mojom::MojoCompositorFrameSinkRequest request,
-    cc::mojom::MojoCompositorFrameSinkClientPtr client)
-    : provider_(provider),
-      support_(cc::CompositorFrameSinkSupport::Create(
-          this,
-          provider->GetSurfaceManager(),
-          frame_sink_id,
-          false /* is_root */,
-          true /* handles_frame_sink_id_invalidation */,
-          true /* needs_sync_points */)),
-      client_(std::move(client)),
-      binding_(this, std::move(request)) {
-  binding_.set_connection_error_handler(
-      base::Bind(&OffscreenCanvasCompositorFrameSink::OnClientConnectionLost,
-                 base::Unretained(this)));
-}
-
-OffscreenCanvasCompositorFrameSink::~OffscreenCanvasCompositorFrameSink() {
-  provider_->OnCompositorFrameSinkClientDestroyed(support_->frame_sink_id());
-}
-
-void OffscreenCanvasCompositorFrameSink::SetNeedsBeginFrame(
-    bool needs_begin_frame) {
-  support_->SetNeedsBeginFrame(needs_begin_frame);
-}
-
-void OffscreenCanvasCompositorFrameSink::SubmitCompositorFrame(
-    const cc::LocalSurfaceId& local_surface_id,
-    cc::CompositorFrame frame) {
-  // TODO(samans): This will need to do something similar to
-  // GpuCompositorFrameSink.
-  support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
-}
-
-void OffscreenCanvasCompositorFrameSink::BeginFrameDidNotSwap(
-    const cc::BeginFrameAck& begin_frame_ack) {
-  support_->BeginFrameDidNotSwap(begin_frame_ack);
-}
-
-void OffscreenCanvasCompositorFrameSink::EvictFrame() {
-  support_->EvictFrame();
-}
-
-void OffscreenCanvasCompositorFrameSink::DidReceiveCompositorFrameAck(
-    const cc::ReturnedResourceArray& resources) {
-  if (client_)
-    client_->DidReceiveCompositorFrameAck(resources);
-}
-
-void OffscreenCanvasCompositorFrameSink::OnBeginFrame(
-    const cc::BeginFrameArgs& args) {
-  if (client_)
-    client_->OnBeginFrame(args);
-}
-
-void OffscreenCanvasCompositorFrameSink::ReclaimResources(
-    const cc::ReturnedResourceArray& resources) {
-  if (client_)
-    client_->ReclaimResources(resources);
-}
-
-void OffscreenCanvasCompositorFrameSink::WillDrawSurface(
-    const cc::LocalSurfaceId& local_surface_id,
-    const gfx::Rect& damage_rect) {}
-
-void OffscreenCanvasCompositorFrameSink::OnClientConnectionLost() {
-  provider_->OnCompositorFrameSinkClientConnectionLost(
-      support_->frame_sink_id());
-}
-
-}  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.h b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.h
deleted file mode 100644
index 3110b32b..0000000
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_H_
-#define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_H_
-
-#include "cc/ipc/compositor_frame.mojom.h"
-#include "cc/ipc/mojo_compositor_frame_sink.mojom.h"
-#include "cc/resources/transferable_resource.h"
-#include "cc/surfaces/compositor_frame_sink_support.h"
-#include "cc/surfaces/compositor_frame_sink_support_client.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-
-namespace content {
-
-class OffscreenCanvasCompositorFrameSinkProviderImpl;
-
-class OffscreenCanvasCompositorFrameSink
-    : public cc::CompositorFrameSinkSupportClient,
-      public cc::mojom::MojoCompositorFrameSink {
- public:
-  OffscreenCanvasCompositorFrameSink(
-      OffscreenCanvasCompositorFrameSinkProviderImpl* provider,
-      const cc::FrameSinkId& frame_sink_id,
-      cc::mojom::MojoCompositorFrameSinkRequest request,
-      cc::mojom::MojoCompositorFrameSinkClientPtr client);
-
-  ~OffscreenCanvasCompositorFrameSink() override;
-
-  // Overridden from cc::mojom::MojoCompositorFrameSink:
-  void SetNeedsBeginFrame(bool needs_begin_frame) override;
-  void SubmitCompositorFrame(const cc::LocalSurfaceId& local_surface_id,
-                             cc::CompositorFrame frame) override;
-  void BeginFrameDidNotSwap(const cc::BeginFrameAck& begin_frame_ack) override;
-  void EvictFrame() override;
-
-  // Overridden from cc::CompositorFrameSinkSupportClient:
-  void DidReceiveCompositorFrameAck(
-      const cc::ReturnedResourceArray& resources) override;
-  void OnBeginFrame(const cc::BeginFrameArgs& args) override;
-  void ReclaimResources(const cc::ReturnedResourceArray& resources) override;
-  void WillDrawSurface(const cc::LocalSurfaceId& local_surface_id,
-                       const gfx::Rect& damage_rect) override;
-
- private:
-  void OnClientConnectionLost();
-
-  OffscreenCanvasCompositorFrameSinkProviderImpl* const provider_;
-
-  std::unique_ptr<cc::CompositorFrameSinkSupport> support_;
-  cc::mojom::MojoCompositorFrameSinkClientPtr client_;
-  cc::ReturnedResourceArray surface_returned_resources_;
-  mojo::Binding<cc::mojom::MojoCompositorFrameSink> binding_;
-
-  DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasCompositorFrameSink);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_H_
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
index ef4cbcd..4a87ca3 100644
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
@@ -31,38 +31,14 @@
   return g_manager.Pointer();
 }
 
-void OffscreenCanvasCompositorFrameSinkManager::RegisterFrameSinkToParent(
-    const cc::FrameSinkId& child_frame_sink_id) {
-  auto surface_iter = registered_surface_instances_.find(child_frame_sink_id);
-  if (surface_iter == registered_surface_instances_.end())
-    return;
-  OffscreenCanvasSurfaceImpl* surfaceImpl = surface_iter->second;
-  if (surfaceImpl->parent_frame_sink_id().is_valid()) {
-    GetSurfaceManager()->RegisterFrameSinkHierarchy(
-        surfaceImpl->parent_frame_sink_id(), child_frame_sink_id);
-  }
-}
-
-void OffscreenCanvasCompositorFrameSinkManager::UnregisterFrameSinkFromParent(
-    const cc::FrameSinkId& child_frame_sink_id) {
-  auto surface_iter = registered_surface_instances_.find(child_frame_sink_id);
-  if (surface_iter == registered_surface_instances_.end())
-    return;
-  OffscreenCanvasSurfaceImpl* surfaceImpl = surface_iter->second;
-  if (surfaceImpl->parent_frame_sink_id().is_valid()) {
-    GetSurfaceManager()->UnregisterFrameSinkHierarchy(
-        surfaceImpl->parent_frame_sink_id(), child_frame_sink_id);
-  }
-}
-
 void OffscreenCanvasCompositorFrameSinkManager::OnSurfaceCreated(
     const cc::SurfaceInfo& surface_info) {
   auto surface_iter =
       registered_surface_instances_.find(surface_info.id().frame_sink_id());
   if (surface_iter == registered_surface_instances_.end())
     return;
-  OffscreenCanvasSurfaceImpl* surfaceImpl = surface_iter->second;
-  surfaceImpl->OnSurfaceCreated(surface_info);
+  OffscreenCanvasSurfaceImpl* surface_impl = surface_iter->second;
+  surface_impl->OnSurfaceCreated(surface_info);
 }
 
 void OffscreenCanvasCompositorFrameSinkManager::
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h
index 7cd9ea8..2d3a2599 100644
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h
@@ -20,12 +20,6 @@
 
   static OffscreenCanvasCompositorFrameSinkManager* GetInstance();
 
-  // Registration of the frame sink with the given frame sink id to its parent
-  // frame sink (if it has one), so that parent frame is able to send signals
-  // to it on begin frame.
-  void RegisterFrameSinkToParent(const cc::FrameSinkId& frame_sink_id);
-  void UnregisterFrameSinkFromParent(const cc::FrameSinkId& frame_sink_id);
-
   void RegisterOffscreenCanvasSurfaceInstance(
       const cc::FrameSinkId& frame_sink_id,
       OffscreenCanvasSurfaceImpl* offscreen_canvas_surface);
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc
deleted file mode 100644
index adef5ba..0000000
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h"
-
-#include "base/memory/ptr_util.h"
-#include "content/browser/compositor/surface_utils.h"
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.h"
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-
-namespace content {
-
-OffscreenCanvasCompositorFrameSinkProviderImpl::
-    OffscreenCanvasCompositorFrameSinkProviderImpl() {}
-
-OffscreenCanvasCompositorFrameSinkProviderImpl::
-    ~OffscreenCanvasCompositorFrameSinkProviderImpl() {}
-
-void OffscreenCanvasCompositorFrameSinkProviderImpl::Add(
-    blink::mojom::OffscreenCanvasCompositorFrameSinkProviderRequest request) {
-  bindings_.AddBinding(this, std::move(request));
-}
-
-void OffscreenCanvasCompositorFrameSinkProviderImpl::CreateCompositorFrameSink(
-    const cc::FrameSinkId& frame_sink_id,
-    cc::mojom::MojoCompositorFrameSinkClientPtr client,
-    cc::mojom::MojoCompositorFrameSinkRequest request) {
-  compositor_frame_sinks_[frame_sink_id] =
-      base::MakeUnique<OffscreenCanvasCompositorFrameSink>(
-          this, frame_sink_id, std::move(request), std::move(client));
-
-  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-      ->RegisterFrameSinkToParent(frame_sink_id);
-}
-
-cc::SurfaceManager*
-OffscreenCanvasCompositorFrameSinkProviderImpl::GetSurfaceManager() {
-  return content::GetSurfaceManager();
-}
-
-void OffscreenCanvasCompositorFrameSinkProviderImpl::
-    OnCompositorFrameSinkClientConnectionLost(
-        const cc::FrameSinkId& frame_sink_id) {
-  // TODO(fsamuel, xlai): Investigate why this function is not fired when user
-  // close down the window that has OffscreenCanvas commit().
-  compositor_frame_sinks_.erase(frame_sink_id);
-}
-
-void OffscreenCanvasCompositorFrameSinkProviderImpl::
-    OnCompositorFrameSinkClientDestroyed(const cc::FrameSinkId& frame_sink_id) {
-  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-      ->UnregisterFrameSinkFromParent(frame_sink_id);
-}
-
-}  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h
deleted file mode 100644
index 5da4335..0000000
--- a/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_PROVIDER_IMPL_H_
-#define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_PROVIDER_IMPL_H_
-
-#include <unordered_map>
-
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-#include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
-
-namespace content {
-
-// TODO(fsamuel): This should be replaced with the FrameSinkManager interface.
-class OffscreenCanvasCompositorFrameSinkProviderImpl
-    : public blink::mojom::OffscreenCanvasCompositorFrameSinkProvider {
- public:
-  OffscreenCanvasCompositorFrameSinkProviderImpl();
-  ~OffscreenCanvasCompositorFrameSinkProviderImpl() override;
-
-  void Add(
-      blink::mojom::OffscreenCanvasCompositorFrameSinkProviderRequest request);
-
-  // blink::mojom::OffscreenCanvasCompositorFrameSinkProvider implementation.
-  void CreateCompositorFrameSink(
-      const cc::FrameSinkId& frame_sink_id,
-      cc::mojom::MojoCompositorFrameSinkClientPtr client,
-      cc::mojom::MojoCompositorFrameSinkRequest request) override;
-
-  cc::SurfaceManager* GetSurfaceManager();
-
-  void OnCompositorFrameSinkClientConnectionLost(
-      const cc::FrameSinkId& frame_sink_id);
-  void OnCompositorFrameSinkClientDestroyed(
-      const cc::FrameSinkId& frame_sink_id);
-
- private:
-  std::unordered_map<cc::FrameSinkId,
-                     std::unique_ptr<OffscreenCanvasCompositorFrameSink>,
-                     cc::FrameSinkIdHash>
-      compositor_frame_sinks_;
-
-  mojo::BindingSet<blink::mojom::OffscreenCanvasCompositorFrameSinkProvider>
-      bindings_;
-
-  DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasCompositorFrameSinkProviderImpl);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_PROVIDER_IMPL_H_
diff --git a/content/browser/renderer_host/offscreen_canvas_provider_impl.cc b/content/browser/renderer_host/offscreen_canvas_provider_impl.cc
new file mode 100644
index 0000000..9d578ad
--- /dev/null
+++ b/content/browser/renderer_host/offscreen_canvas_provider_impl.cc
@@ -0,0 +1,47 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/renderer_host/offscreen_canvas_provider_impl.h"
+
+#include "content/browser/compositor/surface_utils.h"
+#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
+#include "content/browser/renderer_host/offscreen_canvas_surface_impl.h"
+
+namespace content {
+
+OffscreenCanvasProviderImpl::OffscreenCanvasProviderImpl() = default;
+
+OffscreenCanvasProviderImpl::~OffscreenCanvasProviderImpl() = default;
+
+void OffscreenCanvasProviderImpl::Add(
+    blink::mojom::OffscreenCanvasProviderRequest request) {
+  bindings_.AddBinding(this, std::move(request));
+}
+
+void OffscreenCanvasProviderImpl::CreateOffscreenCanvasSurface(
+    const cc::FrameSinkId& parent_frame_sink_id,
+    const cc::FrameSinkId& frame_sink_id,
+    cc::mojom::FrameSinkManagerClientPtr client,
+    blink::mojom::OffscreenCanvasSurfaceRequest request) {
+  OffscreenCanvasSurfaceImpl::Create(parent_frame_sink_id, frame_sink_id,
+                                     std::move(client), std::move(request));
+}
+
+void OffscreenCanvasProviderImpl::CreateCompositorFrameSink(
+    const cc::FrameSinkId& frame_sink_id,
+    cc::mojom::MojoCompositorFrameSinkClientPtr client,
+    cc::mojom::MojoCompositorFrameSinkRequest request) {
+  // TODO(kylechar): Add test for bad |frame_sink_id|.
+  auto* manager = OffscreenCanvasCompositorFrameSinkManager::GetInstance();
+  auto* surface_impl = manager->GetSurfaceInstance(frame_sink_id);
+  if (!surface_impl) {
+    DLOG(ERROR) << "No OffscreenCanvasSurfaceImpl for " << frame_sink_id;
+    return;
+  }
+
+  surface_impl->CreateCompositorFrameSink(std::move(client),
+                                          std::move(request));
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_provider_impl.h b/content/browser/renderer_host/offscreen_canvas_provider_impl.h
new file mode 100644
index 0000000..be2c52c
--- /dev/null
+++ b/content/browser/renderer_host/offscreen_canvas_provider_impl.h
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_PROVIDER_IMPL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_PROVIDER_IMPL_H_
+
+#include "cc/surfaces/frame_sink_id.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
+
+namespace content {
+
+// Creates OffscreenCanvasSurfaces and MojoCompositorFrameSinks for a renderer.
+class OffscreenCanvasProviderImpl
+    : public blink::mojom::OffscreenCanvasProvider {
+ public:
+  OffscreenCanvasProviderImpl();
+  ~OffscreenCanvasProviderImpl() override;
+
+  void Add(blink::mojom::OffscreenCanvasProviderRequest request);
+
+  // blink::mojom::OffscreenCanvasProvider implementation.
+  void CreateOffscreenCanvasSurface(
+      const cc::FrameSinkId& parent_frame_sink_id,
+      const cc::FrameSinkId& frame_sink_id,
+      cc::mojom::FrameSinkManagerClientPtr client,
+      blink::mojom::OffscreenCanvasSurfaceRequest request) override;
+  void CreateCompositorFrameSink(
+      const cc::FrameSinkId& frame_sink_id,
+      cc::mojom::MojoCompositorFrameSinkClientPtr client,
+      cc::mojom::MojoCompositorFrameSinkRequest request) override;
+
+ private:
+  mojo::BindingSet<blink::mojom::OffscreenCanvasProvider> bindings_;
+
+  DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasProviderImpl);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_PROVIDER_IMPL_H_
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.cc b/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.cc
deleted file mode 100644
index 8d101f1..0000000
--- a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h"
-
-#include "base/memory/ptr_util.h"
-#include "cc/surfaces/frame_sink_id.h"
-#include "content/browser/renderer_host/offscreen_canvas_surface_impl.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-
-namespace content {
-
-// static
-void OffscreenCanvasSurfaceFactoryImpl::Create(
-    blink::mojom::OffscreenCanvasSurfaceFactoryRequest request) {
-  mojo::MakeStrongBinding(base::MakeUnique<OffscreenCanvasSurfaceFactoryImpl>(),
-                          std::move(request));
-}
-
-void OffscreenCanvasSurfaceFactoryImpl::CreateOffscreenCanvasSurface(
-    const cc::FrameSinkId& parent_frame_sink_id,
-    const cc::FrameSinkId& frame_sink_id,
-    cc::mojom::FrameSinkManagerClientPtr client,
-    blink::mojom::OffscreenCanvasSurfaceRequest request) {
-  OffscreenCanvasSurfaceImpl::Create(parent_frame_sink_id, frame_sink_id,
-                                     std::move(client), std::move(request));
-}
-
-}  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h b/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h
deleted file mode 100644
index 879e001..0000000
--- a/content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_FACTORY_IMPL_H_
-#define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_FACTORY_IMPL_H_
-
-#include "cc/ipc/frame_sink_manager.mojom.h"
-#include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
-
-namespace content {
-
-class OffscreenCanvasSurfaceFactoryImpl
-    : public blink::mojom::OffscreenCanvasSurfaceFactory {
- public:
-  OffscreenCanvasSurfaceFactoryImpl() {}
-  ~OffscreenCanvasSurfaceFactoryImpl() override {}
-
-  static void Create(
-      blink::mojom::OffscreenCanvasSurfaceFactoryRequest request);
-
-  // blink::mojom::OffscreenCanvasSurfaceFactory implementation.
-  void CreateOffscreenCanvasSurface(
-      const cc::FrameSinkId& parent_frame_sink_id,
-      const cc::FrameSinkId& frame_sink_id,
-      cc::mojom::FrameSinkManagerClientPtr client,
-      blink::mojom::OffscreenCanvasSurfaceRequest request) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasSurfaceFactoryImpl);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_FACTORY_IMPL_H_
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
index dd9218c..48ea6864 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
+++ b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
@@ -8,11 +8,10 @@
 
 #include "base/bind_helpers.h"
 #include "base/memory/ptr_util.h"
-#include "cc/surfaces/surface.h"
 #include "cc/surfaces/surface_manager.h"
+#include "content/browser/compositor/frame_sink_manager_host.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
-#include "content/public/browser/browser_thread.h"
 
 namespace content {
 
@@ -28,10 +27,12 @@
 }
 
 OffscreenCanvasSurfaceImpl::~OffscreenCanvasSurfaceImpl() {
-  if (frame_sink_id_.is_valid()) {
-    OffscreenCanvasCompositorFrameSinkManager::GetInstance()
-        ->UnregisterOffscreenCanvasSurfaceInstance(frame_sink_id_);
+  if (has_created_compositor_frame_sink_) {
+    GetFrameSinkManagerHost()->UnregisterFrameSinkHierarchy(
+        parent_frame_sink_id_, frame_sink_id_);
   }
+  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
+      ->UnregisterOffscreenCanvasSurfaceInstance(frame_sink_id_);
 }
 
 // static
@@ -48,6 +49,23 @@
       mojo::MakeStrongBinding(std::move(impl), std::move(request));
 }
 
+void OffscreenCanvasSurfaceImpl::CreateCompositorFrameSink(
+    cc::mojom::MojoCompositorFrameSinkClientPtr client,
+    cc::mojom::MojoCompositorFrameSinkRequest request) {
+  if (has_created_compositor_frame_sink_) {
+    DLOG(ERROR) << "CreateCompositorFrameSink() called more than once.";
+    return;
+  }
+
+  GetFrameSinkManagerHost()->CreateCompositorFrameSink(
+      frame_sink_id_, std::move(request),
+      mojo::MakeRequest(&compositor_frame_sink_private_), std::move(client));
+
+  GetFrameSinkManagerHost()->RegisterFrameSinkHierarchy(parent_frame_sink_id_,
+                                                        frame_sink_id_);
+  has_created_compositor_frame_sink_ = true;
+}
+
 void OffscreenCanvasSurfaceImpl::OnSurfaceCreated(
     const cc::SurfaceInfo& surface_info) {
   DCHECK_EQ(surface_info.id().frame_sink_id(), frame_sink_id_);
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_impl.h b/content/browser/renderer_host/offscreen_canvas_surface_impl.h
index ab3bcb2..3f76a2b 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_impl.h
+++ b/content/browser/renderer_host/offscreen_canvas_surface_impl.h
@@ -7,12 +7,13 @@
 
 #include "cc/ipc/frame_sink_manager.mojom.h"
 #include "cc/surfaces/surface_id.h"
-#include "mojo/public/cpp/bindings/interface_request.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom.h"
 
 namespace content {
 
+// The browser owned object for an offscreen canvas connection. Holds
+// connections to both the renderer and frame sink manager.
 class CONTENT_EXPORT OffscreenCanvasSurfaceImpl
     : public blink::mojom::OffscreenCanvasSurface {
  public:
@@ -26,6 +27,14 @@
                      cc::mojom::FrameSinkManagerClientPtr client,
                      blink::mojom::OffscreenCanvasSurfaceRequest request);
 
+  // Creates a MojoCompositorFrameSink connection to FrameSinkManager for an
+  // offscreen canvas client. The corresponding private interface will be owned
+  // here to control CompositorFrameSink lifetime. This should only ever be
+  // called once.
+  void CreateCompositorFrameSink(
+      cc::mojom::MojoCompositorFrameSinkClientPtr client,
+      cc::mojom::MojoCompositorFrameSinkRequest request);
+
   void OnSurfaceCreated(const cc::SurfaceInfo& surface_info);
 
   // blink::mojom::OffscreenCanvasSurface implementation.
@@ -47,11 +56,18 @@
   cc::mojom::FrameSinkManagerClientPtr client_;
   mojo::StrongBindingPtr<blink::mojom::OffscreenCanvasSurface> binding_;
 
+  // Private connection for the CompositorFrameSink. The CompositorFrameSink
+  // will not be destroyed until both private and offscreen canvas client
+  // connections are closed.
+  cc::mojom::MojoCompositorFrameSinkPrivatePtr compositor_frame_sink_private_;
+
   // Surface-related state
   const cc::FrameSinkId frame_sink_id_;
   cc::LocalSurfaceId current_local_surface_id_;
   const cc::FrameSinkId parent_frame_sink_id_;
 
+  bool has_created_compositor_frame_sink_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasSurfaceImpl);
 };
 
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 05f7cfe..9986ff6 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -109,8 +109,7 @@
 #include "content/browser/renderer_host/media/media_stream_dispatcher_host.h"
 #include "content/browser/renderer_host/media/peer_connection_tracker_host.h"
 #include "content/browser/renderer_host/media/video_capture_host.h"
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h"
-#include "content/browser/renderer_host/offscreen_canvas_surface_factory_impl.h"
+#include "content/browser/renderer_host/offscreen_canvas_provider_impl.h"
 #include "content/browser/renderer_host/pepper/pepper_message_filter.h"
 #include "content/browser/renderer_host/pepper/pepper_renderer_connection.h"
 #include "content/browser/renderer_host/render_message_filter.h"
@@ -1238,16 +1237,13 @@
 
   AddUIThreadInterface(
       registry.get(),
-      base::Bind(&RenderProcessHostImpl::
-                     CreateOffscreenCanvasCompositorFrameSinkProvider,
+      base::Bind(&RenderProcessHostImpl::CreateOffscreenCanvasProvider,
                  base::Unretained(this)));
 
   AddUIThreadInterface(registry.get(),
                        base::Bind(&RenderProcessHostImpl::BindFrameSinkProvider,
                                   base::Unretained(this)));
 
-  AddUIThreadInterface(registry.get(),
-                       base::Bind(&OffscreenCanvasSurfaceFactoryImpl::Create));
   AddUIThreadInterface(
       registry.get(),
       base::Bind(&BackgroundSyncContext::CreateService,
@@ -1363,12 +1359,12 @@
   gpu_client_->Add(std::move(request));
 }
 
-void RenderProcessHostImpl::CreateOffscreenCanvasCompositorFrameSinkProvider(
-    blink::mojom::OffscreenCanvasCompositorFrameSinkProviderRequest request) {
+void RenderProcessHostImpl::CreateOffscreenCanvasProvider(
+    blink::mojom::OffscreenCanvasProviderRequest request) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!offscreen_canvas_provider_) {
-    offscreen_canvas_provider_.reset(
-        new OffscreenCanvasCompositorFrameSinkProviderImpl());
+    offscreen_canvas_provider_ =
+        base::MakeUnique<OffscreenCanvasProviderImpl>();
   }
   offscreen_canvas_provider_->Add(std::move(request));
 }
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index 7ef0f6a..aa6898d 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -24,7 +24,7 @@
 #include "content/browser/child_process_launcher.h"
 #include "content/browser/dom_storage/session_storage_namespace_impl.h"
 #include "content/browser/renderer_host/frame_sink_provider_impl.h"
-#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_provider_impl.h"
+#include "content/browser/renderer_host/offscreen_canvas_provider_impl.h"
 #include "content/browser/webrtc/webrtc_eventlog_host.h"
 #include "content/common/associated_interfaces.mojom.h"
 #include "content/common/content_export.h"
@@ -347,8 +347,8 @@
       mojom::AssociatedInterfaceAssociatedRequest request) override;
 
   void CreateMusGpuRequest(ui::mojom::GpuRequest request);
-  void CreateOffscreenCanvasCompositorFrameSinkProvider(
-      blink::mojom::OffscreenCanvasCompositorFrameSinkProviderRequest request);
+  void CreateOffscreenCanvasProvider(
+      blink::mojom::OffscreenCanvasProviderRequest request);
   void BindFrameSinkProvider(mojom::FrameSinkProviderRequest request);
   void CreateStoragePartitionService(
       mojo::InterfaceRequest<mojom::StoragePartitionService> request);
@@ -606,8 +606,7 @@
   std::unique_ptr<PushMessagingManager, BrowserThread::DeleteOnIOThread>
       push_messaging_manager_;
 
-  std::unique_ptr<OffscreenCanvasCompositorFrameSinkProviderImpl>
-      offscreen_canvas_provider_;
+  std::unique_ptr<OffscreenCanvasProviderImpl> offscreen_canvas_provider_;
 
   mojom::RouteProviderAssociatedPtr remote_route_provider_;
   mojom::RendererAssociatedPtr renderer_interface_;
diff --git a/content/public/app/mojo/content_browser_manifest.json b/content/public/app/mojo/content_browser_manifest.json
index 0fedcfd..8100d6d 100644
--- a/content/public/app/mojo/content_browser_manifest.json
+++ b/content/public/app/mojo/content_browser_manifest.json
@@ -21,8 +21,7 @@
           "blink::mojom::Hyphenation",
           "blink::mojom::MimeRegistry",
           "blink::mojom::NotificationService",
-          "blink::mojom::OffscreenCanvasCompositorFrameSinkProvider",
-          "blink::mojom::OffscreenCanvasSurfaceFactory",
+          "blink::mojom::OffscreenCanvasProvider",
           "blink::mojom::PermissionService",
           "blink::mojom::WebSocket",
           "content::mojom::FieldTrialRecorder",
diff --git a/gpu/ipc/client/gpu_memory_buffer_impl_native_pixmap.cc b/gpu/ipc/client/gpu_memory_buffer_impl_native_pixmap.cc
index c4d900f4..12b1da6 100644
--- a/gpu/ipc/client/gpu_memory_buffer_impl_native_pixmap.cc
+++ b/gpu/ipc/client/gpu_memory_buffer_impl_native_pixmap.cc
@@ -84,9 +84,6 @@
 bool GpuMemoryBufferImplNativePixmap::IsConfigurationSupported(
     gfx::BufferFormat format,
     gfx::BufferUsage usage) {
-#if defined(OS_LINUX)
-  return false;
-#endif
   return gpu::IsNativeGpuMemoryBufferConfigurationSupported(format, usage);
 }
 
diff --git a/gpu/ipc/common/gpu_memory_buffer_support.cc b/gpu/ipc/common/gpu_memory_buffer_support.cc
index 73ac6b6..77f98359 100644
--- a/gpu/ipc/common/gpu_memory_buffer_support.cc
+++ b/gpu/ipc/common/gpu_memory_buffer_support.cc
@@ -7,8 +7,8 @@
 #include "base/logging.h"
 #include "build/build_config.h"
 
-#if defined(USE_OZONE)
-#include "ui/ozone/public/client_native_pixmap_factory_ozone.h"
+#if defined(OS_LINUX)
+#include "ui/gfx/client_native_pixmap_factory.h"
 #endif
 
 namespace gpu {
@@ -17,7 +17,7 @@
 #if defined(OS_MACOSX)
   return gfx::IO_SURFACE_BUFFER;
 #endif
-#if defined(USE_OZONE)
+#if defined(OS_LINUX)
   return gfx::NATIVE_PIXMAP;
 #endif
   return gfx::EMPTY_BUFFER;
@@ -47,7 +47,7 @@
   return false;
 #endif
 
-#if defined(USE_OZONE)
+#if defined(OS_LINUX)
   if (!gfx::ClientNativePixmapFactory::GetInstance()) {
     // unittests don't have to set ClientNativePixmapFactory.
     return false;
diff --git a/gpu/ipc/service/BUILD.gn b/gpu/ipc/service/BUILD.gn
index b0e1540..abec3f8b 100644
--- a/gpu/ipc/service/BUILD.gn
+++ b/gpu/ipc/service/BUILD.gn
@@ -112,16 +112,16 @@
     libs += [ "android" ]
   }
   if (is_linux) {
-    sources += [ "image_transport_surface_linux.cc" ]
+    sources += [
+      "gpu_memory_buffer_factory_native_pixmap.cc",
+      "gpu_memory_buffer_factory_native_pixmap.h",
+      "image_transport_surface_linux.cc",
+    ]
   }
   if (use_x11) {
     sources += [ "x_util.h" ]
   }
   if (use_ozone) {
-    sources += [
-      "gpu_memory_buffer_factory_ozone_native_pixmap.cc",
-      "gpu_memory_buffer_factory_ozone_native_pixmap.h",
-    ]
     deps += [ "//ui/ozone" ]
   }
 }
@@ -177,8 +177,10 @@
   if (is_mac) {
     sources += [ "gpu_memory_buffer_factory_io_surface_unittest.cc" ]
   }
+  if (is_linux) {
+    sources += [ "gpu_memory_buffer_factory_native_pixmap_unittest.cc" ]
+  }
   if (use_ozone) {
-    sources += [ "gpu_memory_buffer_factory_ozone_native_pixmap_unittest.cc" ]
     deps += [ "//ui/ozone" ]
   }
 }
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory.cc b/gpu/ipc/service/gpu_memory_buffer_factory.cc
index 3204827..8e179a4 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory.cc
+++ b/gpu/ipc/service/gpu_memory_buffer_factory.cc
@@ -12,8 +12,8 @@
 #include "gpu/ipc/service/gpu_memory_buffer_factory_io_surface.h"
 #endif
 
-#if defined(USE_OZONE)
-#include "gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.h"
+#if defined(OS_LINUX)
+#include "gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h"
 #endif
 
 namespace gpu {
@@ -24,8 +24,8 @@
 #if defined(OS_MACOSX)
   return base::WrapUnique(new GpuMemoryBufferFactoryIOSurface);
 #endif
-#if defined(USE_OZONE)
-  return base::WrapUnique(new GpuMemoryBufferFactoryOzoneNativePixmap);
+#if defined(OS_LINUX)
+  return base::WrapUnique(new GpuMemoryBufferFactoryNativePixmap);
 #endif
   return nullptr;
 }
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.cc b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
similarity index 75%
rename from gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.cc
rename to gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
index 955f0abb..cc08eb86 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.cc
+++ b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
@@ -2,31 +2,32 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.h"
+#include "gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h"
 
 #include "ui/gfx/client_native_pixmap.h"
 #include "ui/gfx/native_pixmap.h"
 #include "ui/gl/gl_image_native_pixmap.h"
-#include "ui/ozone/public/client_native_pixmap_factory_ozone.h"
+
+#if defined(USE_OZONE)
 #include "ui/ozone/public/ozone_platform.h"
 #include "ui/ozone/public/surface_factory_ozone.h"
+#endif
 
 namespace gpu {
 
-GpuMemoryBufferFactoryOzoneNativePixmap::
-    GpuMemoryBufferFactoryOzoneNativePixmap() {}
+GpuMemoryBufferFactoryNativePixmap::GpuMemoryBufferFactoryNativePixmap() {}
 
-GpuMemoryBufferFactoryOzoneNativePixmap::
-    ~GpuMemoryBufferFactoryOzoneNativePixmap() {}
+GpuMemoryBufferFactoryNativePixmap::~GpuMemoryBufferFactoryNativePixmap() {}
 
 gfx::GpuMemoryBufferHandle
-GpuMemoryBufferFactoryOzoneNativePixmap::CreateGpuMemoryBuffer(
+GpuMemoryBufferFactoryNativePixmap::CreateGpuMemoryBuffer(
     gfx::GpuMemoryBufferId id,
     const gfx::Size& size,
     gfx::BufferFormat format,
     gfx::BufferUsage usage,
     int client_id,
     SurfaceHandle surface_handle) {
+#if defined(USE_OZONE)
   scoped_refptr<gfx::NativePixmap> pixmap =
       ui::OzonePlatform::GetInstance()
           ->GetSurfaceFactoryOzone()
@@ -52,9 +53,13 @@
   }
 
   return new_handle;
+#else
+  NOTIMPLEMENTED();
+  return gfx::GpuMemoryBufferHandle();
+#endif
 }
 
-void GpuMemoryBufferFactoryOzoneNativePixmap::DestroyGpuMemoryBuffer(
+void GpuMemoryBufferFactoryNativePixmap::DestroyGpuMemoryBuffer(
     gfx::GpuMemoryBufferId id,
     int client_id) {
   base::AutoLock lock(native_pixmaps_lock_);
@@ -62,12 +67,12 @@
   native_pixmaps_.erase(key);
 }
 
-ImageFactory* GpuMemoryBufferFactoryOzoneNativePixmap::AsImageFactory() {
+ImageFactory* GpuMemoryBufferFactoryNativePixmap::AsImageFactory() {
   return this;
 }
 
 scoped_refptr<gl::GLImage>
-GpuMemoryBufferFactoryOzoneNativePixmap::CreateImageForGpuMemoryBuffer(
+GpuMemoryBufferFactoryNativePixmap::CreateImageForGpuMemoryBuffer(
     const gfx::GpuMemoryBufferHandle& handle,
     const gfx::Size& size,
     gfx::BufferFormat format,
@@ -90,10 +95,16 @@
 
   // Create new pixmap from handle if one doesn't already exist.
   if (!pixmap) {
+#if defined(USE_OZONE)
     pixmap = ui::OzonePlatform::GetInstance()
                  ->GetSurfaceFactoryOzone()
                  ->CreateNativePixmapFromHandle(surface_handle, size, format,
                                                 handle.native_pixmap_handle);
+#else
+    // TODO(j.isorce): implement this to enable glCreateImageCHROMIUM on Linux.
+    // On going in http://codereview.chromium.org/2705213005, crbug.com/584248.
+    NOTIMPLEMENTED();
+#endif
     if (!pixmap.get()) {
       DLOG(ERROR) << "Failed to create pixmap from handle";
       return nullptr;
@@ -117,15 +128,19 @@
 }
 
 scoped_refptr<gl::GLImage>
-GpuMemoryBufferFactoryOzoneNativePixmap::CreateAnonymousImage(
+GpuMemoryBufferFactoryNativePixmap::CreateAnonymousImage(
     const gfx::Size& size,
     gfx::BufferFormat format,
     unsigned internalformat) {
-  scoped_refptr<gfx::NativePixmap> pixmap =
-      ui::OzonePlatform::GetInstance()
-          ->GetSurfaceFactoryOzone()
-          ->CreateNativePixmap(gpu::kNullSurfaceHandle, size, format,
-                               gfx::BufferUsage::SCANOUT);
+  scoped_refptr<gfx::NativePixmap> pixmap;
+#if defined(USE_OZONE)
+  pixmap = ui::OzonePlatform::GetInstance()
+               ->GetSurfaceFactoryOzone()
+               ->CreateNativePixmap(gpu::kNullSurfaceHandle, size, format,
+                                    gfx::BufferUsage::SCANOUT);
+#else
+  NOTIMPLEMENTED();
+#endif
   if (!pixmap.get()) {
     LOG(ERROR) << "Failed to create pixmap " << size.ToString() << " format "
                << static_cast<int>(format);
@@ -141,7 +156,7 @@
   return image;
 }
 
-unsigned GpuMemoryBufferFactoryOzoneNativePixmap::RequiredTextureType() {
+unsigned GpuMemoryBufferFactoryNativePixmap::RequiredTextureType() {
   return GL_TEXTURE_EXTERNAL_OES;
 }
 
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.h b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h
similarity index 81%
rename from gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.h
rename to gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h
index 9bb4db7..463d0682 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.h
+++ b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.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 GPU_IPC_SERVICE_GPU_MEMORY_BUFFER_FACTORY_OZONE_NATIVE_PIXMAP_H_
-#define GPU_IPC_SERVICE_GPU_MEMORY_BUFFER_FACTORY_OZONE_NATIVE_PIXMAP_H_
+#ifndef GPU_IPC_SERVICE_GPU_MEMORY_BUFFER_FACTORY_NATIVE_PIXMAP_H_
+#define GPU_IPC_SERVICE_GPU_MEMORY_BUFFER_FACTORY_NATIVE_PIXMAP_H_
 
 #include <unordered_map>
 #include <utility>
@@ -22,12 +22,12 @@
 
 namespace gpu {
 
-class GPU_EXPORT GpuMemoryBufferFactoryOzoneNativePixmap
+class GPU_EXPORT GpuMemoryBufferFactoryNativePixmap
     : public GpuMemoryBufferFactory,
       public ImageFactory {
  public:
-  GpuMemoryBufferFactoryOzoneNativePixmap();
-  ~GpuMemoryBufferFactoryOzoneNativePixmap() override;
+  GpuMemoryBufferFactoryNativePixmap();
+  ~GpuMemoryBufferFactoryNativePixmap() override;
 
   // Overridden from GpuMemoryBufferFactory:
   gfx::GpuMemoryBufferHandle CreateGpuMemoryBuffer(
@@ -64,9 +64,9 @@
   NativePixmapMap native_pixmaps_;
   base::Lock native_pixmaps_lock_;
 
-  DISALLOW_COPY_AND_ASSIGN(GpuMemoryBufferFactoryOzoneNativePixmap);
+  DISALLOW_COPY_AND_ASSIGN(GpuMemoryBufferFactoryNativePixmap);
 };
 
 }  // namespace gpu
 
-#endif  // GPU_IPC_SERVICE_GPU_MEMORY_BUFFER_FACTORY_OZONE_NATIVE_PIXMAP_H_
+#endif  // GPU_IPC_SERVICE_GPU_MEMORY_BUFFER_FACTORY_NATIVE_PIXMAP_H_
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap_unittest.cc b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap_unittest.cc
similarity index 62%
rename from gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap_unittest.cc
rename to gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap_unittest.cc
index 7fcf626..2c381184 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap_unittest.cc
+++ b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap_unittest.cc
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "gpu/ipc/service/gpu_memory_buffer_factory_ozone_native_pixmap.h"
+#include "gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h"
 #include "gpu/ipc/service/gpu_memory_buffer_factory_test_template.h"
 
 namespace gpu {
 namespace {
 
-INSTANTIATE_TYPED_TEST_CASE_P(GpuMemoryBufferFactoryOzoneNativePixmap,
+INSTANTIATE_TYPED_TEST_CASE_P(GpuMemoryBufferFactoryNativePixmap,
                               GpuMemoryBufferFactoryTest,
-                              GpuMemoryBufferFactoryOzoneNativePixmap);
+                              GpuMemoryBufferFactoryNativePixmap);
 
 }  // namespace
 }  // namespace gpu
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index ea8089c..67dcbb4 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -438,7 +438,6 @@
     "lib/headless_web_contents_browsertest.cc",
     "public/util/dom_tree_extractor_browsertest.cc",
     "public/util/flat_dom_tree_extractor_browsertest.cc",
-    "public/util/protocol_handler_request_id_browsertest.cc",
     "test/headless_browser_test.cc",
     "test/headless_browser_test.h",
     "test/headless_test_launcher.cc",
diff --git a/headless/public/util/deterministic_http_protocol_handler.cc b/headless/public/util/deterministic_http_protocol_handler.cc
index 6a186b99..dd905b64 100644
--- a/headless/public/util/deterministic_http_protocol_handler.cc
+++ b/headless/public/util/deterministic_http_protocol_handler.cc
@@ -22,27 +22,20 @@
   ~NopGenericURLRequestJobDelegate() override {}
 
   // GenericURLRequestJob::Delegate methods:
-  bool BlockOrRewriteRequest(
-      const GURL& url,
-      const std::string& devtools_id,
-      const std::string& method,
-      const std::string& referrer,
-      GenericURLRequestJob::RewriteCallback callback) override {
-    return false;
+  void OnPendingRequest(PendingRequest* pending_request) override {
+    pending_request->AllowRequest();
   }
 
-  const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
-      const GURL& url,
-      const std::string& devtools_id,
-      const std::string& method,
-      const net::HttpRequestHeaders& request_headers) override {
-    return nullptr;
+  void OnResourceLoadFailed(const Request* request, net::Error error) override {
   }
 
-  void OnResourceLoadComplete(const GURL& final_url,
-                              const std::string& devtools_id,
-                              const std::string& mime_type,
-                              int http_response_code) override {}
+  void OnResourceLoadComplete(
+      const Request* request,
+      const GURL& final_url,
+      int http_response_code,
+      scoped_refptr<net::HttpResponseHeaders> response_headers,
+      const char* body,
+      size_t body_size) override {}
 
  private:
   DISALLOW_COPY_AND_ASSIGN(NopGenericURLRequestJobDelegate);
diff --git a/headless/public/util/generic_url_request_job.cc b/headless/public/util/generic_url_request_job.cc
index 83d9f8f86..b618913e 100644
--- a/headless/public/util/generic_url_request_job.cc
+++ b/headless/public/util/generic_url_request_job.cc
@@ -8,10 +8,14 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/browser/web_contents.h"
 #include "headless/public/util/url_request_dispatcher.h"
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/base/upload_bytes_element_reader.h"
 #include "net/cookies/cookie_store.h"
 #include "net/http/http_response_headers.h"
 #include "net/url_request/url_request_context.h"
@@ -42,6 +46,8 @@
       url_fetcher_(std::move(url_fetcher)),
       origin_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       delegate_(delegate),
+      request_resource_info_(
+          content::ResourceRequestInfo::ForRequest(request_)),
       weak_factory_(this) {}
 
 GenericURLRequestJob::~GenericURLRequestJob() {
@@ -53,67 +59,21 @@
   DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
   extra_request_headers_ = headers;
 
-  if (extra_request_headers_.GetHeader(kDevtoolsRequestId,
-                                       &devtools_request_id_)) {
-    extra_request_headers_.RemoveHeader(kDevtoolsRequestId);
-  }
+  // TODO(alexclarke): Remove kDevtoolsRequestId
+  extra_request_headers_.RemoveHeader(kDevtoolsRequestId);
 }
 
 void GenericURLRequestJob::Start() {
-  DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
-  if (!delegate_->BlockOrRewriteRequest(
-          request_->url(), devtools_request_id_, request_->method(),
-          request_->referrer(),
-          base::Bind(&GenericURLRequestJob::OnRewriteResult,
-                     weak_factory_.GetWeakPtr(), origin_task_runner_))) {
-    PrepareCookies(request_->url(), request_->method(),
-                   url::Origin(request_->first_party_for_cookies()));
-  }
+  PrepareCookies(request_->url(), request_->method(),
+                 url::Origin(request_->first_party_for_cookies()),
+                 base::Bind(&Delegate::OnPendingRequest,
+                            base::Unretained(delegate_), this));
 }
 
-// static
-void GenericURLRequestJob::OnRewriteResult(
-    base::WeakPtr<GenericURLRequestJob> weak_this,
-    const scoped_refptr<base::SingleThreadTaskRunner>& origin_task_runner,
-    RewriteResult result,
-    const GURL& url,
-    const std::string& method) {
-  if (!origin_task_runner->RunsTasksOnCurrentThread()) {
-    origin_task_runner->PostTask(
-        FROM_HERE,
-        base::Bind(&GenericURLRequestJob::OnRewriteResultOnOriginThread,
-                   weak_this, result, url, method));
-    return;
-  }
-  if (weak_this)
-    weak_this->OnRewriteResultOnOriginThread(result, url, method);
-}
-
-void GenericURLRequestJob::OnRewriteResultOnOriginThread(
-    RewriteResult result,
-    const GURL& url,
-    const std::string& method) {
-  DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
-  switch (result) {
-    case RewriteResult::kAllow:
-      // Note that we use the rewritten url for selecting cookies.
-      // Also, rewriting does not affect the request initiator.
-      PrepareCookies(url, method, url::Origin(url));
-      break;
-    case RewriteResult::kDeny:
-      DispatchStartError(net::ERR_FILE_NOT_FOUND);
-      break;
-    case RewriteResult::kFailure:
-      DispatchStartError(net::ERR_UNEXPECTED);
-      break;
-    default:
-      DCHECK(false);
-  }
-};
-
 void GenericURLRequestJob::PrepareCookies(const GURL& rewritten_url,
                                           const std::string& method,
-                                          const url::Origin& site_for_cookies) {
+                                          const url::Origin& site_for_cookies,
+                                          const base::Closure& done_callback) {
   DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
   net::CookieStore* cookie_store = request_->context()->cookie_store();
   net::CookieOptions options;
@@ -138,12 +98,14 @@
   cookie_store->GetCookieListWithOptionsAsync(
       rewritten_url, options,
       base::Bind(&GenericURLRequestJob::OnCookiesAvailable,
-                 weak_factory_.GetWeakPtr(), rewritten_url, method));
+                 weak_factory_.GetWeakPtr(), rewritten_url, method,
+                 done_callback));
 }
 
 void GenericURLRequestJob::OnCookiesAvailable(
     const GURL& rewritten_url,
     const std::string& method,
+    const base::Closure& done_callback,
     const net::CookieList& cookie_list) {
   DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
   // TODO(alexclarke): Set user agent.
@@ -155,23 +117,13 @@
   extra_request_headers_.SetHeader(net::HttpRequestHeaders::kReferer,
                                    request_->referrer());
 
-  // The resource may have been supplied in the request.
-  const HttpResponse* matched_resource = delegate_->MaybeMatchResource(
-      rewritten_url, devtools_request_id_, method, extra_request_headers_);
-
-  if (matched_resource) {
-    OnFetchCompleteExtractHeaders(
-        matched_resource->final_url, matched_resource->http_response_code,
-        matched_resource->response_data, matched_resource->response_data_size);
-  } else {
-    url_fetcher_->StartFetch(rewritten_url, method, extra_request_headers_,
-                             devtools_request_id_, this);
-  }
+  done_callback.Run();
 }
 
 void GenericURLRequestJob::OnFetchStartError(net::Error error) {
   DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
   DispatchStartError(error);
+  delegate_->OnResourceLoadFailed(this, error);
 }
 
 void GenericURLRequestJob::OnFetchComplete(
@@ -189,11 +141,8 @@
 
   DispatchHeadersComplete();
 
-  std::string mime_type;
-  GetMimeType(&mime_type);
-
-  delegate_->OnResourceLoadComplete(final_url, devtools_request_id_, mime_type,
-                                    http_response_code);
+  delegate_->OnResourceLoadComplete(this, final_url, http_response_code,
+                                    response_headers_, body_, body_size_);
 }
 
 int GenericURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size) {
@@ -236,4 +185,145 @@
   load_timing_info->receive_headers_end = response_time_;
 }
 
+const net::URLRequest* GenericURLRequestJob::GetURLRequest() const {
+  return request_;
+}
+
+int GenericURLRequestJob::GetFrameTreeNodeId() const {
+  return request_resource_info_->GetFrameTreeNodeId();
+}
+
+std::string GenericURLRequestJob::GetDevToolsAgentHostId() const {
+  return content::DevToolsAgentHost::GetOrCreateFor(
+             request_resource_info_->GetWebContentsGetterForRequest().Run())
+      ->GetId();
+}
+
+Request::ResourceType GenericURLRequestJob::GetResourceType() const {
+  switch (request_resource_info_->GetResourceType()) {
+    case content::RESOURCE_TYPE_MAIN_FRAME:
+      return Request::ResourceType::MAIN_FRAME;
+    case content::RESOURCE_TYPE_SUB_FRAME:
+      return Request::ResourceType::SUB_FRAME;
+    case content::RESOURCE_TYPE_STYLESHEET:
+      return Request::ResourceType::STYLESHEET;
+    case content::RESOURCE_TYPE_SCRIPT:
+      return Request::ResourceType::SCRIPT;
+    case content::RESOURCE_TYPE_IMAGE:
+      return Request::ResourceType::IMAGE;
+    case content::RESOURCE_TYPE_FONT_RESOURCE:
+      return Request::ResourceType::FONT_RESOURCE;
+    case content::RESOURCE_TYPE_SUB_RESOURCE:
+      return Request::ResourceType::SUB_RESOURCE;
+    case content::RESOURCE_TYPE_OBJECT:
+      return Request::ResourceType::OBJECT;
+    case content::RESOURCE_TYPE_MEDIA:
+      return Request::ResourceType::MEDIA;
+    case content::RESOURCE_TYPE_WORKER:
+      return Request::ResourceType::WORKER;
+    case content::RESOURCE_TYPE_SHARED_WORKER:
+      return Request::ResourceType::SHARED_WORKER;
+    case content::RESOURCE_TYPE_PREFETCH:
+      return Request::ResourceType::PREFETCH;
+    case content::RESOURCE_TYPE_FAVICON:
+      return Request::ResourceType::FAVICON;
+    case content::RESOURCE_TYPE_XHR:
+      return Request::ResourceType::XHR;
+    case content::RESOURCE_TYPE_PING:
+      return Request::ResourceType::PING;
+    case content::RESOURCE_TYPE_SERVICE_WORKER:
+      return Request::ResourceType::SERVICE_WORKER;
+    case content::RESOURCE_TYPE_CSP_REPORT:
+      return Request::ResourceType::CSP_REPORT;
+    case content::RESOURCE_TYPE_PLUGIN_RESOURCE:
+      return Request::ResourceType::PLUGIN_RESOURCE;
+    default:
+      NOTREACHED() << "Unrecognized resource type";
+      return Request::ResourceType::MAIN_FRAME;
+  }
+}
+
+namespace {
+std::string GetUploadData(net::URLRequest* request) {
+  if (!request->has_upload())
+    return "";
+
+  const net::UploadDataStream* stream = request->get_upload();
+  if (!stream->GetElementReaders())
+    return "";
+
+  DCHECK_EQ(1u, stream->GetElementReaders()->size());
+  const net::UploadBytesElementReader* reader =
+      (*stream->GetElementReaders())[0]->AsBytesReader();
+  return std::string(reader->bytes(), reader->length());
+}
+}  // namespace
+
+const Request* GenericURLRequestJob::GetRequest() const {
+  return this;
+}
+
+void GenericURLRequestJob::AllowRequest() {
+  if (!origin_task_runner_->RunsTasksOnCurrentThread()) {
+    origin_task_runner_->PostTask(
+        FROM_HERE, base::Bind(&GenericURLRequestJob::AllowRequest,
+                              weak_factory_.GetWeakPtr()));
+    return;
+  }
+
+  url_fetcher_->StartFetch(request_->url(), request_->method(),
+                           GetUploadData(request_), extra_request_headers_,
+                           this);
+}
+
+void GenericURLRequestJob::BlockRequest(net::Error error) {
+  if (!origin_task_runner_->RunsTasksOnCurrentThread()) {
+    origin_task_runner_->PostTask(
+        FROM_HERE, base::Bind(&GenericURLRequestJob::BlockRequest,
+                              weak_factory_.GetWeakPtr(), error));
+    return;
+  }
+
+  DispatchStartError(error);
+}
+
+void GenericURLRequestJob::ModifyRequest(
+    const GURL& url,
+    const std::string& method,
+    const std::string& post_data,
+    const net::HttpRequestHeaders& request_headers) {
+  if (!origin_task_runner_->RunsTasksOnCurrentThread()) {
+    origin_task_runner_->PostTask(
+        FROM_HERE, base::Bind(&GenericURLRequestJob::ModifyRequest,
+                              weak_factory_.GetWeakPtr(), url, method,
+                              post_data, request_headers));
+    return;
+  }
+
+  extra_request_headers_ = request_headers;
+  PrepareCookies(
+      request_->url(), request_->method(),
+      url::Origin(request_->first_party_for_cookies()),
+      base::Bind(&URLFetcher::StartFetch, base::Unretained(url_fetcher_.get()),
+                 url, method, post_data, request_headers, this));
+}
+
+void GenericURLRequestJob::MockResponse(
+    std::unique_ptr<MockResponseData> mock_response) {
+  if (!origin_task_runner_->RunsTasksOnCurrentThread()) {
+    origin_task_runner_->PostTask(
+        FROM_HERE, base::Bind(&GenericURLRequestJob::MockResponse,
+                              weak_factory_.GetWeakPtr(),
+                              base::Passed(std::move(mock_response))));
+    return;
+  }
+
+  mock_response_ = std::move(mock_response);
+
+  OnFetchCompleteExtractHeaders(request_->url(),
+                                mock_response_->http_response_code,
+                                mock_response_->response_data.data(),
+                                mock_response_->response_data.size());
+}
+
 }  // namespace headless
diff --git a/headless/public/util/generic_url_request_job.h b/headless/public/util/generic_url_request_job.h
index 909befa21..da6a4972 100644
--- a/headless/public/util/generic_url_request_job.h
+++ b/headless/public/util/generic_url_request_job.h
@@ -25,10 +25,93 @@
 class IOBuffer;
 }  // namespace net
 
+namespace content {
+class ResourceRequestInfo;
+}  // namespace content
+
 namespace headless {
 
 class URLRequestDispatcher;
 
+// Wrapper around net::URLRequest with helpers to access select metadata.
+class Request {
+ public:
+  virtual const net::URLRequest* GetURLRequest() const = 0;
+
+  // The frame from which the request came from.
+  virtual int GetFrameTreeNodeId() const = 0;
+
+  // The devtools agent host id for the page where the request came from.
+  virtual std::string GetDevToolsAgentHostId() const = 0;
+
+  enum class ResourceType {
+    MAIN_FRAME = 0,
+    SUB_FRAME = 1,
+    STYLESHEET = 2,
+    SCRIPT = 3,
+    IMAGE = 4,
+    FONT_RESOURCE = 5,
+    SUB_RESOURCE = 6,
+    OBJECT = 7,
+    MEDIA = 8,
+    WORKER = 9,
+    SHARED_WORKER = 10,
+    PREFETCH = 11,
+    FAVICON = 12,
+    XHR = 13,
+    PING = 14,
+    SERVICE_WORKER = 15,
+    CSP_REPORT = 16,
+    PLUGIN_RESOURCE = 17,
+    LAST_TYPE
+  };
+
+  virtual ResourceType GetResourceType() const = 0;
+
+ protected:
+  Request() {}
+  virtual ~Request() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+// Details of a pending request received by GenericURLRequestJob which must be
+// either Allowed, Blocked, Modified or have it's response Mocked.
+class PendingRequest {
+ public:
+  virtual const Request* GetRequest() const = 0;
+
+  // Allows the request to proceed as normal.
+  virtual void AllowRequest() = 0;
+
+  // Causes the request to fail with the specified |error|.
+  virtual void BlockRequest(net::Error error) = 0;
+
+  // Allows the request to be completely re-written.
+  virtual void ModifyRequest(
+      const GURL& url,
+      const std::string& method,
+      const std::string& post_data,
+      const net::HttpRequestHeaders& request_headers) = 0;
+
+  struct MockResponseData {
+    int http_response_code = 0;
+    std::string response_data;
+  };
+
+  // Instead of fetching the request, |mock_response| is returned instead.
+  virtual void MockResponse(
+      std::unique_ptr<MockResponseData> mock_response) = 0;
+
+ protected:
+  PendingRequest() {}
+  virtual ~PendingRequest() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PendingRequest);
+};
+
 // Intended for use in a protocol handler, this ManagedDispatchURLRequestJob has
 // the following features:
 //
@@ -37,47 +120,30 @@
 //    fetcher is invoked.
 class HEADLESS_EXPORT GenericURLRequestJob
     : public ManagedDispatchURLRequestJob,
-      public URLFetcher::ResultListener {
+      public URLFetcher::ResultListener,
+      public PendingRequest,
+      public Request {
  public:
-  enum class RewriteResult { kAllow, kDeny, kFailure };
-  using RewriteCallback = base::Callback<
-      void(RewriteResult result, const GURL& url, const std::string& method)>;
-
-  struct HttpResponse {
-    GURL final_url;
-    int http_response_code;
-
-    // The HTTP headers and response body. Note the lifetime of |response_data|
-    // is expected to outlive the GenericURLRequestJob.
-    const char* response_data;  // NOT OWNED
-    size_t response_data_size;
-  };
-
   class Delegate {
    public:
-    // Allows the delegate to rewrite the URL for a given request. Return true
-    // to signal that the rewrite is in progress and |callback| will be called
-    // with the result, or false to indicate that no rewriting is necessary.
-    // Called on an arbitrary thread. |callback| can be called on any thread.
-    virtual bool BlockOrRewriteRequest(const GURL& url,
-                                       const std::string& devtools_id,
-                                       const std::string& method,
-                                       const std::string& referrer,
-                                       RewriteCallback callback) = 0;
+    // Notifies the delegate of an PendingRequest which must either be
+    // allowed, blocked, modifed or it's response mocked. Called on an arbitrary
+    // thread.
+    virtual void OnPendingRequest(PendingRequest* pending_request) = 0;
 
-    // Allows the delegate to synchronously fulfill a request with a reply.
-    // Called on an arbitrary thread.
-    virtual const HttpResponse* MaybeMatchResource(
-        const GURL& url,
-        const std::string& devtools_id,
-        const std::string& method,
-        const net::HttpRequestHeaders& request_headers) = 0;
+    // Notifies the delegate of any fetch failure. Called on an arbitrary
+    // thread.
+    virtual void OnResourceLoadFailed(const Request* request,
+                                      net::Error error) = 0;
 
     // Signals that a resource load has finished. Called on an arbitrary thread.
-    virtual void OnResourceLoadComplete(const GURL& final_url,
-                                        const std::string& devtools_id,
-                                        const std::string& mime_type,
-                                        int http_response_code) = 0;
+    virtual void OnResourceLoadComplete(
+        const Request* request,
+        const GURL& final_url,
+        int http_response_code,
+        scoped_refptr<net::HttpResponseHeaders> response_headers,
+        const char* body,
+        size_t body_size) = 0;
 
    protected:
     virtual ~Delegate() {}
@@ -110,29 +176,40 @@
                        const char* body,
                        size_t body_size) override;
 
+ protected:
+  // Request implementation:
+  const net::URLRequest* GetURLRequest() const override;
+  int GetFrameTreeNodeId() const override;
+  std::string GetDevToolsAgentHostId() const override;
+  ResourceType GetResourceType() const override;
+
+  // PendingRequest implementation:
+  const Request* GetRequest() const override;
+  void AllowRequest() override;
+  void BlockRequest(net::Error error) override;
+  void ModifyRequest(const GURL& url,
+                     const std::string& method,
+                     const std::string& post_data,
+                     const net::HttpRequestHeaders& request_headers) override;
+  void MockResponse(std::unique_ptr<MockResponseData> mock_response) override;
+
  private:
-  static void OnRewriteResult(
-      base::WeakPtr<GenericURLRequestJob> weak_this,
-      const scoped_refptr<base::SingleThreadTaskRunner>& origin_task_runner,
-      RewriteResult result,
-      const GURL& url,
-      const std::string& method);
-  void OnRewriteResultOnOriginThread(RewriteResult result,
-                                     const GURL& url,
-                                     const std::string& method);
   void PrepareCookies(const GURL& rewritten_url,
                       const std::string& method,
-                      const url::Origin& site_for_cookies);
+                      const url::Origin& site_for_cookies,
+                      const base::Closure& done_callback);
   void OnCookiesAvailable(const GURL& rewritten_url,
                           const std::string& method,
+                          const base::Closure& done_callback,
                           const net::CookieList& cookie_list);
 
   std::unique_ptr<URLFetcher> url_fetcher_;
   net::HttpRequestHeaders extra_request_headers_;
   scoped_refptr<net::HttpResponseHeaders> response_headers_;
   scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
-  std::string devtools_request_id_;
+  std::unique_ptr<MockResponseData> mock_response_;
   Delegate* delegate_;          // Not owned.
+  const content::ResourceRequestInfo* request_resource_info_;  // Not owned.
   const char* body_ = nullptr;  // Not owned.
   int http_response_code_ = 0;
   size_t body_size_ = 0;
diff --git a/headless/public/util/generic_url_request_job_test.cc b/headless/public/util/generic_url_request_job_test.cc
index 02e52cc0..b4aec67 100644
--- a/headless/public/util/generic_url_request_job_test.cc
+++ b/headless/public/util/generic_url_request_job_test.cc
@@ -18,11 +18,15 @@
 #include "headless/public/util/expedited_dispatcher.h"
 #include "headless/public/util/testing/generic_url_request_mocks.h"
 #include "headless/public/util/url_fetcher.h"
+#include "net/base/elements_upload_data_stream.h"
+#include "net/base/upload_bytes_element_reader.h"
 #include "net/http/http_response_headers.h"
 #include "net/url_request/url_request_job_factory_impl.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using testing::_;
+
 std::ostream& operator<<(std::ostream& os, const base::Value& value) {
   std::string json;
   base::JSONWriter::WriteWithOptions(
@@ -41,21 +45,25 @@
 
 namespace {
 
+class MockDelegate : public MockGenericURLRequestJobDelegate {
+ public:
+  MOCK_METHOD2(OnResourceLoadFailed,
+               void(const Request* request, net::Error error));
+};
+
 class MockFetcher : public URLFetcher {
  public:
   MockFetcher(base::DictionaryValue* fetch_request,
-              const std::string& json_reply)
-      : fetch_reply_(base::JSONReader::Read(json_reply, base::JSON_PARSE_RFC)),
-        fetch_request_(fetch_request) {
-    CHECK(fetch_reply_) << "Invalid json: " << json_reply;
-  }
+              std::map<std::string, std::string>* json_fetch_reply_map)
+      : json_fetch_reply_map_(json_fetch_reply_map),
+        fetch_request_(fetch_request) {}
 
   ~MockFetcher() override {}
 
   void StartFetch(const GURL& url,
                   const std::string& method,
+                  const std::string& post_data,
                   const net::HttpRequestHeaders& request_headers,
-                  const std::string& devtools_request_id,
                   ResultListener* result_listener) override {
     // Record the request.
     fetch_request_->SetString("url", url.spec());
@@ -65,10 +73,22 @@
       headers->SetString(it.name(), it.value());
     }
     fetch_request_->Set("headers", std::move(headers));
+    if (!post_data.empty())
+      fetch_request_->SetString("post_data", post_data);
+
+    const auto find_it = json_fetch_reply_map_->find(url.spec());
+    if (find_it == json_fetch_reply_map_->end()) {
+      result_listener->OnFetchStartError(net::ERR_ADDRESS_UNREACHABLE);
+      return;
+    }
 
     // Return the canned response.
+    std::unique_ptr<base::Value> fetch_reply(
+        base::JSONReader::Read(find_it->second, base::JSON_PARSE_RFC));
+    CHECK(fetch_reply) << "Invalid json: " << find_it->second;
+
     base::DictionaryValue* reply_dictionary;
-    ASSERT_TRUE(fetch_reply_->GetAsDictionary(&reply_dictionary));
+    ASSERT_TRUE(fetch_reply->GetAsDictionary(&reply_dictionary));
     std::string final_url;
     ASSERT_TRUE(reply_dictionary->GetString("url", &final_url));
     int http_response_code;
@@ -94,7 +114,7 @@
   }
 
  private:
-  std::unique_ptr<base::Value> fetch_reply_;
+  std::map<std::string, std::string>* json_fetch_reply_map_;  // NOT OWNED
   base::DictionaryValue* fetch_request_;  // NOT OWNED
   std::string response_data_;  // Here to ensure the required lifetime.
 };
@@ -102,13 +122,13 @@
 class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
  public:
   // Details of the fetch will be stored in |fetch_request|.
-  // The fetch response will be created from parsing |json_fetch_reply_|.
+  // The fetch response will be created from parsing |json_fetch_reply_map|.
   MockProtocolHandler(base::DictionaryValue* fetch_request,
-                      std::string* json_fetch_reply,
+                      std::map<std::string, std::string>* json_fetch_reply_map,
                       URLRequestDispatcher* dispatcher,
                       GenericURLRequestJob::Delegate* job_delegate)
       : fetch_request_(fetch_request),
-        json_fetch_reply_(json_fetch_reply),
+        json_fetch_reply_map_(json_fetch_reply_map),
         job_delegate_(job_delegate),
         dispatcher_(dispatcher) {}
 
@@ -118,13 +138,13 @@
       net::NetworkDelegate* network_delegate) const override {
     return new GenericURLRequestJob(
         request, network_delegate, dispatcher_,
-        base::MakeUnique<MockFetcher>(fetch_request_, *json_fetch_reply_),
+        base::MakeUnique<MockFetcher>(fetch_request_, json_fetch_reply_map_),
         job_delegate_);
   }
 
  private:
   base::DictionaryValue* fetch_request_;          // NOT OWNED
-  std::string* json_fetch_reply_;                 // NOT OWNED
+  std::map<std::string, std::string>* json_fetch_reply_map_;  // NOT OWNED
   GenericURLRequestJob::Delegate* job_delegate_;  // NOT OWNED
   URLRequestDispatcher* dispatcher_;              // NOT OWNED
 };
@@ -136,16 +156,16 @@
   GenericURLRequestJobTest() : dispatcher_(message_loop_.task_runner()) {
     url_request_job_factory_.SetProtocolHandler(
         "https", base::WrapUnique(new MockProtocolHandler(
-                     &fetch_request_, &json_fetch_reply_, &dispatcher_,
+                     &fetch_request_, &json_fetch_reply_map_, &dispatcher_,
                      &job_delegate_)));
     url_request_context_.set_job_factory(&url_request_job_factory_);
     url_request_context_.set_cookie_store(&cookie_store_);
   }
 
-  std::unique_ptr<net::URLRequest> CreateAndCompleteJob(
+  std::unique_ptr<net::URLRequest> CreateAndCompleteGetJob(
       const GURL& url,
       const std::string& json_reply) {
-    json_fetch_reply_ = json_reply;
+    json_fetch_reply_map_[url.spec()] = json_reply;
 
     std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
         url, net::DEFAULT_PRIORITY, &request_delegate_));
@@ -164,17 +184,21 @@
 
   MockURLRequestDelegate request_delegate_;
   base::DictionaryValue fetch_request_;  // The request sent to MockFetcher.
-  std::string json_fetch_reply_;         // The reply to be sent by MockFetcher.
-  MockGenericURLRequestJobDelegate job_delegate_;
+  std::map<std::string, std::string>
+      json_fetch_reply_map_;  // Replies to be sent by MockFetcher.
+  MockDelegate job_delegate_;
 };
 
-TEST_F(GenericURLRequestJobTest, BasicRequestParams) {
-  // TODO(alexclarke): Lobby for raw string literals and use them here!
-  json_fetch_reply_ =
-      "{\"url\":\"https://example.com\","
-      " \"http_response_code\":200,"
-      " \"data\":\"Reply\","
-      " \"headers\":{\"Content-Type\":\"text/plain\"}}";
+TEST_F(GenericURLRequestJobTest, BasicGetRequestParams) {
+  json_fetch_reply_map_["https://example.com/"] = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
 
   std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
       GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_));
@@ -185,30 +209,79 @@
   request->Start();
   base::RunLoop().RunUntilIdle();
 
-  std::string expected_request_json =
-      "{\"url\": \"https://example.com/\","
-      " \"method\": \"GET\","
-      " \"headers\": {"
-      "   \"Accept\": \"text/plain\","
-      "   \"Cookie\": \"\","
-      "   \"Extra-Header\": \"Value\","
-      "   \"Referer\": \"https://referrer.example.com/\","
-      "   \"User-Agent\": \"TestBrowser\""
-      " }"
-      "}";
+  std::string expected_request_json = R"(
+      {
+        "url": "https://example.com/",
+        "method": "GET",
+        "headers": {
+          "Accept": "text/plain",
+          "Cookie": "",
+          "Extra-Header": "Value",
+          "Referer": "https://referrer.example.com/",
+          "User-Agent": "TestBrowser"
+        }
+      })";
+
+  EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
+}
+
+TEST_F(GenericURLRequestJobTest, BasicPostRequestParams) {
+  json_fetch_reply_map_["https://example.com/"] = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
+
+  std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
+      GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_));
+  request->SetReferrer("https://referrer.example.com");
+  request->SetExtraRequestHeaderByName("Extra-Header", "Value", true);
+  request->SetExtraRequestHeaderByName("User-Agent", "TestBrowser", true);
+  request->SetExtraRequestHeaderByName("Accept", "text/plain", true);
+  request->set_method("POST");
+
+  std::string post_data = "lorem ipsom";
+  request->set_upload(net::ElementsUploadDataStream::CreateWithReader(
+      base::MakeUnique<net::UploadBytesElementReader>(post_data.data(),
+                                                      post_data.size()),
+      0));
+  request->Start();
+  base::RunLoop().RunUntilIdle();
+
+  std::string expected_request_json = R"(
+      {
+        "url": "https://example.com/",
+        "method": "POST",
+        "post_data": "lorem ipsom",
+        "headers": {
+          "Accept": "text/plain",
+          "Cookie": "",
+          "Extra-Header": "Value",
+          "Referer": "https://referrer.example.com/",
+          "User-Agent": "TestBrowser"
+        }
+      })";
 
   EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
 }
 
 TEST_F(GenericURLRequestJobTest, BasicRequestProperties) {
-  std::string reply =
-      "{\"url\":\"https://example.com\","
-      " \"http_response_code\":200,"
-      " \"data\":\"Reply\","
-      " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+  std::string reply = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
 
   std::unique_ptr<net::URLRequest> request(
-      CreateAndCompleteJob(GURL("https://example.com"), reply));
+      CreateAndCompleteGetJob(GURL("https://example.com"), reply));
 
   EXPECT_EQ(200, request->GetResponseCode());
 
@@ -227,14 +300,18 @@
 }
 
 TEST_F(GenericURLRequestJobTest, BasicRequestContents) {
-  std::string reply =
-      "{\"url\":\"https://example.com\","
-      " \"http_response_code\":200,"
-      " \"data\":\"Reply\","
-      " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+  std::string reply = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
 
   std::unique_ptr<net::URLRequest> request(
-      CreateAndCompleteJob(GURL("https://example.com"), reply));
+      CreateAndCompleteGetJob(GURL("https://example.com"), reply));
 
   const int kBufferSize = 256;
   scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
@@ -249,14 +326,18 @@
 }
 
 TEST_F(GenericURLRequestJobTest, ReadInParts) {
-  std::string reply =
-      "{\"url\":\"https://example.com\","
-      " \"http_response_code\":200,"
-      " \"data\":\"Reply\","
-      " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+  std::string reply = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
 
   std::unique_ptr<net::URLRequest> request(
-      CreateAndCompleteJob(GURL("https://example.com"), reply));
+      CreateAndCompleteGetJob(GURL("https://example.com"), reply));
 
   const int kBufferSize = 3;
   scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
@@ -332,41 +413,200 @@
       /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
 
-  std::string reply =
-      "{\"url\":\"https://example.com\","
-      " \"http_response_code\":200,"
-      " \"data\":\"Reply\","
-      " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+  std::string reply = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
 
   std::unique_ptr<net::URLRequest> request(
-      CreateAndCompleteJob(GURL("https://example.com"), reply));
+      CreateAndCompleteGetJob(GURL("https://example.com"), reply));
 
-  std::string expected_request_json =
-      "{\"url\": \"https://example.com/\","
-      " \"method\": \"GET\","
-      " \"headers\": {"
-      "   \"Cookie\": \"basic_cookie=1; secure_cookie=2; http_only_cookie=3\","
-      "   \"Referer\": \"\""
-      " }"
-      "}";
+  std::string expected_request_json = R"(
+      {
+        "url": "https://example.com/",
+        "method": "GET",
+        "headers": {
+          "Cookie": "basic_cookie=1; secure_cookie=2; http_only_cookie=3",
+          "Referer": ""
+        }
+      })";
 
   EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
 }
 
 TEST_F(GenericURLRequestJobTest, DelegateBlocksLoading) {
-  std::string reply =
-      "{\"url\":\"https://example.com\","
-      " \"http_response_code\":200,"
-      " \"data\":\"Reply\","
-      " \"headers\":{\"Content-Type\":\"text/html; charset=UTF-8\"}}";
+  std::string reply = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
 
-  job_delegate_.SetShouldBlock(true);
+  job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) {
+    pending_request->BlockRequest(net::ERR_FILE_NOT_FOUND);
+  }));
 
   std::unique_ptr<net::URLRequest> request(
-      CreateAndCompleteJob(GURL("https://example.com"), reply));
+      CreateAndCompleteGetJob(GURL("https://example.com"), reply));
 
   EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status());
   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request->status().error());
 }
 
+TEST_F(GenericURLRequestJobTest, DelegateModifiesRequest) {
+  json_fetch_reply_map_["https://example.com/"] = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Welcome to example.com",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
+
+  json_fetch_reply_map_["https://othersite.com/"] = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Welcome to othersite.com",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
+
+  // Turn the GET into a POST to a different site.
+  job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) {
+    net::HttpRequestHeaders headers;
+    headers.SetHeader("TestHeader", "Hello");
+    pending_request->ModifyRequest(GURL("https://othersite.com"), "POST",
+                                   "Some post data!", headers);
+  }));
+
+  std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
+      GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_));
+  request->Start();
+  base::RunLoop().RunUntilIdle();
+
+  std::string expected_request_json = R"(
+      {
+        "url": "https://othersite.com/",
+        "method": "POST",
+        "post_data": "Some post data!",
+        "headers": {
+          "TestHeader": "Hello"
+        }
+      })";
+
+  EXPECT_THAT(fetch_request_, MatchesJson(expected_request_json));
+
+  EXPECT_EQ(200, request->GetResponseCode());
+  // The modification should not be visible to the URlRequest.
+  EXPECT_EQ("https://example.com/", request->url().spec());
+  EXPECT_EQ("GET", request->method());
+
+  const int kBufferSize = 256;
+  scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+  int bytes_read;
+  EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
+  EXPECT_EQ(24, bytes_read);
+  EXPECT_EQ("Welcome to othersite.com",
+            std::string(buffer->data(), bytes_read));
+}
+
+TEST_F(GenericURLRequestJobTest, DelegateMocks404Response) {
+  std::string reply = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Reply",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
+
+  job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) {
+    std::unique_ptr<GenericURLRequestJob::MockResponseData> mock_response_data(
+        new GenericURLRequestJob::MockResponseData());
+    mock_response_data->http_response_code = 404;
+    mock_response_data->response_data = "HTTP/1.1 404 Not Found\r\n\r\n";
+    pending_request->MockResponse(std::move(mock_response_data));
+  }));
+
+  std::unique_ptr<net::URLRequest> request(
+      CreateAndCompleteGetJob(GURL("https://example.com"), reply));
+
+  EXPECT_EQ(404, request->GetResponseCode());
+}
+
+TEST_F(GenericURLRequestJobTest, DelegateMocks302Response) {
+  job_delegate_.SetPolicy(base::Bind([](PendingRequest* pending_request) {
+    if (pending_request->GetRequest()->GetURLRequest()->url().spec() ==
+        "https://example.com/") {
+      std::unique_ptr<GenericURLRequestJob::MockResponseData>
+          mock_response_data(new GenericURLRequestJob::MockResponseData());
+      mock_response_data->http_response_code = 302;
+      mock_response_data->response_data =
+          "HTTP/1.1 302 Found\r\n"
+          "Location: https://foo.com/\r\n\r\n";
+      pending_request->MockResponse(std::move(mock_response_data));
+    } else {
+      pending_request->AllowRequest();
+    }
+  }));
+
+  json_fetch_reply_map_["https://example.com/"] = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Welcome to example.com",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
+
+  json_fetch_reply_map_["https://foo.com/"] = R"(
+      {
+        "url": "https://example.com",
+        "http_response_code": 200,
+        "data": "Welcome to foo.com",
+        "headers": {
+          "Content-Type": "text/html; charset=UTF-8"
+        }
+      })";
+
+  std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
+      GURL("https://example.com"), net::DEFAULT_PRIORITY, &request_delegate_));
+  request->Start();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(200, request->GetResponseCode());
+  EXPECT_EQ("https://foo.com/", request->url().spec());
+
+  const int kBufferSize = 256;
+  scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+  int bytes_read;
+  EXPECT_TRUE(request->Read(buffer.get(), kBufferSize, &bytes_read));
+  EXPECT_EQ(18, bytes_read);
+  EXPECT_EQ("Welcome to foo.com", std::string(buffer->data(), bytes_read));
+}
+
+TEST_F(GenericURLRequestJobTest, OnResourceLoadFailed) {
+  EXPECT_CALL(job_delegate_,
+              OnResourceLoadFailed(_, net::ERR_ADDRESS_UNREACHABLE));
+
+  std::unique_ptr<net::URLRequest> request(url_request_context_.CreateRequest(
+      GURL("https://i-dont-exist.com"), net::DEFAULT_PRIORITY,
+      &request_delegate_));
+  request->Start();
+  base::RunLoop().RunUntilIdle();
+}
+
 }  // namespace headless
diff --git a/headless/public/util/http_url_fetcher.cc b/headless/public/util/http_url_fetcher.cc
index a3171b3f..ac40607 100644
--- a/headless/public/util/http_url_fetcher.cc
+++ b/headless/public/util/http_url_fetcher.cc
@@ -4,7 +4,9 @@
 
 #include "headless/public/util/http_url_fetcher.h"
 
+#include "net/base/elements_upload_data_stream.h"
 #include "net/base/io_buffer.h"
+#include "net/base/upload_bytes_element_reader.h"
 #include "net/cert/cert_status_flags.h"
 #include "net/http/http_response_headers.h"
 #include "net/url_request/url_request.h"
@@ -16,6 +18,7 @@
  public:
   Delegate(const GURL& rewritten_url,
            const std::string& method,
+           const std::string& post_data,
            const net::HttpRequestHeaders& request_headers,
            const net::URLRequestContext* url_request_context,
            ResultListener* result_listener);
@@ -56,6 +59,7 @@
 HttpURLFetcher::Delegate::Delegate(
     const GURL& rewritten_url,
     const std::string& method,
+    const std::string& post_data,
     const net::HttpRequestHeaders& request_headers,
     const net::URLRequestContext* url_request_context,
     ResultListener* result_listener)
@@ -66,6 +70,14 @@
                                                   this)),
       result_listener_(result_listener) {
   request_->set_method(method);
+
+  if (!post_data.empty()) {
+    request_->set_upload(net::ElementsUploadDataStream::CreateWithReader(
+        base::MakeUnique<net::UploadBytesElementReader>(post_data.data(),
+                                                        post_data.size()),
+        0));
+  }
+
   request_->SetExtraRequestHeaders(request_headers);
   request_->Start();
 }
@@ -183,11 +195,12 @@
 
 void HttpURLFetcher::StartFetch(const GURL& rewritten_url,
                                 const std::string& method,
+                                const std::string& post_data,
                                 const net::HttpRequestHeaders& request_headers,
-                                const std::string& devtools_request_id,
                                 ResultListener* result_listener) {
-  delegate_.reset(new Delegate(rewritten_url, method, request_headers,
-                               url_request_context_, result_listener));
+  delegate_.reset(new Delegate(rewritten_url, method, post_data,
+                               request_headers, url_request_context_,
+                               result_listener));
 }
 
 }  // namespace headless
diff --git a/headless/public/util/http_url_fetcher.h b/headless/public/util/http_url_fetcher.h
index 5049b4ec..c69d9fb2 100644
--- a/headless/public/util/http_url_fetcher.h
+++ b/headless/public/util/http_url_fetcher.h
@@ -24,8 +24,8 @@
   // URLFetcher implementation:
   void StartFetch(const GURL& rewritten_url,
                   const std::string& method,
+                  const std::string& post_data,
                   const net::HttpRequestHeaders& request_headers,
-                  const std::string& devtools_request_id,
                   ResultListener* result_listener) override;
 
  private:
diff --git a/headless/public/util/protocol_handler_request_id_browsertest.cc b/headless/public/util/protocol_handler_request_id_browsertest.cc
deleted file mode 100644
index 68a34e25..0000000
--- a/headless/public/util/protocol_handler_request_id_browsertest.cc
+++ /dev/null
@@ -1,234 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/run_loop.h"
-#include "content/public/common/browser_side_navigation_policy.h"
-#include "content/public/test/browser_test.h"
-#include "headless/public/devtools/domains/network.h"
-#include "headless/public/devtools/domains/page.h"
-#include "headless/public/headless_devtools_client.h"
-#include "headless/public/util/expedited_dispatcher.h"
-#include "headless/public/util/generic_url_request_job.h"
-#include "headless/public/util/url_fetcher.h"
-#include "headless/test/headless_browser_test.h"
-#include "net/url_request/url_request_job_factory.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-using testing::ContainerEq;
-
-namespace headless {
-
-namespace {
-// Keep in sync with X_DevTools_Request_Id defined in HTTPNames.json5.
-const char kDevtoolsRequestId[] = "X-DevTools-Request-Id";
-}  // namespace
-
-namespace {
-class RequestIdCorrelationProtocolHandler
-    : public net::URLRequestJobFactory::ProtocolHandler {
- public:
-  explicit RequestIdCorrelationProtocolHandler(
-      scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
-      : test_delegate_(new TestDelegate(this)),
-        dispatcher_(new ExpeditedDispatcher(io_thread_task_runner)) {}
-
-  ~RequestIdCorrelationProtocolHandler() override {}
-
-  struct Response {
-    Response() {}
-    Response(const std::string& body, const std::string& mime_type)
-        : data("HTTP/1.1 200 OK\r\nContent-Type: " + mime_type + "\r\n\r\n" +
-               body) {}
-
-    std::string data;
-  };
-
-  void InsertResponse(const std::string& url, const Response& response) {
-    response_map_[url] = response;
-  }
-
-  const Response* GetResponse(const std::string& url) const {
-    std::map<std::string, Response>::const_iterator find_it =
-        response_map_.find(url);
-    if (find_it == response_map_.end())
-      return nullptr;
-    return &find_it->second;
-  }
-
-  class MockURLFetcher : public URLFetcher {
-   public:
-    explicit MockURLFetcher(
-        const RequestIdCorrelationProtocolHandler* protocol_handler)
-        : protocol_handler_(protocol_handler) {}
-    ~MockURLFetcher() override {}
-
-    // URLFetcher implementation:
-    void StartFetch(const GURL& url,
-                    const std::string& method,
-                    const net::HttpRequestHeaders& request_headers,
-                    const std::string& devtools_request_id,
-                    ResultListener* result_listener) override {
-      const Response* response = protocol_handler_->GetResponse(url.spec());
-      if (!response)
-        result_listener->OnFetchStartError(net::ERR_FILE_NOT_FOUND);
-
-      // The header used for correlation should not be sent to the fetcher.
-      EXPECT_FALSE(request_headers.HasHeader(kDevtoolsRequestId));
-
-      result_listener->OnFetchCompleteExtractHeaders(
-          url, 200, response->data.c_str(), response->data.size());
-    }
-
-   private:
-    const RequestIdCorrelationProtocolHandler* protocol_handler_;
-
-    DISALLOW_COPY_AND_ASSIGN(MockURLFetcher);
-  };
-
-  class TestDelegate : public GenericURLRequestJob::Delegate {
-   public:
-    explicit TestDelegate(RequestIdCorrelationProtocolHandler* protocol_handler)
-        : protocol_handler_(protocol_handler) {}
-    ~TestDelegate() override {}
-
-    // GenericURLRequestJob::Delegate implementation:
-    bool BlockOrRewriteRequest(
-        const GURL& url,
-        const std::string& devtools_id,
-        const std::string& method,
-        const std::string& referrer,
-        GenericURLRequestJob::RewriteCallback callback) override {
-      protocol_handler_->url_to_devtools_id_[url.spec()] = devtools_id;
-      return false;
-    }
-
-    const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
-        const GURL& url,
-        const std::string& devtools_id,
-        const std::string& method,
-        const net::HttpRequestHeaders& request_headers) override {
-      return nullptr;
-    }
-
-    void OnResourceLoadComplete(const GURL& final_url,
-                                const std::string& devtools_id,
-                                const std::string& mime_type,
-                                int http_response_code) override {}
-
-   private:
-    RequestIdCorrelationProtocolHandler* protocol_handler_;
-
-    DISALLOW_COPY_AND_ASSIGN(TestDelegate);
-  };
-
-  // net::URLRequestJobFactory::ProtocolHandler implementation::
-  net::URLRequestJob* MaybeCreateJob(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) const override {
-    return new GenericURLRequestJob(
-        request, network_delegate, dispatcher_.get(),
-        base::MakeUnique<MockURLFetcher>(this), test_delegate_.get());
-  }
-
-  std::map<std::string, std::string> url_to_devtools_id_;
-
- private:
-  std::unique_ptr<TestDelegate> test_delegate_;
-  std::unique_ptr<ExpeditedDispatcher> dispatcher_;
-  std::map<std::string, Response> response_map_;
-
-  DISALLOW_COPY_AND_ASSIGN(RequestIdCorrelationProtocolHandler);
-};
-
-const char* kIndexHtml = R"(
-<html>
-<head>
-<link rel="stylesheet" type="text/css" href="style1.css">
-<link rel="stylesheet" type="text/css" href="style2.css">
-</head>
-<body>Hello.
-</body>
-</html>)";
-
-const char* kStyle1 = R"(
-.border {
-  border: 1px solid #000;
-})";
-
-const char* kStyle2 = R"(
-.border {
-  border: 2px solid #fff;
-})";
-
-}  // namespace
-
-class ProtocolHandlerRequestIdCorrelationTest
-    : public HeadlessAsyncDevTooledBrowserTest,
-      public network::Observer,
-      public page::Observer {
- public:
-  void RunDevTooledTest() override {
-    if (content::IsBrowserSideNavigationEnabled()) {
-      // TODO: get this working with PlzNavigate.
-      // See discussion on https://codereview.chromium.org/2695923010/
-      FinishAsynchronousTest();
-      return;
-    }
-
-    EXPECT_TRUE(embedded_test_server()->Start());
-    devtools_client_->GetPage()->AddObserver(this);
-    devtools_client_->GetPage()->Enable();
-
-    base::RunLoop run_loop;
-    devtools_client_->GetNetwork()->AddObserver(this);
-    devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure());
-    base::MessageLoop::ScopedNestableTaskAllower nest_loop(
-        base::MessageLoop::current());
-    run_loop.Run();
-
-    devtools_client_->GetPage()->Navigate("http://foo.com/index.html");
-  }
-
-  ProtocolHandlerMap GetProtocolHandlers() override {
-    ProtocolHandlerMap protocol_handlers;
-    std::unique_ptr<RequestIdCorrelationProtocolHandler> http_handler(
-        new RequestIdCorrelationProtocolHandler(browser()->BrowserIOThread()));
-    http_handler_ = http_handler.get();
-    http_handler_->InsertResponse("http://foo.com/index.html",
-                                  {kIndexHtml, "text/html"});
-    http_handler_->InsertResponse("http://foo.com/style1.css",
-                                  {kStyle1, "text/css"});
-    http_handler_->InsertResponse("http://foo.com/style2.css",
-                                  {kStyle2, "text/css"});
-    protocol_handlers[url::kHttpScheme] = std::move(http_handler);
-    return protocol_handlers;
-  }
-
-  // network::Observer implementation:
-  void OnRequestWillBeSent(
-      const network::RequestWillBeSentParams& params) override {
-    url_to_devtools_id_[params.GetRequest()->GetUrl()] = params.GetRequestId();
-    EXPECT_FALSE(params.GetRequest()->GetHeaders()->HasKey(kDevtoolsRequestId));
-  }
-
-  // page::Observer implementation:
-  void OnLoadEventFired(const page::LoadEventFiredParams& params) override {
-    // Make sure that our protocol handler saw the same url : devtools ids as
-    // our OnRequestWillBeSent event listener did.
-    EXPECT_THAT(url_to_devtools_id_,
-                ContainerEq(http_handler_->url_to_devtools_id_));
-    EXPECT_EQ(3u, url_to_devtools_id_.size());
-    FinishAsynchronousTest();
-  }
-
- private:
-  std::map<std::string, std::string> url_to_devtools_id_;
-  RequestIdCorrelationProtocolHandler* http_handler_;  // NOT OWNED
-};
-
-HEADLESS_ASYNC_DEVTOOLED_TEST_F(ProtocolHandlerRequestIdCorrelationTest);
-
-}  // namespace headless
diff --git a/headless/public/util/testing/generic_url_request_mocks.cc b/headless/public/util/testing/generic_url_request_mocks.cc
index 3d5e682..4a7d37de 100644
--- a/headless/public/util/testing/generic_url_request_mocks.cc
+++ b/headless/public/util/testing/generic_url_request_mocks.cc
@@ -15,45 +15,43 @@
 
 // MockGenericURLRequestJobDelegate
 MockGenericURLRequestJobDelegate::MockGenericURLRequestJobDelegate()
-    : should_block_(false),
-      main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+    : main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+
 MockGenericURLRequestJobDelegate::~MockGenericURLRequestJobDelegate() {}
 
-bool MockGenericURLRequestJobDelegate::BlockOrRewriteRequest(
-    const GURL& url,
-    const std::string& devtools_id,
-    const std::string& method,
-    const std::string& referrer,
-    GenericURLRequestJob::RewriteCallback callback) {
-  if (should_block_) {
-    // Simulate the client acknowledging the callback from a different thread.
-    main_thread_task_runner_->PostTask(
-        FROM_HERE, base::Bind(
-                       [](GenericURLRequestJob::RewriteCallback callback,
-                          std::string method) {
-                         callback.Run(
-                             GenericURLRequestJob::RewriteResult::kDeny, GURL(),
-                             method);
-                       },
-                       callback, method));
-  }
-  return should_block_;
+// GenericURLRequestJob::Delegate methods:
+void MockGenericURLRequestJobDelegate::OnPendingRequest(
+    PendingRequest* pending_request) {
+  // Simulate the client acknowledging the callback from a different thread.
+  main_thread_task_runner_->PostTask(
+      FROM_HERE, base::Bind(&MockGenericURLRequestJobDelegate::ApplyPolicy,
+                            base::Unretained(this), pending_request));
 }
 
-const GenericURLRequestJob::HttpResponse*
-MockGenericURLRequestJobDelegate::MaybeMatchResource(
-    const GURL& url,
-    const std::string& devtools_id,
-    const std::string& method,
-    const net::HttpRequestHeaders& request_headers) {
-  return nullptr;
+void MockGenericURLRequestJobDelegate::SetPolicy(Policy policy) {
+  policy_ = policy;
 }
 
+void MockGenericURLRequestJobDelegate::ApplyPolicy(
+    PendingRequest* pending_request) {
+  if (policy_.is_null()) {
+    pending_request->AllowRequest();
+  } else {
+    policy_.Run(pending_request);
+  }
+}
+
+void MockGenericURLRequestJobDelegate::OnResourceLoadFailed(
+    const Request* request,
+    net::Error error) {}
+
 void MockGenericURLRequestJobDelegate::OnResourceLoadComplete(
+    const Request* request,
     const GURL& final_url,
-    const std::string& devtools_id,
-    const std::string& mime_type,
-    int http_response_code) {}
+    int http_response_code,
+    scoped_refptr<net::HttpResponseHeaders> response_headers,
+    const char* body,
+    size_t body_size) {}
 
 // MockCookieStore
 MockCookieStore::MockCookieStore() {}
diff --git a/headless/public/util/testing/generic_url_request_mocks.h b/headless/public/util/testing/generic_url_request_mocks.h
index 50c1d60..218c3ce 100644
--- a/headless/public/util/testing/generic_url_request_mocks.h
+++ b/headless/public/util/testing/generic_url_request_mocks.h
@@ -25,28 +25,25 @@
   MockGenericURLRequestJobDelegate();
   ~MockGenericURLRequestJobDelegate() override;
 
-  bool BlockOrRewriteRequest(
-      const GURL& url,
-      const std::string& devtools_id,
-      const std::string& method,
-      const std::string& referrer,
-      GenericURLRequestJob::RewriteCallback callback) override;
+  // GenericURLRequestJob::Delegate methods:
+  void OnPendingRequest(PendingRequest* pending_request) override;
+  void OnResourceLoadFailed(const Request* request, net::Error error) override;
+  void OnResourceLoadComplete(
+      const Request* request,
+      const GURL& final_url,
+      int http_response_code,
+      scoped_refptr<net::HttpResponseHeaders> response_headers,
+      const char* body,
+      size_t body_size) override;
 
-  const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
-      const GURL& url,
-      const std::string& devtools_id,
-      const std::string& method,
-      const net::HttpRequestHeaders& request_headers) override;
+  using Policy = base::Callback<void(PendingRequest* pending_request)>;
 
-  void OnResourceLoadComplete(const GURL& final_url,
-                              const std::string& devtools_id,
-                              const std::string& mime_type,
-                              int http_response_code) override;
-
-  void SetShouldBlock(bool should_block) { should_block_ = should_block; }
+  void SetPolicy(Policy policy);
 
  private:
-  bool should_block_;
+  void ApplyPolicy(PendingRequest* pending_request);
+
+  Policy policy_;
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
 
   DISALLOW_COPY_AND_ASSIGN(MockGenericURLRequestJobDelegate);
diff --git a/headless/public/util/url_fetcher.cc b/headless/public/util/url_fetcher.cc
index 86fd3b3..8d8d33f 100644
--- a/headless/public/util/url_fetcher.cc
+++ b/headless/public/util/url_fetcher.cc
@@ -37,17 +37,4 @@
                   response_data_size - read_offset);
 }
 
-void URLFetcher::StartFetch(const GURL& rewritten_url,
-                            const std::string& method,
-                            const net::HttpRequestHeaders& request_headers,
-                            const std::string& devtools_request_id,
-                            ResultListener* result_listener) {
-  StartFetch(rewritten_url, method, request_headers, result_listener);
-}
-
-void URLFetcher::StartFetch(const GURL& rewritten_url,
-                            const std::string& method,
-                            const net::HttpRequestHeaders& request_headers,
-                            ResultListener* result_listener) {}
-
 }  // namespace headless
diff --git a/headless/public/util/url_fetcher.h b/headless/public/util/url_fetcher.h
index 41bf3fa..df0cc0ff 100644
--- a/headless/public/util/url_fetcher.h
+++ b/headless/public/util/url_fetcher.h
@@ -60,17 +60,11 @@
     DISALLOW_COPY_AND_ASSIGN(ResultListener);
   };
 
-  // Instructs the sub-class to fetch the resource.
-  virtual void StartFetch(const GURL& rewritten_url,
+  virtual void StartFetch(const GURL& url,
                           const std::string& method,
+                          const std::string& post_data,
                           const net::HttpRequestHeaders& request_headers,
-                          const std::string& devtools_request_id,
-                          ResultListener* result_listener);
-  //  TODO(alexclarke): Make the above pure virtual and remove this.
-  virtual void StartFetch(const GURL& rewritten_url,
-                          const std::string& method,
-                          const net::HttpRequestHeaders& request_headers,
-                          ResultListener* result_listener);
+                          ResultListener* result_listener) = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(URLFetcher);
diff --git a/ios/chrome/widget_extension/BUILD.gn b/ios/chrome/widget_extension/BUILD.gn
index 7fbc32a..31fed23e 100644
--- a/ios/chrome/widget_extension/BUILD.gn
+++ b/ios/chrome/widget_extension/BUILD.gn
@@ -29,7 +29,7 @@
 
   deps = [
     "//base",
-    "//base:i18n",
+    "//components/open_from_clipboard:open_from_clipboard_impl",
     "//components/prefs",
     "//components/variations",
     "//components/version_info",
@@ -41,7 +41,6 @@
     "//ios/third_party/material_components_ios",
     "//net",
     "//ui/base",
-    "//url",
   ]
 
   libs = [
diff --git a/ios/chrome/widget_extension/DEPS b/ios/chrome/widget_extension/DEPS
new file mode 100644
index 0000000..5349816
--- /dev/null
+++ b/ios/chrome/widget_extension/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+components/open_from_clipboard",
+  "-url",
+]
diff --git a/ios/chrome/widget_extension/widget_view.h b/ios/chrome/widget_extension/widget_view.h
index 9e2ff38..6b6bd57 100644
--- a/ios/chrome/widget_extension/widget_view.h
+++ b/ios/chrome/widget_extension/widget_view.h
@@ -11,8 +11,16 @@
 // view.
 @protocol WidgetViewActionTarget
 
-// Called when the user taps the fake omnibox.
-- (void)openApp:(id)sender;
+// Called when the user taps the Search button.
+- (void)openSearch:(id)sender;
+// Called when the user taps the Incognito Search button.
+- (void)openIncognito:(id)sender;
+// Called when the user taps the Voice Search button.
+- (void)openVoice:(id)sender;
+// Called when the user taps the QR Code button.
+- (void)openQRCode:(id)sender;
+// Called when the user taps the Open Copied URL section.
+- (void)openCopiedURL:(id)sender;
 
 @end
 
@@ -27,6 +35,9 @@
 - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
 - (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
 
+// Updates the copied URL.
+- (void)updateCopiedURL:(NSString*)copiedURL;
+
 @end
 
 #endif  // IOS_CHROME_WIDGET_EXTENSION_WIDGET_VIEW_H_
diff --git a/ios/chrome/widget_extension/widget_view.mm b/ios/chrome/widget_extension/widget_view.mm
index d746fd0..d078fe0 100644
--- a/ios/chrome/widget_extension/widget_view.mm
+++ b/ios/chrome/widget_extension/widget_view.mm
@@ -25,6 +25,8 @@
   __weak id<WidgetViewActionTarget> _target;
 }
 
+@property(nonatomic, copy) NSString* copiedURL;
+@property(nonatomic, strong) UILabel* copiedURLLabel;
 @property(nonatomic, weak) UIView* cursor;
 
 // Creates and adds a fake omnibox with blinking cursor to the view and sets the
@@ -35,6 +37,9 @@
 
 @implementation WidgetView
 
+@synthesize copiedURL = _copiedURL;
+@synthesize copiedURLLabel = _copiedURLLabel;
+
 @synthesize cursor = _cursor;
 
 - (instancetype)initWithActionTarget:(id<WidgetViewActionTarget>)target {
@@ -52,7 +57,7 @@
 
   UIGestureRecognizer* tapRecognizer =
       [[UITapGestureRecognizer alloc] initWithTarget:_target
-                                              action:@selector(openApp:)];
+                                              action:@selector(openSearch:)];
 
   [fakebox addGestureRecognizer:tapRecognizer];
   [self addSubview:fakebox];
@@ -93,4 +98,9 @@
                    completion:nil];
 }
 
+- (void)updateCopiedURL:(NSString*)copiedURL {
+  self.copiedURL = copiedURL;
+  self.copiedURLLabel.text = copiedURL;
+}
+
 @end
diff --git a/ios/chrome/widget_extension/widget_view_controller.h b/ios/chrome/widget_extension/widget_view_controller.h
index 57cb0af..6026622 100644
--- a/ios/chrome/widget_extension/widget_view_controller.h
+++ b/ios/chrome/widget_extension/widget_view_controller.h
@@ -5,9 +5,10 @@
 #ifndef IOS_CHROME_WIDGET_EXTENSION_WIDGET_VIEW_CONTROLLER_H_
 #define IOS_CHROME_WIDGET_EXTENSION_WIDGET_VIEW_CONTROLLER_H_
 
+#import <NotificationCenter/NotificationCenter.h>
 #import <UIKit/UIKit.h>
 
-@interface WidgetViewController : UIViewController
+@interface WidgetViewController : UIViewController<NCWidgetProviding>
 
 @end
 
diff --git a/ios/chrome/widget_extension/widget_view_controller.mm b/ios/chrome/widget_extension/widget_view_controller.mm
index aa93999c..b68b97a 100644
--- a/ios/chrome/widget_extension/widget_view_controller.mm
+++ b/ios/chrome/widget_extension/widget_view_controller.mm
@@ -6,25 +6,61 @@
 
 #import <NotificationCenter/NotificationCenter.h>
 
+#include "base/ios/ios_util.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
+#include "components/open_from_clipboard/clipboard_recent_content_impl_ios.h"
 #include "ios/chrome/common/app_group/app_group_constants.h"
-#include "ios/chrome/common/x_callback_url.h"
 #import "ios/chrome/widget_extension/widget_view.h"
-#import "net/base/mac/url_conversions.h"
-#include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+namespace {
+// Using GURL in the extension is not wanted as it includes ICU which makes the
+// extension binary much larger; therefore, ios/chrome/common/x_callback_url.h
+// cannot be used. This class makes a very basic use of x-callback-url, so no
+// full implementation is required.
+NSString* const kXCallbackURLHost = @"x-callback-url";
+}  // namespace
+
 @interface WidgetViewController ()<WidgetViewActionTarget>
 @property(nonatomic, weak) WidgetView* widgetView;
+@property(nonatomic, strong) NSURL* copiedURL;
+@property(nonatomic, strong)
+    ClipboardRecentContentImplIOS* clipboardRecentContent;
+
+// Updates the widget with latest data from the clipboard. Returns whether any
+// visual updates occured.
+- (BOOL)updateWidget;
+// Opens the main application with the given |command|.
+- (void)openAppWithCommand:(NSString*)command;
+// Opens the main application with the given |command| and |parameter|.
+- (void)openAppWithCommand:(NSString*)command parameter:(NSString*)parameter;
+// Returns the dictionary of commands to pass via user defaults to open the main
+// application for a given |command| and |parameter|.
++ (NSDictionary*)dictForCommand:(NSString*)command
+                      parameter:(NSString*)parameter;
+
 @end
 
 @implementation WidgetViewController
 
 @synthesize widgetView = _widgetView;
+@synthesize copiedURL = _copiedURL;
+@synthesize clipboardRecentContent = _clipboardRecentContent;
+
+- (instancetype)init {
+  self = [super init];
+  if (self) {
+    _clipboardRecentContent = [[ClipboardRecentContentImplIOS alloc]
+        initWithAuthorizedSchemes:[NSSet setWithObjects:@"http", @"https", nil]
+                     userDefaults:app_group::GetGroupUserDefaults()
+                         delegate:nil];
+  }
+  return self;
+}
 
 #pragma mark - UIViewController
 
@@ -37,25 +73,106 @@
   self.widgetView = widgetView;
   [self.view addSubview:self.widgetView];
 
-  [self.widgetView setTranslatesAutoresizingMaskIntoConstraints:NO];
+  if (base::ios::IsRunningOnIOS10OrLater()) {
+    self.extensionContext.widgetLargestAvailableDisplayMode =
+        NCWidgetDisplayModeExpanded;
+  }
+
+  self.widgetView.translatesAutoresizingMaskIntoConstraints = NO;
+
+  NSLayoutConstraint* heightAnchor = [self.widgetView.heightAnchor
+      constraintEqualToAnchor:self.view.heightAnchor];
+  heightAnchor.priority = 900;
+
   [NSLayoutConstraint activateConstraints:@[
     [self.widgetView.leadingAnchor
-        constraintEqualToAnchor:[self.view leadingAnchor]],
+        constraintEqualToAnchor:self.view.leadingAnchor],
+    [self.widgetView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
     [self.widgetView.trailingAnchor
-        constraintEqualToAnchor:[self.view trailingAnchor]],
-    [self.widgetView.heightAnchor
-        constraintEqualToAnchor:[self.view heightAnchor]],
-    [self.widgetView.widthAnchor
-        constraintEqualToAnchor:[self.view widthAnchor]]
+        constraintEqualToAnchor:self.view.trailingAnchor],
+    heightAnchor,
+    [self.widgetView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
   ]];
 }
 
-- (void)openApp:(id)sender {
+- (void)viewWillAppear:(BOOL)animated {
+  [super viewWillAppear:animated];
+  [self updateWidget];
+}
+
+- (void)widgetPerformUpdateWithCompletionHandler:
+    (void (^)(NCUpdateResult))completionHandler {
+  completionHandler([self updateWidget] ? NCUpdateResultNewData
+                                        : NCUpdateResultNoData);
+}
+
+- (BOOL)updateWidget {
+  NSURL* url = [_clipboardRecentContent recentURLFromClipboard];
+
+  if (![url isEqual:self.copiedURL]) {
+    self.copiedURL = url;
+    [self.widgetView updateCopiedURL:self.copiedURL.absoluteString];
+    return YES;
+  }
+  return NO;
+}
+
+#pragma mark - NCWidgetProviding
+
+- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode
+                         withMaximumSize:(CGSize)maxSize {
+  CGSize fittingSize = [self.widgetView
+      systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
+  if (fittingSize.height > maxSize.height) {
+    self.preferredContentSize = maxSize;
+  } else {
+    self.preferredContentSize = fittingSize;
+  }
+}
+
+#pragma mark - WidgetViewActionTarget
+
+- (void)openSearch:(id)sender {
+  [self openAppWithCommand:base::SysUTF8ToNSString(
+                               app_group::kChromeAppGroupFocusOmniboxCommand)];
+}
+
+- (void)openIncognito:(id)sender {
+  [self
+      openAppWithCommand:base::SysUTF8ToNSString(
+                             app_group::kChromeAppGroupIncognitoSearchCommand)];
+}
+
+- (void)openVoice:(id)sender {
+  [self openAppWithCommand:base::SysUTF8ToNSString(
+                               app_group::kChromeAppGroupVoiceSearchCommand)];
+}
+
+- (void)openQRCode:(id)sender {
+  [self openAppWithCommand:base::SysUTF8ToNSString(
+                               app_group::kChromeAppGroupQRScannerCommand)];
+}
+
+- (void)openCopiedURL:(id)sender {
+  DCHECK(self.copiedURL);
+  [self openAppWithCommand:base::SysUTF8ToNSString(
+                               app_group::kChromeAppGroupOpenURLCommand)
+                 parameter:self.copiedURL.absoluteString];
+}
+
+#pragma mark - internal
+
+- (void)openAppWithCommand:(NSString*)command {
+  return [self openAppWithCommand:command parameter:nil];
+}
+
+- (void)openAppWithCommand:(NSString*)command parameter:(NSString*)parameter {
   NSUserDefaults* sharedDefaults =
       [[NSUserDefaults alloc] initWithSuiteName:app_group::ApplicationGroup()];
   NSString* defaultsKey =
       base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandPreference);
-  [sharedDefaults setObject:[WidgetViewController commandDict]
+  [sharedDefaults setObject:[WidgetViewController dictForCommand:command
+                                                       parameter:parameter]
                      forKey:defaultsKey];
   [sharedDefaults synchronize];
 
@@ -63,22 +180,37 @@
       objectForInfoDictionaryKey:@"KSChannelChromeScheme"]);
   if (!scheme)
     return;
-  const GURL openURL =
-      CreateXCallbackURL(base::SysNSStringToUTF8(scheme),
-                         app_group::kChromeAppGroupXCallbackCommand);
-  [self.extensionContext openURL:net::NSURLWithGURL(openURL)
-               completionHandler:nil];
+
+  NSURLComponents* urlComponents = [NSURLComponents new];
+  urlComponents.scheme = scheme;
+  urlComponents.host = kXCallbackURLHost;
+  urlComponents.path = [NSString
+      stringWithFormat:@"/%@", base::SysUTF8ToNSString(
+                                   app_group::kChromeAppGroupXCallbackCommand)];
+
+  NSURL* openURL = [urlComponents URL];
+  [self.extensionContext openURL:openURL completionHandler:nil];
 }
 
-+ (NSDictionary*)commandDict {
-  NSString* command =
-      base::SysUTF8ToNSString(app_group::kChromeAppGroupFocusOmniboxCommand);
++ (NSDictionary*)dictForCommand:(NSString*)command
+                      parameter:(NSString*)parameter {
   NSString* timePrefKey =
       base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandTimePreference);
   NSString* appPrefKey =
       base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandAppPreference);
   NSString* commandPrefKey = base::SysUTF8ToNSString(
       app_group::kChromeAppGroupCommandCommandPreference);
+
+  if (parameter) {
+    NSString* paramPrefKey = base::SysUTF8ToNSString(
+        app_group::kChromeAppGroupCommandParameterPreference);
+    return @{
+      timePrefKey : [NSDate date],
+      appPrefKey : @"TodayExtension",
+      commandPrefKey : command,
+      paramPrefKey : parameter,
+    };
+  }
   return @{
     timePrefKey : [NSDate date],
     appPrefKey : @"TodayExtension",
diff --git a/media/audio/audio_output_delegate.h b/media/audio/audio_output_delegate.h
index 0d9c540..3c12177 100644
--- a/media/audio/audio_output_delegate.h
+++ b/media/audio/audio_output_delegate.h
@@ -32,9 +32,10 @@
 
     // Called when construction is finished and the stream is ready for
     // playout.
-    virtual void OnStreamCreated(int stream_id,
-                                 base::SharedMemory* shared_memory,
-                                 base::CancelableSyncSocket* socket) = 0;
+    virtual void OnStreamCreated(
+        int stream_id,
+        base::SharedMemory* shared_memory,
+        std::unique_ptr<base::CancelableSyncSocket> socket) = 0;
 
     // Called if stream encounters an error and has become unusable.
     virtual void OnStreamError(int stream_id) = 0;
diff --git a/media/mojo/services/mojo_audio_output_stream.cc b/media/mojo/services/mojo_audio_output_stream.cc
index 1be3171..cd656b82 100644
--- a/media/mojo/services/mojo_audio_output_stream.cc
+++ b/media/mojo/services/mojo_audio_output_stream.cc
@@ -58,7 +58,7 @@
 void MojoAudioOutputStream::OnStreamCreated(
     int stream_id,
     base::SharedMemory* shared_memory,
-    base::CancelableSyncSocket* foreign_socket) {
+    std::unique_ptr<base::CancelableSyncSocket> foreign_socket) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(stream_created_callback_);
   DCHECK(shared_memory);
@@ -71,7 +71,7 @@
   mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
       foreign_memory_handle, shared_memory->requested_size(), false);
   mojo::ScopedHandle socket_handle =
-      mojo::WrapPlatformFile(foreign_socket->handle());
+      mojo::WrapPlatformFile(foreign_socket->Release());
 
   DCHECK(buffer_handle.is_valid());
   DCHECK(socket_handle.is_valid());
diff --git a/media/mojo/services/mojo_audio_output_stream.h b/media/mojo/services/mojo_audio_output_stream.h
index 533424e..ca5e5e5 100644
--- a/media/mojo/services/mojo_audio_output_stream.h
+++ b/media/mojo/services/mojo_audio_output_stream.h
@@ -47,9 +47,10 @@
   void SetVolume(double volume) override;
 
   // AudioOutputDelegate::EventHandler implementation.
-  void OnStreamCreated(int stream_id,
-                       base::SharedMemory* shared_memory,
-                       base::CancelableSyncSocket* foreign_socket) override;
+  void OnStreamCreated(
+      int stream_id,
+      base::SharedMemory* shared_memory,
+      std::unique_ptr<base::CancelableSyncSocket> foreign_socket) override;
   void OnStreamError(int stream_id) override;
 
   // Closes connection to client and notifies owner.
diff --git a/media/mojo/services/mojo_audio_output_stream_unittest.cc b/media/mojo/services/mojo_audio_output_stream_unittest.cc
index c2b02a6..ce55ce88 100644
--- a/media/mojo/services/mojo_audio_output_stream_unittest.cc
+++ b/media/mojo/services/mojo_audio_output_stream_unittest.cc
@@ -35,6 +35,26 @@
 using AudioOutputStream = mojom::AudioOutputStream;
 using AudioOutputStreamPtr = mojo::InterfacePtr<AudioOutputStream>;
 
+class TestCancelableSyncSocket : public base::CancelableSyncSocket {
+ public:
+  TestCancelableSyncSocket() {}
+
+  void ExpectOwnershipTransfer() { expect_ownership_transfer_ = true; }
+
+  ~TestCancelableSyncSocket() override {
+    // When the handle is sent over mojo, mojo takes ownership over it and
+    // closes it. We have to make sure we do not also retain the handle in the
+    // sync socket, as the sync socket closes the handle on destruction.
+    if (expect_ownership_transfer_)
+      EXPECT_EQ(handle(), kInvalidHandle);
+  }
+
+ private:
+  bool expect_ownership_transfer_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(TestCancelableSyncSocket);
+};
+
 class MockDelegate : NON_EXPORTED_BASE(public AudioOutputDelegate) {
  public:
   MockDelegate() {}
@@ -111,7 +131,8 @@
 
 class MojoAudioOutputStreamTest : public Test {
  public:
-  MojoAudioOutputStreamTest() {}
+  MojoAudioOutputStreamTest()
+      : foreign_socket_(base::MakeUnique<TestCancelableSyncSocket>()) {}
 
   AudioOutputStreamPtr CreateAudioOutput() {
     AudioOutputStreamPtr p;
@@ -132,14 +153,15 @@
     mock_delegate_factory_.PrepareDelegateForCreation(
         base::WrapUnique(delegate_));
     EXPECT_TRUE(
-        base::CancelableSyncSocket::CreatePair(&local_, &foreign_socket_));
+        base::CancelableSyncSocket::CreatePair(&local_, foreign_socket_.get()));
     EXPECT_TRUE(mem_.CreateAnonymous(kShmemSize));
     EXPECT_CALL(mock_delegate_factory_, MockCreateDelegate(NotNull()))
         .WillOnce(SaveArg<0>(&delegate_event_handler_));
   }
 
   base::MessageLoop loop_;
-  base::CancelableSyncSocket local_, foreign_socket_;
+  base::CancelableSyncSocket local_;
+  std::unique_ptr<TestCancelableSyncSocket> foreign_socket_;
   base::SharedMemory mem_;
   StrictMock<MockDelegate>* delegate_ = nullptr;
   AudioOutputDelegate::EventHandler* delegate_event_handler_ = nullptr;
@@ -178,7 +200,9 @@
   base::RunLoop().RunUntilIdle();
 
   ASSERT_NE(nullptr, delegate_event_handler_);
-  delegate_event_handler_->OnStreamCreated(kStreamId, &mem_, &foreign_socket_);
+  foreign_socket_->ExpectOwnershipTransfer();
+  delegate_event_handler_->OnStreamCreated(kStreamId, &mem_,
+                                           std::move(foreign_socket_));
   audio_output_ptr->Play();
   impl_.reset();
   base::RunLoop().RunUntilIdle();
@@ -191,7 +215,9 @@
   EXPECT_CALL(client_, GotNotification());
 
   ASSERT_NE(nullptr, delegate_event_handler_);
-  delegate_event_handler_->OnStreamCreated(kStreamId, &mem_, &foreign_socket_);
+  foreign_socket_->ExpectOwnershipTransfer();
+  delegate_event_handler_->OnStreamCreated(kStreamId, &mem_,
+                                           std::move(foreign_socket_));
 
   base::RunLoop().RunUntilIdle();
 }
@@ -231,7 +257,9 @@
   base::RunLoop().RunUntilIdle();
 
   ASSERT_NE(nullptr, delegate_event_handler_);
-  delegate_event_handler_->OnStreamCreated(kStreamId, &mem_, &foreign_socket_);
+  foreign_socket_->ExpectOwnershipTransfer();
+  delegate_event_handler_->OnStreamCreated(kStreamId, &mem_,
+                                           std::move(foreign_socket_));
   delegate_event_handler_->OnStreamError(kStreamId);
 
   base::RunLoop().RunUntilIdle();
diff --git a/media/renderers/video_renderer_impl_unittest.cc b/media/renderers/video_renderer_impl_unittest.cc
index 18743170..b57cc19 100644
--- a/media/renderers/video_renderer_impl_unittest.cc
+++ b/media/renderers/video_renderer_impl_unittest.cc
@@ -560,7 +560,13 @@
   InitializeRenderer(&new_stream, false, true);
 }
 
-TEST_F(VideoRendererImplTest, DestroyWhileInitializing) {
+// crbug.com/697171.
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_DestroyWhileInitializing DISABLED_DestroyWhileInitializing
+#else
+#define MAYBE_DestroyWhileInitializing DestroyWhileInitializing
+#endif
+TEST_F(VideoRendererImplTest, MAYBE_DestroyWhileInitializing) {
   CallInitialize(&demuxer_stream_, NewExpectedStatusCB(PIPELINE_ERROR_ABORT),
                  false, PIPELINE_OK);
   Destroy();
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Document-createEvent-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Document-createEvent-expected.txt
index ce12dec..6771837 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Document-createEvent-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Document-createEvent-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 331 tests; 304 PASS, 27 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 331 tests; 305 PASS, 26 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS AnimationEvent should be an alias for AnimationEvent. 
 PASS createEvent('AnimationEvent') should be initialized correctly. 
 PASS animationevent should be an alias for AnimationEvent. 
@@ -292,9 +292,7 @@
 PASS Should throw NOT_SUPPORTED_ERR for pluralized non-legacy event interface "PresentationConnectionAvailableEvents" 
 PASS Should throw NOT_SUPPORTED_ERR for non-legacy event interface "PresentationConnectionCloseEvent" 
 PASS Should throw NOT_SUPPORTED_ERR for pluralized non-legacy event interface "PresentationConnectionCloseEvents" 
-FAIL Should throw NOT_SUPPORTED_ERR for non-legacy event interface "ProgressEvent" assert_throws: function "function () {
-        var evt = document.createEvent(eventInterface);
-      }" did not throw
+PASS Should throw NOT_SUPPORTED_ERR for non-legacy event interface "ProgressEvent" 
 PASS Should throw NOT_SUPPORTED_ERR for pluralized non-legacy event interface "ProgressEvents" 
 PASS Should throw NOT_SUPPORTED_ERR for non-legacy event interface "PromiseRejectionEvent" 
 PASS Should throw NOT_SUPPORTED_ERR for pluralized non-legacy event interface "PromiseRejectionEvents" 
diff --git a/third_party/WebKit/LayoutTests/fast/events/event-creation-expected.txt b/third_party/WebKit/LayoutTests/fast/events/event-creation-expected.txt
index fe9ab579..f4789d0f 100644
--- a/third_party/WebKit/LayoutTests/fast/events/event-creation-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/events/event-creation-expected.txt
@@ -72,9 +72,6 @@
 PASS document.createEvent('PopStateEvent') instanceof window.PopStateEvent is true
 PASS document.createEvent('PopStateEvent') instanceof window.Event is true
 PASS document.createEvent('PopStateEvent').constructor === window.PopStateEvent is true
-PASS document.createEvent('ProgressEvent') instanceof window.ProgressEvent is true
-PASS document.createEvent('ProgressEvent') instanceof window.Event is true
-PASS document.createEvent('ProgressEvent').constructor === window.ProgressEvent is true
 PASS document.createEvent('TextEvent') instanceof window.TextEvent is true
 PASS document.createEvent('TextEvent') instanceof window.UIEvent is true
 PASS document.createEvent('TextEvent') instanceof window.Event is true
diff --git a/third_party/WebKit/LayoutTests/fast/events/event-creation.html b/third_party/WebKit/LayoutTests/fast/events/event-creation.html
index 1a2c028..e363cdf 100644
--- a/third_party/WebKit/LayoutTests/fast/events/event-creation.html
+++ b/third_party/WebKit/LayoutTests/fast/events/event-creation.html
@@ -108,11 +108,6 @@
     shouldBeTrue("document.createEvent('PopStateEvent') instanceof window.Event");
     shouldBeTrue("document.createEvent('PopStateEvent').constructor === window.PopStateEvent");
 
-    // ProgressEvent
-    shouldBeTrue("document.createEvent('ProgressEvent') instanceof window.ProgressEvent");
-    shouldBeTrue("document.createEvent('ProgressEvent') instanceof window.Event");
-    shouldBeTrue("document.createEvent('ProgressEvent').constructor === window.ProgressEvent");
-
     // TextEvent
     shouldBeTrue("document.createEvent('TextEvent') instanceof window.TextEvent");
     shouldBeTrue("document.createEvent('TextEvent') instanceof window.UIEvent");
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/text-match-document-change-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/text-match-document-change-expected.png
index 400bcf9..744ffd4b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/text-match-document-change-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/text-match-document-change-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png
index 400bcf9..792b6d9d 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/text-match-document-change-expected.png b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/text-match-document-change-expected.png
index 3cab44d..744ffd4b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/text-match-document-change-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/text-match-document-change-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png
index 3cab44d..744ffd4b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/text-match-document-change-expected.png
Binary files differ
diff --git a/third_party/WebKit/Source/build/scripts/make_event_factory.py b/third_party/WebKit/Source/build/scripts/make_event_factory.py
index bea63b68..582a6c1e0a 100755
--- a/third_party/WebKit/Source/build/scripts/make_event_factory.py
+++ b/third_party/WebKit/Source/build/scripts/make_event_factory.py
@@ -86,7 +86,6 @@
             or name == 'MutationEvents'
             or name == 'PageTransitionEvent'
             or name == 'PopStateEvent'
-            or name == 'ProgressEvent'
             or name == 'StorageEvent'
             or name == 'SVGEvents'
             or name == 'TextEvent'
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
index 43fb66f..fc3101d 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
@@ -508,6 +508,8 @@
        node_iterator != end; ++node_iterator) {
     // inner loop; process each marker in this node
     const Node& node = *node_iterator->key;
+    if (!node.isConnected())
+      continue;
     MarkerLists* markers = node_iterator->value.Get();
     for (size_t marker_list_index = 0;
          marker_list_index < DocumentMarker::kMarkerTypeIndexesCount;
diff --git a/third_party/WebKit/Source/core/frame/UseCounter.h b/third_party/WebKit/Source/core/frame/UseCounter.h
index 87f225e..515ef265 100644
--- a/third_party/WebKit/Source/core/frame/UseCounter.h
+++ b/third_party/WebKit/Source/core/frame/UseCounter.h
@@ -895,7 +895,6 @@
     kDocumentCreateEventMutationEvent = 1173,
     kDocumentCreateEventPageTransitionEvent = 1174,
     kDocumentCreateEventPopStateEvent = 1176,
-    kDocumentCreateEventProgressEvent = 1177,
     kDocumentCreateEventTextEvent = 1182,
     kDocumentCreateEventTransitionEvent = 1183,
     kDocumentCreateEventWheelEvent = 1184,
diff --git a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
index 8630a777..02c63f86 100644
--- a/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
+++ b/third_party/WebKit/Source/platform/graphics/CanvasSurfaceLayerBridge.cpp
@@ -67,15 +67,15 @@
       new OffscreenCanvasSurfaceReferenceFactory(weak_factory_.GetWeakPtr());
 
   DCHECK(!service_.is_bound());
-  mojom::blink::OffscreenCanvasSurfaceFactoryPtr service_factory;
+  mojom::blink::OffscreenCanvasProviderPtr provider;
   Platform::Current()->GetInterfaceProvider()->GetInterface(
-      mojo::MakeRequest(&service_factory));
+      mojo::MakeRequest(&provider));
   // TODO(xlai): Ensure OffscreenCanvas commit() is still functional when a
   // frame-less HTML canvas's document is reparenting under another frame.
   // See crbug.com/683172.
-  service_factory->CreateOffscreenCanvasSurface(
-      parent_frame_sink_id_, frame_sink_id_,
-      binding_.CreateInterfacePtrAndBind(), mojo::MakeRequest(&service_));
+  provider->CreateOffscreenCanvasSurface(parent_frame_sink_id_, frame_sink_id_,
+                                         binding_.CreateInterfacePtrAndBind(),
+                                         mojo::MakeRequest(&service_));
 }
 
 CanvasSurfaceLayerBridge::~CanvasSurfaceLayerBridge() {
diff --git a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
index 29d8a5e7..9e849684 100644
--- a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
+++ b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasFrameDispatcherImpl.cpp
@@ -48,7 +48,7 @@
     // mojo channel for this special case.
     current_local_surface_id_ = local_surface_id_allocator_.GenerateId();
     DCHECK(!sink_.is_bound());
-    mojom::blink::OffscreenCanvasCompositorFrameSinkProviderPtr provider;
+    mojom::blink::OffscreenCanvasProviderPtr provider;
     Platform::Current()->GetInterfaceProvider()->GetInterface(
         mojo::MakeRequest(&provider));
     provider->CreateCompositorFrameSink(frame_sink_id_,
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp
index 34ced16..edcd8be5 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp
@@ -54,7 +54,8 @@
             new WebGraphicsContext3DProviderSoftwareRenderingForTests(
                 std::move(gl)));
     drawing_buffer_ = DrawingBufferForTests::Create(
-        std::move(provider), nullptr, initial_size, DrawingBuffer::kPreserve);
+        std::move(provider), nullptr, initial_size, DrawingBuffer::kPreserve,
+        kDisableMultisampling);
     CHECK(drawing_buffer_);
   }
 
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
index 8d8becf..692dafc7 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
@@ -66,7 +66,9 @@
 
 class DrawingBufferTest : public Test {
  protected:
-  void SetUp() override {
+  void SetUp() override { Init(kDisableMultisampling); }
+
+  void Init(UseMultisampling use_multisampling) {
     IntSize initial_size(kInitialWidth, kInitialHeight);
     std::unique_ptr<GLES2InterfaceForTests> gl =
         WTF::WrapUnique(new GLES2InterfaceForTests);
@@ -76,7 +78,8 @@
         WTF::WrapUnique(
             new WebGraphicsContext3DProviderForTests(std::move(gl)));
     drawing_buffer_ = DrawingBufferForTests::Create(
-        std::move(provider), gl_, initial_size, DrawingBuffer::kPreserve);
+        std::move(provider), gl_, initial_size, DrawingBuffer::kPreserve,
+        use_multisampling);
     CHECK(drawing_buffer_);
   }
 
@@ -126,6 +129,27 @@
   RefPtr<DrawingBufferForTests> drawing_buffer_;
 };
 
+class DrawingBufferTestMultisample : public DrawingBufferTest {
+ protected:
+  void SetUp() override { Init(kEnableMultisampling); }
+};
+
+TEST_F(DrawingBufferTestMultisample, verifyMultisampleResolve) {
+  // Initial state: already marked changed, multisampled
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
+  EXPECT_TRUE(drawing_buffer_->ExplicitResolveOfMultisampleData());
+
+  // Resolve the multisample buffer
+  drawing_buffer_->ResolveAndBindForReadAndDraw();
+
+  // After resolve, acknowledge new content
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
+  // No new content
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
+
+  drawing_buffer_->BeginDestruction();
+}
+
 TEST_F(DrawingBufferTest, verifyResizingProperlyAffectsMailboxes) {
   VerifyStateWasRestored();
   cc::TextureMailbox texture_mailbox;
@@ -135,7 +159,7 @@
   IntSize alternate_size(kInitialWidth, kAlternateHeight);
 
   // Produce one mailbox at size 100x100.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   VerifyStateWasRestored();
@@ -148,7 +172,7 @@
   VerifyStateWasRestored();
 
   // Produce a mailbox at this size.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   EXPECT_EQ(alternate_size, gl_->MostRecentlyProducedSize());
@@ -162,7 +186,7 @@
   VerifyStateWasRestored();
 
   // Prepare another mailbox and verify that it's the correct size.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
@@ -170,7 +194,7 @@
 
   // Prepare one final mailbox and verify that it's the correct size.
   release_callback->Run(gpu::SyncToken(), false /* lostResource */);
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   VerifyStateWasRestored();
@@ -193,23 +217,23 @@
   IntSize initial_size(kInitialWidth, kInitialHeight);
 
   // Produce mailboxes.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   drawing_buffer_->ClearFramebuffers(GL_STENCIL_BUFFER_BIT);
   VerifyStateWasRestored();
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox1,
                                                      &release_callback1));
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   drawing_buffer_->ClearFramebuffers(GL_DEPTH_BUFFER_BIT);
   VerifyStateWasRestored();
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox2,
                                                      &release_callback2));
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   drawing_buffer_->ClearFramebuffers(GL_COLOR_BUFFER_BIT);
   VerifyStateWasRestored();
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox3,
                                                      &release_callback3));
 
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   release_callback1->Run(gpu::SyncToken(), false /* lostResource */);
 
   drawing_buffer_->BeginDestruction();
@@ -219,11 +243,11 @@
   drawing_buffer_.Clear();
   ASSERT_EQ(live, true);
 
-  raw_pointer->MarkContentsChanged();
+  EXPECT_FALSE(raw_pointer->MarkContentsChanged());
   release_callback2->Run(gpu::SyncToken(), false /* lostResource */);
   ASSERT_EQ(live, true);
 
-  raw_pointer->MarkContentsChanged();
+  EXPECT_FALSE(raw_pointer->MarkContentsChanged());
   release_callback3->Run(gpu::SyncToken(), false /* lostResource */);
   ASSERT_EQ(live, false);
 }
@@ -239,27 +263,27 @@
   cc::TextureMailbox texture_mailbox3;
   std::unique_ptr<cc::SingleReleaseCallback> release_callback3;
 
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox1,
                                                      &release_callback1));
   VerifyStateWasRestored();
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox2,
                                                      &release_callback2));
   VerifyStateWasRestored();
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox3,
                                                      &release_callback3));
   VerifyStateWasRestored();
 
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   release_callback1->Run(gpu::SyncToken(), true /* lostResource */);
   EXPECT_EQ(live, true);
 
   drawing_buffer_->BeginDestruction();
   EXPECT_EQ(live, true);
 
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   release_callback2->Run(gpu::SyncToken(), false /* lostResource */);
   EXPECT_EQ(live, true);
 
@@ -267,7 +291,7 @@
   drawing_buffer_.Clear();
   EXPECT_EQ(live, true);
 
-  raw_ptr->MarkContentsChanged();
+  EXPECT_FALSE(raw_ptr->MarkContentsChanged());
   release_callback3->Run(gpu::SyncToken(), true /* lostResource */);
   EXPECT_EQ(live, false);
 }
@@ -281,29 +305,29 @@
   std::unique_ptr<cc::SingleReleaseCallback> release_callback3;
 
   // Produce mailboxes.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox1,
                                                      &release_callback1));
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox2,
                                                      &release_callback2));
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox3,
                                                      &release_callback3));
 
   // Release mailboxes by specific order; 1, 3, 2.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   release_callback1->Run(gpu::SyncToken(), false /* lostResource */);
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   release_callback3->Run(gpu::SyncToken(), false /* lostResource */);
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   release_callback2->Run(gpu::SyncToken(), false /* lostResource */);
 
   // The first recycled mailbox must be 2. 1 and 3 were deleted by FIFO order
   // because DrawingBuffer never keeps more than one mailbox.
   cc::TextureMailbox recycled_texture_mailbox1;
   std::unique_ptr<cc::SingleReleaseCallback> recycled_release_callback1;
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(
       &recycled_texture_mailbox1, &recycled_release_callback1));
   EXPECT_EQ(texture_mailbox2.mailbox(), recycled_texture_mailbox1.mailbox());
@@ -311,7 +335,7 @@
   // The second recycled mailbox must be a new mailbox.
   cc::TextureMailbox recycled_texture_mailbox2;
   std::unique_ptr<cc::SingleReleaseCallback> recycled_release_callback2;
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(
       &recycled_texture_mailbox2, &recycled_release_callback2));
   EXPECT_NE(texture_mailbox1.mailbox(), recycled_texture_mailbox2.mailbox());
@@ -328,7 +352,7 @@
   std::unique_ptr<cc::SingleReleaseCallback> release_callback;
 
   // Produce mailboxes.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
@@ -342,7 +366,7 @@
   // m_drawingBuffer will wait for the sync point when recycling.
   EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken());
 
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   // m_drawingBuffer waits for the sync point when recycling in
@@ -375,7 +399,8 @@
     image_id0_ = gl_->NextImageIdToBeCreated();
     EXPECT_CALL(*gl_, BindTexImage2DMock(image_id0_)).Times(1);
     drawing_buffer_ = DrawingBufferForTests::Create(
-        std::move(provider), gl_, initial_size, DrawingBuffer::kPreserve);
+        std::move(provider), gl_, initial_size, DrawingBuffer::kPreserve,
+        kDisableMultisampling);
     CHECK(drawing_buffer_);
     testing::Mock::VerifyAndClearExpectations(gl_);
   }
@@ -399,7 +424,7 @@
   GLuint image_id1 = gl_->NextImageIdToBeCreated();
   EXPECT_CALL(*gl_, BindTexImage2DMock(image_id1)).Times(1);
   // Produce one mailbox at size 100x100.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
@@ -423,7 +448,7 @@
   GLuint image_id3 = gl_->NextImageIdToBeCreated();
   EXPECT_CALL(*gl_, BindTexImage2DMock(image_id3)).Times(1);
   // Produce a mailbox at this size.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   EXPECT_EQ(alternate_size, gl_->MostRecentlyProducedSize());
@@ -446,7 +471,7 @@
   GLuint image_id5 = gl_->NextImageIdToBeCreated();
   EXPECT_CALL(*gl_, BindTexImage2DMock(image_id5)).Times(1);
   // Prepare another mailbox and verify that it's the correct size.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
@@ -455,7 +480,7 @@
 
   // Prepare one final mailbox and verify that it's the correct size.
   release_callback->Run(gpu::SyncToken(), false /* lostResource */);
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
@@ -482,7 +507,7 @@
   // as expected.
   EXPECT_CALL(*gl_, BindTexImage2DMock(_)).Times(1);
   IntSize initial_size(kInitialWidth, kInitialHeight);
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox1,
                                                      &release_callback1));
   EXPECT_TRUE(texture_mailbox1.is_overlay_candidate());
@@ -492,7 +517,7 @@
   // Force image CHROMIUM creation failure. Request another mailbox. It should
   // still be provided, but this time with allowOverlay = false.
   gl_->SetCreateImageChromiumFail(true);
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox2,
                                                      &release_callback2));
   EXPECT_FALSE(texture_mailbox2.is_overlay_candidate());
@@ -502,7 +527,7 @@
   // correctly created with allowOverlay = true.
   EXPECT_CALL(*gl_, BindTexImage2DMock(_)).Times(1);
   gl_->SetCreateImageChromiumFail(false);
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox3,
                                                      &release_callback3));
   EXPECT_TRUE(texture_mailbox3.is_overlay_candidate());
@@ -673,7 +698,7 @@
   std::unique_ptr<cc::SingleReleaseCallback> release_callback;
 
   // Produce mailboxes.
-  drawing_buffer_->MarkContentsChanged();
+  EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTextureMailbox(&texture_mailbox,
                                                      &release_callback));
 
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
index fa44cfc..7cc062f 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
@@ -18,21 +18,27 @@
   kAlternateHeight = 50,
 };
 
+enum UseMultisampling {
+  kDisableMultisampling,
+  kEnableMultisampling,
+};
+
 class DrawingBufferForTests : public DrawingBuffer {
  public:
   static PassRefPtr<DrawingBufferForTests> Create(
       std::unique_ptr<WebGraphicsContext3DProvider> context_provider,
       DrawingBuffer::Client* client,
       const IntSize& size,
-      PreserveDrawingBuffer preserve) {
+      PreserveDrawingBuffer preserve,
+      UseMultisampling use_multisampling) {
     std::unique_ptr<Extensions3DUtil> extensions_util =
         Extensions3DUtil::Create(context_provider->ContextGL());
     RefPtr<DrawingBufferForTests> drawing_buffer =
         AdoptRef(new DrawingBufferForTests(std::move(context_provider),
                                            std::move(extensions_util), client,
                                            preserve));
-    bool multisample_extension_supported = false;
-    if (!drawing_buffer->Initialize(size, multisample_extension_supported)) {
+    if (!drawing_buffer->Initialize(
+            size, use_multisampling != kDisableMultisampling)) {
       drawing_buffer->BeginDestruction();
       return nullptr;
     }
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py
index 1369b74..1a830a3 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py
@@ -793,7 +793,8 @@
             log_callback('installing apk if necessary')
             self._device.Install(driver_host_path)
         except (device_errors.CommandFailedError,
-                device_errors.CommandTimeoutError) as exc:
+                device_errors.CommandTimeoutError,
+                device_errors.DeviceUnreachableError) as exc:
             self._abort('Failed to install %s onto device: %s' % (driver_host_path, str(exc)))
 
     def _push_fonts(self, log_callback):
diff --git a/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom b/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom
index 9626b9d..d17270b 100644
--- a/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom
+++ b/third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_canvas_surface.mojom
@@ -4,7 +4,6 @@
 
 module blink.mojom;
 
-import "cc/ipc/compositor_frame.mojom";
 import "cc/ipc/frame_sink_id.mojom";
 import "cc/ipc/frame_sink_manager.mojom";
 import "cc/ipc/mojo_compositor_frame_sink.mojom";
@@ -16,17 +15,20 @@
   Satisfy(cc.mojom.SurfaceSequence sequence);
 };
 
-interface OffscreenCanvasSurfaceFactory {
+// Creates OffscreenCanvasSurface and MojoCompositorFrameSink instances for use
+// with offscreen canvas.
+interface OffscreenCanvasProvider {
+  // TODO(kylechar): Observer interface shouldn't be FrameSinkManagerClient.
+  // Create an OffscreenCanvasSurface for |frame_sink_id|. |client| will observe
+  // any changes to the SurfaceId associated with |frame_sink_id|.
   CreateOffscreenCanvasSurface(cc.mojom.FrameSinkId parent_frame_sink_id,
                                cc.mojom.FrameSinkId frame_sink_id,
                                cc.mojom.FrameSinkManagerClient client,
-                               OffscreenCanvasSurface& service);
-};
+                               OffscreenCanvasSurface& surface);
 
-interface OffscreenCanvasCompositorFrameSinkProvider {
-  // TODO(fsamuel, xlai): Replace this with FrameSinkManager
+  // Create an MojoCompositorFrameSink for |frame_sink_id|. This must happen
+  // after creating an OffsreenCanvasSurface for |frame_sink_id|.
   CreateCompositorFrameSink(cc.mojom.FrameSinkId frame_sink_id,
                             cc.mojom.MojoCompositorFrameSinkClient client,
                             cc.mojom.MojoCompositorFrameSink& sink);
-};
-
+};
\ No newline at end of file
diff --git a/tools/cygprofile/profile_android_startup.py b/tools/cygprofile/profile_android_startup.py
index 96d1b4c..914f7d19 100755
--- a/tools/cygprofile/profile_android_startup.py
+++ b/tools/cygprofile/profile_android_startup.py
@@ -230,7 +230,8 @@
     self._device.PushChangedFiles([(self._cygprofile_tests, device_path)])
     try:
       self._device.RunShellCommand(device_path, check_return=True)
-    except device_errors.CommandFailedError:
+    except (device_errors.CommandFailedError,
+            device_errors.DeviceUnreachableError):
       # TODO(jbudorick): Let the exception propagate up once clients can
       # handle it.
       logging.exception('Failure while running cygprofile_unittests:')