diff --git a/AUTHORS b/AUTHORS
index 0835163..20ba2287 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -816,6 +816,7 @@
 Debug Wang <debugwang@tencent.com>
 Zeqin Chen <talonchen@tencent.com>
 Yong Wang <ccyongwang@tencent.com>
+Charles Vaughn <cvaughn@gmail.com>
 
 BlackBerry Limited <*@blackberry.com>
 Code Aurora Forum <*@codeaurora.org>
@@ -837,4 +838,5 @@
 ARM Holdings <*@arm.com>
 Macadamian <*@macadamian.com>
 IBM Inc. <*@*.ibm.com>
+Tableau Software <*@tableau.com>
 Akamai Inc. <*@akamai.com>
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance.py b/build/android/pylib/instrumentation/instrumentation_test_instance.py
index ef260f7a..93e3a633 100644
--- a/build/android/pylib/instrumentation/instrumentation_test_instance.py
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance.py
@@ -498,7 +498,6 @@
     self._store_tombstones = False
     self._initializeTombstonesAttributes(args)
 
-    self._should_save_images = None
     self._should_save_logcat = None
     self._initializeLogAttributes(args)
 
@@ -679,7 +678,6 @@
 
   def _initializeLogAttributes(self, args):
     self._should_save_logcat = bool(args.json_results_file)
-    self._should_save_images = bool(args.json_results_file)
 
   def _initializeEditPrefsAttributes(self, args):
     if not hasattr(args, 'shared_prefs_file'):
@@ -740,10 +738,6 @@
     return self._flags
 
   @property
-  def should_save_images(self):
-    return self._should_save_images
-
-  @property
   def should_save_logcat(self):
     return self._should_save_logcat
 
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 7bfe3f5..1cc82f96 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -6,7 +6,6 @@
 import os
 import posixpath
 import re
-import tempfile
 import time
 
 from devil.android import device_errors
@@ -15,25 +14,15 @@
 from devil.utils import reraiser_thread
 from pylib import valgrind_tools
 from pylib.android import logdog_logcat_monitor
-from pylib.constants import host_paths
 from pylib.base import base_test_result
 from pylib.instrumentation import instrumentation_test_instance
 from pylib.local.device import local_device_environment
 from pylib.local.device import local_device_test_run
-from pylib.utils import google_storage_helper
 from pylib.utils import logdog_helper
 from py_trace_event import trace_event
 from py_utils import contextlib_ext
-from py_utils import tempfile_ext
 import tombstones
 
-try:
-  from PIL import Image  # pylint: disable=import-error
-  from PIL import ImageChops  # pylint: disable=import-error
-  can_compute_diffs = True
-except ImportError:
-  can_compute_diffs = False
-
 _TAG = 'test_runner_py'
 
 TIMEOUT_ANNOTATIONS = [
@@ -46,15 +35,6 @@
   ('SmallTest', 1 * 60),
 ]
 
-_RE_RENDER_IMAGE_NAME = re.compile(
-      r'(?P<test_class>\w+)\.'
-      r'(?P<description>\w+)\.'
-      r'(?P<device_model>\w+)\.'
-      r'(?P<orientation>port|land)\.png')
-
-RENDER_TESTS_RESULTS_DIR = {
-  'ChromePublicTest': 'chrome/test/data/android/render_tests'
-}
 
 # TODO(jbudorick): Make this private once the instrumentation test_runner is
 # deprecated.
@@ -341,8 +321,6 @@
       if logcat_url:
         result.SetLink('logcat', logcat_url)
 
-    self._ProcessRenderTestResults(device, results)
-
     # Update the result name if the test used flags.
     if flags:
       for r in results:
@@ -374,19 +352,11 @@
         file_name = '%s-%s.png' % (
             test_display_name,
             time.strftime('%Y%m%dT%H%M%S', time.localtime()))
-        screenshot_file = device.TakeScreenshot(
+        saved_dir = device.TakeScreenshot(
             os.path.join(self._test_instance.screenshot_dir, file_name))
         logging.info(
             'Saved screenshot for %s to %s.',
-            test_display_name, screenshot_file)
-        if self._test_instance.should_save_images:
-          link = google_storage_helper.upload(
-              google_storage_helper.unique_name('screenshot', device=device),
-              screenshot_file,
-              bucket='chromium-render-tests')
-          for result in results:
-            result.SetLink('failure_screenshot', link)
-
+            test_display_name, saved_dir)
       logging.info('detected failure in %s. raw output:', test_display_name)
       for l in output:
         logging.info('  %s', l)
@@ -398,6 +368,7 @@
             else None)
         device.ClearApplicationState(self._test_instance.package_info.package,
                                      permissions=permissions)
+
     else:
       logging.debug('raw output from %s:', test_display_name)
       for l in output:
@@ -425,95 +396,6 @@
           result.SetLink('tombstones', tombstones_url)
     return results, None
 
-  def _ProcessRenderTestResults(self, device, results):
-    render_results_dir = RENDER_TESTS_RESULTS_DIR.get(self._test_instance.suite)
-    if not render_results_dir:
-      return
-
-    failure_images_device_dir = posixpath.join(
-        device.GetExternalStoragePath(),
-        'chromium_tests_root', render_results_dir, 'failures')
-    if not device.FileExists(failure_images_device_dir):
-      return
-
-    if self._test_instance.should_save_images:
-      with tempfile_ext.NamedTemporaryDirectory() as temp_dir:
-        device.PullFile(failure_images_device_dir, temp_dir)
-        device.RemovePath(failure_images_device_dir, recursive=True)
-
-        for failure_filename in os.listdir(
-            os.path.join(temp_dir, 'failures')):
-
-          m = _RE_RENDER_IMAGE_NAME.match(failure_filename)
-          if not m:
-            logging.warning('Unexpected file in render test failures: %s',
-                            failure_filename)
-            continue
-
-          failure_filepath = os.path.join(
-              temp_dir, 'failures', failure_filename)
-          failure_link = google_storage_helper.upload(
-              google_storage_helper.unique_name(
-                  failure_filename, device=device),
-              failure_filepath,
-              bucket='chromium-render-tests')
-
-          golden_filepath = os.path.join(
-              host_paths.DIR_SOURCE_ROOT, render_results_dir,
-              failure_filename)
-          if not os.path.exists(golden_filepath):
-            logging.error('Cannot find golden image for %s', failure_filename)
-            continue
-          golden_link = google_storage_helper.upload(
-              google_storage_helper.unique_name(
-                  failure_filename, device=device),
-              golden_filepath,
-              bucket='chromium-render-tests')
-
-          if can_compute_diffs:
-            diff_filename = '_diff'.join(
-                os.path.splitext(failure_filename))
-            diff_filepath = os.path.join(temp_dir, diff_filename)
-            (ImageChops.difference(
-                Image.open(failure_filepath), Image.open(golden_filepath))
-                    .convert('L')
-                    .point(lambda i: 255 if i else 0)
-                    .save(diff_filepath))
-            diff_link = google_storage_helper.upload(
-                google_storage_helper.unique_name(
-                    diff_filename, device=device),
-                diff_filepath,
-                bucket='chromium-render-tests')
-          else:
-            diff_link = ''
-            logging.error('Error importing PIL library. Image diffs for '
-                          'render test results will not be computed.')
-
-          with tempfile.NamedTemporaryFile(suffix='.html') as temp_html:
-            temp_html.write('''
-                <html>
-                <table>
-                <tr>
-                  <th>Failure</th>
-                  <th>Golden</th>
-                  <th>Diff</th>
-                </tr>
-                <tr>
-                  <td><img src="%s"/></td>
-                  <td><img src="%s"/></td>
-                  <td><img src="%s"/></td>
-                </tr>
-                </table>
-                </html>
-                ''' % (failure_link, golden_link, diff_link))
-            html_results_link = google_storage_helper.upload(
-                google_storage_helper.unique_name(
-                    'render_html', device=device),
-                temp_html.name,
-                bucket='chromium-render-tests')
-            for result in results:
-              result.SetLink(failure_filename, html_results_link)
-
   #override
   def _ShouldRetry(self, test):
     if 'RetryOnFailure' in test.get('annotations', {}):
diff --git a/build/android/pylib/utils/google_storage_helper.py b/build/android/pylib/utils/google_storage_helper.py
deleted file mode 100644
index 2fbbc15..0000000
--- a/build/android/pylib/utils/google_storage_helper.py
+++ /dev/null
@@ -1,57 +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.
-
-"""Helper functions to upload data to Google Storage.
-
-Text data should be streamed to logdog using |logdog_helper| module.
-Due to logdog not having image or HTML viewer, those instead should be uploaded
-to Google Storage directly using this module.
-"""
-
-import logging
-import os
-import sys
-import time
-
-from devil.utils import cmd_helper
-from pylib.constants import host_paths
-from pylib.utils import decorators
-
-sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'build'))
-import find_depot_tools  # pylint: disable=import-error
-
-_URL_TEMPLATE = 'https://storage.googleapis.com/%s/'
-
-
-@decorators.NoRaiseException(default_return_value='')
-def upload(name, filepath, bucket):
-  """Uploads data to Google Storage.
-
-  Args:
-    name: Name of the file on Google Storage.
-    filepath: Path to file you want to upload.
-    bucket: Bucket to upload file to.
-  """
-  gs_path = os.path.join('gs://%s/' % bucket, name)
-  logging.info('Uploading %s to %s', filepath, gs_path)
-  cmd_helper.RunCmd(
-      [os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gsutil.py'), 'cp',
-       filepath, gs_path])
-
-  return os.path.join(_URL_TEMPLATE % bucket, name)
-
-
-def unique_name(basename, timestamp=True, device=None):
-  """Helper function for creating a unique name for a logdog stream.
-
-  Args:
-    basename: Base of the unique name.
-    timestamp: Whether or not to add a timestamp to name.
-    device: Device to add device serial of to name.
-  """
-  return '%s%s%s' % (
-      basename,
-      '_%s' % time.strftime('%Y%m%dT%H%M%S', time.localtime())
-      if timestamp else '',
-      '_%s' % device.serial if device else '')
diff --git a/build/android/pylib/utils/logdog_helper.py b/build/android/pylib/utils/logdog_helper.py
index fc933d3..8dfab7e 100644
--- a/build/android/pylib/utils/logdog_helper.py
+++ b/build/android/pylib/utils/logdog_helper.py
@@ -27,7 +27,7 @@
   Returns:
     Link to view uploaded text in logdog viewer.
   """
-  logging.info('Writing text to logdog stream, %s', name)
+  logging.debug('Writing text to logdog stream, %s', name)
   with get_logdog_client().text(name) as stream:
     stream.write(data)
     return stream.get_viewer_url()
@@ -43,7 +43,7 @@
   Returns:
     A file like object. close() file when done.
   """
-  logging.info('Opening text logdog stream, %s', name)
+  logging.debug('Opening text logdog stream, %s', name)
   return get_logdog_client().open_text(name)
 
 
@@ -58,7 +58,7 @@
   Returns:
     Link to view uploaded binary in logdog viewer.
   """
-  logging.info('Writing binary to logdog stream, %s', name)
+  logging.debug('Writing binary to logdog stream, %s', name)
   with get_logdog_client().binary(name) as stream:
     with open(binary_path, 'r') as f:
       stream.write(f.read())
@@ -82,3 +82,4 @@
 def get_logdog_client():
   logging.debug('Getting logdog client.')
   return bootstrap.ButlerBootstrap.probe().stream_client()
+
diff --git a/build/android/render_tests/process_render_test_results.py b/build/android/render_tests/process_render_test_results.py
new file mode 100755
index 0000000..9ab0d1b
--- /dev/null
+++ b/build/android/render_tests/process_render_test_results.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+#
+# 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.
+
+import argparse
+import collections
+import logging
+import os
+import posixpath
+import re
+import shutil
+import sys
+import tempfile
+import zipfile
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
+import devil_chromium
+from devil.android import device_utils
+from devil.utils import cmd_helper
+from pylib.constants import host_paths
+
+sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'build'))
+import find_depot_tools  # pylint: disable=import-error
+
+sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'))
+import jinja2  # pylint: disable=import-error
+
+try:
+  from PIL import Image  # pylint: disable=import-error
+  from PIL import ImageChops  # pylint: disable=import-error
+  can_compute_diffs = True
+except ImportError:
+  can_compute_diffs = False
+  logging.exception('Error importing PIL library. Image diffs will not be '
+                    'displayed properly unless PIL module is installed.')
+
+_RE_IMAGE_NAME = re.compile(
+    r'(?P<test_class>\w+)\.'
+    r'(?P<description>\w+)\.'
+    r'(?P<device_model>\w+)\.'
+    r'(?P<orientation>port|land)\.png')
+
+_RENDER_TEST_BASE_URL = 'https://storage.googleapis.com/chromium-render-tests/'
+_RENDER_TEST_BUCKET = 'gs://chromium-render-tests/'
+
+_JINJA_TEMPLATE_DIR = os.path.dirname(os.path.abspath(__file__))
+_JINJA_TEMPLATE_FILENAME = 'render_webpage.html.jinja2'
+
+
+def _UploadFiles(upload_dir, files):
+  """Upload files to the render tests GS bucket."""
+  if files:
+    google_storage_upload_dir = os.path.join(_RENDER_TEST_BUCKET, upload_dir)
+    cmd = [os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gsutil.py'),
+           '-m', 'cp']
+    cmd.extend(files)
+    cmd.append(google_storage_upload_dir)
+    cmd_helper.RunCmd(cmd)
+
+
+def _GoogleStorageUrl(upload_dir, filename):
+  return os.path.join(
+      _RENDER_TEST_BASE_URL, upload_dir, os.path.basename(filename))
+
+
+def _ComputeImageDiff(failure_image, golden_image):
+  """Compute mask showing which pixels are different between two images."""
+  return (ImageChops.difference(failure_image, golden_image)
+      .convert('L')
+      .point(lambda i: 255 if i else 0))
+
+
+def ProcessRenderTestResults(devices, render_results_dir,
+                             upload_dir, html_file):
+  """Grabs render results from device and generates webpage displaying results.
+
+  Args:
+    devices: List of DeviceUtils objects to grab results from.
+    render_results_path: Path where render test results are storage.
+        Will look for failures render test results on the device in
+        /sdcard/chromium_tests_root/<render_results_path>/failures/
+        and will look for golden images at Chromium src/<render_results_path>/.
+    upload_dir: Directory to upload the render test results to.
+    html_file: File to write the test results to.
+  """
+  results_dict = collections.defaultdict(lambda: collections.defaultdict(list))
+
+  diff_upload_dir = os.path.join(upload_dir, 'diffs')
+  failure_upload_dir = os.path.join(upload_dir, 'failures')
+  golden_upload_dir = os.path.join(upload_dir, 'goldens')
+
+  diff_images = []
+  failure_images = []
+  golden_images = []
+
+  temp_dir = None
+  try:
+    temp_dir = tempfile.mkdtemp()
+
+    for device in devices:
+      failures_device_dir = posixpath.join(
+          device.GetExternalStoragePath(),
+          'chromium_tests_root', render_results_dir, 'failures')
+      device.PullFile(failures_device_dir, temp_dir)
+
+    for failure_filename in os.listdir(os.path.join(temp_dir, 'failures')):
+      m = _RE_IMAGE_NAME.match(failure_filename)
+      if not m:
+        logging.warning(
+            'Unexpected file in render test failures, %s', failure_filename)
+        continue
+      failure_file = os.path.join(temp_dir, 'failures', failure_filename)
+
+      # Check to make sure we have golden image for this failure.
+      golden_file = os.path.join(
+          host_paths.DIR_SOURCE_ROOT, render_results_dir, failure_filename)
+      if not os.path.exists(golden_file):
+        logging.error('Cannot find golden image for %s', failure_filename)
+        continue
+
+      # Compute image diff between failure and golden.
+      if can_compute_diffs:
+        diff_image = _ComputeImageDiff(
+            Image.open(failure_file), Image.open(golden_file))
+        diff_filename = '_diff'.join(
+            os.path.splitext(os.path.basename(failure_file)))
+        diff_file = os.path.join(temp_dir, diff_filename)
+        diff_image.save(diff_file)
+        diff_images.append(diff_file)
+
+      failure_images.append(failure_file)
+      golden_images.append(golden_file)
+
+      test_class = m.group('test_class')
+      device_model = m.group('device_model')
+
+      results_entry = {
+          'description': m.group('description'),
+          'orientation': m.group('orientation'),
+          'failure_image': _GoogleStorageUrl(failure_upload_dir, failure_file),
+          'golden_image': _GoogleStorageUrl(golden_upload_dir, golden_file),
+      }
+      if can_compute_diffs:
+        results_entry.update(
+            {'diff_image': _GoogleStorageUrl(diff_upload_dir, diff_file)})
+      results_dict[test_class][device_model].append(results_entry)
+
+    if can_compute_diffs:
+      _UploadFiles(diff_upload_dir, diff_images)
+    _UploadFiles(failure_upload_dir, failure_images)
+    _UploadFiles(golden_upload_dir, golden_images)
+
+    if failure_images:
+      failures_zipfile = os.path.join(temp_dir, 'failures.zip')
+      with zipfile.ZipFile(failures_zipfile, mode='w') as zf:
+        for failure_file in failure_images:
+          zf.write(failure_file, os.path.join(
+              render_results_dir, os.path.basename(failure_file)))
+        failure_zip_url = _GoogleStorageUrl(upload_dir, failures_zipfile)
+      _UploadFiles(upload_dir, [failures_zipfile])
+    else:
+      failure_zip_url = None
+
+    jinja2_env = jinja2.Environment(
+        loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR),
+        trim_blocks=True)
+    template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME)
+    #  pylint: disable=no-member
+    processed_template_output = template.render(
+        full_results=dict(results_dict),
+        failure_zip_url=failure_zip_url, show_diffs=can_compute_diffs)
+    #  pylint: enable=no-member
+    with open(html_file, 'wb') as f:
+      f.write(processed_template_output)
+  finally:
+    if temp_dir:
+      shutil.rmtree(temp_dir)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+
+  parser.add_argument('--render-results-dir',
+                      required=True,
+                      help='Path on device to look for render test images')
+  parser.add_argument('--output-html-file',
+                      required=True,
+                      help='File to output the results webpage.')
+  parser.add_argument('-d', '--device', dest='devices', action='append',
+                      default=[],
+                      help='Device to look for render test results on. '
+                           'Default is to look on all connected devices.')
+  parser.add_argument('--adb-path', type=os.path.abspath,
+                      help='Absolute path to the adb binary to use.')
+  parser.add_argument('--buildername', type=str, required=True,
+                      help='Bot buildername. Used to generate path to upload '
+                           'render test results')
+  parser.add_argument('--build-number', type=str, required=True,
+                      help='Bot build number. Used to generate path to upload '
+                           'render test results')
+
+  args = parser.parse_args()
+  devil_chromium.Initialize(adb_path=args.adb_path)
+  devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices)
+
+  upload_dir = os.path.join(args.buildername, args.build_number)
+  ProcessRenderTestResults(
+      devices, args.render_results_dir, upload_dir, args.output_html_file)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/build/android/render_tests/render_webpage.html.jinja2 b/build/android/render_tests/render_webpage.html.jinja2
new file mode 100644
index 0000000..b5ea6039
--- /dev/null
+++ b/build/android/render_tests/render_webpage.html.jinja2
@@ -0,0 +1,84 @@
+<!--
+ * 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.
+-->
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
+    <link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.blue-indigo.min.css">
+    <script defer src="https://code.getmdl.io/1.2.1/material.min.js"></script>
+
+    <style>
+      div.text-element {
+        text-align: center;
+      }
+      body {
+          background-color: #efefef;
+      }
+    </style>
+  </head>
+
+  <body>
+    {% if failure_zip_url is not none %}
+      <a href="{{ failure_zip_url }}">
+        <div class="mdl-color--primary" width="100%">
+          <h3>Download Image Zip</h3>
+        </div>
+      </a>
+    {% endif %}
+
+    {% for test_class, device_results in full_results.iteritems() %}
+      <div class="mdl-color--primary" width="100%">
+        <h3>{{ test_class }}</h3>
+      </div>
+
+      <div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
+        <div class="mdl-tabs__tab-bar">
+          {% for device_model, _ in device_results.iteritems() %}
+            <a href="#{{ device_model }}-panel" class="mdl-tabs__tab">{{ device_model }}</a>
+          {% endfor %}
+        </div>
+
+        {% for device_model, test_results in device_results.iteritems() %}
+          <div class="mdl-tabs__panel" id="{{ device_model }}-panel">
+
+            <div class="mdl-grid">
+              <div class="mdl-cell mdl-cell--3-col text-element"><b>Description</b></div>
+              <div class="mdl-cell mdl-cell--3-col text-element"><b>Golden</b></div>
+              <div class="mdl-cell mdl-cell--3-col text-element"><b>Failure</b></div>
+              {% if show_diffs %}
+                <div class="mdl-cell mdl-cell--3-col text-element"><b>Diff</b></div>
+              {% endif %}
+            </div>
+            {% for result in test_results %}
+              <div class="mdl-grid">
+                <div class="mdl-cell mdl-cell--3-col text-element">
+                  {{ result['description'] }}
+                </div>
+                <div class="mdl-cell mdl-cell--3-col">
+                  <a href="{{ result['golden_image'] }}">
+                    <img class="mdl-shadow--2dp" src="{{ result['golden_image'] }}" width="100%">
+                  </a>
+                </div>
+                <div class="mdl-cell mdl-cell--3-col mdl-shadow--2dp">
+                  <a href="{{ result['failure_image'] }}">
+                    <img src="{{ result['failure_image'] }}" width="100%">
+                  </a>
+                </div>
+                {% if show_diffs %}
+                  <div class="mdl-cell mdl-cell--3-col mdl-shadow--2dp">
+                    <a href="{{ result['diff_image'] }}">
+                      <img src="{{ result['diff_image'] }}" width="100%">
+                    </a>
+                  </div>
+                {% endif %}
+              </div>
+            {% endfor %}
+          </div>
+        {% endfor %}
+      </div>
+    {% endfor %}
+  </body>
+</html>
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index 1cb513a..2bf69ff 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -13,7 +13,6 @@
 ../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py
 ../../third_party/catapult/common/py_utils/py_utils/contextlib_ext.py
 ../../third_party/catapult/common/py_utils/py_utils/lock.py
-../../third_party/catapult/common/py_utils/py_utils/tempfile_ext.py
 ../../third_party/catapult/dependency_manager/dependency_manager/__init__.py
 ../../third_party/catapult/dependency_manager/dependency_manager/archive_info.py
 ../../third_party/catapult/dependency_manager/dependency_manager/base_config.py
@@ -121,7 +120,6 @@
 ../../tools/swarming_client/libs/logdog/stream.py
 ../../tools/swarming_client/libs/logdog/streamname.py
 ../../tools/swarming_client/libs/logdog/varint.py
-../find_depot_tools.py
 ../util/lib/common/unittest_util.py
 devil_chromium.py
 pylib/__init__.py
@@ -179,7 +177,6 @@
 pylib/utils/decorators.py
 pylib/utils/device_dependencies.py
 pylib/utils/dexdump.py
-pylib/utils/google_storage_helper.py
 pylib/utils/logdog_helper.py
 pylib/utils/proguard.py
 pylib/utils/repo_utils.py
diff --git a/build/toolchain/win/setup_toolchain.py b/build/toolchain/win/setup_toolchain.py
index ec60564..e0fcb273 100644
--- a/build/toolchain/win/setup_toolchain.py
+++ b/build/toolchain/win/setup_toolchain.py
@@ -127,6 +127,11 @@
                                        os.environ['GYP_MSVS_OVERRIDE_PATH'],
                                        'VC/vcvarsall.bat'))
     if not os.path.exists(script_path):
+      # vcvarsall.bat for VS 2017 fails if run after running vcvarsall.bat from
+      # VS 2013 or VS 2015. Fix this by clearing the vsinstalldir environment
+      # variable.
+      if 'VSINSTALLDIR' in os.environ:
+        del os.environ['VSINSTALLDIR']
       other_path = os.path.normpath(os.path.join(
                                         os.environ['GYP_MSVS_OVERRIDE_PATH'],
                                         'VC/Auxiliary/Build/vcvarsall.bat'))
diff --git a/cc/surfaces/compositor_frame_sink_support.cc b/cc/surfaces/compositor_frame_sink_support.cc
index 0d2e90bd..23e4481 100644
--- a/cc/surfaces/compositor_frame_sink_support.cc
+++ b/cc/surfaces/compositor_frame_sink_support.cc
@@ -134,11 +134,14 @@
 
   if (!client_)
     return;
-  client_->DidReceiveCompositorFrameAck();
+
+  // We return the resources before sending an ack so they can be reused in
+  // making the next CompositorFrame.
   if (!surface_returned_resources_.empty()) {
     client_->ReclaimResources(surface_returned_resources_);
     surface_returned_resources_.clear();
   }
+  client_->DidReceiveCompositorFrameAck();
 }
 
 void CompositorFrameSinkSupport::ForceReclaimResources() {
diff --git a/cc/surfaces/compositor_frame_sink_support_unittest.cc b/cc/surfaces/compositor_frame_sink_support_unittest.cc
index 83cf8ed..d738e21 100644
--- a/cc/surfaces/compositor_frame_sink_support_unittest.cc
+++ b/cc/surfaces/compositor_frame_sink_support_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "cc/surfaces/compositor_frame_sink_support.h"
 
+#include "base/debug/stack_trace.h"
 #include "base/macros.h"
 #include "cc/output/compositor_frame.h"
 #include "cc/surfaces/compositor_frame_sink_support_client.h"
@@ -18,6 +19,9 @@
 using testing::UnorderedElementsAre;
 using testing::IsEmpty;
 using testing::SizeIs;
+using testing::Invoke;
+using testing::_;
+using testing::InSequence;
 
 namespace cc {
 namespace test {
@@ -29,6 +33,34 @@
 constexpr FrameSinkId kChildFrameSink2(65564, 0);
 constexpr FrameSinkId kArbitraryFrameSink(1337, 7331);
 
+class MockCompositorFrameSinkSupportClient
+    : public CompositorFrameSinkSupportClient {
+ public:
+  MockCompositorFrameSinkSupportClient() {
+    ON_CALL(*this, ReclaimResources(_))
+        .WillByDefault(Invoke(
+            this,
+            &MockCompositorFrameSinkSupportClient::ReclaimResourcesInternal));
+  }
+
+  ReturnedResourceArray& last_returned_resources() {
+    return last_returned_resources_;
+  }
+
+  // CompositorFrameSinkSupportClient implementation.
+  MOCK_METHOD0(DidReceiveCompositorFrameAck, void());
+  MOCK_METHOD1(OnBeginFrame, void(const BeginFrameArgs&));
+  MOCK_METHOD1(ReclaimResources, void(const ReturnedResourceArray&));
+  MOCK_METHOD2(WillDrawSurface, void(const LocalSurfaceId&, const gfx::Rect&));
+
+ private:
+  void ReclaimResourcesInternal(const ReturnedResourceArray& resources) {
+    last_returned_resources_ = resources;
+  }
+
+  ReturnedResourceArray last_returned_resources_;
+};
+
 std::vector<SurfaceId> empty_surface_ids() {
   return std::vector<SurfaceId>();
 }
@@ -71,8 +103,7 @@
 
 }  // namespace
 
-class CompositorFrameSinkSupportTest : public testing::Test,
-                                       public CompositorFrameSinkSupportClient {
+class CompositorFrameSinkSupportTest : public testing::Test {
  public:
   CompositorFrameSinkSupportTest()
       : surface_manager_(SurfaceManager::LifetimeType::REFERENCES) {}
@@ -124,10 +155,6 @@
     return begin_frame_source_.get();
   }
 
-  ReturnedResourceArray& last_returned_resources() {
-    return last_returned_resources_;
-  }
-
   // testing::Test:
   void SetUp() override {
     testing::Test::SetUp();
@@ -138,20 +165,20 @@
                                      begin_frame_source_.get()));
     surface_manager_.SetDependencyTracker(std::move(dependency_tracker));
     supports_.push_back(base::MakeUnique<CompositorFrameSinkSupport>(
-        this, &surface_manager_, kDisplayFrameSink, true /* is_root */,
-        true /* handles_frame_sink_id_invalidation */,
+        &support_client_, &surface_manager_, kDisplayFrameSink,
+        true /* is_root */, true /* handles_frame_sink_id_invalidation */,
         true /* needs_sync_points */));
     supports_.push_back(base::MakeUnique<CompositorFrameSinkSupport>(
-        this, &surface_manager_, kParentFrameSink, false /* is_root */,
-        true /* handles_frame_sink_id_invalidation */,
+        &support_client_, &surface_manager_, kParentFrameSink,
+        false /* is_root */, true /* handles_frame_sink_id_invalidation */,
         true /* needs_sync_points */));
     supports_.push_back(base::MakeUnique<CompositorFrameSinkSupport>(
-        this, &surface_manager_, kChildFrameSink1, false /* is_root */,
-        true /* handles_frame_sink_id_invalidation */,
+        &support_client_, &surface_manager_, kChildFrameSink1,
+        false /* is_root */, true /* handles_frame_sink_id_invalidation */,
         true /* needs_sync_points */));
     supports_.push_back(base::MakeUnique<CompositorFrameSinkSupport>(
-        this, &surface_manager_, kChildFrameSink2, false /* is_root */,
-        true /* handles_frame_sink_id_invalidation */,
+        &support_client_, &surface_manager_, kChildFrameSink2,
+        false /* is_root */, true /* handles_frame_sink_id_invalidation */,
         true /* needs_sync_points */));
 
     // Normally, the BeginFrameSource would be registered by the Display. We
@@ -173,20 +200,13 @@
     supports_.clear();
   }
 
-  // CompositorFrameSinkSupportClient implementation.
-  void DidReceiveCompositorFrameAck() override {}
-  void OnBeginFrame(const BeginFrameArgs& args) override {}
-  void ReclaimResources(const ReturnedResourceArray& resources) override {
-    last_returned_resources_ = resources;
-  }
-  void WillDrawSurface(const LocalSurfaceId& local_surface_id,
-                       const gfx::Rect& damage_rect) override {}
+ protected:
+  testing::NiceMock<MockCompositorFrameSinkSupportClient> support_client_;
 
  private:
   SurfaceManager surface_manager_;
   std::unique_ptr<FakeExternalBeginFrameSource> begin_frame_source_;
   std::vector<std::unique_ptr<CompositorFrameSinkSupport>> supports_;
-  ReturnedResourceArray last_returned_resources_;
 
   DISALLOW_COPY_AND_ASSIGN(CompositorFrameSinkSupportTest);
 };
@@ -582,7 +602,7 @@
   EXPECT_FALSE(parent_surface()->HasPendingFrame());
   EXPECT_THAT(parent_surface()->blocking_surfaces_for_testing(), IsEmpty());
   ReturnedResource returned_resource = resource.ToReturnedResource();
-  EXPECT_THAT(last_returned_resources(),
+  EXPECT_THAT(support_client_.last_returned_resources(),
               UnorderedElementsAre(returned_resource));
 }
 
@@ -947,5 +967,22 @@
   EXPECT_EQ(ack, begin_frame_source()->LastAckForObserver(&display_support()));
 }
 
+// Checks whether the resources are returned before we send an ack.
+TEST_F(CompositorFrameSinkSupportTest, ReturnResourcesBeforeAck) {
+  const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+  TransferableResource resource;
+  resource.id = 1234;
+  parent_support().SubmitCompositorFrame(
+      parent_id.local_surface_id(),
+      MakeCompositorFrameWithResources(empty_surface_ids(), {resource}));
+  {
+    InSequence x;
+    EXPECT_CALL(support_client_, ReclaimResources(_));
+    EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck());
+  }
+  parent_support().SubmitCompositorFrame(parent_id.local_surface_id(),
+                                         CompositorFrame());
+}
+
 }  // namespace test
 }  // namespace cc
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
index 305caf0..202d6afa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
@@ -22,7 +22,6 @@
 import org.chromium.webapk.lib.client.WebApkVersion;
 
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
 /**
  * WebApkUpdateManager manages when to check for updates to the WebAPK's Web Manifest, and sends
@@ -31,15 +30,6 @@
 public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
     private static final String TAG = "WebApkUpdateManager";
 
-    /** Number of milliseconds between checks for whether the WebAPK's Web Manifest has changed. */
-    public static final long FULL_CHECK_UPDATE_INTERVAL = TimeUnit.DAYS.toMillis(3L);
-
-    /**
-     * Number of milliseconds to wait before re-requesting an updated WebAPK from the WebAPK
-     * server if the previous update attempt failed.
-     */
-    public static final long RETRY_UPDATE_DURATION = TimeUnit.HOURS.toMillis(12L);
-
     /**
      * Number of times to wait for updating the WebAPK after it is moved to the background prior
      * to doing the update while the WebAPK is in the foreground.
@@ -64,11 +54,6 @@
     /** The WebappDataStorage with cached data about prior update requests. */
     private WebappDataStorage mStorage;
 
-    /**
-     * Whether the previous WebAPK update succeeded. True if there has not been any update attempts.
-     */
-    private boolean mPreviousUpdateSucceeded;
-
     private WebApkUpdateDataFetcher mFetcher;
 
     /**
@@ -93,16 +78,15 @@
     }
 
     /**
-     * Checks whether the WebAPK's Web Manifest has changed. Requests an updated WebAPK if the
-     * Web Manifest has changed. Skips the check if the check was done recently.
+     * Checks whether the WebAPK's Web Manifest has changed. Requests an updated WebAPK if the Web
+     * Manifest has changed. Skips the check if the check was done recently.
      * @param tab  The tab of the WebAPK.
      * @param info The WebApkInfo of the WebAPK.
      */
     public void updateIfNeeded(Tab tab, WebApkInfo info) {
         mInfo = info;
-        mPreviousUpdateSucceeded = didPreviousUpdateSucceed();
 
-        if (!shouldCheckIfWebManifestUpdated(mInfo, mPreviousUpdateSucceeded)) return;
+        if (!shouldCheckIfWebManifestUpdated(mInfo)) return;
 
         mFetcher = buildFetcher();
         mFetcher.start(tab, mInfo, this);
@@ -165,15 +149,15 @@
         }
 
         if (!needsUpgrade) {
-            if (!mPreviousUpdateSucceeded) {
-                recordUpdate(mStorage, true);
+            if (!mStorage.didPreviousUpdateSucceed()) {
+                recordUpdate(mStorage, true /* success */, false /* relaxUpdates */);
             }
             return;
         }
 
         // Set WebAPK update as having failed in case that Chrome is killed prior to
         // {@link onBuiltWebApk} being called.
-        recordUpdate(mStorage, false);
+        recordUpdate(mStorage, false /* success */, false /* relaxUpdates*/);
 
         if (fetchedInfo != null) {
             scheduleUpdate(fetchedInfo, bestIconUrl, false /* isManifestStale */);
@@ -183,7 +167,7 @@
         // Tell the server that the our version of the Web Manifest might be stale and to ignore
         // our Web Manifest data if the server's Web Manifest data is newer. This scenario can
         // occur if the Web Manifest is temporarily unreachable.
-        scheduleUpdate(mInfo, "", true /* isManifestStale */);
+        scheduleUpdate(mInfo, "" /* bestIconUrl */, true /* isManifestStale */);
     }
 
     /**
@@ -263,11 +247,6 @@
         mFetcher = null;
     }
 
-    /** Returns the current time. In a separate function for the sake of testing. */
-    protected long currentTimeMillis() {
-        return System.currentTimeMillis();
-    }
-
     /**
      * Reads the WebAPK's version code. Returns 0 on failure.
      */
@@ -284,19 +263,6 @@
     }
 
     /**
-     * Returns whether the previous WebAPK update attempt succeeded. Returns true if there has not
-     * been any update attempts.
-     */
-    private boolean didPreviousUpdateSucceed() {
-        long lastUpdateCompletionTime = mStorage.getLastWebApkUpdateRequestCompletionTime();
-        if (lastUpdateCompletionTime == WebappDataStorage.LAST_USED_INVALID
-                || lastUpdateCompletionTime == WebappDataStorage.LAST_USED_UNSET) {
-            return true;
-        }
-        return mStorage.getDidLastWebApkUpdateRequestSucceed();
-    }
-
-    /**
      * Whether there is a new version of the //chrome/android/webapk/shell_apk code.
      */
     private static boolean isShellApkVersionOutOfDate(WebApkInfo info) {
@@ -307,11 +273,9 @@
      * Returns whether the Web Manifest should be refetched to check whether it has been updated.
      * TODO: Make this method static once there is a static global clock class.
      * @param info Meta data from WebAPK's Android Manifest.
-     * @param previousUpdateSucceeded Whether the previous update attempt succeeded.
      * True if there has not been any update attempts.
      */
-    private boolean shouldCheckIfWebManifestUpdated(WebApkInfo info,
-            boolean previousUpdateSucceeded) {
+    private boolean shouldCheckIfWebManifestUpdated(WebApkInfo info) {
         if (!sUpdatesEnabled) {
             return false;
         }
@@ -325,25 +289,20 @@
 
         if (isShellApkVersionOutOfDate(info)) return true;
 
-        long now = currentTimeMillis();
-        long sinceLastCheckDurationMs = now - mStorage.getLastCheckForWebManifestUpdateTime();
-        if (sinceLastCheckDurationMs >= FULL_CHECK_UPDATE_INTERVAL) return true;
-
-        long sinceLastUpdateRequestDurationMs =
-                now - mStorage.getLastWebApkUpdateRequestCompletionTime();
-        return sinceLastUpdateRequestDurationMs >= RETRY_UPDATE_DURATION
-                && !previousUpdateSucceeded;
+        return mStorage.shouldCheckForUpdate();
     }
 
     /**
      * Updates {@link WebappDataStorage} with the time of the latest WebAPK update and whether the
      * WebAPK update succeeded.
      */
-    private static void recordUpdate(WebappDataStorage storage, boolean success) {
+    private static void recordUpdate(
+            WebappDataStorage storage, boolean success, boolean relaxUpdates) {
         // Update the request time and result together. It prevents getting a correct request time
         // but a result from the previous request.
         storage.updateTimeOfLastWebApkUpdateRequestCompletion();
         storage.updateDidLastWebApkUpdateRequestSucceed(success);
+        storage.setRelaxedUpdates(relaxUpdates);
     }
 
     /**
@@ -400,11 +359,11 @@
      * fails.
      */
     @CalledByNative
-    private static void onBuiltWebApk(String id, boolean success) {
+    private static void onBuiltWebApk(String id, boolean success, boolean relaxUpdates) {
         WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(id);
         if (storage == null) return;
 
-        recordUpdate(storage, success);
+        recordUpdate(storage, success, relaxUpdates);
     }
 
     private static native void nativeUpdateAsync(String id, String startUrl, String scope,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
index cc68e661..bc4d7e1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
@@ -51,16 +51,28 @@
             "last_check_web_manifest_update_time";
 
     // The last time that the WebAPK update request completed (successfully or unsuccessfully).
-    static final String KEY_LAST_WEBAPK_UPDATE_REQUEST_COMPLETE_TIME =
-            "last_webapk_update_request_complete_time";
+    static final String KEY_LAST_UPDATE_REQUEST_COMPLETE_TIME = "last_update_request_complete_time";
 
     // Whether the last WebAPK update request succeeded.
-    static final String KEY_DID_LAST_WEBAPK_UPDATE_REQUEST_SUCCEED =
-            "did_last_webapk_update_request_succeed";
+    static final String KEY_DID_LAST_UPDATE_REQUEST_SUCCEED = "did_last_update_request_succeed";
 
     // The number of times that updating a WebAPK in the background has been requested.
     static final String KEY_UPDATE_REQUESTED = "update_requested";
 
+    // Whether to check updates less frequently.
+    static final String KEY_RELAX_UPDATES = "relax_updates";
+
+    // Number of milliseconds between checks for whether the WebAPK's Web Manifest has changed.
+    public static final long UPDATE_INTERVAL = TimeUnit.DAYS.toMillis(3L);
+
+    // Number of milliseconds between checks of updates for a WebAPK that is expected to check
+    // updates less frequently. crbug.com/680128.
+    public static final long RELAXED_UPDATE_INTERVAL = TimeUnit.DAYS.toMillis(30L);
+
+    // Number of milliseconds to wait before re-requesting an updated WebAPK from the WebAPK
+    // server if the previous update attempt failed.
+    public static final long RETRY_UPDATE_DURATION = TimeUnit.HOURS.toMillis(12L);
+
     // Unset/invalid constants for last used times and URLs. 0 is used as the null last used time as
     // WebappRegistry assumes that this is always a valid timestamp.
     static final long LAST_USED_UNSET = 0;
@@ -302,9 +314,10 @@
         editor.remove(KEY_URL);
         editor.remove(KEY_SCOPE);
         editor.remove(KEY_LAST_CHECK_WEB_MANIFEST_UPDATE_TIME);
-        editor.remove(KEY_LAST_WEBAPK_UPDATE_REQUEST_COMPLETE_TIME);
-        editor.remove(KEY_DID_LAST_WEBAPK_UPDATE_REQUEST_SUCCEED);
+        editor.remove(KEY_LAST_UPDATE_REQUEST_COMPLETE_TIME);
+        editor.remove(KEY_DID_LAST_UPDATE_REQUEST_SUCCEED);
         editor.remove(KEY_UPDATE_REQUESTED);
+        editor.remove(KEY_RELAX_UPDATES);
         editor.apply();
     }
 
@@ -367,7 +380,7 @@
      * Returns the completion time of the last check for whether the WebAPK's Web Manifest was
      * updated. This time needs to be set when the WebAPK is registered.
      */
-    long getLastCheckForWebManifestUpdateTime() {
+    private long getLastCheckForWebManifestUpdateTime() {
         return mPreferences.getLong(KEY_LAST_CHECK_WEB_MANIFEST_UPDATE_TIME, LAST_USED_INVALID);
     }
 
@@ -376,7 +389,7 @@
      */
     void updateTimeOfLastWebApkUpdateRequestCompletion() {
         mPreferences.edit()
-                .putLong(KEY_LAST_WEBAPK_UPDATE_REQUEST_COMPLETE_TIME, sClock.currentTimeMillis())
+                .putLong(KEY_LAST_UPDATE_REQUEST_COMPLETE_TIME, sClock.currentTimeMillis())
                 .apply();
     }
 
@@ -385,24 +398,21 @@
      * This time needs to be set when the WebAPK is registered.
      */
     long getLastWebApkUpdateRequestCompletionTime() {
-        return mPreferences.getLong(
-                KEY_LAST_WEBAPK_UPDATE_REQUEST_COMPLETE_TIME, LAST_USED_INVALID);
+        return mPreferences.getLong(KEY_LAST_UPDATE_REQUEST_COMPLETE_TIME, LAST_USED_INVALID);
     }
 
     /**
-     * Updates the result of whether the last update request to WebAPK Server succeeded.
+     * Updates whether the last update request to WebAPK Server succeeded.
      */
     void updateDidLastWebApkUpdateRequestSucceed(boolean success) {
-        mPreferences.edit()
-                .putBoolean(KEY_DID_LAST_WEBAPK_UPDATE_REQUEST_SUCCEED, success)
-                .apply();
+        mPreferences.edit().putBoolean(KEY_DID_LAST_UPDATE_REQUEST_SUCCEED, success).apply();
     }
 
     /**
      * Returns whether the last update request to WebAPK Server succeeded.
      */
     boolean getDidLastWebApkUpdateRequestSucceed() {
-        return mPreferences.getBoolean(KEY_DID_LAST_WEBAPK_UPDATE_REQUEST_SUCCEED, false);
+        return mPreferences.getBoolean(KEY_DID_LAST_UPDATE_REQUEST_SUCCEED, false);
     }
 
     /**
@@ -426,6 +436,42 @@
         return mPreferences.getInt(KEY_UPDATE_REQUESTED, 0);
     }
 
+    /**
+     * Returns whether the previous WebAPK update attempt succeeded. Returns true if there has not
+     * been any update attempts.
+     */
+    boolean didPreviousUpdateSucceed() {
+        long lastUpdateCompletionTime = getLastWebApkUpdateRequestCompletionTime();
+        if (lastUpdateCompletionTime == WebappDataStorage.LAST_USED_INVALID
+                || lastUpdateCompletionTime == WebappDataStorage.LAST_USED_UNSET) {
+            return true;
+        }
+        return getDidLastWebApkUpdateRequestSucceed();
+    }
+
+    /** Sets whether we should check for updates less frequently. */
+    void setRelaxedUpdates(boolean relaxUpdates) {
+        mPreferences.edit().putBoolean(KEY_RELAX_UPDATES, relaxUpdates).apply();
+    }
+
+    /** Returns whether we should check for updates less frequently. */
+    private boolean shouldRelaxUpdates() {
+        return mPreferences.getBoolean(KEY_RELAX_UPDATES, false);
+    }
+
+    /** Returns whether we should check for update. */
+    boolean shouldCheckForUpdate() {
+        long checkUpdatesInterval =
+                shouldRelaxUpdates() ? RELAXED_UPDATE_INTERVAL : UPDATE_INTERVAL;
+        long now = sClock.currentTimeMillis();
+        long sinceLastCheckDurationMs = now - getLastCheckForWebManifestUpdateTime();
+        if (sinceLastCheckDurationMs >= checkUpdatesInterval) return true;
+
+        long sinceLastUpdateRequestDurationMs = now - getLastWebApkUpdateRequestCompletionTime();
+        return sinceLastUpdateRequestDurationMs >= WebappDataStorage.RETRY_UPDATE_DURATION
+                && !didPreviousUpdateSucceed();
+    }
+
     protected WebappDataStorage(String webappId) {
         mId = webappId;
         mPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
index 9d1e5be..4dfc80a9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
@@ -101,16 +101,14 @@
     }
 
     private static class TestWebApkUpdateManager extends WebApkUpdateManager {
-        private WebappDataStorage.Clock mClock;
         private TestWebApkUpdateDataFetcher mFetcher;
         private boolean mUpdateRequested;
         private String mUpdateName;
         private boolean mDestroyedFetcher;
         private boolean mIsWebApkForeground;
 
-        public TestWebApkUpdateManager(WebappDataStorage.Clock clock, WebappDataStorage storage) {
+        public TestWebApkUpdateManager(WebappDataStorage storage) {
             super(null, storage);
-            mClock = clock;
         }
 
         /**
@@ -168,11 +166,6 @@
             mDestroyedFetcher = true;
         }
 
-        @Override
-        protected long currentTimeMillis() {
-            return mClock.currentTimeMillis();
-        }
-
         public void setIsWebApkForeground(boolean isForeground) {
             mIsWebApkForeground = isForeground;
         }
@@ -309,7 +302,7 @@
      * is-update-needed check has been triggered.
      */
     private boolean updateIfNeededChecksForUpdatedWebManifest() {
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         return updateManager.updateCheckStarted();
     }
@@ -321,9 +314,9 @@
     private boolean checkUpdateNeededForFetchedManifest(
             ManifestData androidManifestData, ManifestData fetchedManifestData) {
         registerWebApk(androidManifestData, WebApkVersion.CURRENT_SHELL_APK_VERSION);
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
         updateManager.onGotManifestData(
@@ -358,67 +351,14 @@
     }
 
     /**
-     * Test that if the WebAPK update failed (e.g. because the WebAPK server is not reachable) that
-     * the is-update-needed check is retried after less time than if the WebAPK update had
-     * succeeded.
-     * The is-update-needed check is the first step in retrying to update the WebAPK.
-     */
-    @Test
-    public void testCheckUpdateMoreFrequentlyIfUpdateFails() {
-        assertTrue(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL
-                > WebApkUpdateManager.RETRY_UPDATE_DURATION);
-
-        WebappDataStorage storage = getStorage();
-
-        assertTrue(storage.getDidLastWebApkUpdateRequestSucceed());
-        assertFalse(updateIfNeededChecksForUpdatedWebManifest());
-        mClock.advance(WebApkUpdateManager.RETRY_UPDATE_DURATION);
-        assertFalse(updateIfNeededChecksForUpdatedWebManifest());
-
-        // Advance all of the time stamps.
-        storage.updateTimeOfLastCheckForUpdatedWebManifest();
-        storage.updateTimeOfLastWebApkUpdateRequestCompletion();
-
-        storage.updateDidLastWebApkUpdateRequestSucceed(false);
-        assertFalse(updateIfNeededChecksForUpdatedWebManifest());
-        mClock.advance(WebApkUpdateManager.RETRY_UPDATE_DURATION);
-        assertTrue(updateIfNeededChecksForUpdatedWebManifest());
-    }
-
-    /**
-     * Test that if there was no previous WebAPK update attempt that the is-update-needed check is
-     * done after the usual delay (as opposed to the shorter delay if the previous WebAPK update
-     * failed.)
-     */
-    @Test
-    public void testRegularCheckIntervalIfNoPriorWebApkUpdate() {
-        assertTrue(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL
-                > WebApkUpdateManager.RETRY_UPDATE_DURATION);
-
-        getStorage().delete();
-        WebappDataStorage storage = getStorage();
-
-        // Done when WebAPK is registered in {@link WebApkActivity}.
-        storage.updateTimeOfLastCheckForUpdatedWebManifest();
-
-        assertFalse(updateIfNeededChecksForUpdatedWebManifest());
-        mClock.advance(WebApkUpdateManager.RETRY_UPDATE_DURATION);
-        assertFalse(updateIfNeededChecksForUpdatedWebManifest());
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL
-                - WebApkUpdateManager.RETRY_UPDATE_DURATION);
-        assertTrue(updateIfNeededChecksForUpdatedWebManifest());
-    }
-
-    /**
      * Test that the is-update-needed check is tried the next time that the WebAPK is launched if
      * Chrome is killed prior to the initial URL finishing loading.
      */
     @Test
     public void testCheckOnNextLaunchIfClosePriorToFirstPageLoad() {
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
         {
-            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock,
-                    getStorage());
+            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
             updateIfNeeded(updateManager);
             assertTrue(updateManager.updateCheckStarted());
         }
@@ -429,8 +369,7 @@
 
         {
             // Relaunching the WebAPK should do an is-update-needed check.
-            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock,
-                    getStorage());
+            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
             updateIfNeeded(updateManager);
             assertTrue(updateManager.updateCheckStarted());
             onGotUnchangedWebManifestData(updateManager);
@@ -438,8 +377,7 @@
 
         {
             // Relaunching the WebAPK should not do an is-update-needed-check.
-            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock,
-                    getStorage());
+            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
             updateIfNeeded(updateManager);
             assertFalse(updateManager.updateCheckStarted());
         }
@@ -454,10 +392,9 @@
     @Test
     public void testUpdateNotNeeded() {
         long initialTime = mClock.currentTimeMillis();
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock,
-                getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
         onGotUnchangedWebManifestData(updateManager);
@@ -478,9 +415,9 @@
     public void testMarkUpdateAsSucceededIfUpdateNoLongerNeeded() {
         WebappDataStorage storage = getStorage();
         storage.updateDidLastWebApkUpdateRequestSucceed(false);
-        mClock.advance(WebApkUpdateManager.RETRY_UPDATE_DURATION);
+        mClock.advance(WebappDataStorage.RETRY_UPDATE_DURATION);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
         onGotUnchangedWebManifestData(updateManager);
@@ -497,9 +434,9 @@
      */
     @Test
     public void testMarkUpdateAsFailedIfClosePriorToUpdateCompleting() {
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
         ManifestData manifestData = defaultManifestData();
@@ -528,9 +465,9 @@
     @Test
     public void testShellApkOutOfDateNoWebManifest() {
         registerWebApk(defaultManifestData(), WebApkVersion.CURRENT_SHELL_APK_VERSION - 1);
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
 
@@ -551,9 +488,9 @@
     @Test
     public void testShellApkOutOfDateStillHasWebManifest() {
         registerWebApk(defaultManifestData(), WebApkVersion.CURRENT_SHELL_APK_VERSION - 1);
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
 
@@ -576,9 +513,9 @@
      */
     @Test
     public void testStartUrlRedirectsToPageWithUpdatedWebManifest() {
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
 
@@ -610,9 +547,9 @@
      */
     @Test
     public void testStartUrlRedirectsToPageWithUnchangedWebManifest() {
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateIfNeeded(updateManager);
         updateManager.onWebManifestForInitialUrlNotWebApkCompatible();
         onGotManifestData(updateManager, defaultManifestData());
@@ -736,14 +673,13 @@
 
     @Test
     public void testForceUpdateWhenUncompletedUpdateRequestRechesMaximumTimes() {
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
         ManifestData differentManifestData = defaultManifestData();
         differentManifestData.name = DIFFERENT_NAME;
         WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(WEBAPK_ID);
 
         for (int i = 0; i < 3; ++i) {
-            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock,
-                    getStorage());
+            TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
             updateManager.setIsWebApkForeground(true);
             updateIfNeeded(updateManager);
 
@@ -753,7 +689,7 @@
             assertEquals(i + 1, storage.getUpdateRequests());
         }
 
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateManager.setIsWebApkForeground(true);
         updateIfNeeded(updateManager);
 
@@ -769,8 +705,8 @@
         differentManifestData.name = DIFFERENT_NAME;
         WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(WEBAPK_ID);
 
-        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
-        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock, getStorage());
+        mClock.advance(WebappDataStorage.UPDATE_INTERVAL);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(getStorage());
         updateManager.setIsWebApkForeground(true);
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java
index f9bc08c..c0b6fbca 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.webapps;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -26,6 +27,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.blink_public.platform.WebDisplayMode;
 import org.chromium.chrome.browser.ShortcutHelper;
+import org.chromium.chrome.browser.webapps.WebappDataStorage.Clock;
 import org.chromium.testing.local.BackgroundShadowAsyncTask;
 import org.chromium.testing.local.LocalRobolectricTestRunner;
 
@@ -63,6 +65,10 @@
             updateTime(currentTime);
         }
 
+        public void advance(long millis) {
+            mCurrentTime += millis;
+        }
+
         public void updateTime(long currentTime) {
             mCurrentTime = currentTime;
         }
@@ -88,6 +94,7 @@
     @After
     public void tearDown() {
         mSharedPreferences.edit().clear().apply();
+        WebappDataStorage.setClockForTests(new Clock());
     }
 
     @Test
@@ -315,6 +322,89 @@
                 mSharedPreferences.getBoolean(WebappDataStorage.KEY_IS_ICON_GENERATED, true));
     }
 
+    /**
+     * Test that if the WebAPK update failed (e.g. because the WebAPK server is not reachable) that
+     * the is-update-needed check is retried after less time than if the WebAPK update had
+     * succeeded. The is-update-needed check is the first step in retrying to update the WebAPK.
+     */
+    @Test
+    public void testCheckUpdateMoreFrequentlyIfUpdateFails() {
+        assertTrue(WebappDataStorage.UPDATE_INTERVAL > WebappDataStorage.RETRY_UPDATE_DURATION);
+
+        final TestClock clock = new TestClock(System.currentTimeMillis());
+        WebappDataStorage storage = getStorage(clock);
+
+        storage.updateTimeOfLastWebApkUpdateRequestCompletion();
+        storage.updateDidLastWebApkUpdateRequestSucceed(true);
+
+        assertFalse(storage.shouldCheckForUpdate());
+        clock.advance(WebappDataStorage.RETRY_UPDATE_DURATION);
+        assertFalse(storage.shouldCheckForUpdate());
+
+        // Advance all of the time stamps.
+        storage.updateTimeOfLastCheckForUpdatedWebManifest();
+        storage.updateTimeOfLastWebApkUpdateRequestCompletion();
+        storage.updateDidLastWebApkUpdateRequestSucceed(false);
+
+        assertFalse(storage.shouldCheckForUpdate());
+        clock.advance(WebappDataStorage.RETRY_UPDATE_DURATION);
+        assertTrue(storage.shouldCheckForUpdate());
+
+        // Verifies that {@link WebappDataStorage#shouldCheckForUpdate()} returns true because the
+        // previous update failed, no matter whether we want to check update less frequently.
+        storage.setRelaxedUpdates(true);
+        assertTrue(storage.shouldCheckForUpdate());
+    }
+
+    /**
+     * Test that if there was no previous WebAPK update attempt that the is-update-needed check is
+     * done after the usual delay (as opposed to the shorter delay if the previous WebAPK update
+     * failed.)
+     */
+    @Test
+    public void testRegularCheckIntervalIfNoPriorWebApkUpdate() {
+        assertTrue(WebappDataStorage.UPDATE_INTERVAL > WebappDataStorage.RETRY_UPDATE_DURATION);
+
+        final TestClock clock = new TestClock(System.currentTimeMillis());
+        WebappDataStorage storage = getStorage(clock);
+
+        assertFalse(storage.shouldCheckForUpdate());
+        clock.advance(WebappDataStorage.RETRY_UPDATE_DURATION);
+        assertFalse(storage.shouldCheckForUpdate());
+        clock.advance(WebappDataStorage.UPDATE_INTERVAL - WebappDataStorage.RETRY_UPDATE_DURATION);
+        assertTrue(storage.shouldCheckForUpdate());
+    }
+
+    /**
+     * Test that if there was no previous WebAPK update attempt and the relax-update flag is set to
+     * true, the is-update-needed check is done after the relaxed update interval (as opposed to the
+     * usual delay.)
+     */
+    @Test
+    public void testRelaxedUpdates() {
+        assertTrue(WebappDataStorage.RELAXED_UPDATE_INTERVAL > WebappDataStorage.UPDATE_INTERVAL);
+
+        final TestClock clock = new TestClock(System.currentTimeMillis());
+        WebappDataStorage storage = getStorage(clock);
+
+        storage.setRelaxedUpdates(true);
+
+        clock.advance(WebappDataStorage.UPDATE_INTERVAL);
+        assertFalse(storage.shouldCheckForUpdate());
+        clock.advance(
+                WebappDataStorage.RELAXED_UPDATE_INTERVAL - WebappDataStorage.UPDATE_INTERVAL);
+        assertTrue(storage.shouldCheckForUpdate());
+    }
+
+    private WebappDataStorage getStorage(TestClock clock) {
+        WebappDataStorage.setClockForTests(clock);
+        WebappDataStorage storage = WebappDataStorage.open("test");
+
+        // Done when WebAPK is registered in {@link WebApkActivity}.
+        storage.updateTimeOfLastCheckForUpdatedWebManifest();
+        return storage;
+    }
+
     // TODO(lalitm) - There seems to be a bug in Robolectric where a Bitmap
     // produced from a byte stream is hardcoded to be a 100x100 bitmap with
     // ARGB_8888 pixel format. Because of this, we need to work around the
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 9c3879c..c39213e 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2071,7 +2071,6 @@
     deps += [
       "//chrome/browser/safe_browsing:chunk_proto",
       "//chrome/common/safe_browsing:proto",
-      "//components/safe_browsing:csd_proto",
       "//components/safe_browsing:safe_browsing",
       "//components/safe_browsing/common:common",
       "//components/safe_browsing/password_protection",
@@ -4333,7 +4332,6 @@
     "//components/password_manager/core/browser:test_support",
     "//components/policy/core/browser:test_support",
     "//components/prefs:test_support",
-    "//components/safe_browsing:csd_proto",
     "//components/search_engines:test_support",
     "//components/sessions:test_support",
     "//components/subresource_filter/core/browser:test_support",
diff --git a/chrome/browser/android/banners/app_banner_infobar_delegate_android.cc b/chrome/browser/android/banners/app_banner_infobar_delegate_android.cc
index 2fa5463..0414641 100644
--- a/chrome/browser/android/banners/app_banner_infobar_delegate_android.cc
+++ b/chrome/browser/android/banners/app_banner_infobar_delegate_android.cc
@@ -374,6 +374,7 @@
 
 void AppBannerInfoBarDelegateAndroid::OnWebApkInstallFinished(
     bool success,
+    bool relax_updates,
     const std::string& webapk_package_name) {
   if (!success) {
     OnWebApkInstallFailed();
diff --git a/chrome/browser/android/banners/app_banner_infobar_delegate_android.h b/chrome/browser/android/banners/app_banner_infobar_delegate_android.h
index b1eb133..b1a2dfb 100644
--- a/chrome/browser/android/banners/app_banner_infobar_delegate_android.h
+++ b/chrome/browser/android/banners/app_banner_infobar_delegate_android.h
@@ -126,6 +126,7 @@
   bool TriggeredFromBanner() const;
   void SendBannerAccepted();
   void OnWebApkInstallFinished(bool success,
+                               bool relax_updates,
                                const std::string& webapk_package_name);
 
   // Called when a WebAPK install fails.
diff --git a/chrome/browser/android/vr_shell/BUILD.gn b/chrome/browser/android/vr_shell/BUILD.gn
index 3cda088..fd7b38e 100644
--- a/chrome/browser/android/vr_shell/BUILD.gn
+++ b/chrome/browser/android/vr_shell/BUILD.gn
@@ -19,8 +19,6 @@
       "animation.h",
       "easing.cc",
       "easing.h",
-      "mailbox_to_surface_bridge.cc",
-      "mailbox_to_surface_bridge.h",
       "non_presenting_gvr_delegate.cc",
       "non_presenting_gvr_delegate.h",
       "ui_elements.cc",
@@ -74,7 +72,6 @@
       "//content/public/common",
       "//device/gamepad",
       "//device/vr",
-      "//services/ui/public/cpp/gpu",
       "//ui/android",
       "//ui/base",
       "//ui/display",
diff --git a/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.cc b/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.cc
deleted file mode 100644
index d8f375b..0000000
--- a/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.cc
+++ /dev/null
@@ -1,333 +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 "chrome/browser/android/vr_shell/mailbox_to_surface_bridge.h"
-
-#include <string>
-
-#include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "base/sys_info.h"
-#include "cc/output/context_provider.h"
-#include "content/public/browser/android/compositor.h"
-#include "gpu/GLES2/gl2extchromium.h"
-#include "gpu/command_buffer/client/gles2_interface.h"
-#include "gpu/command_buffer/common/mailbox.h"
-#include "gpu/command_buffer/common/mailbox_holder.h"
-#include "gpu/command_buffer/common/sync_token.h"
-#include "gpu/ipc/client/gpu_channel_host.h"
-#include "gpu/ipc/common/gpu_surface_tracker.h"
-#include "services/ui/public/cpp/gpu/context_provider_command_buffer.h"
-#include "ui/gl/android/surface_texture.h"
-
-#include <android/native_window_jni.h>
-
-#define VOID_OFFSET(x) reinterpret_cast<void*>(x)
-#define SHADER(Src) #Src
-
-namespace {
-
-const char kQuadCopyVertex[] =
-    SHADER(attribute vec4 a_Position; attribute vec2 a_TexCoordinate;
-           varying vec2 v_TexCoordinate;
-           void main() {
-             v_TexCoordinate = a_TexCoordinate;
-             gl_Position = a_Position;
-           });
-
-const char kQuadCopyFragment[] = SHADER(
-    precision highp float; uniform sampler2D u_Texture;
-    varying vec2 v_TexCoordinate;
-    void main() { gl_FragColor = texture2D(u_Texture, v_TexCoordinate); });
-
-const float kQuadVertices[] = {
-    // clang-format off
-    // x     y    u,   v
-    -1.f,  1.f, 0.f, 1.f,
-    -1.f, -1.f, 0.f, 0.f,
-     1.f, -1.f, 1.f, 0.f,
-     1.f,  1.f, 1.f, 1.f};
-static constexpr int kQuadVerticesSize = sizeof(kQuadVertices);
-
-GLuint CompileShader(gpu::gles2::GLES2Interface* gl,
-                     GLenum shader_type,
-                     const GLchar* shader_source) {
-  GLuint shader_handle = gl->CreateShader(shader_type);
-  if (shader_handle != 0) {
-    // Pass in the shader source.
-    GLint len = strlen(shader_source);
-    gl->ShaderSource(shader_handle, 1, &shader_source, &len);
-    // Compile the shader.
-    gl->CompileShader(shader_handle);
-    // Get the compilation status.
-    GLint status = 0;
-    gl->GetShaderiv(shader_handle, GL_COMPILE_STATUS, &status);
-    if (status == GL_FALSE) {
-      GLint info_log_length = 0;
-      gl->GetShaderiv(shader_handle, GL_INFO_LOG_LENGTH, &info_log_length);
-      auto str_info_log = base::MakeUnique<GLchar[]>(info_log_length + 1);
-      gl->GetShaderInfoLog(shader_handle, info_log_length, nullptr,
-                           str_info_log.get());
-      DLOG(ERROR) << "Error compiling shader: " << str_info_log.get();
-      gl->DeleteShader(shader_handle);
-      shader_handle = 0;
-    }
-  }
-
-  return shader_handle;
-}
-
-GLuint CreateAndLinkProgram(gpu::gles2::GLES2Interface* gl,
-                            GLuint vertex_shader_handle,
-                            GLuint fragment_shader_handle) {
-  GLuint program_handle = gl->CreateProgram();
-
-  if (program_handle != 0) {
-    // Bind the vertex shader to the program.
-    gl->AttachShader(program_handle, vertex_shader_handle);
-
-    // Bind the fragment shader to the program.
-    gl->AttachShader(program_handle, fragment_shader_handle);
-
-    // Link the two shaders together into a program.
-    gl->LinkProgram(program_handle);
-
-    // Get the link status.
-    GLint link_status = 0;
-    gl->GetProgramiv(program_handle, GL_LINK_STATUS, &link_status);
-
-    // If the link failed, delete the program.
-    if (link_status == GL_FALSE) {
-      GLint info_log_length;
-      gl->GetProgramiv(program_handle, GL_INFO_LOG_LENGTH, &info_log_length);
-
-      auto str_info_log = base::MakeUnique<GLchar[]>(info_log_length + 1);
-      gl->GetProgramInfoLog(program_handle, info_log_length, nullptr,
-                            str_info_log.get());
-      DLOG(ERROR) << "Error compiling program: " << str_info_log.get();
-      gl->DeleteProgram(program_handle);
-      program_handle = 0;
-    }
-  }
-
-  return program_handle;
-}
-
-GLuint ConsumeTexture(gpu::gles2::GLES2Interface* gl,
-                      const gpu::MailboxHolder& mailbox) {
-  TRACE_EVENT0("gpu", "MailboxToSurfaceBridge::ConsumeTexture");
-  gl->WaitSyncTokenCHROMIUM(mailbox.sync_token.GetConstData());
-
-  return gl->CreateAndConsumeTextureCHROMIUM(GL_TEXTURE_2D,
-                                             mailbox.mailbox.name);
-}
-
-}  // namespace
-
-namespace vr_shell {
-
-MailboxToSurfaceBridge::MailboxToSurfaceBridge() : weak_ptr_factory_(this) {}
-
-MailboxToSurfaceBridge::~MailboxToSurfaceBridge() {
-  if (surface_handle_) {
-    // Unregister from the surface tracker to avoid a resource leak.
-    gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
-    tracker->UnregisterViewSurface(surface_handle_);
-  }
-  DestroyContext();
-}
-
-void MailboxToSurfaceBridge::OnContextAvailable(
-    scoped_refptr<cc::ContextProvider> provider) {
-  // Must save a reference to the ContextProvider to keep it alive,
-  // otherwise the GL context created from it becomes invalid.
-  context_provider_ = std::move(provider);
-
-  if (!context_provider_->BindToCurrentThread()) {
-    DLOG(ERROR) << "Failed to init ContextProvider";
-    return;
-  }
-
-  gl_ = context_provider_->ContextGL();
-
-  if (!gl_) {
-    DLOG(ERROR) << "Did not get a GL context";
-    return;
-  }
-  InitializeRenderer();
-}
-
-void MailboxToSurfaceBridge::CreateSurface(
-    gl::SurfaceTexture* surface_texture) {
-  ANativeWindow* window = surface_texture->CreateSurface();
-  gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
-  ANativeWindow_acquire(window);
-  // Skip ANativeWindow_setBuffersGeometry, the default size appears to work.
-  surface_handle_ = tracker->AddSurfaceForNativeWidget(window);
-
-  auto surface = base::MakeUnique<gl::ScopedJavaSurface>(surface_texture);
-  tracker->RegisterViewSurface(surface_handle_, surface->j_surface().obj());
-  // Unregistering happens in the destructor.
-  ANativeWindow_release(window);
-
-  // Our attributes must be compatible with the shared offscreen
-  // surface used by virtualized contexts, otherwise mailbox
-  // synchronization doesn't work properly - it assumes a shared
-  // underlying GL context. See GetCompositorContextAttributes
-  // in content/browser/renderer_host/compositor_impl_android.cc
-  // and crbug.com/699330.
-
-  gpu::gles2::ContextCreationAttribHelper attributes;
-  attributes.alpha_size = -1;
-  attributes.red_size = 8;
-  attributes.green_size = 8;
-  attributes.blue_size = 8;
-  attributes.stencil_size = 0;
-  attributes.depth_size = 0;
-  attributes.samples = 0;
-  attributes.sample_buffers = 0;
-  attributes.bind_generates_resource = false;
-  if (base::SysInfo::IsLowEndDevice()) {
-    attributes.alpha_size = 0;
-    attributes.red_size = 5;
-    attributes.green_size = 6;
-    attributes.blue_size = 5;
-  }
-
-  content::Compositor::CreateContextProvider(
-      surface_handle_, attributes, gpu::SharedMemoryLimits::ForMailboxContext(),
-      base::Bind(&MailboxToSurfaceBridge::OnContextAvailable,
-                 weak_ptr_factory_.GetWeakPtr()));
-}
-
-void MailboxToSurfaceBridge::ResizeSurface(int width, int height) {
-  if (!gl_) {
-    // We're not initialized yet, save the requested size for later.
-    needs_resize_ = true;
-    resize_width_ = width;
-    resize_height_ = height;
-    return;
-  }
-  gl_->ResizeCHROMIUM(width, height, 1.f, false);
-  gl_->Viewport(0, 0, width, height);
-}
-
-bool MailboxToSurfaceBridge::CopyMailboxToSurfaceAndSwap(
-    const gpu::MailboxHolder& mailbox) {
-  if (!gl_) {
-    // We may not have a context yet, i.e. due to surface initialization
-    // being incomplete. This is not an error, but we obviously can't draw
-    // yet.
-    return false;
-  }
-
-  if (needs_resize_) {
-    ResizeSurface(resize_width_, resize_height_);
-    needs_resize_ = false;
-  }
-
-  GLuint sourceTexture = ConsumeTexture(gl_, mailbox);
-  DrawQuad(sourceTexture);
-  gl_->SwapBuffers();
-  return true;
-}
-
-void MailboxToSurfaceBridge::DestroyContext() {
-  gl_ = nullptr;
-  context_provider_ = nullptr;
-}
-
-void MailboxToSurfaceBridge::InitializeRenderer() {
-  GLuint vertex_shader_handle =
-      CompileShader(gl_, GL_VERTEX_SHADER, kQuadCopyVertex);
-  if (!vertex_shader_handle) {
-    DestroyContext();
-    return;
-  }
-
-  GLuint fragment_shader_handle =
-      CompileShader(gl_, GL_FRAGMENT_SHADER, kQuadCopyFragment);
-  if (!fragment_shader_handle) {
-    DestroyContext();
-    return;
-  }
-
-  GLuint program_handle =
-      CreateAndLinkProgram(gl_, vertex_shader_handle, fragment_shader_handle);
-  if (!program_handle) {
-    DestroyContext();
-    return;
-  }
-
-  // Once the program is linked the shader objects are no longer needed
-  gl_->DeleteShader(vertex_shader_handle);
-  gl_->DeleteShader(fragment_shader_handle);
-
-  GLuint position_handle = gl_->GetAttribLocation(program_handle, "a_Position");
-  GLuint texCoord_handle =
-      gl_->GetAttribLocation(program_handle, "a_TexCoordinate");
-  GLuint texUniform_handle =
-      gl_->GetUniformLocation(program_handle, "u_Texture");
-
-  GLuint vertexBuffer = 0;
-  gl_->GenBuffers(1, &vertexBuffer);
-  gl_->BindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
-  gl_->BufferData(GL_ARRAY_BUFFER, kQuadVerticesSize, kQuadVertices,
-                  GL_STATIC_DRAW);
-
-  // Set state once only, we assume that nobody else modifies GL state in a way
-  // that would interfere with our operations.
-  gl_->Disable(GL_CULL_FACE);
-  gl_->DepthMask(GL_FALSE);
-  gl_->Disable(GL_DEPTH_TEST);
-  gl_->Disable(GL_SCISSOR_TEST);
-  gl_->Disable(GL_BLEND);
-  gl_->Disable(GL_POLYGON_OFFSET_FILL);
-
-  // Not using gl_->Viewport, we assume that it defaults to the whole
-  // surface and gets updated by ResizeSurface externally as
-  // appropriate.
-
-  gl_->UseProgram(program_handle);
-
-  // Bind vertex attributes
-  gl_->BindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
-
-  gl_->EnableVertexAttribArray(position_handle);
-  gl_->EnableVertexAttribArray(texCoord_handle);
-
-  static constexpr size_t VERTEX_STRIDE = sizeof(float) * 4;
-  static constexpr size_t POSITION_ELEMENTS = 2;
-  static constexpr size_t TEXCOORD_ELEMENTS = 2;
-  static constexpr size_t POSITION_OFFSET = 0;
-  static constexpr size_t TEXCOORD_OFFSET = sizeof(float) * 2;
-
-  gl_->VertexAttribPointer(position_handle, POSITION_ELEMENTS, GL_FLOAT, false,
-                           VERTEX_STRIDE, VOID_OFFSET(POSITION_OFFSET));
-  gl_->VertexAttribPointer(texCoord_handle, TEXCOORD_ELEMENTS, GL_FLOAT, false,
-                           VERTEX_STRIDE, VOID_OFFSET(TEXCOORD_OFFSET));
-
-  gl_->ActiveTexture(GL_TEXTURE0);
-  gl_->Uniform1i(texUniform_handle, 0);
-}
-
-void MailboxToSurfaceBridge::DrawQuad(unsigned int texture_handle) {
-  // No need to clear since we're redrawing on top of the entire
-  // viewport, but let the GPU know we don't need the old content
-  // anymore.
-  GLenum discards[] = {GL_COLOR_EXT};
-  gl_->DiscardFramebufferEXT(GL_FRAMEBUFFER, arraysize(discards), discards);
-
-  // Configure texture. This is a 1:1 pixel copy since the surface
-  // size is resized to match the source canvas, so we can use
-  // GL_NEAREST.
-  gl_->BindTexture(GL_TEXTURE_2D, texture_handle);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-  gl_->DrawArrays(GL_TRIANGLE_FAN, 0, 4);
-}
-
-}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.h b/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.h
deleted file mode 100644
index 8ba1540..0000000
--- a/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ANDROID_VR_SHELL_MAILBOX_TO_SURFACE_BRIDGE_H_
-#define CHROME_BROWSER_ANDROID_VR_SHELL_MAILBOX_TO_SURFACE_BRIDGE_H_
-
-#include "base/memory/weak_ptr.h"
-
-namespace gl {
-class SurfaceTexture;
-}
-
-namespace gpu {
-struct MailboxHolder;
-namespace gles2 {
-class GLES2Interface;
-}
-}
-
-namespace cc {
-class ContextProvider;
-}
-
-namespace vr_shell {
-
-class MailboxToSurfaceBridge {
- public:
-  MailboxToSurfaceBridge();
-  ~MailboxToSurfaceBridge();
-
-  void CreateSurface(gl::SurfaceTexture*);
-
-  void ResizeSurface(int width, int height);
-
-  // Returns true if swapped successfully. This can fail if the GL
-  // context isn't ready for use yet, in that case the caller
-  // won't get a new frame on the SurfaceTexture.
-  bool CopyMailboxToSurfaceAndSwap(const gpu::MailboxHolder& mailbox);
-
- private:
-  void OnContextAvailable(scoped_refptr<cc::ContextProvider>);
-  void InitializeRenderer();
-  void DestroyContext();
-  void DrawQuad(unsigned int textureHandle);
-
-  scoped_refptr<cc::ContextProvider> context_provider_;
-  gpu::gles2::GLES2Interface* gl_ = nullptr;
-  int surface_handle_ = 0;
-
-  // Saved state for a pending resize, the dimensions are only
-  // valid if needs_resize_ is true.
-  bool needs_resize_ = false;
-  int resize_width_;
-  int resize_height_;
-
-  // Must be last.
-  base::WeakPtrFactory<MailboxToSurfaceBridge> weak_ptr_factory_;
-};
-
-}  // namespace vr_shell
-
-#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_MAILBOX_TO_SURFACE_BRIDGE_H_
diff --git a/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.cc b/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.cc
index c28141e..c25da54 100644
--- a/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.cc
+++ b/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.cc
@@ -20,11 +20,7 @@
     : task_runner_(base::ThreadTaskRunnerHandle::Get()),
       binding_(this),
       weak_ptr_factory_(this) {
-  // Context may be null, see VrShellDelegate#createNonPresentingNativeContext
-  // for possible reasons a context could fail to be created. For example,
-  // the user might uninstall apps or clear data after VR support was detected.
-  if (context)
-    gvr_api_ = gvr::GvrApi::WrapNonOwned(context);
+  gvr_api_ = gvr::GvrApi::WrapNonOwned(context);
 }
 
 NonPresentingGvrDelegate::~NonPresentingGvrDelegate() {
@@ -44,8 +40,7 @@
 void NonPresentingGvrDelegate::Pause() {
   vsync_task_.Cancel();
   vsync_paused_ = true;
-  if (gvr_api_)
-    gvr_api_->PauseTracking();
+  gvr_api_->PauseTracking();
 }
 
 void NonPresentingGvrDelegate::Resume() {
@@ -65,14 +60,12 @@
 
 void NonPresentingGvrDelegate::StopVSyncLoop() {
   vsync_task_.Cancel();
-  binding_.Close();
   if (!callback_.is_null()) {
     base::ResetAndReturn(&callback_)
         .Run(nullptr, base::TimeDelta(), -1,
-             device::mojom::VRVSyncProvider::Status::CLOSING);
+             device::mojom::VRVSyncProvider::Status::RETRY);
   }
-  if (gvr_api_)
-    gvr_api_->PauseTracking();
+  gvr_api_->PauseTracking();
   // If the loop is stopped, it's not considered to be paused.
   vsync_paused_ = false;
 }
@@ -80,10 +73,8 @@
 void NonPresentingGvrDelegate::StartVSyncLoop() {
   vsync_task_.Reset(
       base::Bind(&NonPresentingGvrDelegate::OnVSync, base::Unretained(this)));
-  if (gvr_api_) {
-    gvr_api_->RefreshViewerProfile();
-    gvr_api_->ResumeTracking();
-  }
+  gvr_api_->RefreshViewerProfile();
+  gvr_api_->ResumeTracking();
   OnVSync();
 }
 
@@ -145,12 +136,6 @@
   gvr::ClockTimePoint target_time = gvr::GvrApi::GetTimePointNow();
   target_time.monotonic_system_time_nanos += kPredictionTimeWithoutVsyncNanos;
 
-  if (!gvr_api_) {
-    callback.Run(device::mojom::VRPosePtr(nullptr), time, -1,
-                 device::mojom::VRVSyncProvider::Status::SUCCESS);
-    return;
-  }
-
   gvr::Mat4f head_mat = gvr_api_->ApplyNeckModel(
       gvr_api_->GetHeadSpaceFromStartSpaceRotation(target_time), 1.0f);
   callback.Run(VrShell::VRPosePtrFromGvrPose(head_mat), time, -1,
@@ -171,11 +156,6 @@
 void NonPresentingGvrDelegate::CreateVRDisplayInfo(
     const base::Callback<void(device::mojom::VRDisplayInfoPtr)>& callback,
     uint32_t device_id) {
-  if (!gvr_api_) {
-    callback.Run(device::mojom::VRDisplayInfoPtr(nullptr));
-    return;
-  }
-
   callback.Run(VrShell::CreateVRDisplayInfo(
       gvr_api_.get(), device::kInvalidRenderTargetSize, device_id));
 }
diff --git a/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.h b/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.h
index 741b5d515..0bf9104 100644
--- a/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.h
+++ b/chrome/browser/android/vr_shell/non_presenting_gvr_delegate.h
@@ -28,12 +28,10 @@
 
   // GvrDelegate implementation
   void SetWebVRSecureOrigin(bool secure_origin) override {}
-  void SubmitWebVRFrame(int16_t frame_index,
-                        const gpu::MailboxHolder& mailbox) override {}
+  void SubmitWebVRFrame() override {}
   void UpdateWebVRTextureBounds(int16_t frame_index,
                                 const gvr::Rectf& left_bounds,
-                                const gvr::Rectf& right_bounds,
-                                const gvr::Sizei& source_size) override {}
+                                const gvr::Rectf& right_bounds) override {}
   void OnVRVsyncProviderRequest(
       device::mojom::VRVSyncProviderRequest request) override;
   void UpdateVSyncInterval(int64_t timebase_nanos,
diff --git a/chrome/browser/android/vr_shell/vr_shell.cc b/chrome/browser/android/vr_shell/vr_shell.cc
index f9b506a..21193ab 100644
--- a/chrome/browser/android/vr_shell/vr_shell.cc
+++ b/chrome/browser/android/vr_shell/vr_shell.cc
@@ -36,7 +36,6 @@
 #include "content/public/common/referrer.h"
 #include "device/vr/android/gvr/gvr_device.h"
 #include "device/vr/android/gvr/gvr_device_provider.h"
-#include "gpu/command_buffer/common/mailbox.h"
 #include "jni/VrShellImpl_jni.h"
 #include "third_party/WebKit/public/platform/WebInputEvent.h"
 #include "ui/android/view_android.h"
@@ -171,7 +170,6 @@
 }
 
 VrShell::~VrShell() {
-  delegate_provider_->RemoveDelegate();
   {
     // The GvrLayout is, and must always be, used only on the UI thread, and the
     // GvrApi used for rendering should only be used from the GL thread as it's
@@ -187,6 +185,7 @@
     base::ThreadRestrictions::ScopedAllowIO allow_io;
     gl_thread_.reset();
   }
+  delegate_provider_->RemoveDelegate();
   g_instance = nullptr;
 }
 
@@ -272,7 +271,6 @@
     metrics_helper_->SetWebVREnabled(enabled);
   PostToGlThreadWhenReady(base::Bind(&VrShellGl::SetWebVrMode,
                                      gl_thread_->GetVrShellGl(), enabled));
-
   html_interface_->SetMode(enabled ? UiInterface::Mode::WEB_VR
                                    : UiInterface::Mode::STANDARD);
 }
@@ -326,22 +324,14 @@
   html_interface_->SetWebVRSecureOrigin(secure_origin);
 }
 
-void VrShell::SubmitWebVRFrame(int16_t frame_index,
-                               const gpu::MailboxHolder& mailbox) {
-  TRACE_EVENT1("gpu", "SubmitWebVRFrame", "frame", frame_index);
-
-  PostToGlThreadWhenReady(base::Bind(&VrShellGl::SubmitWebVRFrame,
-                                     gl_thread_->GetVrShellGl(), frame_index,
-                                     mailbox));
-}
+void VrShell::SubmitWebVRFrame() {}
 
 void VrShell::UpdateWebVRTextureBounds(int16_t frame_index,
                                        const gvr::Rectf& left_bounds,
-                                       const gvr::Rectf& right_bounds,
-                                       const gvr::Sizei& source_size) {
+                                       const gvr::Rectf& right_bounds) {
   PostToGlThreadWhenReady(base::Bind(&VrShellGl::UpdateWebVRTextureBounds,
                                      gl_thread_->GetVrShellGl(), frame_index,
-                                     left_bounds, right_bounds, source_size));
+                                     left_bounds, right_bounds));
 }
 
 bool VrShell::SupportsPresentation() {
@@ -395,10 +385,6 @@
 }
 
 void VrShell::GvrDelegateReady() {
-  PostToGlThreadWhenReady(base::Bind(
-      &VrShellGl::SetSubmitClient, gl_thread_->GetVrShellGl(),
-      base::Passed(
-          delegate_provider_->TakeSubmitFrameClient().PassInterface())));
   delegate_provider_->SetDelegate(this, gvr_api_);
 }
 
@@ -616,7 +602,6 @@
 device::mojom::VRPosePtr VrShell::VRPosePtrFromGvrPose(gvr::Mat4f head_mat) {
   device::mojom::VRPosePtr pose = device::mojom::VRPose::New();
 
-  pose->timestamp = base::Time::Now().ToJsTime();
   pose->orientation.emplace(4);
 
   gfx::Transform inv_transform(
@@ -646,7 +631,7 @@
 
 device::mojom::VRDisplayInfoPtr VrShell::CreateVRDisplayInfo(
     gvr::GvrApi* gvr_api,
-    gvr::Sizei recommended_size,
+    gvr::Sizei compositor_size,
     uint32_t device_id) {
   TRACE_EVENT0("input", "GvrDevice::GetVRDevice");
 
@@ -675,8 +660,8 @@
         (eye == GVR_LEFT_EYE) ? device->leftEye : device->rightEye;
     eye_params->fieldOfView = device::mojom::VRFieldOfView::New();
     eye_params->offset.resize(3);
-    eye_params->renderWidth = recommended_size.width / 2;
-    eye_params->renderHeight = recommended_size.height;
+    eye_params->renderWidth = compositor_size.width / 2;
+    eye_params->renderHeight = compositor_size.height;
 
     gvr::BufferViewport eye_viewport = gvr_api->CreateBufferViewport();
     gvr_buffer_viewports.GetBufferViewport(eye, &eye_viewport);
diff --git a/chrome/browser/android/vr_shell/vr_shell.h b/chrome/browser/android/vr_shell/vr_shell.h
index d52c567f..2d6e529 100644
--- a/chrome/browser/android/vr_shell/vr_shell.h
+++ b/chrome/browser/android/vr_shell/vr_shell.h
@@ -32,10 +32,6 @@
 class WebContents;
 }
 
-namespace gpu {
-struct MailboxHolder;
-}
-
 namespace ui {
 class WindowAndroid;
 }
@@ -177,7 +173,7 @@
   static device::mojom::VRPosePtr VRPosePtrFromGvrPose(gvr::Mat4f head_mat);
   static device::mojom::VRDisplayInfoPtr CreateVRDisplayInfo(
       gvr::GvrApi* gvr_api,
-      gvr::Sizei recommended_size,
+      gvr::Sizei compositor_size,
       uint32_t device_id);
 
  private:
@@ -194,12 +190,10 @@
 
   // device::GvrDelegate implementation
   void SetWebVRSecureOrigin(bool secure_origin) override;
-  void SubmitWebVRFrame(int16_t frame_index,
-                        const gpu::MailboxHolder& mailbox) override;
+  void SubmitWebVRFrame() override;
   void UpdateWebVRTextureBounds(int16_t frame_index,
                                 const gvr::Rectf& left_bounds,
-                                const gvr::Rectf& right_bounds,
-                                const gvr::Sizei& source_size) override;
+                                const gvr::Rectf& right_bounds) override;
   void OnVRVsyncProviderRequest(
       device::mojom::VRVSyncProviderRequest request) override;
   void UpdateVSyncInterval(int64_t timebase_nanos,
diff --git a/chrome/browser/android/vr_shell/vr_shell_delegate.cc b/chrome/browser/android/vr_shell/vr_shell_delegate.cc
index 4563d1a0..17a7ea3 100644
--- a/chrome/browser/android/vr_shell/vr_shell_delegate.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_delegate.cc
@@ -46,9 +46,14 @@
 
 void VrShellDelegate::SetDelegate(device::GvrDelegate* delegate,
                                   gvr_context* context) {
+  context_ = context;
   delegate_ = delegate;
   // Clean up the non-presenting delegate.
-  if (delegate_ && non_presenting_delegate_) {
+  if (non_presenting_delegate_) {
+    device::mojom::VRVSyncProviderRequest request =
+        non_presenting_delegate_->OnSwitchToPresentingDelegate();
+    if (request.is_pending())
+      delegate->OnVRVsyncProviderRequest(std::move(request));
     non_presenting_delegate_ = nullptr;
     JNIEnv* env = AttachCurrentThread();
     Java_VrShellDelegate_shutdownNonPresentingNativeContext(
@@ -131,16 +136,8 @@
   Java_VrShellDelegate_openNewTab(env, j_vr_shell_delegate_.obj(), incognito);
 }
 
-device::mojom::VRSubmitFrameClientPtr VrShellDelegate::TakeSubmitFrameClient() {
-  return std::move(submit_client_);
-}
-
 void VrShellDelegate::SetDeviceProvider(
     device::GvrDeviceProvider* device_provider) {
-  if (device_provider_ == device_provider)
-    return;
-  if (device_provider_)
-    ClearDeviceProvider();
   CHECK(!device_provider_);
   device_provider_ = device_provider;
   if (!delegate_)
@@ -157,7 +154,6 @@
 }
 
 void VrShellDelegate::RequestWebVRPresent(
-    device::mojom::VRSubmitFrameClientPtr submit_client,
     const base::Callback<void(bool)>& callback) {
   if (!present_callback_.is_null()) {
     // Can only handle one request at a time. This is also extremely unlikely to
@@ -167,7 +163,6 @@
   }
 
   present_callback_ = std::move(callback);
-  submit_client_ = std::move(submit_client);
 
   // If/When VRShell is ready for use it will call SetPresentResult.
   JNIEnv* env = AttachCurrentThread();
@@ -185,11 +180,19 @@
   return weak_ptr_factory_.GetWeakPtr();
 }
 
+void VrShellDelegate::OnVRVsyncProviderRequest(
+    device::mojom::VRVSyncProviderRequest request) {
+  GetDelegate()->OnVRVsyncProviderRequest(std::move(request));
+}
+
 void VrShellDelegate::CreateNonPresentingDelegate() {
   JNIEnv* env = AttachCurrentThread();
   gvr_context* context = reinterpret_cast<gvr_context*>(
       Java_VrShellDelegate_createNonPresentingNativeContext(
           env, j_vr_shell_delegate_.obj()));
+  if (!context)
+    return;
+  context_ = context;
   non_presenting_delegate_ =
       base::MakeUnique<NonPresentingGvrDelegate>(context);
   non_presenting_delegate_->UpdateVSyncInterval(timebase_nanos_,
diff --git a/chrome/browser/android/vr_shell/vr_shell_delegate.h b/chrome/browser/android/vr_shell/vr_shell_delegate.h
index 539c3cd..3b5161d 100644
--- a/chrome/browser/android/vr_shell/vr_shell_delegate.h
+++ b/chrome/browser/android/vr_shell/vr_shell_delegate.h
@@ -49,17 +49,16 @@
   void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
   void ShowTab(int id);
   void OpenNewTab(bool incognito);
-  device::mojom::VRSubmitFrameClientPtr TakeSubmitFrameClient();
 
   device::GvrDeviceProvider* device_provider() { return device_provider_; }
+  void OnVRVsyncProviderRequest(device::mojom::VRVSyncProviderRequest request);
   base::WeakPtr<VrShellDelegate> GetWeakPtr();
 
  private:
   // device::GvrDelegateProvider implementation
   void SetDeviceProvider(device::GvrDeviceProvider* device_provider) override;
   void ClearDeviceProvider() override;
-  void RequestWebVRPresent(device::mojom::VRSubmitFrameClientPtr submit_client,
-                           const base::Callback<void(bool)>& callback) override;
+  void RequestWebVRPresent(const base::Callback<void(bool)>& callback) override;
   void ExitWebVRPresent() override;
   device::GvrDelegate* GetDelegate() override;
   void SetListeningForActivate(bool listening) override;
@@ -73,7 +72,10 @@
   base::Callback<void(bool)> present_callback_;
   int64_t timebase_nanos_ = 0;
   double interval_seconds_ = 0;
-  device::mojom::VRSubmitFrameClientPtr submit_client_;
+
+  // TODO(mthiesse): Remove the need for this to be stored here.
+  // crbug.com/674594
+  gvr_context* context_ = nullptr;
 
   base::WeakPtrFactory<VrShellDelegate> weak_ptr_factory_;
 
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.cc b/chrome/browser/android/vr_shell/vr_shell_gl.cc
index 576a36ed..d466f32 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.cc
@@ -12,7 +12,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "chrome/browser/android/vr_shell/mailbox_to_surface_bridge.h"
 #include "chrome/browser/android/vr_shell/ui_elements.h"
 #include "chrome/browser/android/vr_shell/ui_scene.h"
 #include "chrome/browser/android/vr_shell/vr_controller.h"
@@ -81,12 +80,9 @@
 // 2-3 frames.
 static constexpr unsigned kPoseRingBufferSize = 8;
 
-// Default downscale factor for computing the recommended WebVR
-// renderWidth/Height from the 1:1 pixel mapped size. Using a rather
-// aggressive downscale due to the high overhead of copying pixels
-// twice before handing off to GVR. For comparison, the polyfill
-// uses approximately 0.55 on a Pixel XL.
-static constexpr float kWebVrRecommendedResolutionScale = 0.5;
+// Magic numbers used to mark valid pose index values encoded in frame
+// data. Must match the magic numbers used in blink's VRDisplay.cpp.
+static constexpr std::array<uint8_t, 2> kWebVrPosePixelMagicNumbers{{42, 142}};
 
 float Distance(const gvr::Vec3f& vec1, const gvr::Vec3f& vec2) {
   float xdiff = (vec1.x - vec2.x);
@@ -176,12 +172,6 @@
 
 VrShellGl::~VrShellGl() {
   vsync_task_.Cancel();
-  // TODO(mthiesse): Can we omit the Close() here? Concern is that if
-  // both ends of the connection ever live in the same process for
-  // some reason, we could receive another VSync request in response
-  // to the closing message in the destructor but fail to respond to
-  // the callback.
-  binding_.Close();
   if (!callback_.is_null()) {
     // When this VSync provider is going away we have to respond to pending
     // callbacks, so instead of providing a VSync, tell the requester to try
@@ -189,7 +179,13 @@
     // to this message will go through some other VSyncProvider.
     base::ResetAndReturn(&callback_)
         .Run(nullptr, base::TimeDelta(), -1,
-             device::mojom::VRVSyncProvider::Status::CLOSING);
+             device::mojom::VRVSyncProvider::Status::RETRY);
+  }
+  if (binding_.is_bound()) {
+    main_thread_task_runner_->PostTask(
+        FROM_HERE,
+        base::Bind(&VrShellDelegate::OnVRVsyncProviderRequest,
+                   delegate_provider_, base::Passed(binding_.Unbind())));
   }
 }
 
@@ -236,48 +232,24 @@
     return;
   }
 
-  unsigned int textures[3];
-  glGenTextures(3, textures);
+  unsigned int textures[2];
+  glGenTextures(2, textures);
   ui_texture_id_ = textures[0];
   content_texture_id_ = textures[1];
-  webvr_texture_id_ = textures[2];
   ui_surface_texture_ = gl::SurfaceTexture::Create(ui_texture_id_);
   content_surface_texture_ = gl::SurfaceTexture::Create(content_texture_id_);
-  webvr_surface_texture_ = gl::SurfaceTexture::Create(webvr_texture_id_);
   CreateUiSurface();
   CreateContentSurface();
   ui_surface_texture_->SetFrameAvailableCallback(base::Bind(
       &VrShellGl::OnUIFrameAvailable, weak_ptr_factory_.GetWeakPtr()));
   content_surface_texture_->SetFrameAvailableCallback(base::Bind(
       &VrShellGl::OnContentFrameAvailable, weak_ptr_factory_.GetWeakPtr()));
-  webvr_surface_texture_->SetFrameAvailableCallback(base::Bind(
-      &VrShellGl::OnWebVRFrameAvailable, weak_ptr_factory_.GetWeakPtr()));
-  ui_surface_texture_->SetDefaultBufferSize(ui_tex_physical_size_.width,
-                                            ui_tex_physical_size_.height);
   content_surface_texture_->SetDefaultBufferSize(
       content_tex_physical_size_.width, content_tex_physical_size_.height);
+  ui_surface_texture_->SetDefaultBufferSize(ui_tex_physical_size_.width,
+                                            ui_tex_physical_size_.height);
   InitializeRenderer();
 
-  // Pick a reasonable default size for the WebVR transfer surface
-  // based on a downscaled 1:1 render resolution. This size will also
-  // be reported to the client via CreateVRDisplayInfo as the
-  // client-recommended renderWidth/renderHeight and for the GVR
-  // framebuffer. If the client chooses a different size or resizes it
-  // while presenting, we'll resize the transfer surface and GVR
-  // framebuffer to match.
-  gvr::Sizei render_target_size =
-      gvr_api_->GetMaximumEffectiveRenderTargetSize();
-  gvr::Sizei webvr_size = {static_cast<int>(render_target_size.width *
-                                            kWebVrRecommendedResolutionScale),
-                           static_cast<int>(render_target_size.height *
-                                            kWebVrRecommendedResolutionScale)};
-
-  // TODO(klausw,crbug.com/699350): should we round the recommended
-  // size to a multiple of 2^N pixels to be friendlier to the GPU? The
-  // exact size doesn't matter.
-
-  CreateOrResizeWebVRSurface(webvr_size);
-
   vsync_task_.Reset(base::Bind(&VrShellGl::OnVSync, base::Unretained(this)));
   OnVSync();
 
@@ -300,73 +272,6 @@
                             ui_surface_->j_surface().obj()));
 }
 
-void VrShellGl::CreateOrResizeWebVRSurface(const gvr::Sizei& size) {
-  if (!webvr_surface_texture_) {
-    DLOG(ERROR) << "No WebVR surface texture available";
-    return;
-  }
-
-  // ContentPhysicalBoundsChanged is getting called twice with
-  // identical sizes? Avoid thrashing the existing context.
-  if (size == webvr_surface_size_) {
-    return;
-  }
-
-  if (!size.width || !size.height) {
-    // Invalid size, defer until a new size arrives on a future bounds update.
-    return;
-  }
-
-  webvr_surface_texture_->SetDefaultBufferSize(size.width, size.height);
-  webvr_surface_size_ = size;
-
-  if (mailbox_bridge_) {
-    mailbox_bridge_->ResizeSurface(size.width, size.height);
-  } else {
-    mailbox_bridge_ = base::MakeUnique<MailboxToSurfaceBridge>();
-    mailbox_bridge_->CreateSurface(webvr_surface_texture_.get());
-  }
-}
-
-void VrShellGl::SubmitWebVRFrame(int16_t frame_index,
-                                 const gpu::MailboxHolder& mailbox) {
-  TRACE_EVENT0("gpu", "VrShellGl::SubmitWebVRFrame");
-
-  // Swapping twice on a Surface without calling updateTexImage in
-  // between can lose frames, so don't draw+swap if we already have
-  // a pending frame we haven't consumed yet.
-  bool swapped = false;
-  if (pending_frames_.empty()) {
-    swapped = mailbox_bridge_->CopyMailboxToSurfaceAndSwap(mailbox);
-    if (swapped) {
-      // Tell OnWebVRFrameAvailable to expect a new frame to arrive on
-      // the SurfaceTexture, and save the associated frame index.
-      pending_frames_.emplace(frame_index);
-    }
-  }
-  // Always notify the client that we're done with the mailbox even
-  // if we haven't drawn it, so that it's eligible for destruction.
-  submit_client_->OnSubmitFrameTransferred();
-  if (!swapped) {
-    // We dropped without drawing, report this as completed rendering
-    // now to unblock the client. We're not going to receive it in
-    // OnWebVRFrameAvailable where we'd normally report that.
-    submit_client_->OnSubmitFrameRendered();
-  }
-
-  TRACE_EVENT0("gpu", "VrShellGl::glFinish");
-  // This is a load-bearing glFinish, please don't remove it without
-  // before/after timing comparisons. Goal is to clear the GPU queue
-  // of the native GL context to avoid stalls later in GVR frame
-  // acquire/submit.
-  glFinish();
-}
-
-void VrShellGl::SetSubmitClient(
-    device::mojom::VRSubmitFrameClientPtrInfo submit_client_info) {
-  submit_client_.Bind(std::move(submit_client_info));
-}
-
 void VrShellGl::OnUIFrameAvailable() {
   ui_surface_texture_->UpdateTexImage();
 }
@@ -376,30 +281,42 @@
   received_frame_ = true;
 }
 
-void VrShellGl::OnWebVRFrameAvailable() {
-  // A "while" loop here is a bad idea. It's legal to call
-  // UpdateTexImage repeatedly even if no frames are available, but
-  // that does *not* wait for a new frame, it just reuses the most
-  // recent one. That would mess up the count.
-  if (pending_frames_.empty()) {
-    // We're expecting a frame, but it's not here yet. Retry in OnVsync.
-    ++premature_received_frames_;
-    return;
+bool VrShellGl::GetPixelEncodedFrameIndex(uint16_t* frame_index) {
+  TRACE_EVENT0("gpu", "VrShellGl::GetPixelEncodedFrameIndex");
+  if (!received_frame_) {
+    if (last_frame_index_ == (uint16_t)-1)
+      return false;
+    *frame_index = last_frame_index_;
+    return true;
   }
+  received_frame_ = false;
 
-  webvr_surface_texture_->UpdateTexImage();
-  int frame_index = pending_frames_.front();
-  TRACE_EVENT1("gpu", "VrShellGl::OnWebVRFrameAvailable", "frame", frame_index);
-  pending_frames_.pop();
+  // Read the pose index encoded in a bottom left pixel as color values.
+  // See also third_party/WebKit/Source/modules/vr/VRDisplay.cpp which
+  // encodes the pose index, and device/vr/android/gvr/gvr_device.cc
+  // which tracks poses. Returns the low byte (0..255) if valid, or -1
+  // if not valid due to bad magic number.
+  uint8_t pixels[4];
+  // Assume we're reading from the framebuffer we just wrote to.
+  // That's true currently, we may need to use glReadBuffer(GL_BACK)
+  // or equivalent if the rendering setup changes in the future.
+  glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
 
-  // It is legal for the WebVR client to submit a new frame now, since
-  // we've consumed the image. TODO(klausw): would timing be better if
-  // we move the "rendered" notification after draw, or suppress
-  // the next vsync until that's done?
-
-  submit_client_->OnSubmitFrameRendered();
-
-  DrawFrame(frame_index);
+  // Check for the magic number written by VRDevice.cpp on submit.
+  // This helps avoid glitches from garbage data in the render
+  // buffer that can appear during initialization or resizing. These
+  // often appear as flashes of all-black or all-white pixels.
+  if (pixels[1] == kWebVrPosePixelMagicNumbers[0] &&
+      pixels[2] == kWebVrPosePixelMagicNumbers[1]) {
+    // Pose is good.
+    *frame_index = pixels[0];
+    last_frame_index_ = pixels[0];
+    return true;
+  }
+  VLOG(1) << "WebVR: reject decoded pose index " << static_cast<int>(pixels[0])
+          << ", bad magic number " << static_cast<int>(pixels[1]) << ", "
+          << static_cast<int>(pixels[2]);
+  return false;
 }
 
 void VrShellGl::GvrInit(gvr_context* gvr_api) {
@@ -424,6 +341,12 @@
 }
 
 void VrShellGl::InitializeRenderer() {
+  // While WebVR is going through the compositor path, it shares
+  // the same texture ID. This will change once it gets its own
+  // surface, but store it separately to avoid future confusion.
+  // TODO(klausw,crbug.com/655722): remove this.
+  webvr_texture_id_ = content_texture_id_;
+
   gvr_api_->InitializeGl();
   webvr_head_pose_.assign(kPoseRingBufferSize,
                           gvr_api_->GetHeadSpaceFromStartSpaceRotation(
@@ -433,7 +356,6 @@
   // For kFramePrimaryBuffer (primary VrShell and WebVR content)
   specs.push_back(gvr_api_->CreateBufferSpec());
   render_size_primary_ = specs[kFramePrimaryBuffer].GetSize();
-  render_size_vrshell_ = render_size_primary_;
 
   // For kFrameHeadlockedBuffer (for WebVR insecure content warning).
   // Set this up at fixed resolution, the (smaller) FOV gets set below.
@@ -710,33 +632,15 @@
       base::Bind(target, weak_vr_shell_, base::Passed(std::move(event))));
 }
 
-void VrShellGl::DrawFrame(int16_t frame_index) {
-  TRACE_EVENT1("gpu", "VrShellGl::DrawFrame", "frame", frame_index);
+void VrShellGl::DrawFrame() {
+  TRACE_EVENT0("gpu", "VrShellGl::DrawFrame");
 
   // Reset the viewport list to just the pair of viewports for the
   // primary buffer each frame. Head-locked viewports get added by
   // DrawVrShell if needed.
   buffer_viewport_list_->SetToRecommendedBufferViewports();
 
-  // If needed, resize the primary buffer for use with WebVR.
-  if (web_vr_mode_) {
-    if (render_size_primary_ != webvr_surface_size_) {
-      if (!webvr_surface_size_.width) {
-        return;
-      }
-      render_size_primary_ = webvr_surface_size_;
-      swap_chain_->ResizeBuffer(kFramePrimaryBuffer, render_size_primary_);
-    }
-  } else {
-    if (render_size_primary_ != render_size_vrshell_) {
-      render_size_primary_ = render_size_vrshell_;
-      swap_chain_->ResizeBuffer(kFramePrimaryBuffer, render_size_primary_);
-    }
-  }
-
-  TRACE_EVENT_BEGIN0("gpu", "VrShellGl::AcquireFrame");
   gvr::Frame frame = swap_chain_->AcquireFrame();
-  TRACE_EVENT_END0("gpu", "VrShellGl::AcquireFrame");
   if (!frame.is_valid()) {
     return;
   }
@@ -745,12 +649,18 @@
     DrawWebVr();
   }
 
+  uint16_t frame_index;
   gvr::Mat4f head_pose;
 
   // When using async reprojection, we need to know which pose was used in
-  // the WebVR app for drawing this frame. Only needed if reprojection is
-  // in use.
-  if (web_vr_mode_ && gvr_api_->GetAsyncReprojectionEnabled()) {
+  // the WebVR app for drawing this frame. Due to unknown amounts of
+  // buffering in the compositor and SurfaceTexture, we read the pose number
+  // from a corner pixel. There's no point in doing this for legacy
+  // distortion rendering since that doesn't need a pose, and reading back
+  // pixels is an expensive operation. TODO(klausw,crbug.com/655722): stop
+  // doing this once we have working no-compositor rendering for WebVR.
+  if (web_vr_mode_ && gvr_api_->GetAsyncReprojectionEnabled() &&
+      GetPixelEncodedFrameIndex(&frame_index)) {
     static_assert(!((kPoseRingBufferSize - 1) & kPoseRingBufferSize),
                   "kPoseRingBufferSize must be a power of 2");
     head_pose = webvr_head_pose_[frame_index % kPoseRingBufferSize];
@@ -774,10 +684,9 @@
       if (index > frame_index && index <= frame_index + kPoseRingBufferSize)
         break;
 
-      const WebVrBounds& bounds = pending_bounds_.front().second;
-      webvr_left_viewport_->SetSourceUv(bounds.left_bounds);
-      webvr_right_viewport_->SetSourceUv(bounds.right_bounds);
-      CreateOrResizeWebVRSurface(bounds.source_size);
+      const BoundsPair& bounds = pending_bounds_.front().second;
+      webvr_left_viewport_->SetSourceUv(bounds.first);
+      webvr_right_viewport_->SetSourceUv(bounds.second);
       pending_bounds_.pop();
     }
     buffer_viewport_list_->SetBufferViewport(GVR_LEFT_EYE,
@@ -803,31 +712,21 @@
   // Update the render position of all UI elements (including desktop).
   scene_->UpdateTransforms(TimeInMicroseconds());
 
-  {
-    TRACE_EVENT0("gpu", "VrShellGl::UpdateController");
-    UpdateController(GetForwardVector(head_pose));
-  }
+  UpdateController(GetForwardVector(head_pose));
 
-  // Finish drawing in the primary buffer, and draw the headlocked buffer
-  // if needed. This must be the last drawing call, this method will
-  // return with no frame being bound.
-  DrawVrShellAndUnbind(head_pose, frame);
+  DrawVrShell(head_pose, frame);
 
-  {
-    TRACE_EVENT0("gpu", "VrShellGl::Submit");
-    frame.Submit(*buffer_viewport_list_, head_pose);
-  }
+  frame.Unbind();
+  frame.Submit(*buffer_viewport_list_, head_pose);
 
   // No need to swap buffers for surfaceless rendering.
   if (!surfaceless_rendering_) {
     // TODO(mthiesse): Support asynchronous SwapBuffers.
-    TRACE_EVENT0("gpu", "VrShellGl::SwapBuffers");
     surface_->SwapBuffers();
   }
 }
 
-void VrShellGl::DrawVrShellAndUnbind(const gvr::Mat4f& head_pose,
-                                     gvr::Frame& frame) {
+void VrShellGl::DrawVrShell(const gvr::Mat4f& head_pose, gvr::Frame& frame) {
   TRACE_EVENT0("gpu", "VrShellGl::DrawVrShell");
   std::vector<const ContentRectangle*> head_locked_elements;
   std::vector<const ContentRectangle*> world_elements;
@@ -864,7 +763,6 @@
     DrawUiView(&head_pose, world_elements, render_size_primary_,
                kViewportListPrimaryOffset);
   }
-  frame.Unbind();  // Done with the primary buffer.
 
   if (!head_locked_elements.empty()) {
     // Add head-locked viewports. The list gets reset to just
@@ -877,15 +775,23 @@
         *headlocked_right_viewport_);
 
     // Bind the headlocked framebuffer.
+    // TODO(mthiesse): We don't unbind this? Maybe some cleanup is in order
+    // here.
     frame.BindBuffer(kFrameHeadlockedBuffer);
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     DrawUiView(nullptr, head_locked_elements, render_size_headlocked_,
                kViewportListHeadlockedOffset);
-    frame.Unbind();  // Done with the headlocked buffer.
   }
 }
 
+gvr::Sizei VrShellGl::GetWebVRCompositorSurfaceSize() {
+  // This is a stopgap while we're using the WebVR compositor rendering path.
+  // TODO(klausw,crbug.com/655722): Remove this method and member once we're
+  // using a separate WebVR render surface.
+  return content_tex_physical_size_;
+}
+
 void VrShellGl::DrawUiView(const gvr::Mat4f* head_pose,
                            const std::vector<const ContentRectangle*>& elements,
                            const gvr::Sizei& render_size,
@@ -1092,7 +998,7 @@
   glDisable(GL_BLEND);
   glDisable(GL_POLYGON_OFFSET_FILL);
 
-  glViewport(0, 0, webvr_surface_size_.width, webvr_surface_size_.height);
+  glViewport(0, 0, render_size_primary_.width, render_size_primary_.height);
   vr_shell_renderer_->GetWebVrRenderer()->Draw(webvr_texture_id_);
 }
 
@@ -1123,14 +1029,13 @@
 
 void VrShellGl::UpdateWebVRTextureBounds(int16_t frame_index,
                                          const gvr::Rectf& left_bounds,
-                                         const gvr::Rectf& right_bounds,
-                                         const gvr::Sizei& source_size) {
+                                         const gvr::Rectf& right_bounds) {
   if (frame_index < 0) {
     webvr_left_viewport_->SetSourceUv(left_bounds);
     webvr_right_viewport_->SetSourceUv(right_bounds);
   } else {
     pending_bounds_.emplace(
-        frame_index, WebVrBounds(left_bounds, right_bounds, source_size));
+        std::make_pair(frame_index, std::make_pair(left_bounds, right_bounds)));
   }
 }
 
@@ -1164,12 +1069,6 @@
 }
 
 void VrShellGl::OnVSync() {
-  while (premature_received_frames_ > 0) {
-    TRACE_EVENT0("gpu", "VrShellGl::OnWebVRFrameAvailableRetry");
-    --premature_received_frames_;
-    OnWebVRFrameAvailable();
-  }
-
   base::TimeTicks now = base::TimeTicks::Now();
   base::TimeTicks target;
 
@@ -1189,9 +1088,7 @@
     pending_vsync_ = true;
     pending_time_ = time;
   }
-  if (!web_vr_mode_) {
-    DrawFrame(-1);
-  }
+  DrawFrame();
 }
 
 void VrShellGl::OnRequest(device::mojom::VRVSyncProviderRequest request) {
@@ -1261,11 +1158,8 @@
 void VrShellGl::CreateVRDisplayInfo(
     const base::Callback<void(device::mojom::VRDisplayInfoPtr)>& callback,
     uint32_t device_id) {
-  // This assumes that the initial webvr_surface_size_ was set to the
-  // appropriate recommended render resolution as the default size during
-  // InitializeGl. Revisit if the initialization order changes.
   device::mojom::VRDisplayInfoPtr info = VrShell::CreateVRDisplayInfo(
-      gvr_api_.get(), webvr_surface_size_, device_id);
+      gvr_api_.get(), content_tex_physical_size_, device_id);
   main_thread_task_runner_->PostTask(
       FROM_HERE,
       base::Bind(&RunVRDisplayInfoCallback, callback, base::Passed(&info)));
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.h b/chrome/browser/android/vr_shell/vr_shell_gl.h
index 54bc7d3..b04cb8a07 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.h
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.h
@@ -37,13 +37,8 @@
 class SurfaceTexture;
 }
 
-namespace gpu {
-struct MailboxHolder;
-}
-
 namespace vr_shell {
 
-class MailboxToSurfaceBridge;
 class UiScene;
 class VrController;
 class VrShell;
@@ -51,14 +46,6 @@
 class VrShellRenderer;
 struct ContentRectangle;
 
-struct WebVrBounds {
-  WebVrBounds(gvr::Rectf left, gvr::Rectf right, gvr::Sizei size)
-      : left_bounds(left), right_bounds(right), source_size(size) {}
-  gvr::Rectf left_bounds;
-  gvr::Rectf right_bounds;
-  gvr::Sizei source_size;
-};
-
 // This class manages all GLThread owned objects and GL rendering for VrShell.
 // It is not threadsafe and must only be used on the GL thread.
 class VrShellGl : public device::mojom::VRVSyncProvider {
@@ -85,7 +72,6 @@
   void OnResume();
 
   void SetWebVrMode(bool enabled);
-  void CreateOrResizeWebVRSurface(const gvr::Sizei& size);
   void CreateContentSurface();
   void ContentBoundsChanged(int width, int height);
   void ContentPhysicalBoundsChanged(int width, int height);
@@ -95,8 +81,8 @@
 
   void UpdateWebVRTextureBounds(int16_t frame_index,
                                 const gvr::Rectf& left_bounds,
-                                const gvr::Rectf& right_bounds,
-                                const gvr::Sizei& source_size);
+                                const gvr::Rectf& right_bounds);
+  gvr::Sizei GetWebVRCompositorSurfaceSize();
 
   void UpdateScene(std::unique_ptr<base::ListValue> commands);
 
@@ -107,15 +93,12 @@
   void CreateVRDisplayInfo(
       const base::Callback<void(device::mojom::VRDisplayInfoPtr)>& callback,
       uint32_t device_id);
-  void SubmitWebVRFrame(int16_t frame_index, const gpu::MailboxHolder& mailbox);
-  void SetSubmitClient(
-      device::mojom::VRSubmitFrameClientPtrInfo submit_client_info);
 
  private:
   void GvrInit(gvr_context* gvr_api);
   void InitializeRenderer();
-  void DrawFrame(int16_t frame_index);
-  void DrawVrShellAndUnbind(const gvr::Mat4f& head_pose, gvr::Frame& frame);
+  void DrawFrame();
+  void DrawVrShell(const gvr::Mat4f& head_pose, gvr::Frame& frame);
   void DrawUiView(const gvr::Mat4f* head_pose,
                   const std::vector<const ContentRectangle*>& elements,
                   const gvr::Sizei& render_size,
@@ -137,7 +120,6 @@
   void CreateUiSurface();
   void OnUIFrameAvailable();
   void OnContentFrameAvailable();
-  void OnWebVRFrameAvailable();
   bool GetPixelEncodedFrameIndex(uint16_t* frame_index);
 
   void OnVSync();
@@ -153,8 +135,6 @@
   int ui_texture_id_ = 0;
   // samplerExternalOES texture data for main content image.
   int content_texture_id_ = 0;
-  // samplerExternalOES texture data for WebVR content image.
-  int webvr_texture_id_ = 0;
 
   std::unique_ptr<UiScene> scene_;
 
@@ -162,7 +142,6 @@
   scoped_refptr<gl::GLContext> context_;
   scoped_refptr<gl::SurfaceTexture> ui_surface_texture_;
   scoped_refptr<gl::SurfaceTexture> content_surface_texture_;
-  scoped_refptr<gl::SurfaceTexture> webvr_surface_texture_;
 
   std::unique_ptr<gl::ScopedJavaSurface> ui_surface_;
   std::unique_ptr<gl::ScopedJavaSurface> content_surface_;
@@ -175,19 +154,13 @@
   std::unique_ptr<gvr::BufferViewport> webvr_left_viewport_;
   std::unique_ptr<gvr::BufferViewport> webvr_right_viewport_;
   std::unique_ptr<gvr::SwapChain> swap_chain_;
-  std::queue<std::pair<uint8_t, WebVrBounds>> pending_bounds_;
-  int premature_received_frames_ = 0;
-  std::queue<uint16_t> pending_frames_;
-  std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
+  using BoundsPair = std::pair<gvr::Rectf, gvr::Rectf>;
+  std::queue<std::pair<uint8_t, BoundsPair>> pending_bounds_;
 
   // Current sizes for the render buffers.
   gvr::Sizei render_size_primary_;
   gvr::Sizei render_size_headlocked_;
 
-  // Intended render_size_primary_ for use by VrShell, so that it
-  // can be restored after exiting WebVR mode.
-  gvr::Sizei render_size_vrshell_;
-
   std::unique_ptr<VrShellRenderer> vr_shell_renderer_;
 
   bool touch_pending_ = false;
@@ -202,10 +175,10 @@
   int content_tex_css_width_ = 0;
   int content_tex_css_height_ = 0;
   gvr::Sizei content_tex_physical_size_ = {0, 0};
-  gvr::Sizei webvr_surface_size_ = {0, 0};
   gvr::Sizei ui_tex_physical_size_ = {0, 0};
 
   std::vector<gvr::Mat4f> webvr_head_pose_;
+  int webvr_texture_id_ = 0;
   bool web_vr_mode_;
   bool ready_to_draw_ = false;
   bool surfaceless_rendering_;
@@ -222,7 +195,6 @@
   GetVSyncCallback callback_;
   bool received_frame_ = false;
   mojo::Binding<device::mojom::VRVSyncProvider> binding_;
-  device::mojom::VRSubmitFrameClientPtr submit_client_;
 
   base::WeakPtr<VrShell> weak_vr_shell_;
   base::WeakPtr<VrShellDelegate> delegate_provider_;
diff --git a/chrome/browser/android/vr_shell/vr_shell_renderer.cc b/chrome/browser/android/vr_shell/vr_shell_renderer.cc
index 0ce95a9..55ce9510 100644
--- a/chrome/browser/android/vr_shell/vr_shell_renderer.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_renderer.cc
@@ -406,9 +406,11 @@
                         VOID_OFFSET(kTextureCoordinateDataOffset));
   glEnableVertexAttribArray(tex_coord_handle_);
 
-  // Bind texture. This is a 1:1 pixel copy since the source surface
-  // and renderbuffer destination size are resized to match, so use
-  // GL_NEAREST.
+  // Bind texture. Ideally this should be a 1:1 pixel copy. (Or even more
+  // ideally, a zero copy reuse of the texture.) For now, we're using an
+  // undersized render target for WebVR, so GL_LINEAR makes it look slightly
+  // less chunky. TODO(klausw): change this to GL_NEAREST once we're doing
+  // a 1:1 copy since that should be more efficient.
   glActiveTexture(GL_TEXTURE0);
   glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_handle);
   glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
diff --git a/chrome/browser/android/webapk/webapk.proto b/chrome/browser/android/webapk/webapk.proto
index 27fc96b..614d514 100644
--- a/chrome/browser/android/webapk/webapk.proto
+++ b/chrome/browser/android/webapk/webapk.proto
@@ -26,7 +26,15 @@
   // Unique id identifying session with WebAPK server.
   optional string token = 6;
 
-  reserved 4, 5;
+  // This flag may be used, for example, if the WebAPK server is unable to
+  // fulfill an update request and likely won't be able to fulfill subsequent
+  // update requests from this WebAPK. One case where the server is unable to
+  // fulfill update requests is if the Web Manifest is dynamically generated
+  // based on the user's country etc. Therefore, we want the client to back off
+  // from spamming update requests too often.
+  optional bool relax_updates = 8;
+
+  reserved 4, 5, 7;
 }
 
 // Sent as part of request to create or update a WebAPK.
diff --git a/chrome/browser/android/webapk/webapk_install_service.cc b/chrome/browser/android/webapk/webapk_install_service.cc
index 153543f..07974e5 100644
--- a/chrome/browser/android/webapk/webapk_install_service.cc
+++ b/chrome/browser/android/webapk/webapk_install_service.cc
@@ -64,7 +64,8 @@
     const GURL& web_manifest_url,
     const FinishCallback& finish_callback,
     bool success,
+    bool relax_updates,
     const std::string& webapk_package_name) {
-  finish_callback.Run(success, webapk_package_name);
+  finish_callback.Run(success, relax_updates, webapk_package_name);
   installs_.erase(web_manifest_url);
 }
diff --git a/chrome/browser/android/webapk/webapk_install_service.h b/chrome/browser/android/webapk/webapk_install_service.h
index e22dc9a..0a4e9d9 100644
--- a/chrome/browser/android/webapk/webapk_install_service.h
+++ b/chrome/browser/android/webapk/webapk_install_service.h
@@ -12,6 +12,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/android/webapk/webapk_installer.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "url/gurl.h"
 
@@ -26,11 +27,7 @@
 // WebAPK on the server, download it, and install it.
 class WebApkInstallService : public KeyedService {
  public:
-  // Called when the creation/updating of a WebAPK is finished or failed.
-  // Parameters:
-  // - whether the process succeeds.
-  // - the package name of the WebAPK.
-  using FinishCallback = base::Callback<void(bool, const std::string&)>;
+  using FinishCallback = WebApkInstaller::FinishCallback;
 
   static WebApkInstallService* Get(content::BrowserContext* browser_context);
 
@@ -64,6 +61,7 @@
   void OnFinishedInstall(const GURL& web_manifest_url,
                          const FinishCallback& finish_callback,
                          bool success,
+                         bool relax_updates,
                          const std::string& webapk_package_name);
 
   content::BrowserContext* browser_context_;
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index de4e576..90ea2332 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -57,6 +57,7 @@
 const int kWorldReadableFilePermission = base::FILE_PERMISSION_READ_BY_USER |
                                          base::FILE_PERMISSION_READ_BY_GROUP |
                                          base::FILE_PERMISSION_READ_BY_OTHERS;
+const int kDefaultWebApkVersion = 1;
 
 // Returns the WebAPK server URL based on the command line.
 GURL GetServerUrl() {
@@ -349,6 +350,8 @@
       server_url_(GetServerUrl()),
       webapk_download_url_timeout_ms_(kWebApkDownloadUrlTimeoutMs),
       download_timeout_ms_(kDownloadTimeoutMs),
+      relax_updates_(false),
+      webapk_version_(kDefaultWebApkVersion),
       task_type_(UNDEFINED),
       weak_ptr_factory_(this) {
   CreateJavaRef();
@@ -416,9 +419,10 @@
   }
 
   GURL signed_download_url(response->signed_download_url());
-  // https://crbug.com/680131. The server sends an empty URL if the server does
-  // not have a newer WebAPK to update to.
   if (task_type_ == UPDATE && signed_download_url.is_empty()) {
+    // https://crbug.com/680131. The server sends an empty URL if the server
+    // does not have a newer WebAPK to update to.
+    relax_updates_ = response->relax_updates();
     OnSuccess();
     return;
   }
@@ -623,11 +627,11 @@
 }
 
 void WebApkInstaller::OnSuccess() {
-  finish_callback_.Run(true, webapk_package_);
+  finish_callback_.Run(true, relax_updates_, webapk_package_);
   delete this;
 }
 
 void WebApkInstaller::OnFailure() {
-  finish_callback_.Run(false, webapk_package_);
+  finish_callback_.Run(false, relax_updates_, webapk_package_);
   delete this;
 }
diff --git a/chrome/browser/android/webapk/webapk_installer.h b/chrome/browser/android/webapk/webapk_installer.h
index d47d7522..b3d8176 100644
--- a/chrome/browser/android/webapk/webapk_installer.h
+++ b/chrome/browser/android/webapk/webapk_installer.h
@@ -43,7 +43,9 @@
   // Parameters:
   // - whether the process succeeds.
   // - the package name of the WebAPK.
-  using FinishCallback = base::Callback<void(bool, const std::string&)>;
+  // - true if Chrome received a "request updates less frequently" directive
+  //   from the WebAPK server.
+  using FinishCallback = base::Callback<void(bool, bool, const std::string&)>;
 
   ~WebApkInstaller() override;
 
@@ -270,6 +272,9 @@
   // WebAPK package name.
   std::string webapk_package_;
 
+  // Whether the server wants the WebAPK to request updates less frequently.
+  bool relax_updates_;
+
   // WebAPK version code.
   int webapk_version_;
 
diff --git a/chrome/browser/android/webapk/webapk_installer_unittest.cc b/chrome/browser/android/webapk/webapk_installer_unittest.cc
index f16681f8..ae4c1cc 100644
--- a/chrome/browser/android/webapk/webapk_installer_unittest.cc
+++ b/chrome/browser/android/webapk/webapk_installer_unittest.cc
@@ -162,7 +162,9 @@
   bool success() { return success_; }
 
  private:
-  void OnCompleted(bool success, const std::string& webapk_package) {
+  void OnCompleted(bool success,
+                   bool relax_updates,
+                   const std::string& webapk_package) {
     success_ = success;
     on_completed_callback_.Run();
   }
diff --git a/chrome/browser/android/webapk/webapk_update_manager.cc b/chrome/browser/android/webapk/webapk_update_manager.cc
index 235dab3..56142f82 100644
--- a/chrome/browser/android/webapk/webapk_update_manager.cc
+++ b/chrome/browser/android/webapk/webapk_update_manager.cc
@@ -30,6 +30,7 @@
 // static
 void WebApkUpdateManager::OnBuiltWebApk(const std::string& id,
                                         bool success,
+                                        bool relax_updates,
                                         const std::string& webapk_package) {
   JNIEnv* env = base::android::AttachCurrentThread();
 
@@ -42,7 +43,8 @@
 
   base::android::ScopedJavaLocalRef<jstring> java_id =
       base::android::ConvertUTF8ToJavaString(env, id);
-  Java_WebApkUpdateManager_onBuiltWebApk(env, java_id.obj(), success);
+  Java_WebApkUpdateManager_onBuiltWebApk(env, java_id.obj(), success,
+                                         relax_updates);
 }
 
 // static JNI method.
@@ -116,7 +118,8 @@
   if (install_service->IsInstallInProgress(info.manifest_url)) {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
-        base::Bind(&WebApkUpdateManager::OnBuiltWebApk, id, false, ""));
+        base::Bind(&WebApkUpdateManager::OnBuiltWebApk, id, false /* success */,
+                   false /* relax_updates */, "" /* webapk_package */));
     return;
   }
   install_service->UpdateAsync(
diff --git a/chrome/browser/android/webapk/webapk_update_manager.h b/chrome/browser/android/webapk/webapk_update_manager.h
index b6f37c7..285c3cf6 100644
--- a/chrome/browser/android/webapk/webapk_update_manager.h
+++ b/chrome/browser/android/webapk/webapk_update_manager.h
@@ -22,6 +22,7 @@
   // WebAPK will be successfully updated.
   static void OnBuiltWebApk(const std::string& id,
                             bool success,
+                            bool relax_updates,
                             const std::string& webapk_package);
 
  private:
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index e89cac16..58eb352 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -255,6 +255,8 @@
 
       <if expr="not is_android and not is_ios">
         <!-- MD Bookmarks. -->
+        <include name="IDR_MD_BOOKMARKS_ACTIONS_HTML" file="resources\md_bookmarks\actions.html" type="BINDATA" />
+        <include name="IDR_MD_BOOKMARKS_ACTIONS_JS" file="resources\md_bookmarks\actions.js" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_APP_HTML" file="resources\md_bookmarks\app.html" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_APP_JS" file="resources\md_bookmarks\app.js" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_BOOKMARKS_HTML" file="resources\md_bookmarks\bookmarks.html" type="BINDATA" />
@@ -280,6 +282,8 @@
         <include name="IDR_MD_BOOKMARKS_STORE_JS" file="resources\md_bookmarks\store.js" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_TOOLBAR_HTML" file="resources\md_bookmarks\toolbar.html" type="BINDATA" />
         <include name="IDR_MD_BOOKMARKS_TOOLBAR_JS" file="resources\md_bookmarks\toolbar.js" type="BINDATA" />
+        <include name="IDR_MD_BOOKMARKS_UTIL_HTML" file="resources\md_bookmarks\util.html" type="BINDATA" />
+        <include name="IDR_MD_BOOKMARKS_UTIL_JS" file="resources\md_bookmarks\util.js" type="BINDATA" />
 
         <!-- MD History. -->
         <include name="IDR_MD_HISTORY_CONSTANTS_HTML" file="resources\md_history\constants.html" type="BINDATA" />
diff --git a/chrome/browser/captive_portal/OWNERS b/chrome/browser/captive_portal/OWNERS
index 747dc15..a122871 100644
--- a/chrome/browser/captive_portal/OWNERS
+++ b/chrome/browser/captive_portal/OWNERS
@@ -1 +1,3 @@
-mmenke@chromium.org
\ No newline at end of file
+mmenke@chromium.org
+
+# COMPONENT: Internals>Network
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 2086fd8..26d6c02 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -69,7 +69,6 @@
     "//components/pairing",
     "//components/policy:generated",
     "//components/proxy_config",
-    "//components/safe_browsing:csd_proto",
     "//components/safe_browsing_db:metadata_proto",
     "//components/session_manager/core",
     "//components/sync_wifi",
diff --git a/chrome/browser/geolocation/geolocation_permission_context_android.cc b/chrome/browser/geolocation/geolocation_permission_context_android.cc
index ed7edd5e..d13cd8d 100644
--- a/chrome/browser/geolocation/geolocation_permission_context_android.cc
+++ b/chrome/browser/geolocation/geolocation_permission_context_android.cc
@@ -38,11 +38,12 @@
 }
 
 ContentSetting GeolocationPermissionContextAndroid::GetPermissionStatusInternal(
+    content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   ContentSetting value =
       GeolocationPermissionContext::GetPermissionStatusInternal(
-          requesting_origin, embedding_origin);
+          render_frame_host, requesting_origin, embedding_origin);
 
   if (value == CONTENT_SETTING_ASK && requesting_origin == embedding_origin) {
     // Consult the DSE Geolocation setting. Note that this only needs to be
@@ -93,8 +94,9 @@
 
   GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
   ContentSetting content_setting =
-      GeolocationPermissionContext::GetPermissionStatus(requesting_frame_origin,
-                                                        embedding_origin)
+      GeolocationPermissionContext::GetPermissionStatus(
+          nullptr /* render_frame_host */, requesting_frame_origin,
+          embedding_origin)
           .content_setting;
   std::vector<ContentSettingsType> content_settings_types;
   content_settings_types.push_back(CONTENT_SETTINGS_TYPE_GEOLOCATION);
diff --git a/chrome/browser/geolocation/geolocation_permission_context_android.h b/chrome/browser/geolocation/geolocation_permission_context_android.h
index 146c163..3b89e421 100644
--- a/chrome/browser/geolocation/geolocation_permission_context_android.h
+++ b/chrome/browser/geolocation/geolocation_permission_context_android.h
@@ -49,6 +49,7 @@
  protected:
   // GeolocationPermissionContext:
   ContentSetting GetPermissionStatusInternal(
+      content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       const GURL& embedding_origin) const override;
 
diff --git a/chrome/browser/media/midi_permission_context_unittest.cc b/chrome/browser/media/midi_permission_context_unittest.cc
index 2feb452..9b90980f 100644
--- a/chrome/browser/media/midi_permission_context_unittest.cc
+++ b/chrome/browser/media/midi_permission_context_unittest.cc
@@ -143,10 +143,14 @@
                               std::string()));
 
   EXPECT_EQ(CONTENT_SETTING_BLOCK,
-            permission_context.GetPermissionStatus(insecure_url, insecure_url)
+            permission_context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     insecure_url, insecure_url)
                 .content_setting);
 
   EXPECT_EQ(CONTENT_SETTING_BLOCK,
-            permission_context.GetPermissionStatus(insecure_url, secure_url)
+            permission_context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     insecure_url, secure_url)
                 .content_setting);
 }
diff --git a/chrome/browser/media/protected_media_identifier_permission_context.cc b/chrome/browser/media/protected_media_identifier_permission_context.cc
index 1dbfcc9..90e3081 100644
--- a/chrome/browser/media/protected_media_identifier_permission_context.cc
+++ b/chrome/browser/media/protected_media_identifier_permission_context.cc
@@ -83,6 +83,7 @@
 
 ContentSetting
 ProtectedMediaIdentifierPermissionContext::GetPermissionStatusInternal(
+    content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   DVLOG(1) << __func__ << ": (" << requesting_origin.spec() << ", "
@@ -94,8 +95,8 @@
   }
 
   ContentSetting content_setting =
-      PermissionContextBase::GetPermissionStatusInternal(requesting_origin,
-                                                         embedding_origin);
+      PermissionContextBase::GetPermissionStatusInternal(
+          render_frame_host, requesting_origin, embedding_origin);
   DCHECK(content_setting == CONTENT_SETTING_ALLOW ||
          content_setting == CONTENT_SETTING_BLOCK ||
          content_setting == CONTENT_SETTING_ASK);
diff --git a/chrome/browser/media/protected_media_identifier_permission_context.h b/chrome/browser/media/protected_media_identifier_permission_context.h
index b5e22f7..8bb0c68 100644
--- a/chrome/browser/media/protected_media_identifier_permission_context.h
+++ b/chrome/browser/media/protected_media_identifier_permission_context.h
@@ -46,6 +46,7 @@
                         const BrowserPermissionCallback& callback) override;
 #endif  // defined(OS_CHROMEOS)
   ContentSetting GetPermissionStatusInternal(
+      content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       const GURL& embedding_origin) const override;
   void CancelPermissionRequest(content::WebContents* web_contents,
diff --git a/chrome/browser/media/webrtc/media_permission.cc b/chrome/browser/media/webrtc/media_permission.cc
index 13b3dcaf..b330bdf 100644
--- a/chrome/browser/media/webrtc/media_permission.cc
+++ b/chrome/browser/media/webrtc/media_permission.cc
@@ -115,45 +115,3 @@
     *denial_reason = content::MEDIA_DEVICE_PERMISSION_DENIED;
   return content_setting;
 }
-
-ContentSetting MediaPermission::GetPermissionStatusWithDeviceRequired(
-    const std::string& device_id,
-    content::MediaStreamRequestResult* denial_reason) const {
-  // Deny the request if there is no device attached to the OS of the requested
-  // type.
-  if (!HasAvailableDevices(device_id)) {
-    *denial_reason = content::MEDIA_DEVICE_NO_HARDWARE;
-    return CONTENT_SETTING_BLOCK;
-  }
-
-  return GetPermissionStatus(denial_reason);
-}
-
-bool MediaPermission::HasAvailableDevices(const std::string& device_id) const {
-  const content::MediaStreamDevices* devices = nullptr;
-  if (content_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
-    devices =
-        &MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices();
-  } else if (content_type_ == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
-    devices =
-        &MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
-  } else {
-    NOTREACHED();
-  }
-
-  // TODO(tommi): It's kind of strange to have this here since if we fail this
-  // test, there'll be a UI shown that indicates to the user that access to
-  // non-existing audio/video devices has been denied.  The user won't have
-  // any way to change that but there will be a UI shown which indicates that
-  // access is blocked.
-  if (devices->empty())
-    return false;
-
-  // Note: we check device_id before dereferencing devices. If the requested
-  // device id is non-empty, then the corresponding device list must not be
-  // NULL.
-  if (!device_id.empty() && !devices->FindById(device_id))
-    return false;
-
-  return true;
-}
diff --git a/chrome/browser/media/webrtc/media_permission.h b/chrome/browser/media/webrtc/media_permission.h
index 1ab5471..ef7a883 100644
--- a/chrome/browser/media/webrtc/media_permission.h
+++ b/chrome/browser/media/webrtc/media_permission.h
@@ -34,12 +34,6 @@
   ContentSetting GetPermissionStatus(
       content::MediaStreamRequestResult* denial_reason) const;
 
-  // Returns the status of the permission as with GetPermissionStatus but also
-  // checks that the specified |device_id| is an available device.
-  ContentSetting GetPermissionStatusWithDeviceRequired(
-      const std::string& device_id,
-      content::MediaStreamRequestResult* denial_reason) const;
-
  private:
   bool HasAvailableDevices(const std::string& device_id) const;
 
diff --git a/chrome/browser/media/webrtc/media_stream_device_permission_context.cc b/chrome/browser/media/webrtc/media_stream_device_permission_context.cc
index 2d34b90..80b05a5 100644
--- a/chrome/browser/media/webrtc/media_stream_device_permission_context.cc
+++ b/chrome/browser/media/webrtc/media_stream_device_permission_context.cc
@@ -33,6 +33,7 @@
 }
 
 ContentSetting MediaStreamDevicePermissionContext::GetPermissionStatusInternal(
+    content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   // TODO(raymes): Merge this policy check into content settings
@@ -63,7 +64,7 @@
   // Check the content setting. TODO(raymes): currently mic/camera permission
   // doesn't consider the embedder.
   ContentSetting setting = PermissionContextBase::GetPermissionStatusInternal(
-      requesting_origin, requesting_origin);
+      render_frame_host, requesting_origin, requesting_origin);
 
   if (setting == CONTENT_SETTING_DEFAULT)
     setting = CONTENT_SETTING_ASK;
diff --git a/chrome/browser/media/webrtc/media_stream_device_permission_context.h b/chrome/browser/media/webrtc/media_stream_device_permission_context.h
index 274edcf1..beee90d9 100644
--- a/chrome/browser/media/webrtc/media_stream_device_permission_context.h
+++ b/chrome/browser/media/webrtc/media_stream_device_permission_context.h
@@ -26,6 +26,7 @@
   // TODO(xhwang): GURL.GetOrigin() shouldn't be used as the origin. Need to
   // refactor to use url::Origin. crbug.com/527149 is filed for this.
   ContentSetting GetPermissionStatusInternal(
+      content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       const GURL& embedding_origin) const override;
 
diff --git a/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc b/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc
index 97a2d10..2f78422c 100644
--- a/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc
+++ b/chrome/browser/media/webrtc/media_stream_device_permission_context_unittest.cc
@@ -66,11 +66,15 @@
                                       content_settings_type, std::string()));
 
     EXPECT_EQ(CONTENT_SETTING_ASK,
-              permission_context.GetPermissionStatus(insecure_url, insecure_url)
+              permission_context
+                  .GetPermissionStatus(nullptr /* render_frame_host */,
+                                       insecure_url, insecure_url)
                   .content_setting);
 
     EXPECT_EQ(CONTENT_SETTING_ASK,
-              permission_context.GetPermissionStatus(insecure_url, secure_url)
+              permission_context
+                  .GetPermissionStatus(nullptr /* render_frame_host */,
+                                       insecure_url, secure_url)
                   .content_setting);
   }
 
@@ -87,7 +91,9 @@
                                       std::string()));
 
     EXPECT_EQ(CONTENT_SETTING_ASK,
-              permission_context.GetPermissionStatus(secure_url, secure_url)
+              permission_context
+                  .GetPermissionStatus(nullptr /* render_frame_host */,
+                                       secure_url, secure_url)
                   .content_setting);
   }
 
diff --git a/chrome/browser/media/webrtc/media_stream_devices_controller.cc b/chrome/browser/media/webrtc/media_stream_devices_controller.cc
index e2ad13c..ea1c264 100644
--- a/chrome/browser/media/webrtc/media_stream_devices_controller.cc
+++ b/chrome/browser/media/webrtc/media_stream_devices_controller.cc
@@ -173,8 +173,83 @@
   RequestMap::key_type key_;
 };
 
+bool HasAvailableDevices(ContentSettingsType content_type,
+                         const std::string& device_id) {
+  const content::MediaStreamDevices* devices = nullptr;
+  if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC) {
+    devices =
+        &MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices();
+  } else if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
+    devices =
+        &MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
+  } else {
+    NOTREACHED();
+  }
+
+  // TODO(tommi): It's kind of strange to have this here since if we fail this
+  // test, there'll be a UI shown that indicates to the user that access to
+  // non-existing audio/video devices has been denied.  The user won't have
+  // any way to change that but there will be a UI shown which indicates that
+  // access is blocked.
+  if (devices->empty())
+    return false;
+
+  // Note: we check device_id before dereferencing devices. If the requested
+  // device id is non-empty, then the corresponding device list must not be
+  // NULL.
+  if (!device_id.empty() && !devices->FindById(device_id))
+    return false;
+
+  return true;
+}
+
 }  // namespace
 
+// Stores whether a permission has been requested or blocked during the course
+// of a permission request, as well as the denial reason
+class MediaStreamDevicesController::MediaPermissionStatus {
+ public:
+  explicit MediaPermissionStatus(const content::MediaStreamRequest& request)
+      : audio_requested_(
+            ContentTypeIsRequested(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+                                   request)),
+        video_requested_(
+            ContentTypeIsRequested(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+                                   request)) {}
+
+  ~MediaPermissionStatus() {}
+
+  bool audio_requested() const { return audio_requested_; }
+  bool video_requested() const { return video_requested_; }
+
+  bool audio_blocked() const { return audio_blocked_; }
+  bool video_blocked() const { return video_blocked_; }
+
+  content::MediaStreamRequestResult denial_reason() const {
+    return denial_reason_;
+  }
+
+  void SetAudioBlocked(content::MediaStreamRequestResult denial_reason) {
+    DCHECK(audio_requested_);
+    audio_blocked_ = true;
+    denial_reason_ = denial_reason;
+  }
+
+  void SetVideoBlocked(content::MediaStreamRequestResult denial_reason) {
+    DCHECK(video_requested_);
+    video_blocked_ = true;
+    denial_reason_ = denial_reason;
+  }
+
+ private:
+  bool audio_requested_ = false;
+  bool video_requested_ = false;
+  bool audio_blocked_ = false;
+  bool video_blocked_ = false;
+
+  content::MediaStreamRequestResult denial_reason_ = content::MEDIA_DEVICE_OK;
+};
+
 // Implementation of PermissionPromptDelegate which actually shows a permission
 // prompt.
 class MediaStreamDevicesController::PermissionPromptDelegateImpl
@@ -330,8 +405,28 @@
     const content::MediaStreamRequest& request,
     const content::MediaResponseCallback& callback,
     PermissionPromptDelegate* delegate) {
+  if (request.request_type == content::MEDIA_OPEN_DEVICE_PEPPER_ONLY) {
+    MediaPermissionRequestLogger::LogRequest(
+        web_contents, request.render_process_id, request.render_frame_id,
+        content::IsOriginSecure(request.security_origin));
+  }
+
+  MediaPermissionStatus initial_permission(request);
+  if (initial_permission.audio_requested() &&
+      !HasAvailableDevices(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
+                           request.requested_audio_device_id)) {
+    initial_permission.SetAudioBlocked(content::MEDIA_DEVICE_NO_HARDWARE);
+  }
+
+  if (initial_permission.video_requested() &&
+      !HasAvailableDevices(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+                           request.requested_video_device_id)) {
+    initial_permission.SetVideoBlocked(content::MEDIA_DEVICE_NO_HARDWARE);
+  }
+
   std::unique_ptr<MediaStreamDevicesController> controller(
-      new MediaStreamDevicesController(web_contents, request, callback));
+      new MediaStreamDevicesController(web_contents, request, callback,
+                                       initial_permission));
   if (!controller->IsAskingForAudio() && !controller->IsAskingForVideo()) {
 #if defined(OS_ANDROID)
     // If either audio or video was previously allowed and Chrome no longer has
@@ -363,21 +458,22 @@
 MediaStreamDevicesController::MediaStreamDevicesController(
     content::WebContents* web_contents,
     const content::MediaStreamRequest& request,
-    const content::MediaResponseCallback& callback)
+    const content::MediaResponseCallback& callback,
+    const MediaPermissionStatus& initial_permission)
     : web_contents_(web_contents), request_(request), callback_(callback) {
-  if (request_.request_type == content::MEDIA_OPEN_DEVICE_PEPPER_ONLY) {
-    MediaPermissionRequestLogger::LogRequest(
-        web_contents, request.render_process_id, request.render_frame_id,
-        content::IsOriginSecure(request_.security_origin));
-  }
   profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
   content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
 
-  content::MediaStreamRequestResult denial_reason = content::MEDIA_DEVICE_OK;
-  old_audio_setting_ = GetContentSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
-                                         request_, &denial_reason);
-  old_video_setting_ = GetContentSetting(
-      CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, request_, &denial_reason);
+  content::MediaStreamRequestResult denial_reason =
+      initial_permission.denial_reason();
+  old_audio_setting_ =
+      GetContentSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, request,
+                        initial_permission.audio_requested(),
+                        initial_permission.audio_blocked(), &denial_reason);
+  old_video_setting_ =
+      GetContentSetting(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, request,
+                        initial_permission.video_requested(),
+                        initial_permission.video_blocked(), &denial_reason);
 
   // If either setting is ask, we show the infobar.
   if (old_audio_setting_ == CONTENT_SETTING_ASK ||
@@ -615,32 +711,32 @@
 ContentSetting MediaStreamDevicesController::GetContentSetting(
     ContentSettingsType content_type,
     const content::MediaStreamRequest& request,
+    bool was_requested,
+    bool was_initially_blocked,
     content::MediaStreamRequestResult* denial_reason) const {
   DCHECK(content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
          content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
+  DCHECK(content::IsOriginSecure(request_.security_origin) ||
+         request_.request_type == content::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
+  if (!was_requested) {
+    // No denial reason set as it will have been previously set.
+    return CONTENT_SETTING_DEFAULT;
+  }
 
-  std::string requested_device_id;
-  if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC)
-    requested_device_id = request.requested_audio_device_id;
-  else if (content_type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA)
-    requested_device_id = request.requested_video_device_id;
+  if (was_initially_blocked) {
+    // No denial reason set as it will have been previously set.
+    return CONTENT_SETTING_BLOCK;
+  }
 
   if (!IsUserAcceptAllowed(content_type)) {
     *denial_reason = content::MEDIA_DEVICE_PERMISSION_DENIED;
     return CONTENT_SETTING_BLOCK;
   }
 
-  if (ContentTypeIsRequested(content_type, request)) {
-    DCHECK(content::IsOriginSecure(request_.security_origin) ||
-           request_.request_type == content::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
-    MediaPermission permission(content_type, request.security_origin,
-                               web_contents_->GetLastCommittedURL().GetOrigin(),
-                               profile_, web_contents_);
-    return permission.GetPermissionStatusWithDeviceRequired(requested_device_id,
-                                                            denial_reason);
-  }
-  // Return the default content setting if the device is not requested.
-  return CONTENT_SETTING_DEFAULT;
+  MediaPermission permission(content_type, request.security_origin,
+                             web_contents_->GetLastCommittedURL().GetOrigin(),
+                             profile_, web_contents_);
+  return permission.GetPermissionStatus(denial_reason);
 }
 
 ContentSetting MediaStreamDevicesController::GetNewSetting(
diff --git a/chrome/browser/media/webrtc/media_stream_devices_controller.h b/chrome/browser/media/webrtc/media_stream_devices_controller.h
index 0e50282..1777cd9 100644
--- a/chrome/browser/media/webrtc/media_stream_devices_controller.h
+++ b/chrome/browser/media/webrtc/media_stream_devices_controller.h
@@ -78,6 +78,7 @@
         std::unique_ptr<MediaStreamDevicesController> controller) = 0;
   };
 
+  class MediaPermissionStatus;
   class PermissionPromptDelegateImpl;
 
   static void RequestPermissionsWithDelegate(
@@ -88,7 +89,8 @@
 
   MediaStreamDevicesController(content::WebContents* web_contents,
                                const content::MediaStreamRequest& request,
-                               const content::MediaResponseCallback& callback);
+                               const content::MediaResponseCallback& callback,
+                               const MediaPermissionStatus& initial_permission);
 
   bool IsAllowedForAudio() const;
   bool IsAllowedForVideo() const;
@@ -119,6 +121,8 @@
   ContentSetting GetContentSetting(
       ContentSettingsType content_type,
       const content::MediaStreamRequest& request,
+      bool was_requested,
+      bool was_initially_blocked,
       content::MediaStreamRequestResult* denial_reason) const;
 
   // Returns the content setting that should apply given an old content setting
diff --git a/chrome/browser/notifications/notification_interactive_uitest.cc b/chrome/browser/notifications/notification_interactive_uitest.cc
index 483c74e..52e0d259 100644
--- a/chrome/browser/notifications/notification_interactive_uitest.cc
+++ b/chrome/browser/notifications/notification_interactive_uitest.cc
@@ -11,22 +11,17 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "base/time/clock.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/infobars/infobar_service.h"
-#include "chrome/browser/notifications/desktop_notification_profile_util.h"
 #include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/notifications/notification_interactive_uitest_support.h"
 #include "chrome/browser/notifications/notification_test_util.h"
-#include "chrome/browser/notifications/web_notification_delegate.h"
-#include "chrome/browser/permissions/permission_request_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
@@ -34,12 +29,8 @@
 #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/content_settings/core/browser/host_content_settings_map.h"
-#include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_source.h"
@@ -97,327 +88,8 @@
 
 const char kExpectedIconUrl[] = "/notifications/no_such_file.png";
 
-class NotificationChangeObserver {
-public:
-  virtual ~NotificationChangeObserver() {}
-  virtual bool Wait() = 0;
-};
-
-class MessageCenterChangeObserver
-    : public message_center::MessageCenterObserver,
-      public NotificationChangeObserver {
- public:
-  MessageCenterChangeObserver()
-      : notification_received_(false) {
-    message_center::MessageCenter::Get()->AddObserver(this);
-  }
-
-  ~MessageCenterChangeObserver() override {
-    message_center::MessageCenter::Get()->RemoveObserver(this);
-  }
-
-  // NotificationChangeObserver:
-  bool Wait() override {
-    if (notification_received_)
-      return true;
-
-    message_loop_runner_ = new content::MessageLoopRunner;
-    message_loop_runner_->Run();
-    return notification_received_;
-  }
-
-  // message_center::MessageCenterObserver:
-  void OnNotificationAdded(const std::string& notification_id) override {
-    OnMessageCenterChanged();
-  }
-
-  void OnNotificationRemoved(const std::string& notification_id,
-                             bool by_user) override {
-    OnMessageCenterChanged();
-  }
-
-  void OnNotificationUpdated(const std::string& notification_id) override {
-    OnMessageCenterChanged();
-  }
-
-  void OnMessageCenterChanged() {
-    notification_received_ = true;
-    if (message_loop_runner_.get())
-      message_loop_runner_->Quit();
-  }
-
-  bool notification_received_;
-  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
-
-  DISALLOW_COPY_AND_ASSIGN(MessageCenterChangeObserver);
-};
-
-// Used to observe the creation of permission prompt without responding.
-class PermissionRequestObserver : public PermissionRequestManager::Observer {
- public:
-  explicit PermissionRequestObserver(content::WebContents* web_contents)
-      : request_manager_(
-            PermissionRequestManager::FromWebContents(web_contents)),
-        request_shown_(false),
-        message_loop_runner_(new content::MessageLoopRunner) {
-    request_manager_->AddObserver(this);
-  }
-  ~PermissionRequestObserver() override {
-    // Safe to remove twice if it happens.
-    request_manager_->RemoveObserver(this);
-  }
-
-  void Wait() { message_loop_runner_->Run(); }
-
-  bool request_shown() { return request_shown_; }
-
- private:
-  // PermissionRequestManager::Observer
-  void OnBubbleAdded() override {
-    request_shown_ = true;
-    request_manager_->RemoveObserver(this);
-    message_loop_runner_->Quit();
-  }
-
-  PermissionRequestManager* request_manager_;
-  bool request_shown_;
-  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
-
-  DISALLOW_COPY_AND_ASSIGN(PermissionRequestObserver);
-};
-
 }  // namespace
 
-class NotificationsTest : public InProcessBrowserTest {
- public:
-  NotificationsTest() {}
-
- protected:
-  int GetNotificationCount();
-  int GetNotificationPopupCount();
-
-  void CloseBrowserWindow(Browser* browser);
-  void CrashTab(Browser* browser, int index);
-
-  void DenyOrigin(const GURL& origin);
-  void AllowOrigin(const GURL& origin);
-  void AllowAllOrigins();
-  void SetDefaultContentSetting(ContentSetting setting);
-
-  std::string CreateNotification(Browser* browser,
-                                 bool wait_for_new_balloon,
-                                 const char* icon,
-                                 const char* title,
-                                 const char* body,
-                                 const char* replace_id);
-  std::string CreateSimpleNotification(Browser* browser,
-                                       bool wait_for_new_balloon);
-  bool RequestAndAcceptPermission(Browser* browser);
-  bool RequestAndDenyPermission(Browser* browser);
-  bool RequestAndDismissPermission(Browser* browser);
-  bool RequestPermissionAndWait(Browser* browser);
-  bool CancelNotification(const char* notification_id, Browser* browser);
-  void GetPrefsByContentSetting(ContentSetting setting,
-                                ContentSettingsForOneType* settings);
-  bool CheckOriginInSetting(const ContentSettingsForOneType& settings,
-                            const GURL& origin);
-
-  GURL GetTestPageURLForFile(const std::string& file) const {
-    return embedded_test_server()->GetURL(
-      std::string("/notifications/") + file);
-  }
-
-  GURL GetTestPageURL() const {
-    return GetTestPageURLForFile("notification_tester.html");
-  }
-
-  content::WebContents* GetActiveWebContents(Browser* browser) {
-    return browser->tab_strip_model()->GetActiveWebContents();
-  }
-
- protected:
-  void EnableFullscreenNotifications() {
-    feature_list_.InitWithFeatures({
-      features::kPreferHtmlOverPlugins,
-      features::kAllowFullscreenWebNotificationsFeature}, {});
-  }
-
-  void DisableFullscreenNotifications() {
-    feature_list_.InitWithFeatures(
-        {features::kPreferHtmlOverPlugins},
-        {features::kAllowFullscreenWebNotificationsFeature});
-  }
-
- private:
-  void DropOriginPreference(const GURL& origin);
-  std::string RequestAndRespondToPermission(
-      Browser* browser,
-      PermissionRequestManager::AutoResponseType bubble_response);
-
-  base::test::ScopedFeatureList feature_list_;
-};
-
-int NotificationsTest::GetNotificationCount() {
-  return message_center::MessageCenter::Get()->NotificationCount();
-}
-
-int NotificationsTest::GetNotificationPopupCount() {
-  return message_center::MessageCenter::Get()->GetPopupNotifications().size();
-}
-
-void NotificationsTest::CloseBrowserWindow(Browser* browser) {
-  content::WindowedNotificationObserver observer(
-      chrome::NOTIFICATION_BROWSER_CLOSED,
-      content::Source<Browser>(browser));
-  browser->window()->Close();
-  observer.Wait();
-}
-
-void NotificationsTest::CrashTab(Browser* browser, int index) {
-  content::CrashTab(browser->tab_strip_model()->GetWebContentsAt(index));
-}
-
-void NotificationsTest::DenyOrigin(const GURL& origin) {
-  DropOriginPreference(origin);
-  DesktopNotificationProfileUtil::DenyPermission(browser()->profile(), origin);
-}
-
-void NotificationsTest::AllowOrigin(const GURL& origin) {
-  DropOriginPreference(origin);
-  DesktopNotificationProfileUtil::GrantPermission(browser()->profile(), origin);
-}
-
-void NotificationsTest::AllowAllOrigins() {
-  // Reset all origins
-  HostContentSettingsMapFactory::GetForProfile(browser()->profile())
-      ->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
-  SetDefaultContentSetting(CONTENT_SETTING_ALLOW);
- }
-
-void NotificationsTest::SetDefaultContentSetting(ContentSetting setting) {
-  HostContentSettingsMapFactory::GetForProfile(browser()->profile())
-      ->SetDefaultContentSetting(CONTENT_SETTINGS_TYPE_NOTIFICATIONS, setting);
-}
-
-std::string NotificationsTest::CreateNotification(
-    Browser* browser,
-    bool wait_for_new_balloon,
-    const char* icon,
-    const char* title,
-    const char* body,
-    const char* replace_id) {
-  std::string script = base::StringPrintf(
-      "createNotification('%s', '%s', '%s', '%s');",
-      icon, title, body, replace_id);
-
-  MessageCenterChangeObserver observer;
-  std::string result;
-  bool success = content::ExecuteScriptAndExtractString(
-      GetActiveWebContents(browser), script, &result);
-  if (success && result != "-1" && wait_for_new_balloon)
-    success = observer.Wait();
-  EXPECT_TRUE(success);
-
-  return result;
-}
-
-std::string NotificationsTest::CreateSimpleNotification(
-    Browser* browser,
-    bool wait_for_new_balloon) {
-  return CreateNotification(
-      browser, wait_for_new_balloon,
-      "no_such_file.png", "My Title", "My Body", "");
-}
-
-std::string NotificationsTest::RequestAndRespondToPermission(
-    Browser* browser,
-    PermissionRequestManager::AutoResponseType bubble_response) {
-  std::string result;
-  content::WebContents* web_contents = GetActiveWebContents(browser);
-  PermissionRequestManager::FromWebContents(web_contents)
-      ->set_auto_response_for_test(bubble_response);
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents, "requestPermission();", &result));
-  return result;
-}
-
-bool NotificationsTest::RequestAndAcceptPermission(Browser* browser) {
-  std::string result = RequestAndRespondToPermission(
-      browser, PermissionRequestManager::ACCEPT_ALL);
-  return "request-callback-granted" == result;
-}
-
-bool NotificationsTest::RequestAndDenyPermission(Browser* browser) {
-  std::string result = RequestAndRespondToPermission(
-      browser, PermissionRequestManager::DENY_ALL);
-  return "request-callback-denied" == result;
-}
-
-bool NotificationsTest::RequestAndDismissPermission(Browser* browser) {
-  std::string result =
-      RequestAndRespondToPermission(browser, PermissionRequestManager::DISMISS);
-  return "request-callback-default" == result;
-}
-
-bool NotificationsTest::RequestPermissionAndWait(Browser* browser) {
-  content::WebContents* web_contents = GetActiveWebContents(browser);
-  ui_test_utils::NavigateToURL(browser, GetTestPageURL());
-  PermissionRequestObserver observer(web_contents);
-  std::string result;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      web_contents, "requestPermissionAndRespond();", &result));
-  EXPECT_EQ("requested", result);
-  observer.Wait();
-  return observer.request_shown();
-}
-
-bool NotificationsTest::CancelNotification(
-    const char* notification_id,
-    Browser* browser) {
-  std::string script = base::StringPrintf(
-      "cancelNotification('%s');",
-      notification_id);
-
-  MessageCenterChangeObserver observer;
-  std::string result;
-  bool success = content::ExecuteScriptAndExtractString(
-      GetActiveWebContents(browser), script, &result);
-  if (!success || result != "1")
-    return false;
-  return observer.Wait();
-}
-
-void NotificationsTest::GetPrefsByContentSetting(
-    ContentSetting setting,
-    ContentSettingsForOneType* settings) {
-  DesktopNotificationProfileUtil::GetNotificationsSettings(
-      browser()->profile(), settings);
-  for (ContentSettingsForOneType::iterator it = settings->begin();
-       it != settings->end(); ) {
-    if (it->setting != setting || it->source.compare("preference") != 0)
-      it = settings->erase(it);
-    else
-      ++it;
-  }
-}
-
-bool NotificationsTest::CheckOriginInSetting(
-    const ContentSettingsForOneType& settings,
-    const GURL& origin) {
-  ContentSettingsPattern pattern =
-      ContentSettingsPattern::FromURLNoWildcard(origin);
-  for (ContentSettingsForOneType::const_iterator it = settings.begin();
-       it != settings.end(); ++it) {
-    if (it->primary_pattern == pattern)
-      return true;
-  }
-  return false;
-}
-
-void NotificationsTest::DropOriginPreference(const GURL& origin) {
-  DesktopNotificationProfileUtil::ClearSetting(browser()->profile(), origin);
-}
-
 // Flaky on Windows, Mac, Linux: http://crbug.com/437414.
 IN_PROC_BROWSER_TEST_F(NotificationsTest, DISABLED_TestUserGestureInfobar) {
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/notifications/notification_interactive_uitest_mac.mm b/chrome/browser/notifications/notification_interactive_uitest_mac.mm
new file mode 100644
index 0000000..9a648ae
--- /dev/null
+++ b/chrome/browser/notifications/notification_interactive_uitest_mac.mm
@@ -0,0 +1,53 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <AppKit/AppKit.h>
+
+#include "chrome/browser/notifications/notification_interactive_uitest_support.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#import "ui/base/test/windowed_nsnotification_observer.h"
+#include "ui/message_center/message_center.h"
+
+// Verify that Chrome becomes the active app if a notification opens a popup
+// when clicked.
+IN_PROC_BROWSER_TEST_F(NotificationsTest, TestPopupShouldActivateApp) {
+  EXPECT_TRUE(embedded_test_server()->Start());
+
+  AllowAllOrigins();
+  ui_test_utils::NavigateToURL(browser(), GetTestPageURL());
+
+  EXPECT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(
+      browser()->window()->GetNativeWindow()));
+
+  {
+    base::scoped_nsobject<WindowedNSNotificationObserver> observer(
+        [[WindowedNSNotificationObserver alloc]
+            initForNotification:NSApplicationDidResignActiveNotification
+                         object:NSApp]);
+    [NSApp hide:nil];
+    [observer wait];
+  }
+  EXPECT_FALSE([NSApp isActive]);
+
+  std::string result = CreateNotification(
+      browser(), true, "", "", "", "",
+      "window.open('', '', 'width=100,height=100').focus();");
+  EXPECT_NE("-1", result);
+
+  auto* message_center = message_center::MessageCenter::Get();
+  message_center::Notification* notification =
+      *message_center->GetVisibleNotifications().begin();
+
+  {
+    base::scoped_nsobject<WindowedNSNotificationObserver> observer(
+        [[WindowedNSNotificationObserver alloc]
+            initForNotification:NSApplicationDidBecomeActiveNotification
+                         object:NSApp]);
+    message_center->ClickOnNotification(notification->id());
+    [observer wait];
+  }
+  EXPECT_TRUE([NSApp isActive]);
+}
diff --git a/chrome/browser/notifications/notification_interactive_uitest_support.cc b/chrome/browser/notifications/notification_interactive_uitest_support.cc
new file mode 100644
index 0000000..73680f9
--- /dev/null
+++ b/chrome/browser/notifications/notification_interactive_uitest_support.cc
@@ -0,0 +1,299 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/notifications/notification_interactive_uitest_support.h"
+
+#include "base/run_loop.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/notifications/desktop_notification_profile_util.h"
+#include "chrome/browser/notifications/web_notification_delegate.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/test/browser_test_utils.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/message_center_observer.h"
+
+namespace {
+
+// Used to observe the creation of permission prompt without responding.
+class PermissionRequestObserver : public PermissionRequestManager::Observer {
+ public:
+  explicit PermissionRequestObserver(content::WebContents* web_contents)
+      : request_manager_(
+            PermissionRequestManager::FromWebContents(web_contents)),
+        request_shown_(false),
+        message_loop_runner_(new content::MessageLoopRunner) {
+    request_manager_->AddObserver(this);
+  }
+  ~PermissionRequestObserver() override {
+    // Safe to remove twice if it happens.
+    request_manager_->RemoveObserver(this);
+  }
+
+  void Wait() { message_loop_runner_->Run(); }
+
+  bool request_shown() { return request_shown_; }
+
+ private:
+  // PermissionRequestManager::Observer
+  void OnBubbleAdded() override {
+    request_shown_ = true;
+    request_manager_->RemoveObserver(this);
+    message_loop_runner_->Quit();
+  }
+
+  PermissionRequestManager* request_manager_;
+  bool request_shown_;
+  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(PermissionRequestObserver);
+};
+
+}  // namespace
+
+class MessageCenterChangeObserver::Impl
+    : public message_center::MessageCenterObserver {
+ public:
+  Impl() : notification_received_(false) {
+    message_center::MessageCenter::Get()->AddObserver(this);
+  }
+
+  ~Impl() override {
+    message_center::MessageCenter::Get()->RemoveObserver(this);
+  }
+
+  bool Wait() {
+    if (notification_received_)
+      return true;
+
+    message_loop_runner_ = new content::MessageLoopRunner;
+    message_loop_runner_->Run();
+    return notification_received_;
+  }
+
+  // message_center::MessageCenterObserver:
+  void OnNotificationAdded(const std::string& notification_id) override {
+    OnMessageCenterChanged();
+  }
+
+  void OnNotificationRemoved(const std::string& notification_id,
+                             bool by_user) override {
+    OnMessageCenterChanged();
+  }
+
+  void OnNotificationUpdated(const std::string& notification_id) override {
+    OnMessageCenterChanged();
+  }
+
+  void OnNotificationClicked(const std::string& notification_id) override {
+    OnMessageCenterChanged();
+  }
+
+  void OnMessageCenterChanged() {
+    notification_received_ = true;
+    if (message_loop_runner_.get())
+      message_loop_runner_->Quit();
+  }
+
+  bool notification_received_;
+  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(Impl);
+};
+
+MessageCenterChangeObserver::MessageCenterChangeObserver() : impl_(new Impl) {}
+MessageCenterChangeObserver::~MessageCenterChangeObserver() = default;
+
+bool MessageCenterChangeObserver::Wait() {
+  return impl_->Wait();
+}
+
+// -----------------------------------------------------------------------------
+
+int NotificationsTest::GetNotificationCount() {
+  return message_center::MessageCenter::Get()->NotificationCount();
+}
+
+int NotificationsTest::GetNotificationPopupCount() {
+  return message_center::MessageCenter::Get()->GetPopupNotifications().size();
+}
+
+void NotificationsTest::CloseBrowserWindow(Browser* browser) {
+  content::WindowedNotificationObserver observer(
+      chrome::NOTIFICATION_BROWSER_CLOSED, content::Source<Browser>(browser));
+  browser->window()->Close();
+  observer.Wait();
+}
+
+void NotificationsTest::CrashTab(Browser* browser, int index) {
+  content::CrashTab(browser->tab_strip_model()->GetWebContentsAt(index));
+}
+
+void NotificationsTest::DenyOrigin(const GURL& origin) {
+  DropOriginPreference(origin);
+  DesktopNotificationProfileUtil::DenyPermission(browser()->profile(), origin);
+}
+
+void NotificationsTest::AllowOrigin(const GURL& origin) {
+  DropOriginPreference(origin);
+  DesktopNotificationProfileUtil::GrantPermission(browser()->profile(), origin);
+}
+
+void NotificationsTest::AllowAllOrigins() {
+  // Reset all origins
+  HostContentSettingsMapFactory::GetForProfile(browser()->profile())
+      ->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
+  SetDefaultContentSetting(CONTENT_SETTING_ALLOW);
+}
+
+void NotificationsTest::SetDefaultContentSetting(ContentSetting setting) {
+  HostContentSettingsMapFactory::GetForProfile(browser()->profile())
+      ->SetDefaultContentSetting(CONTENT_SETTINGS_TYPE_NOTIFICATIONS, setting);
+}
+
+std::string NotificationsTest::CreateNotification(Browser* browser,
+                                                  bool wait_for_new_balloon,
+                                                  const char* icon,
+                                                  const char* title,
+                                                  const char* body,
+                                                  const char* replace_id,
+                                                  const char* onclick) {
+  std::string script = base::StringPrintf(
+      "createNotification('%s', '%s', '%s', '%s', (e) => { %s });", icon, title,
+      body, replace_id, onclick);
+
+  MessageCenterChangeObserver observer;
+  std::string result;
+  bool success = content::ExecuteScriptAndExtractString(
+      GetActiveWebContents(browser), script, &result);
+  if (success && result != "-1" && wait_for_new_balloon)
+    success = observer.Wait();
+  EXPECT_TRUE(success);
+
+  return result;
+}
+
+std::string NotificationsTest::CreateSimpleNotification(
+    Browser* browser,
+    bool wait_for_new_balloon) {
+  return CreateNotification(browser, wait_for_new_balloon, "no_such_file.png",
+                            "My Title", "My Body", "");
+}
+
+std::string NotificationsTest::RequestAndRespondToPermission(
+    Browser* browser,
+    PermissionRequestManager::AutoResponseType bubble_response) {
+  std::string result;
+  content::WebContents* web_contents = GetActiveWebContents(browser);
+  PermissionRequestManager::FromWebContents(web_contents)
+      ->set_auto_response_for_test(bubble_response);
+  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+      web_contents, "requestPermission();", &result));
+  return result;
+}
+
+bool NotificationsTest::RequestAndAcceptPermission(Browser* browser) {
+  std::string result = RequestAndRespondToPermission(
+      browser, PermissionRequestManager::ACCEPT_ALL);
+  return "request-callback-granted" == result;
+}
+
+bool NotificationsTest::RequestAndDenyPermission(Browser* browser) {
+  std::string result = RequestAndRespondToPermission(
+      browser, PermissionRequestManager::DENY_ALL);
+  return "request-callback-denied" == result;
+}
+
+bool NotificationsTest::RequestAndDismissPermission(Browser* browser) {
+  std::string result =
+      RequestAndRespondToPermission(browser, PermissionRequestManager::DISMISS);
+  return "request-callback-default" == result;
+}
+
+bool NotificationsTest::RequestPermissionAndWait(Browser* browser) {
+  content::WebContents* web_contents = GetActiveWebContents(browser);
+  ui_test_utils::NavigateToURL(browser, GetTestPageURL());
+  PermissionRequestObserver observer(web_contents);
+  std::string result;
+  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+      web_contents, "requestPermissionAndRespond();", &result));
+  EXPECT_EQ("requested", result);
+  observer.Wait();
+  return observer.request_shown();
+}
+
+bool NotificationsTest::CancelNotification(const char* notification_id,
+                                           Browser* browser) {
+  std::string script =
+      base::StringPrintf("cancelNotification('%s');", notification_id);
+
+  MessageCenterChangeObserver observer;
+  std::string result;
+  bool success = content::ExecuteScriptAndExtractString(
+      GetActiveWebContents(browser), script, &result);
+  if (!success || result != "1")
+    return false;
+  return observer.Wait();
+}
+
+void NotificationsTest::GetPrefsByContentSetting(
+    ContentSetting setting,
+    ContentSettingsForOneType* settings) {
+  DesktopNotificationProfileUtil::GetNotificationsSettings(browser()->profile(),
+                                                           settings);
+  for (ContentSettingsForOneType::iterator it = settings->begin();
+       it != settings->end();) {
+    if (it->setting != setting || it->source.compare("preference") != 0)
+      it = settings->erase(it);
+    else
+      ++it;
+  }
+}
+
+bool NotificationsTest::CheckOriginInSetting(
+    const ContentSettingsForOneType& settings,
+    const GURL& origin) {
+  ContentSettingsPattern pattern =
+      ContentSettingsPattern::FromURLNoWildcard(origin);
+  for (const auto& setting : settings) {
+    if (setting.primary_pattern == pattern)
+      return true;
+  }
+  return false;
+}
+
+GURL NotificationsTest::GetTestPageURLForFile(const std::string& file) const {
+  return embedded_test_server()->GetURL(std::string("/notifications/") + file);
+}
+
+GURL NotificationsTest::GetTestPageURL() const {
+  return GetTestPageURLForFile("notification_tester.html");
+}
+
+content::WebContents* NotificationsTest::GetActiveWebContents(
+    Browser* browser) {
+  return browser->tab_strip_model()->GetActiveWebContents();
+}
+
+void NotificationsTest::EnableFullscreenNotifications() {
+  feature_list_.InitWithFeatures(
+      {features::kPreferHtmlOverPlugins,
+       features::kAllowFullscreenWebNotificationsFeature},
+      {});
+}
+
+void NotificationsTest::DisableFullscreenNotifications() {
+  feature_list_.InitWithFeatures(
+      {features::kPreferHtmlOverPlugins},
+      {features::kAllowFullscreenWebNotificationsFeature});
+}
+
+void NotificationsTest::DropOriginPreference(const GURL& origin) {
+  DesktopNotificationProfileUtil::ClearSetting(browser()->profile(), origin);
+}
diff --git a/chrome/browser/notifications/notification_interactive_uitest_support.h b/chrome/browser/notifications/notification_interactive_uitest_support.h
new file mode 100644
index 0000000..ff14471
--- /dev/null
+++ b/chrome/browser/notifications/notification_interactive_uitest_support.h
@@ -0,0 +1,79 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_INTERACTIVE_UITEST_SUPPORT_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_INTERACTIVE_UITEST_SUPPORT_H_
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/content_settings/core/common/content_settings.h"
+
+class MessageCenterChangeObserver {
+ public:
+  MessageCenterChangeObserver();
+  ~MessageCenterChangeObserver();
+
+  bool Wait();
+
+ private:
+  class Impl;
+  std::unique_ptr<Impl> impl_;
+
+  DISALLOW_COPY_AND_ASSIGN(MessageCenterChangeObserver);
+};
+
+class NotificationsTest : public InProcessBrowserTest {
+ public:
+  NotificationsTest() {}
+
+ protected:
+  int GetNotificationCount();
+  int GetNotificationPopupCount();
+
+  void CloseBrowserWindow(Browser* browser);
+  void CrashTab(Browser* browser, int index);
+
+  void DenyOrigin(const GURL& origin);
+  void AllowOrigin(const GURL& origin);
+  void AllowAllOrigins();
+  void SetDefaultContentSetting(ContentSetting setting);
+
+  std::string CreateNotification(Browser* browser,
+                                 bool wait_for_new_balloon,
+                                 const char* icon,
+                                 const char* title,
+                                 const char* body,
+                                 const char* replace_id,
+                                 const char* onclick = "");
+  std::string CreateSimpleNotification(Browser* browser,
+                                       bool wait_for_new_balloon);
+  bool RequestAndAcceptPermission(Browser* browser);
+  bool RequestAndDenyPermission(Browser* browser);
+  bool RequestAndDismissPermission(Browser* browser);
+  bool RequestPermissionAndWait(Browser* browser);
+  bool CancelNotification(const char* notification_id, Browser* browser);
+  void GetPrefsByContentSetting(ContentSetting setting,
+                                ContentSettingsForOneType* settings);
+  bool CheckOriginInSetting(const ContentSettingsForOneType& settings,
+                            const GURL& origin);
+
+  GURL GetTestPageURLForFile(const std::string& file) const;
+  GURL GetTestPageURL() const;
+  content::WebContents* GetActiveWebContents(Browser* browser);
+
+ protected:
+  void EnableFullscreenNotifications();
+  void DisableFullscreenNotifications();
+
+ private:
+  void DropOriginPreference(const GURL& origin);
+  std::string RequestAndRespondToPermission(
+      Browser* browser,
+      PermissionRequestManager::AutoResponseType bubble_response);
+
+  base::test::ScopedFeatureList feature_list_;
+};
+
+#endif  // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_INTERACTIVE_UITEST_SUPPORT_H_
diff --git a/chrome/browser/notifications/notification_permission_context.cc b/chrome/browser/notifications/notification_permission_context.cc
index 656c1b31..36c6d91 100644
--- a/chrome/browser/notifications/notification_permission_context.cc
+++ b/chrome/browser/notifications/notification_permission_context.cc
@@ -171,6 +171,7 @@
 NotificationPermissionContext::~NotificationPermissionContext() {}
 
 ContentSetting NotificationPermissionContext::GetPermissionStatusInternal(
+    content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   // Push messaging is only allowed to be granted on top-level origins.
@@ -179,8 +180,8 @@
     return CONTENT_SETTING_BLOCK;
   }
 
-  return PermissionContextBase::GetPermissionStatusInternal(requesting_origin,
-                                                            embedding_origin);
+  return PermissionContextBase::GetPermissionStatusInternal(
+      render_frame_host, requesting_origin, embedding_origin);
 }
 
 void NotificationPermissionContext::ResetPermission(
diff --git a/chrome/browser/notifications/notification_permission_context.h b/chrome/browser/notifications/notification_permission_context.h
index ea9f736..7e17d6b 100644
--- a/chrome/browser/notifications/notification_permission_context.h
+++ b/chrome/browser/notifications/notification_permission_context.h
@@ -19,6 +19,7 @@
 
   // PermissionContextBase implementation.
   ContentSetting GetPermissionStatusInternal(
+      content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       const GURL& embedding_origin) const override;
   void ResetPermission(const GURL& requesting_origin,
diff --git a/chrome/browser/notifications/notification_permission_context_unittest.cc b/chrome/browser/notifications/notification_permission_context_unittest.cc
index f61a25f..21dec7f2 100644
--- a/chrome/browser/notifications/notification_permission_context_unittest.cc
+++ b/chrome/browser/notifications/notification_permission_context_unittest.cc
@@ -117,21 +117,29 @@
                        CONTENT_SETTING_ALLOW);
 
   EXPECT_EQ(CONTENT_SETTING_ALLOW,
-            context.GetPermissionStatus(requesting_origin, embedding_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     requesting_origin, embedding_origin)
                 .content_setting);
 
   EXPECT_EQ(CONTENT_SETTING_ALLOW,
-            context.GetPermissionStatus(requesting_origin, different_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     requesting_origin, different_origin)
                 .content_setting);
 
   context.ResetPermission(requesting_origin, embedding_origin);
 
   EXPECT_EQ(CONTENT_SETTING_ASK,
-            context.GetPermissionStatus(requesting_origin, embedding_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     requesting_origin, embedding_origin)
                 .content_setting);
 
   EXPECT_EQ(CONTENT_SETTING_ASK,
-            context.GetPermissionStatus(requesting_origin, different_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     requesting_origin, different_origin)
                 .content_setting);
 }
 
@@ -147,7 +155,9 @@
                        CONTENT_SETTING_ALLOW);
 
   EXPECT_EQ(CONTENT_SETTING_BLOCK,
-            context.GetPermissionStatus(requesting_origin, embedding_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     requesting_origin, embedding_origin)
                 .content_setting);
 
   context.ResetPermission(requesting_origin, embedding_origin);
@@ -156,13 +166,17 @@
                        CONTENT_SETTING_ALLOW);
 
   EXPECT_EQ(CONTENT_SETTING_ALLOW,
-            context.GetPermissionStatus(embedding_origin, embedding_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     embedding_origin, embedding_origin)
                 .content_setting);
 
   context.ResetPermission(embedding_origin, embedding_origin);
 
   EXPECT_EQ(CONTENT_SETTING_ASK,
-            context.GetPermissionStatus(embedding_origin, embedding_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     embedding_origin, embedding_origin)
                 .content_setting);
 }
 
@@ -173,13 +187,19 @@
 
   NotificationPermissionContext context(profile(),
                                         CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
-  EXPECT_EQ(CONTENT_SETTING_ASK,
-            context.GetPermissionStatus(origin, origin).content_setting);
+  EXPECT_EQ(
+      CONTENT_SETTING_ASK,
+      context
+          .GetPermissionStatus(nullptr /* render_frame_host */, origin, origin)
+          .content_setting);
 
   UpdateContentSetting(&context, origin, origin, CONTENT_SETTING_ALLOW);
 
-  EXPECT_EQ(CONTENT_SETTING_ALLOW,
-            context.GetPermissionStatus(origin, origin).content_setting);
+  EXPECT_EQ(
+      CONTENT_SETTING_ALLOW,
+      context
+          .GetPermissionStatus(nullptr /* render_frame_host */, origin, origin)
+          .content_setting);
 }
 
 // Push notifications requires a secure origin to acquire permission.
@@ -189,23 +209,33 @@
 
   NotificationPermissionContext context(
       profile(), CONTENT_SETTINGS_TYPE_PUSH_MESSAGING);
-  EXPECT_EQ(CONTENT_SETTING_BLOCK,
-            context.GetPermissionStatus(origin, origin).content_setting);
+  EXPECT_EQ(
+      CONTENT_SETTING_BLOCK,
+      context
+          .GetPermissionStatus(nullptr /* render_frame_host */, origin, origin)
+          .content_setting);
 
   UpdateContentSetting(&context, origin, origin, CONTENT_SETTING_ALLOW);
 
-  EXPECT_EQ(CONTENT_SETTING_BLOCK,
-            context.GetPermissionStatus(origin, origin).content_setting);
+  EXPECT_EQ(
+      CONTENT_SETTING_BLOCK,
+      context
+          .GetPermissionStatus(nullptr /* render_frame_host */, origin, origin)
+          .content_setting);
 
   EXPECT_EQ(CONTENT_SETTING_ASK,
-            context.GetPermissionStatus(secure_origin, secure_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     secure_origin, secure_origin)
                 .content_setting);
 
   UpdateContentSetting(&context, secure_origin, secure_origin,
                        CONTENT_SETTING_ALLOW);
 
   EXPECT_EQ(CONTENT_SETTING_ALLOW,
-            context.GetPermissionStatus(secure_origin, secure_origin)
+            context
+                .GetPermissionStatus(nullptr /* render_frame_host */,
+                                     secure_origin, secure_origin)
                 .content_setting);
 }
 
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index 30420b6..f891985 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -64,12 +64,6 @@
 #include "net/url_request/url_request_context.h"
 #include "third_party/re2/src/re2/re2.h"
 
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/safe_browsing/safe_browsing_service.h"
-#include "components/safe_browsing/password_protection/password_protection_service.h"
-#endif
-
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/tab_android.h"
 #include "chrome/browser/password_manager/account_chooser_dialog_android.h"
@@ -182,15 +176,6 @@
       password_manager::prefs::kCredentialsEnableService, GetPrefs());
   ReportMetrics(*saving_and_filling_passwords_enabled_, this, profile_);
   driver_factory_->RequestSendLoggingAvailability();
-
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-  if (CanSetPasswordProtectionService()) {
-    password_reuse_detection_manager_.SetPasswordProtectionService(
-        g_browser_process->safe_browsing_service()
-            ->password_protection_service()
-            ->GetWeakPtr());
-  }
-#endif
 }
 
 ChromePasswordManagerClient::~ChromePasswordManagerClient() {}
@@ -635,14 +620,6 @@
   return true;
 }
 
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-bool ChromePasswordManagerClient::CanSetPasswordProtectionService() {
-  return g_browser_process && g_browser_process->safe_browsing_service() &&
-         g_browser_process->safe_browsing_service()
-             ->password_protection_service();
-}
-#endif
-
 void ChromePasswordManagerClient::AnnotateNavigationEntry(
     bool has_password_field) {
   if (!ShouldAnnotateNavigationEntries(profile_))
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index a695608..3575afd6 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -160,12 +160,6 @@
   // without custom sync passphrase.
   static bool ShouldAnnotateNavigationEntries(Profile* profile);
 
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-  // Return true if we can set PasswordProtectionService in
-  // |password_reuse_detection_manager_|.
-  static bool CanSetPasswordProtectionService();
-#endif
-
   Profile* const profile_;
 
   password_manager::PasswordManager password_manager_;
diff --git a/chrome/browser/permissions/permission_context_base.cc b/chrome/browser/permissions/permission_context_base.cc
index cb5782f5f..7ba013e2 100644
--- a/chrome/browser/permissions/permission_context_base.cc
+++ b/chrome/browser/permissions/permission_context_base.cc
@@ -118,8 +118,9 @@
   // Synchronously check the content setting to see if the user has already made
   // a decision, or if the origin is under embargo. If so, respect that
   // decision.
-  PermissionResult result =
-      GetPermissionStatus(requesting_origin, embedding_origin);
+  // TODO(raymes): Pass in the RenderFrameHost of the request here.
+  PermissionResult result = GetPermissionStatus(
+      nullptr /* render_frame_host */, requesting_origin, embedding_origin);
 
   if (result.content_setting == CONTENT_SETTING_ALLOW ||
       result.content_setting == CONTENT_SETTING_BLOCK) {
@@ -189,6 +190,7 @@
 }
 
 PermissionResult PermissionContextBase::GetPermissionStatus(
+    content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   // If the permission has been disabled through Finch, block all requests.
@@ -203,8 +205,8 @@
                             PermissionStatusSource::UNSPECIFIED);
   }
 
-  ContentSetting content_setting =
-      GetPermissionStatusInternal(requesting_origin, embedding_origin);
+  ContentSetting content_setting = GetPermissionStatusInternal(
+      render_frame_host, requesting_origin, embedding_origin);
   if (content_setting == CONTENT_SETTING_ASK) {
     PermissionResult result =
         PermissionDecisionAutoBlocker::GetForProfile(profile_)
@@ -255,6 +257,7 @@
 }
 
 ContentSetting PermissionContextBase::GetPermissionStatusInternal(
+    content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   return HostContentSettingsMapFactory::GetForProfile(profile_)
diff --git a/chrome/browser/permissions/permission_context_base.h b/chrome/browser/permissions/permission_context_base.h
index 7aa905e..0863bde 100644
--- a/chrome/browser/permissions/permission_context_base.h
+++ b/chrome/browser/permissions/permission_context_base.h
@@ -25,6 +25,7 @@
 class Profile;
 
 namespace content {
+class RenderFrameHost;
 class WebContents;
 }
 
@@ -80,10 +81,14 @@
                                  const BrowserPermissionCallback& callback);
 
   // Returns whether the permission has been granted, denied etc.
+  // |render_frame_host| may be nullptr if the call is coming from a context
+  // other than a specific frame.
   // TODO(meredithl): Ensure that the result accurately reflects whether the
   // origin is blacklisted for this permission.
-  PermissionResult GetPermissionStatus(const GURL& requesting_origin,
-                                       const GURL& embedding_origin) const;
+  PermissionResult GetPermissionStatus(
+      content::RenderFrameHost* render_frame_host,
+      const GURL& requesting_origin,
+      const GURL& embedding_origin) const;
 
   // Resets the permission to its default value.
   virtual void ResetPermission(const GURL& requesting_origin,
@@ -101,6 +106,7 @@
 
  protected:
   virtual ContentSetting GetPermissionStatusInternal(
+      content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       const GURL& embedding_origin) const;
 
diff --git a/chrome/browser/permissions/permission_context_base_unittest.cc b/chrome/browser/permissions/permission_context_base_unittest.cc
index 8479c4f..4bd76503 100644
--- a/chrome/browser/permissions/permission_context_base_unittest.cc
+++ b/chrome/browser/permissions/permission_context_base_unittest.cc
@@ -365,8 +365,8 @@
                                   i + 1);
 #endif
 
-      PermissionResult result =
-          permission_context.GetPermissionStatus(url, url);
+      PermissionResult result = permission_context.GetPermissionStatus(
+          nullptr /* render_frame_host */, url, url);
 
       histograms.ExpectUniqueSample(
           "Permissions.AutoBlocker.EmbargoPromptSuppression",
@@ -409,7 +409,8 @@
         base::Bind(&TestPermissionContext::TrackPermissionDecision,
                    base::Unretained(&permission_context)));
 
-    PermissionResult result = permission_context.GetPermissionStatus(url, url);
+    PermissionResult result = permission_context.GetPermissionStatus(
+        nullptr /* render_frame_host */, url, url);
     EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
     EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
     histograms.ExpectBucketCount(
@@ -538,8 +539,8 @@
       EXPECT_EQ(1u, permission_context.decisions().size());
       ASSERT_EQ(CONTENT_SETTING_ASK, permission_context.decisions()[0]);
       EXPECT_TRUE(permission_context.tab_context_updated());
-      PermissionResult result =
-          permission_context.GetPermissionStatus(url, url);
+      PermissionResult result = permission_context.GetPermissionStatus(
+          nullptr /* render_frame_host */, url, url);
 
       histograms.ExpectTotalCount(
           "Permissions.Prompt.Dismissed.PriorDismissCount.MidiSysEx", i + 1);
@@ -581,7 +582,8 @@
     // Ensure that we finish in the block state.
     TestPermissionContext permission_context(profile(),
                                              CONTENT_SETTINGS_TYPE_MIDI_SYSEX);
-    PermissionResult result = permission_context.GetPermissionStatus(url, url);
+    PermissionResult result = permission_context.GetPermissionStatus(
+        nullptr /* render_frame_host */, url, url);
     EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
     EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
     variations::testing::ClearAllVariationParams();
@@ -741,7 +743,8 @@
         web_contents(), id, url, true /* user_gesture */,
         base::Bind(&TestPermissionContext::TrackPermissionDecision,
                    base::Unretained(&permission_context)));
-    PermissionResult result = permission_context.GetPermissionStatus(url, url);
+    PermissionResult result = permission_context.GetPermissionStatus(
+        nullptr /* render_frame_host */, url, url);
     EXPECT_EQ(expected_permission_status, result.content_setting);
 
     if (expected_permission_status == CONTENT_SETTING_ALLOW) {
diff --git a/chrome/browser/permissions/permission_manager.cc b/chrome/browser/permissions/permission_manager.cc
index dc2d554..3554494d 100644
--- a/chrome/browser/permissions/permission_manager.cc
+++ b/chrome/browser/permissions/permission_manager.cc
@@ -334,17 +334,19 @@
     ContentSettingsType permission,
     const GURL& requesting_origin,
     const GURL& embedding_origin) {
-  if (IsConstantPermission(permission)) {
-    return PermissionResult(GetContentSettingForConstantPermission(permission),
-                            PermissionStatusSource::UNSPECIFIED);
-  }
-  PermissionContextBase* context = GetPermissionContext(permission);
-  PermissionResult result = context->GetPermissionStatus(
-      requesting_origin.GetOrigin(), embedding_origin.GetOrigin());
-  DCHECK(result.content_setting == CONTENT_SETTING_ALLOW ||
-         result.content_setting == CONTENT_SETTING_ASK ||
-         result.content_setting == CONTENT_SETTING_BLOCK);
-  return result;
+  return GetPermissionStatusHelper(permission, nullptr /* render_frame_host */,
+                                   requesting_origin, embedding_origin);
+}
+
+PermissionResult PermissionManager::GetPermissionStatusForFrame(
+    ContentSettingsType permission,
+    content::RenderFrameHost* render_frame_host) {
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(render_frame_host);
+  GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
+  return GetPermissionStatusHelper(
+      permission, render_frame_host,
+      render_frame_host->GetLastCommittedURL().GetOrigin(), embedding_origin);
 }
 
 int PermissionManager::RequestPermission(
@@ -524,3 +526,22 @@
   for (const auto& callback : callbacks)
     callback.Run();
 }
+
+PermissionResult PermissionManager::GetPermissionStatusHelper(
+    ContentSettingsType permission,
+    content::RenderFrameHost* render_frame_host,
+    const GURL& requesting_origin,
+    const GURL& embedding_origin) {
+  if (IsConstantPermission(permission)) {
+    return PermissionResult(GetContentSettingForConstantPermission(permission),
+                            PermissionStatusSource::UNSPECIFIED);
+  }
+  PermissionContextBase* context = GetPermissionContext(permission);
+  PermissionResult result = context->GetPermissionStatus(
+      nullptr /* render_frame_host */, requesting_origin.GetOrigin(),
+      embedding_origin.GetOrigin());
+  DCHECK(result.content_setting == CONTENT_SETTING_ALLOW ||
+         result.content_setting == CONTENT_SETTING_ASK ||
+         result.content_setting == CONTENT_SETTING_BLOCK);
+  return result;
+}
diff --git a/chrome/browser/permissions/permission_manager.h b/chrome/browser/permissions/permission_manager.h
index 033b56a..979b91e 100644
--- a/chrome/browser/permissions/permission_manager.h
+++ b/chrome/browser/permissions/permission_manager.h
@@ -55,6 +55,13 @@
                                        const GURL& requesting_origin,
                                        const GURL& embedding_origin);
 
+  // Returns the permission status for a given frame. This should be preferred
+  // over GetPermissionStatus as additional checks can be performed when we know
+  // the exact context the request is coming from.
+  PermissionResult GetPermissionStatusForFrame(
+      ContentSettingsType permission,
+      content::RenderFrameHost* render_frame_host);
+
   // content::PermissionManager implementation.
   int RequestPermission(
       content::PermissionType permission,
@@ -119,6 +126,12 @@
                                ContentSettingsType content_type,
                                std::string resource_identifier) override;
 
+  PermissionResult GetPermissionStatusHelper(
+      ContentSettingsType permission,
+      content::RenderFrameHost* render_frame_host,
+      const GURL& requesting_origin,
+      const GURL& embedding_origin);
+
   Profile* profile_;
   PendingRequestsMap pending_requests_;
   SubscriptionsMap subscriptions_;
diff --git a/chrome/browser/plugins/flash_permission_context.cc b/chrome/browser/plugins/flash_permission_context.cc
index 0794b45..1fd0711 100644
--- a/chrome/browser/plugins/flash_permission_context.cc
+++ b/chrome/browser/plugins/flash_permission_context.cc
@@ -37,6 +37,7 @@
 FlashPermissionContext::~FlashPermissionContext() {}
 
 ContentSetting FlashPermissionContext::GetPermissionStatusInternal(
+    content::RenderFrameHost* render_frame_host,
     const GURL& requesting_origin,
     const GURL& embedding_origin) const {
   HostContentSettingsMap* host_content_settings_map =
diff --git a/chrome/browser/plugins/flash_permission_context.h b/chrome/browser/plugins/flash_permission_context.h
index 7370c65..7943d962 100644
--- a/chrome/browser/plugins/flash_permission_context.h
+++ b/chrome/browser/plugins/flash_permission_context.h
@@ -19,6 +19,7 @@
  private:
   // PermissionContextBase:
   ContentSetting GetPermissionStatusInternal(
+      content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       const GURL& embedding_origin) const override;
   void UpdateTabContext(const PermissionRequestID& id,
diff --git a/chrome/browser/resources/md_bookmarks/actions.html b/chrome/browser/resources/md_bookmarks/actions.html
new file mode 100644
index 0000000..0af2983
--- /dev/null
+++ b/chrome/browser/resources/md_bookmarks/actions.html
@@ -0,0 +1,2 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="chrome://bookmarks/actions.js"></script>
diff --git a/chrome/browser/resources/md_bookmarks/actions.js b/chrome/browser/resources/md_bookmarks/actions.js
new file mode 100644
index 0000000..1ff0eaa
--- /dev/null
+++ b/chrome/browser/resources/md_bookmarks/actions.js
@@ -0,0 +1,81 @@
+// 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.
+
+/**
+ * @fileoverview Module for functions which produce action objects. These are
+ * listed in one place to document available actions and their parameters.
+ */
+
+cr.define('bookmarks.actions', function() {
+  /**
+   * @param {string} id
+   * @param {{title: string, url: (string|undefined)}} changeInfo
+   * @return {!Action}
+   */
+  function editBookmark(id, changeInfo) {
+    return {
+      name: 'edit-bookmark',
+      id: id,
+      changeInfo: changeInfo,
+    };
+  }
+
+  /**
+   * @param {string} id
+   * @param {string} parentId
+   * @param {number} index
+   * @return {!Action}
+   */
+  function removeBookmark(id, parentId, index) {
+    return {
+      name: 'remove-bookmark',
+      id: id,
+      parentId: parentId,
+      index: index,
+    };
+  }
+
+  /**
+   * @param {NodeList} nodeMap
+   * @return {!Action}
+   */
+  function refreshNodes(nodeMap) {
+    return {
+      name: 'refresh-nodes',
+      nodes: nodeMap,
+    };
+  };
+
+  /**
+   * @param {string} id
+   * @return {!Action}
+   */
+  function selectFolder(id) {
+    return {
+      name: 'select-folder',
+      id: id,
+    };
+  }
+
+  /**
+   * @param {string} id
+   * @param {boolean} open
+   * @return {!Action}
+   */
+  function changeFolderOpen(id, open) {
+    return {
+      name: 'change-folder-open',
+      id: id,
+      open: open,
+    };
+  }
+
+  return {
+    changeFolderOpen: changeFolderOpen,
+    editBookmark: editBookmark,
+    refreshNodes: refreshNodes,
+    removeBookmark: removeBookmark,
+    selectFolder: selectFolder,
+  };
+});
diff --git a/chrome/browser/resources/md_bookmarks/bookmarks_store.js b/chrome/browser/resources/md_bookmarks/bookmarks_store.js
index 59babbd..78fa60a 100644
--- a/chrome/browser/resources/md_bookmarks/bookmarks_store.js
+++ b/chrome/browser/resources/md_bookmarks/bookmarks_store.js
@@ -12,7 +12,7 @@
   /** @constructor */
   function Store() {
     /** @type {!BookmarksPageState} */
-    this.data_ = {};
+    this.data_ = bookmarks.util.createEmptyState();
     /** @type {boolean} */
     this.initialized_ = false;
     /** @type {!Array<!StoreObserver>} */
diff --git a/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp b/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp
index 8e39c88..78f7e98bc 100644
--- a/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp
+++ b/chrome/browser/resources/md_bookmarks/compiled_resources2.gyp
@@ -4,6 +4,16 @@
 {
   'targets': [
     {
+      'target_name': 'actions',
+      'dependencies': [
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
+        '<(EXTERNS_GYP):chrome_extensions',
+        'util',
+        'types',
+      ],
+      'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi']
+    },
+    {
       'target_name': 'app',
       'dependencies': [
         '<(EXTERNS_GYP):chrome_extensions',
@@ -52,6 +62,7 @@
       'target_name': 'reducers',
       'dependencies': [
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
+        'actions',
         'types',
       ],
       'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi'],
@@ -97,5 +108,14 @@
       'target_name': 'types',
       'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi']
     },
+    {
+      'target_name': 'util',
+      'dependencies': [
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
+        '<(EXTERNS_GYP):chrome_extensions',
+        'types',
+      ],
+      'includes': ['../../../../third_party/closure_compiler/compile_js2.gypi']
+    }
   ]
 }
diff --git a/chrome/browser/resources/md_bookmarks/reducers.html b/chrome/browser/resources/md_bookmarks/reducers.html
index 90616b30..06d9b89 100644
--- a/chrome/browser/resources/md_bookmarks/reducers.html
+++ b/chrome/browser/resources/md_bookmarks/reducers.html
@@ -1,2 +1,5 @@
 <link rel="import" href="chrome://resources/html/cr.html">
+<!-- TODO(tsergeant): Remove this import once actions are used elsewhere. -->
+<link rel="import" href="chrome://bookmarks/actions.html">
+
 <script src="chrome://bookmarks/reducers.js"></script>
diff --git a/chrome/browser/resources/md_bookmarks/reducers.js b/chrome/browser/resources/md_bookmarks/reducers.js
index c78731d..4021761c 100644
--- a/chrome/browser/resources/md_bookmarks/reducers.js
+++ b/chrome/browser/resources/md_bookmarks/reducers.js
@@ -10,6 +10,165 @@
  */
 
 cr.define('bookmarks', function() {
+  var NodeState = {};
+
+  /**
+   * @param {NodeList} nodes
+   * @param {string} id
+   * @param {function(BookmarkNode):BookmarkNode} callback
+   * @return {NodeList}
+   */
+  NodeState.modifyNode_ = function(nodes, id, callback) {
+    var nodeModification = {};
+    nodeModification[id] = callback(nodes[id]);
+    return Object.assign({}, nodes, nodeModification);
+  };
+
+  /**
+   * @param {NodeList} nodes
+   * @param {Action} action
+   * @return {NodeList}
+   */
+  NodeState.editBookmark = function(nodes, action) {
+    // Do not allow folders to change URL (making them no longer folders).
+    if (!nodes[action.id].url && action.changeInfo.url)
+      delete action.changeInfo.url;
+
+    return NodeState.modifyNode_(nodes, action.id, function(node) {
+      return /** @type {BookmarkNode} */ (
+          Object.assign({}, node, action.changeInfo));
+    });
+  };
+
+  /**
+   * @param {NodeList} nodes
+   * @param {Action} action
+   * @return {NodeList}
+   */
+  NodeState.removeBookmark = function(nodes, action) {
+    return NodeState.modifyNode_(nodes, action.parentId, function(node) {
+      var newChildren = node.children.slice();
+      newChildren.splice(action.index, 1);
+      return /** @type {BookmarkNode} */ (
+          Object.assign({}, node, {children: newChildren}));
+    });
+  };
+
+  /**
+   * @param {NodeList} nodes
+   * @param {Action} action
+   * @return {NodeList}
+   */
+  NodeState.updateNodes = function(nodes, action) {
+    switch (action.name) {
+      case 'edit-bookmark':
+        return NodeState.editBookmark(nodes, action);
+      case 'remove-bookmark':
+        return NodeState.removeBookmark(nodes, action);
+      case 'refresh-nodes':
+        return action.nodes;
+      default:
+        return nodes;
+    }
+  };
+
+  var SelectedFolderState = {};
+
+  /**
+   * @param {NodeList} nodes
+   * @param {string} ancestorId
+   * @param {string} childId
+   * @return {boolean}
+   */
+  SelectedFolderState.isAncestorOf = function(nodes, ancestorId, childId) {
+    var currentId = childId;
+    // Work upwards through the tree from child.
+    while (currentId) {
+      if (currentId == ancestorId)
+        return true;
+      currentId = nodes[currentId].parentId;
+    }
+    return false;
+  };
+
+  /**
+   * @param {?string} selectedFolder
+   * @param {Action} action
+   * @param {NodeList} nodes
+   * @return {?string}
+   */
+  SelectedFolderState.updateSelectedFolder = function(
+      selectedFolder, action, nodes) {
+    // TODO(tsergeant): It should not be possible to select a non-folder.
+    switch (action.name) {
+      case 'select-folder':
+        return action.id;
+      case 'change-folder-open':
+        // When hiding the selected folder by closing its ancestor, select
+        // that ancestor instead.
+        if (!action.open && selectedFolder &&
+            SelectedFolderState.isAncestorOf(
+                nodes, action.id, selectedFolder)) {
+          return action.id;
+        }
+        return selectedFolder;
+      default:
+        return selectedFolder;
+    }
+  };
+
+  var ClosedFolderState = {};
+
+  /**
+   * @param {ClosedFolderState} state
+   * @param {Action} action
+   * @param {NodeList} nodes
+   * @return {ClosedFolderState}
+   */
+  ClosedFolderState.openAncestorsOf = function(state, action, nodes) {
+    var id = action.id;
+    var modifications = {};
+    var parentId = nodes[id].parentId;
+    while (parentId) {
+      if (state[parentId]) {
+        modifications[parentId] = false;
+      }
+      parentId = nodes[parentId].parentId;
+    }
+
+    return Object.assign({}, state, modifications);
+  };
+
+  /**
+   * @param {ClosedFolderState} state
+   * @param {Action} action
+   * @return {ClosedFolderState}
+   */
+  ClosedFolderState.changeFolderOpen = function(state, action) {
+    var closed = !action.open;
+    var modification = {};
+    modification[action.id] = closed;
+
+    return Object.assign({}, state, modification);
+  };
+
+  /**
+   * @param {ClosedFolderState} state
+   * @param {Action} action
+   * @param {NodeList} nodes
+   * @return {ClosedFolderState}
+   */
+  ClosedFolderState.updateClosedFolders = function(state, action, nodes) {
+    switch (action.name) {
+      case 'change-folder-open':
+        return ClosedFolderState.changeFolderOpen(state, action);
+      case 'select-folder':
+        return ClosedFolderState.openAncestorsOf(state, action, nodes);
+      default:
+        return state;
+    };
+  };
+
   /**
    * Root reducer for the Bookmarks page. This is called by the store in
    * response to an action, and the return value is used to update the UI.
@@ -18,10 +177,19 @@
    * @return {!BookmarksPageState}
    */
   function reduceAction(state, action) {
-    return {};
+    return {
+      nodes: NodeState.updateNodes(state.nodes, action),
+      selectedFolder: SelectedFolderState.updateSelectedFolder(
+          state.selectedFolder, action, state.nodes),
+      closedFolders: ClosedFolderState.updateClosedFolders(
+          state.closedFolders, action, state.nodes),
+    };
   }
 
   return {
     reduceAction: reduceAction,
+    ClosedFolderState: ClosedFolderState,
+    NodeState: NodeState,
+    SelectedFolderState: SelectedFolderState,
   };
 });
diff --git a/chrome/browser/resources/md_bookmarks/store.html b/chrome/browser/resources/md_bookmarks/store.html
index 2fdcb26..42fcd559d 100644
--- a/chrome/browser/resources/md_bookmarks/store.html
+++ b/chrome/browser/resources/md_bookmarks/store.html
@@ -1,6 +1,7 @@
 <!-- New-style store: -->
 <link rel="import" href="chrome://resources/html/cr.html">
 <link rel="import" href="chrome://bookmarks/reducers.html">
+<link rel="import" href="chrome://bookmarks/util.html">
 <script src="chrome://bookmarks/bookmarks_store.js"></script>
 
 <!-- Old-style store: -->
diff --git a/chrome/browser/resources/md_bookmarks/types.js b/chrome/browser/resources/md_bookmarks/types.js
index 3889578e..3494f311 100644
--- a/chrome/browser/resources/md_bookmarks/types.js
+++ b/chrome/browser/resources/md_bookmarks/types.js
@@ -6,7 +6,36 @@
  * @fileoverview Closure typedefs for MD Bookmarks.
  */
 
-/** @typedef {!Object} */
+/**
+ * A normalized version of chrome.bookmarks.BookmarkTreeNode.
+ * @typedef{{
+ *   id: string,
+ *   parentId: (string|undefined),
+ *   url: (string|undefined),
+ *   title: string,
+ *   dateAdded: (number|undefined),
+ *   dateGroupModified: (number|undefined),
+ *   unmodifiable: (string|undefined),
+ *   children: (!Array<string>|undefined),
+ * }}
+ */
+var BookmarkNode;
+
+/**
+ * @typedef{!Object<string, BookmarkNode>}
+ */
+var NodeList;
+
+/** @typedef {!Object<string, boolean>} */
+var ClosedFolderState;
+
+/**
+ * @typedef{{
+ *   nodes: NodeList,
+ *   selectedFolder: ?string,
+ *   closedFolders: ClosedFolderState,
+ * }}
+ */
 var BookmarksPageState;
 
 /** @typedef {{name: string}} */
diff --git a/chrome/browser/resources/md_bookmarks/util.html b/chrome/browser/resources/md_bookmarks/util.html
new file mode 100644
index 0000000..cd1984240
--- /dev/null
+++ b/chrome/browser/resources/md_bookmarks/util.html
@@ -0,0 +1,2 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="chrome://bookmarks/util.js"></script>
diff --git a/chrome/browser/resources/md_bookmarks/util.js b/chrome/browser/resources/md_bookmarks/util.js
new file mode 100644
index 0000000..3bc4703
--- /dev/null
+++ b/chrome/browser/resources/md_bookmarks/util.js
@@ -0,0 +1,63 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Utility functions for the Bookmarks page.
+ */
+
+cr.define('bookmarks.util', function() {
+  /**
+   * @param {!BookmarksPageState} state
+   * @return {!Array<string>}
+   */
+  function getDisplayedList(state) {
+    return assert(state.nodes[assert(state.selectedFolder)].children);
+  }
+
+  /**
+   * @param {BookmarkTreeNode} rootNode
+   * @return {NodeList}
+   */
+  function normalizeNodes(rootNode) {
+    /** @type {NodeList} */
+    var nodeList = {};
+    var stack = [];
+    stack.push(rootNode);
+
+    while (stack.length > 0) {
+      var node = stack.pop();
+      // Node index is not necessary and not kept up-to-date. Remove it from the
+      // data structure so we don't accidentally depend on the incorrect
+      // information.
+      delete node.index;
+      nodeList[node.id] = node;
+      if (!node.children)
+        continue;
+
+      var childIds = [];
+      node.children.forEach(function(child) {
+        childIds.push(child.id);
+        stack.push(child);
+      });
+      node.children = childIds;
+    }
+
+    return nodeList;
+  }
+
+  /** @return {!BookmarksPageState} */
+  function createEmptyState() {
+    return {
+      nodes: {},
+      selectedFolder: '0',
+      closedFolders: {},
+    };
+  }
+
+  return {
+    createEmptyState: createEmptyState,
+    getDisplayedList: getDisplayedList,
+    normalizeNodes: normalizeNodes,
+  };
+});
diff --git a/chrome/browser/resources/settings/controls/settings_dropdown_menu.html b/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
index c834172..bc2cab0 100644
--- a/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
+++ b/chrome/browser/resources/settings/controls/settings_dropdown_menu.html
@@ -15,9 +15,8 @@
         display: inline-flex;
       }
 
-      cr-policy-pref-indicator,
-      select {
-        -webkit-margin-start: var(--checkbox-spacing);
+      cr-policy-pref-indicator {
+        margin: 0 var(--checkbox-spacing);
       }
 
       /* Hide "Custom" value when unselectable. */
diff --git a/chrome/browser/safe_browsing/browser_feature_extractor.cc b/chrome/browser/safe_browsing/browser_feature_extractor.cc
index 5b1967a..aee391d 100644
--- a/chrome/browser/safe_browsing/browser_feature_extractor.cc
+++ b/chrome/browser/safe_browsing/browser_feature_extractor.cc
@@ -22,9 +22,9 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/browser_features.h"
 #include "chrome/browser/safe_browsing/client_side_detection_host.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_types.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_controller.h"
diff --git a/chrome/browser/safe_browsing/browser_feature_extractor_unittest.cc b/chrome/browser/safe_browsing/browser_feature_extractor_unittest.cc
index 240438f..bcefc31 100644
--- a/chrome/browser/safe_browsing/browser_feature_extractor_unittest.cc
+++ b/chrome/browser/safe_browsing/browser_feature_extractor_unittest.cc
@@ -20,11 +20,11 @@
 #include "chrome/browser/safe_browsing/client_side_detection_host.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/history/core/browser/history_backend.h"
 #include "components/history/core/browser/history_service.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "content/public/browser/navigation_controller.h"
diff --git a/chrome/browser/safe_browsing/client_side_detection_host.cc b/chrome/browser/safe_browsing/client_side_detection_host.cc
index b5eb0d7..4bba2f07 100644
--- a/chrome/browser/safe_browsing/client_side_detection_host.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_host.cc
@@ -20,9 +20,9 @@
 #include "chrome/browser/safe_browsing/client_side_detection_service.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
index dff9a32..5ca8bf2 100644
--- a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
@@ -19,10 +19,10 @@
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "content/public/browser/navigation_entry.h"
diff --git a/chrome/browser/safe_browsing/client_side_detection_service.cc b/chrome/browser/safe_browsing/client_side_detection_service.cc
index 275e8146..df4e6e5 100644
--- a/chrome/browser/safe_browsing/client_side_detection_service.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_service.cc
@@ -21,10 +21,10 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/safe_browsing/client_model.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
diff --git a/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc
index b2a4889..9498d88 100644
--- a/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_service_unittest.cc
@@ -22,7 +22,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
 #include "chrome/common/safe_browsing/client_model.pb.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/variations/variations_associated_data.h"
 #include "content/public/test/test_browser_thread.h"
 #include "crypto/sha2.h"
diff --git a/chrome/browser/safe_browsing/client_side_model_loader.cc b/chrome/browser/safe_browsing/client_side_model_loader.cc
index bd2418b..30e181a 100644
--- a/chrome/browser/safe_browsing/client_side_model_loader.cc
+++ b/chrome/browser/safe_browsing/client_side_model_loader.cc
@@ -17,10 +17,10 @@
 #include "base/time/time.h"
 #include "chrome/browser/safe_browsing/protocol_manager.h"
 #include "chrome/common/safe_browsing/client_model.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
 #include "components/safe_browsing/common/safebrowsing_switches.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/variations/variations_associated_data.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
diff --git a/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc b/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc
index f30d5590..8afe6e8 100644
--- a/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc
+++ b/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc
@@ -17,7 +17,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
 #include "chrome/common/safe_browsing/client_model.pb.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/variations/variations_associated_data.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "net/http/http_status_code.h"
diff --git a/chrome/browser/safe_browsing/download_feedback.cc b/chrome/browser/safe_browsing/download_feedback.cc
index 6e43f5a..d38f0811 100644
--- a/chrome/browser/safe_browsing/download_feedback.cc
+++ b/chrome/browser/safe_browsing/download_feedback.cc
@@ -10,7 +10,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/task_runner.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "net/base/net_errors.h"
 
diff --git a/chrome/browser/safe_browsing/download_feedback_unittest.cc b/chrome/browser/safe_browsing/download_feedback_unittest.cc
index 1b465b2..8af5c16 100644
--- a/chrome/browser/safe_browsing/download_feedback_unittest.cc
+++ b/chrome/browser/safe_browsing/download_feedback_unittest.cc
@@ -11,7 +11,7 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "chrome/browser/safe_browsing/two_phase_uploader.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "net/url_request/url_request_test_util.h"
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc
index 1a199a0..c9f6953 100644
--- a/chrome/browser/safe_browsing/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -45,6 +45,7 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/download_protection_util.h"
 #include "chrome/common/safe_browsing/file_type_policies.h"
 #include "chrome/common/safe_browsing/zip_analyzer_results.h"
@@ -54,7 +55,6 @@
 #include "components/history/core/browser/history_service.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safebrowsing_switches.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_item.h"
diff --git a/chrome/browser/safe_browsing/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
index c88eecb..5c40564 100644
--- a/chrome/browser/safe_browsing/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
@@ -34,12 +34,12 @@
 #include "chrome/browser/safe_browsing/local_database_manager.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/file_type_policies_test_util.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safebrowsing_switches.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/test_database_manager.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc
index bf35ec2..3ae87d2 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer.cc
@@ -20,7 +20,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc
index 80dc4812..9cae1aa2 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/browser/safe_browsing/signature_evaluator_mac.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 #define DEVELOPER_ID_APPLICATION_OID "field.1.2.840.113635.100.6.1.13"
 #define DEVELOPER_ID_INTERMEDIATE_OID "field.1.2.840.113635.100.6.2.6"
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc
index 469b736..9eb43c79 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/incident.h"
 #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
 #include "chrome/common/chrome_paths.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc
index 3b6e7ca..d8c45f8 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/common/chrome_version.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc
index 2c17aa4..6ff204f 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_analyzer_win_unittest.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_version.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.cc
index a914bfba..35e0763 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident.cc
@@ -6,7 +6,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_handler_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident_unittest.cc
index bb86600..a6c4503 100644
--- a/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident_unittest.cc
@@ -11,7 +11,7 @@
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/incident_reporting/blacklist_load_analyzer_win.cc b/chrome/browser/safe_browsing/incident_reporting/blacklist_load_analyzer_win.cc
index 4349fc84..97af8bfe 100644
--- a/chrome/browser/safe_browsing/incident_reporting/blacklist_load_analyzer_win.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/blacklist_load_analyzer_win.cc
@@ -21,8 +21,8 @@
 #include "chrome/browser/safe_browsing/path_sanitizer.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome_elf/blacklist/blacklist.h"
-#include "components/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident.cc b/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident.cc
index c62e2b6f..d0b0502 100644
--- a/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident.cc
@@ -6,7 +6,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_handler_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident_unittest.cc
index cd88c8c..ff597e0 100644
--- a/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident_unittest.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "base/memory/ptr_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/incident_reporting/download_metadata_manager_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/download_metadata_manager_unittest.cc
index d06c0f3..af0ed6a1 100644
--- a/chrome/browser/safe_browsing/incident_reporting/download_metadata_manager_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/download_metadata_manager_unittest.cc
@@ -15,8 +15,8 @@
 #include "base/files/file_util.h"
 #include "base/run_loop.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_manager.h"
 #include "content/public/test/mock_download_item.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/environment_data_collection.cc b/chrome/browser/safe_browsing/incident_reporting/environment_data_collection.cc
index faecf27ca..97b8344e 100644
--- a/chrome/browser/safe_browsing/incident_reporting/environment_data_collection.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/environment_data_collection.cc
@@ -15,7 +15,7 @@
 #include "base/threading/sequenced_worker_pool.h"
 #include "build/build_config.h"
 #include "chrome/common/channel_info.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/browser_thread.h"
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win.cc b/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win.cc
index 63211c2..c108780 100644
--- a/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win.cc
@@ -25,8 +25,8 @@
 #include "chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.h"
 #include "chrome/browser/safe_browsing/path_sanitizer.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome_elf/chrome_elf_constants.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/variations/variations_associated_data.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win_unittest.cc
index 46c05a50..e918e61b 100644
--- a/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win_unittest.cc
@@ -20,8 +20,8 @@
 #include "chrome/browser/safe_browsing/incident_reporting/module_integrity_unittest_util_win.h"
 #include "chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.h"
 #include "chrome/browser/safe_browsing/path_sanitizer.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome_elf/chrome_elf_constants.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "net/base/winsock_init.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/extension_data_collection.cc b/chrome/browser/safe_browsing/incident_reporting/extension_data_collection.cc
index d60f976..d2723ab 100644
--- a/chrome/browser/safe_browsing/incident_reporting/extension_data_collection.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/extension_data_collection.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/extensions/install_signer.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_factory.h"
 #include "extensions/browser/extension_registry.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc
index 07fadbc4..c5141eb 100644
--- a/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/extension_data_collection_unittest.cc
@@ -17,10 +17,10 @@
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident.cc b/chrome/browser/safe_browsing/incident_reporting/incident.cc
index 21d0b1b8..f121dd41 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident.cc
@@ -8,7 +8,7 @@
 
 #include "base/logging.h"
 #include "base/time/time.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl.cc b/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl.cc
index 735522ff..59e1ee3 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl.cc
@@ -7,8 +7,8 @@
 #include <utility>
 
 #include "base/metrics/histogram_macros.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/escape.h"
 #include "net/base/load_flags.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl_unittest.cc
index f50fdc44..d55b967 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl_unittest.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "base/test/test_simple_task_runner.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_status_code.h"
 #include "net/url_request/test_url_fetcher_factory.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
index 7c8bf2d..a8449df 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
@@ -34,8 +34,8 @@
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc
index 021265177..515335c 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service_unittest.cc
@@ -30,10 +30,10 @@
 #include "chrome/browser/safe_browsing/incident_reporting/last_download_finder.h"
 #include "chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/last_download_finder.cc b/chrome/browser/safe_browsing/incident_reporting/last_download_finder.cc
index 96d6612a..0199bc0 100644
--- a/chrome/browser/safe_browsing/incident_reporting/last_download_finder.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/last_download_finder.cc
@@ -23,12 +23,12 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/download_protection_util.h"
 #include "chrome/common/safe_browsing/file_type_policies.h"
 #include "components/history/core/browser/download_constants.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/prefs/pref_service.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_source.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc
index ba840982..3fb14fa 100644
--- a/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/last_download_finder_unittest.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
@@ -43,7 +44,6 @@
 #include "components/history/core/browser/history_constants.h"
 #include "components/history/core/browser/history_database_params.h"
 #include "components/history/core/browser/history_service.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.cc b/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.cc
index c1d99e93..286e0617 100644
--- a/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.cc
@@ -17,7 +17,7 @@
 #include "base/scoped_native_library.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/pe_image.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win_unittest.cc
index 9d91af9a..4cd6c656 100644
--- a/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win_unittest.cc
@@ -21,7 +21,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/pe_image.h"
 #include "chrome/browser/safe_browsing/incident_reporting/module_integrity_unittest_util_win.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win.cc b/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win.cc
index c39055b..941dcd1 100644
--- a/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident.h"
 #include "chrome/browser/safe_browsing/path_sanitizer.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 
 #if defined(SAFE_BROWSING_DB_LOCAL)
diff --git a/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win_unittest.cc
index 834ce4f..fe5e886 100644
--- a/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/module_load_analyzer_win_unittest.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/incident.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.cc b/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.cc
index 636a8fe7..0b7d284 100644
--- a/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.cc
@@ -12,7 +12,7 @@
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/user_prefs/tracked/pref_hash_store_transaction.h"
 #include "components/user_prefs/tracked/tracked_preference_helper.h"
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate_unittest.cc
index 1f5691b..8fb94e4 100644
--- a/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate_unittest.cc
@@ -16,7 +16,7 @@
 #include "base/values.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident.h"
 #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/resource_request_detector.cc b/chrome/browser/safe_browsing/incident_reporting/resource_request_detector.cc
index 9c8351fe..4515c990 100644
--- a/chrome/browser/safe_browsing/incident_reporting/resource_request_detector.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/resource_request_detector.cc
@@ -10,7 +10,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/browser/safe_browsing/incident_reporting/resource_request_incident.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/resource_request_detector_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/resource_request_detector_unittest.cc
index 90802044..cbaf712 100644
--- a/chrome/browser/safe_browsing/incident_reporting/resource_request_detector_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/resource_request_detector_unittest.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/incident.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "content/public/browser/resource_request_info.h"
 #include "content/public/common/previews_state.h"
diff --git a/chrome/browser/safe_browsing/incident_reporting/resource_request_incident.cc b/chrome/browser/safe_browsing/incident_reporting/resource_request_incident.cc
index 109b499..8b8a466 100644
--- a/chrome/browser/safe_browsing/incident_reporting/resource_request_incident.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/resource_request_incident.cc
@@ -6,7 +6,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_handler_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident.cc b/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident.cc
index 2f5589e..26d7936 100644
--- a/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident.cc
@@ -6,7 +6,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_handler_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident_unittest.cc
index 27450a5f..88d34b3 100644
--- a/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/suspicious_module_incident_unittest.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "base/memory/ptr_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident.cc b/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident.cc
index bc999fe..223895f4 100644
--- a/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident.cc
@@ -6,7 +6,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_handler_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident_unittest.cc
index c5a9e335b..49d4b37 100644
--- a/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident_unittest.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "base/memory/ptr_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_analyzer.cc b/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_analyzer.cc
index 9949152..fc3b1d0 100644
--- a/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_analyzer.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_analyzer.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/incident_receiver.h"
 #include "chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/variations/service/variations_service.h"
 #include "content/public/browser/browser_thread.h"
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident.cc b/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident.cc
index abd66c1..c409117e 100644
--- a/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident.cc
@@ -6,7 +6,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/safe_browsing/incident_reporting/incident_handler_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident_unittest.cc b/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident_unittest.cc
index 07b0261..27622f3 100644
--- a/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident_unittest.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "base/memory/ptr_util.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/chrome/browser/safe_browsing/notification_image_reporter.cc b/chrome/browser/safe_browsing/notification_image_reporter.cc
index d9e53ac..984fc43 100644
--- a/chrome/browser/safe_browsing/notification_image_reporter.cc
+++ b/chrome/browser/safe_browsing/notification_image_reporter.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/variations/variations_associated_data.h"
diff --git a/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc b/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc
index 2e0a6baf..a24fcea4 100644
--- a/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc
+++ b/chrome/browser/safe_browsing/notification_image_reporter_unittest.cc
@@ -11,9 +11,9 @@
 #include "chrome/browser/safe_browsing/mock_permission_report_sender.h"
 #include "chrome/browser/safe_browsing/ping_manager.h"
 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "components/safe_browsing_db/safe_browsing_prefs.h"
 #include "components/safe_browsing_db/test_database_manager.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
index f37985a8..cac35d20 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
@@ -8,7 +8,7 @@
 #include <deque>
 #include "base/feature_list.h"
 #include "base/supports_user_data.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/protobuf/src/google/protobuf/repeated_field.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/safe_browsing/signature_evaluator_mac.mm b/chrome/browser/safe_browsing/signature_evaluator_mac.mm
index 9ffcb66..415aaf9 100644
--- a/chrome/browser/safe_browsing/signature_evaluator_mac.mm
+++ b/chrome/browser/safe_browsing/signature_evaluator_mac.mm
@@ -18,8 +18,8 @@
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
-#include "components/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc b/chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc
index 3041104..1ffcc58f 100644
--- a/chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc
+++ b/chrome/browser/safe_browsing/signature_evaluator_mac_unittest.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/safe_browsing/incident_reporting/incident.h"
 #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
 #include "chrome/common/chrome_paths.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/safe_browsing/threat_details.h b/chrome/browser/safe_browsing/threat_details.h
index b479e2b0..55a501e7 100644
--- a/chrome/browser/safe_browsing/threat_details.h
+++ b/chrome/browser/safe_browsing/threat_details.h
@@ -22,8 +22,8 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/safe_browsing/common/safebrowsing_types.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "net/base/completion_callback.h"
diff --git a/chrome/browser/safe_browsing/threat_details_cache.cc b/chrome/browser/safe_browsing/threat_details_cache.cc
index a0e7a6a..066e06a 100644
--- a/chrome/browser/safe_browsing/threat_details_cache.cc
+++ b/chrome/browser/safe_browsing/threat_details_cache.cc
@@ -14,8 +14,8 @@
 #include "base/strings/string_util.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/threat_details_cache.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/browser/browser_thread.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/load_flags.h"
diff --git a/chrome/browser/safe_browsing/threat_details_unittest.cc b/chrome/browser/safe_browsing/threat_details_unittest.cc
index 0598592..1122c129 100644
--- a/chrome/browser/safe_browsing/threat_details_unittest.cc
+++ b/chrome/browser/safe_browsing/threat_details_unittest.cc
@@ -19,12 +19,12 @@
 #include "chrome/browser/safe_browsing/threat_details.h"
 #include "chrome/browser/safe_browsing/threat_details_history.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/history/core/browser/history_backend.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/web_contents_tester.h"
diff --git a/chrome/browser/storage/durable_storage_permission_context.cc b/chrome/browser/storage/durable_storage_permission_context.cc
index c6fd62c..eb31debc 100644
--- a/chrome/browser/storage/durable_storage_permission_context.cc
+++ b/chrome/browser/storage/durable_storage_permission_context.cc
@@ -39,12 +39,14 @@
     bool user_gesture,
     const BrowserPermissionCallback& callback) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
-  DCHECK_NE(
-      CONTENT_SETTING_ALLOW,
-      GetPermissionStatus(requesting_origin, embedding_origin).content_setting);
-  DCHECK_NE(
-      CONTENT_SETTING_BLOCK,
-      GetPermissionStatus(requesting_origin, embedding_origin).content_setting);
+  DCHECK_NE(CONTENT_SETTING_ALLOW,
+            GetPermissionStatus(nullptr /* render_frame_host */,
+                                requesting_origin, embedding_origin)
+                .content_setting);
+  DCHECK_NE(CONTENT_SETTING_BLOCK,
+            GetPermissionStatus(nullptr /* render_frame_host */,
+                                requesting_origin, embedding_origin)
+                .content_setting);
 
   // Durable is only allowed to be granted to the top-level origin. Embedding
   // origin is the last committed navigation origin to the web contents.
diff --git a/chrome/browser/storage/durable_storage_permission_context_unittest.cc b/chrome/browser/storage/durable_storage_permission_context_unittest.cc
index 854841a4..1b3dda7 100644
--- a/chrome/browser/storage/durable_storage_permission_context_unittest.cc
+++ b/chrome/browser/storage/durable_storage_permission_context_unittest.cc
@@ -221,5 +221,7 @@
   GURL url("http://www.google.com");
 
   EXPECT_EQ(CONTENT_SETTING_BLOCK,
-            permission_context.GetPermissionStatus(url, url).content_setting);
+            permission_context
+                .GetPermissionStatus(nullptr /* render_frame_host */, url, url)
+                .content_setting);
 }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 6ed5805..8080d56 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1215,7 +1215,6 @@
     deps += [
       "//chrome/browser/safe_browsing:chunk_proto",
       "//chrome/common/safe_browsing:proto",
-      "//components/safe_browsing:csd_proto",
     ]
   }
 
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index d2063c2f..d83e4e7 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -1040,6 +1040,22 @@
                                WebContents* new_contents,
                                int index,
                                int reason) {
+  // Copies the background color from an old WebContents to a new one that
+  // replaces it on the screen. This allows the new WebContents to use the
+  // old one's background color as the starting background color, before having
+  // loaded any contents. As a result, we avoid flashing white when moving to
+  // a new tab. (There is also code in RenderFrameHostManager to do something
+  // similar for intra-tab navigations.)
+  if (old_contents && new_contents) {
+    // While GetMainFrame() is guaranteed to return non-null, GetView() is not,
+    // e.g. between WebContents creation and creation of the
+    // RenderWidgetHostView.
+    RenderWidgetHostView* old_view = old_contents->GetMainFrame()->GetView();
+    RenderWidgetHostView* new_view = new_contents->GetMainFrame()->GetView();
+    if (old_view && new_view)
+      new_view->SetBackgroundColor(old_view->background_color());
+  }
+
   content::RecordAction(UserMetricsAction("ActiveTabChanged"));
 
   // Update the bookmark state, since the BrowserWindow may query it during
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index 4197fba..1a7e806 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -301,12 +301,14 @@
         chrome::NavigateParams::SHOW_WINDOW_INACTIVE) {
       params_->browser->window()->ShowInactive();
     } else if (params_->window_action == chrome::NavigateParams::SHOW_WINDOW) {
-      params_->browser->window()->Show();
+      BrowserWindow* window = params_->browser->window();
+      window->Show();
       // If a user gesture opened a popup window, focus the contents.
       if (params_->user_gesture &&
           params_->disposition == WindowOpenDisposition::NEW_POPUP &&
           params_->target_contents) {
         params_->target_contents->Focus();
+        window->Activate();
       }
     }
   }
diff --git a/chrome/browser/ui/browser_unittest.cc b/chrome/browser/ui/browser_unittest.cc
index 6cfdf0b..62bcd32 100644
--- a/chrome/browser/ui/browser_unittest.cc
+++ b/chrome/browser/ui/browser_unittest.cc
@@ -11,9 +11,11 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "components/zoom/zoom_controller.h"
+#include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/web_contents_tester.h"
+#include "third_party/skia/include/core/SkColor.h"
 
 using content::SiteInstance;
 using content::WebContents;
@@ -66,6 +68,27 @@
   EXPECT_TRUE(contents2->IsCrashed());
 }
 
+TEST_F(BrowserUnitTest, SetBackgroundColorForNewTab) {
+  TabStripModel* tab_strip_model = browser()->tab_strip_model();
+
+  WebContents* contents1 = CreateTestWebContents();
+  tab_strip_model->AppendWebContents(contents1, true);
+  WebContentsTester::For(contents1)->NavigateAndCommit(GURL("about:blank"));
+  WebContentsTester::For(contents1)->TestSetIsLoading(false);
+
+  contents1->GetMainFrame()->GetView()->SetBackgroundColor(SK_ColorRED);
+
+  // Add a second tab in the background.
+  WebContents* contents2 = CreateTestWebContents();
+  tab_strip_model->AppendWebContents(contents2, false);
+  WebContentsTester::For(contents2)->NavigateAndCommit(GURL("about:blank"));
+  WebContentsTester::For(contents2)->TestSetIsLoading(false);
+
+  tab_strip_model->ActivateTabAt(1, true);
+  EXPECT_EQ(SK_ColorRED,
+            contents2->GetMainFrame()->GetView()->background_color());
+}
+
 // Ensure the print command gets disabled when a tab crashes.
 TEST_F(BrowserUnitTest, DisablePrintOnCrashedTab) {
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
diff --git a/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc b/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
index 97a3e24c..4f180d93 100644
--- a/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
+++ b/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
@@ -66,6 +66,8 @@
   AddLocalizedString(source, "title", IDS_MD_BOOKMARK_MANAGER_TITLE);
 
   // Resources.
+  source->AddResourcePath("actions.html", IDR_MD_BOOKMARKS_ACTIONS_HTML);
+  source->AddResourcePath("actions.js", IDR_MD_BOOKMARKS_ACTIONS_JS);
   source->AddResourcePath("app.html", IDR_MD_BOOKMARKS_APP_HTML);
   source->AddResourcePath("app.js", IDR_MD_BOOKMARKS_APP_JS);
   source->AddResourcePath("bookmarks_store.js",
@@ -96,6 +98,8 @@
   source->AddResourcePath("store_client.js", IDR_MD_BOOKMARKS_STORE_CLIENT_JS);
   source->AddResourcePath("toolbar.html", IDR_MD_BOOKMARKS_TOOLBAR_HTML);
   source->AddResourcePath("toolbar.js", IDR_MD_BOOKMARKS_TOOLBAR_JS);
+  source->AddResourcePath("util.html", IDR_MD_BOOKMARKS_UTIL_HTML);
+  source->AddResourcePath("util.js", IDR_MD_BOOKMARKS_UTIL_JS);
   source->SetDefaultResource(IDR_MD_BOOKMARKS_BOOKMARKS_HTML);
   source->SetJsonPath("strings.js");
 
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 1cfcfc2..84f0b40 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -196,7 +196,6 @@
     "//components/policy:generated",
     "//components/policy/core/common",
     "//components/prefs",
-    "//components/safe_browsing:csd_proto",
     "//components/signin/core/common",
     "//components/strings",
     "//components/translate/content/common",
diff --git a/chrome/common/DEPS b/chrome/common/DEPS
index 0d712737..e6b1509c 100644
--- a/chrome/common/DEPS
+++ b/chrome/common/DEPS
@@ -23,7 +23,6 @@
   "+components/password_manager/core/common",
   "+components/policy/core/common",
   "+components/printing/common",
-  "+components/safe_browsing/csd.pb.h",
   "+components/signin/core/common",
   "+components/translate/core/common",
   "+components/url_formatter",
diff --git a/chrome/common/safe_browsing/BUILD.gn b/chrome/common/safe_browsing/BUILD.gn
index 55ccf63..4f56f9f2 100644
--- a/chrome/common/safe_browsing/BUILD.gn
+++ b/chrome/common/safe_browsing/BUILD.gn
@@ -8,6 +8,7 @@
   sources = [
     "client_model.proto",
     "crx_info.proto",
+    "csd.proto",
     "download_file_types.proto",
     "permission_report.proto",
   ]
diff --git a/chrome/common/safe_browsing/binary_feature_extractor.cc b/chrome/common/safe_browsing/binary_feature_extractor.cc
index 538a81e..ed883db 100644
--- a/chrome/common/safe_browsing/binary_feature_extractor.cc
+++ b/chrome/common/safe_browsing/binary_feature_extractor.cc
@@ -10,7 +10,7 @@
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/memory_mapped_file.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "crypto/secure_hash.h"
 #include "crypto/sha2.h"
 
diff --git a/chrome/common/safe_browsing/binary_feature_extractor_mac.cc b/chrome/common/safe_browsing/binary_feature_extractor_mac.cc
index 7d64fae..f0ab808c 100644
--- a/chrome/common/safe_browsing/binary_feature_extractor_mac.cc
+++ b/chrome/common/safe_browsing/binary_feature_extractor_mac.cc
@@ -7,8 +7,8 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
-#include "components/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc b/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc
index ec29387..88fe7b92 100644
--- a/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc
+++ b/chrome/common/safe_browsing/binary_feature_extractor_mac_unittest.cc
@@ -7,7 +7,7 @@
 #include "base/files/file_path.h"
 #include "base/path_service.h"
 #include "chrome/common/chrome_paths.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace safe_browsing {
diff --git a/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc b/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc
index 48b1847..9da2d48 100644
--- a/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc
+++ b/chrome/common/safe_browsing/binary_feature_extractor_unittest.cc
@@ -13,7 +13,7 @@
 #include "base/files/file.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/path_service.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "crypto/sha2.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/common/safe_browsing/binary_feature_extractor_win.cc b/chrome/common/safe_browsing/binary_feature_extractor_win.cc
index ffdb9186..a504631 100644
--- a/chrome/common/safe_browsing/binary_feature_extractor_win.cc
+++ b/chrome/common/safe_browsing/binary_feature_extractor_win.cc
@@ -12,8 +12,8 @@
 
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/pe_image_reader_win.h"
-#include "components/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc b/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc
index c4a6d914..4b2f9df 100644
--- a/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc
+++ b/chrome/common/safe_browsing/binary_feature_extractor_win_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/path_service.h"
 #include "chrome/common/chrome_paths.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "net/cert/x509_cert_types.h"
 #include "net/cert/x509_certificate.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/safe_browsing/csd.proto b/chrome/common/safe_browsing/csd.proto
similarity index 99%
rename from components/safe_browsing/csd.proto
rename to chrome/common/safe_browsing/csd.proto
index 3da6cd68..92b07a2 100644
--- a/components/safe_browsing/csd.proto
+++ b/chrome/common/safe_browsing/csd.proto
@@ -36,6 +36,7 @@
   optional UserPopulation user_population = 1;
 }
 
+
 message ClientPhishingRequest {
   // URL that the client visited.  The CGI parameters are stripped by the
   // client.
@@ -470,7 +471,7 @@
   // True if the .zip or DMG, etc, was 100% successfully unpacked.
   optional bool archive_valid = 26;
 
-  // True if this ClientDownloadRequest is from a whitelisted domain.
+    // True if this ClientDownloadRequest is from a whitelisted domain.
   optional bool skipped_url_whitelist = 28;
 
   // True if this ClientDownloadRequest contains a whitelisted certificate.
@@ -577,7 +578,9 @@
 message ClientDownloadReport {
   // The information of user who provided the feedback.
   // This is going to be useful for handling appeals.
-  message UserInformation { optional string email = 1; }
+  message UserInformation {
+    optional string email = 1;
+  }
 
   enum Reason {
     SHARE = 0;
diff --git a/chrome/common/safe_browsing/download_protection_util.h b/chrome/common/safe_browsing/download_protection_util.h
index dedc2865..ff01cc5 100644
--- a/chrome/common/safe_browsing/download_protection_util.h
+++ b/chrome/common/safe_browsing/download_protection_util.h
@@ -6,7 +6,7 @@
 #define CHROME_COMMON_SAFE_BROWSING_DOWNLOAD_PROTECTION_UTIL_H_
 
 #include "base/files/file_path.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 namespace download_protection_util {
diff --git a/chrome/common/safe_browsing/zip_analyzer.cc b/chrome/common/safe_browsing/zip_analyzer.cc
index ed3bde6..b352ca3 100644
--- a/chrome/common/safe_browsing/zip_analyzer.cc
+++ b/chrome/common/safe_browsing/zip_analyzer.cc
@@ -14,10 +14,10 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/download_protection_util.h"
 #include "chrome/common/safe_browsing/file_type_policies.h"
 #include "chrome/common/safe_browsing/zip_analyzer_results.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "crypto/secure_hash.h"
 #include "crypto/sha2.h"
 #include "third_party/zlib/google/zip_reader.h"
diff --git a/chrome/common/safe_browsing/zip_analyzer.h b/chrome/common/safe_browsing/zip_analyzer.h
index b3109b9..7c8c3730 100644
--- a/chrome/common/safe_browsing/zip_analyzer.h
+++ b/chrome/common/safe_browsing/zip_analyzer.h
@@ -9,7 +9,7 @@
 #define CHROME_COMMON_SAFE_BROWSING_ZIP_ANALYZER_H_
 
 #include "base/files/file.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 namespace zip_analyzer {
diff --git a/chrome/common/safe_browsing/zip_analyzer_results.h b/chrome/common/safe_browsing/zip_analyzer_results.h
index efddbef..7a506cf 100644
--- a/chrome/common/safe_browsing/zip_analyzer_results.h
+++ b/chrome/common/safe_browsing/zip_analyzer_results.h
@@ -11,7 +11,7 @@
 #include <vector>
 
 #include "base/files/file_path.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
 namespace zip_analyzer {
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index 3ef7e5a..b0a28bfb 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -216,7 +216,6 @@
       ]
       deps += [
         "//chrome/common/safe_browsing:proto",
-        "//components/safe_browsing:csd_proto",
         "//third_party/smhasher:murmurhash3",
       ]
     }
diff --git a/chrome/renderer/net/OWNERS b/chrome/renderer/net/OWNERS
index fd1666b4..5ca5854 100644
--- a/chrome/renderer/net/OWNERS
+++ b/chrome/renderer/net/OWNERS
@@ -1,3 +1,5 @@
 jar@chromium.org
 mmenke@chromium.org
 juliatuttle@chromium.org
+
+# COMPONENT: Internals>Network
diff --git a/chrome/renderer/prerender/OWNERS b/chrome/renderer/prerender/OWNERS
index 5d6642e..94c5922 100644
--- a/chrome/renderer/prerender/OWNERS
+++ b/chrome/renderer/prerender/OWNERS
@@ -2,3 +2,5 @@
 gavinp@chromium.org
 mmenke@chromium.org
 pasko@chromium.org
+
+# COMPONENT: Internals>Preload
diff --git a/chrome/renderer/safe_browsing/DEPS b/chrome/renderer/safe_browsing/DEPS
index 3e3e99f0..1a23f04 100644
--- a/chrome/renderer/safe_browsing/DEPS
+++ b/chrome/renderer/safe_browsing/DEPS
@@ -1,6 +1,5 @@
 include_rules = [
   "+components/safe_browsing/common",
-  "+components/safe_browsing/csd.pb.h",
   "+third_party/smhasher",
 ]
 
diff --git a/chrome/renderer/safe_browsing/phishing_classifier.cc b/chrome/renderer/safe_browsing/phishing_classifier.cc
index b6a1882..a2246d5 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier.cc
@@ -15,6 +15,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_util.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
 #include "chrome/renderer/safe_browsing/features.h"
@@ -22,7 +23,6 @@
 #include "chrome/renderer/safe_browsing/phishing_term_feature_extractor.h"
 #include "chrome/renderer/safe_browsing/phishing_url_feature_extractor.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/renderer/render_frame.h"
 #include "crypto/sha2.h"
 #include "third_party/WebKit/public/platform/WebURL.h"
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
index 4ee3b99..c32f907 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
@@ -17,13 +17,13 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/safe_browsing/client_model.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/renderer/safe_browsing/features.h"
 #include "chrome/renderer/safe_browsing/mock_feature_extractor_clock.h"
 #include "chrome/renderer/safe_browsing/murmurhash3_util.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
index 92043a0..258b878 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
@@ -12,11 +12,11 @@
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
 #include "chrome/renderer/safe_browsing/phishing_classifier.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/renderer/document_state.h"
 #include "content/public/renderer/navigation_state.h"
 #include "content/public/renderer/render_frame.h"
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
index 2412c84..5e5b799 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
@@ -7,13 +7,13 @@
 #include <memory>
 
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/renderer/safe_browsing/features.h"
 #include "chrome/renderer/safe_browsing/phishing_classifier.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "chrome/test/base/chrome_unit_test_suite.h"
 #include "components/safe_browsing/common/safebrowsing_messages.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_view.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9072029d..ff7340c2 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -682,6 +682,8 @@
       # panels).
       sources += [
         "../browser/notifications/notification_interactive_uitest.cc",
+        "../browser/notifications/notification_interactive_uitest_mac.mm",
+        "../browser/notifications/notification_interactive_uitest_support.cc",
         "../browser/notifications/platform_notification_service_interactive_uitest.cc",
       ]
     }
@@ -4510,9 +4512,8 @@
       "//third_party/ocmock",
     ]
 
-    sources += [
-      "../browser/ui/cocoa/applescript/apple_event_util_unittest.mm",
-    ]
+    sources +=
+        [ "../browser/ui/cocoa/applescript/apple_event_util_unittest.mm" ]
 
     if (mac_views_browser) {
       # TODO(tapted): Add chrome_unit_tests_views_non_mac_sources.
diff --git a/chrome/test/data/notifications/notification_tester.html b/chrome/test/data/notifications/notification_tester.html
index 20de190..2cfc5f6a 100644
--- a/chrome/test/data/notifications/notification_tester.html
+++ b/chrome/test/data/notifications/notification_tester.html
@@ -14,12 +14,13 @@
 // Returns an id for the notification, which can be used to cancel it with
 // |cancelNotification|. If two notifications are created with the same
 // tag, the second one should replace the first.
-function createNotification(iconUrl, title, text, tag) {
+function createNotification(iconUrl, title, text, tag, onclick) {
   var notification = new Notification(title, {
     icon: iconUrl,
     body: text,
     tag: tag
   });
+  notification.onclick = onclick;
 
   createNotificationHelper(notification, true);
 }
diff --git a/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js b/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js
index a306280d..8fa97fcf 100644
--- a/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js
+++ b/chrome/test/data/webui/md_bookmarks/md_bookmarks_browsertest.js
@@ -82,6 +82,20 @@
   mocha.run();
 });
 
+function MaterialBookmarksReducersTest() {}
+
+MaterialBookmarksReducersTest.prototype = {
+  __proto__: MaterialBookmarksBrowserTest.prototype,
+
+  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+    'reducers_test.js',
+  ]),
+};
+
+TEST_F('MaterialBookmarksReducersTest', 'All', function() {
+  mocha.run();
+});
+
 function MaterialBookmarksStoreClientTest() {}
 
 MaterialBookmarksStoreClientTest.prototype = {
diff --git a/chrome/test/data/webui/md_bookmarks/reducers_test.js b/chrome/test/data/webui/md_bookmarks/reducers_test.js
new file mode 100644
index 0000000..381515db
--- /dev/null
+++ b/chrome/test/data/webui/md_bookmarks/reducers_test.js
@@ -0,0 +1,127 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+suite('closed folder state', function() {
+  var nodes;
+  var initialState;
+
+  setup(function() {
+    nodes = testTree(createFolder('0', [
+      createFolder('1', []),
+    ]));
+    initialState = {};
+  });
+
+  test('toggle folder open state', function() {
+    var action = bookmarks.actions.changeFolderOpen('1', false);
+    var nextState = bookmarks.ClosedFolderState.updateClosedFolders(
+        initialState, action, nodes);
+    assertTrue(nextState['1']);
+    assertFalse(!!nextState['0']);
+  });
+
+  test('select folder with closed parent', function() {
+    var action;
+    var nextState;
+    // Close '0'
+    action = bookmarks.actions.changeFolderOpen('0', false);
+    nextState = bookmarks.ClosedFolderState.updateClosedFolders(
+        initialState, action, nodes);
+    assertTrue(nextState['0']);
+
+    // Should re-open when '1' is selected.
+    action = bookmarks.actions.selectFolder('1');
+    nextState = bookmarks.ClosedFolderState.updateClosedFolders(
+        nextState, action, nodes);
+    assertFalse(nextState['0']);
+  });
+});
+
+suite('selected folder', function() {
+  var nodes;
+  var initialState;
+
+  setup(function() {
+    nodes = testTree(createFolder('0', [
+      createFolder('1', []),
+    ]));
+
+    initialState = '0';
+  });
+
+  test('updates from selectFolder action', function() {
+    var action = bookmarks.actions.selectFolder('1');
+    var newState = bookmarks.SelectedFolderState.updateSelectedFolder(
+        initialState, action, nodes);
+    assertEquals('1', newState);
+  });
+
+  test('updates when parent of selected folder is closed', function() {
+    var action;
+    var newState;
+
+    action = bookmarks.actions.selectFolder('1');
+    newState = bookmarks.SelectedFolderState.updateSelectedFolder(
+        initialState, action, nodes);
+
+    action = bookmarks.actions.changeFolderOpen('0', false);
+    newState = bookmarks.SelectedFolderState.updateSelectedFolder(
+        newState, action, nodes);
+    assertEquals('0', newState);
+  });
+});
+
+suite('node state', function() {
+  var initialState;
+
+  setup(function() {
+    initialState = testTree(createFolder('0', [
+      createFolder(
+          '1',
+          [
+            createItem('2', {title: 'a', url: 'a.com'}),
+            createItem('3'),
+            createFolder('4', []),
+          ]),
+      createFolder('5', []),
+    ]));
+  });
+
+  test('updates when a node is edited', function() {
+    var action = bookmarks.actions.editBookmark('2', {title: 'b'});
+    var nextState = bookmarks.NodeState.updateNodes(initialState, action);
+
+    assertEquals('b', nextState['2'].title);
+    assertEquals('a.com', nextState['2'].url);
+
+    action = bookmarks.actions.editBookmark('2', {title: 'c', url: 'c.com'});
+    nextState = bookmarks.NodeState.updateNodes(nextState, action);
+
+    assertEquals('c', nextState['2'].title);
+    assertEquals('c.com', nextState['2'].url);
+
+    action = bookmarks.actions.editBookmark('4', {title: 'folder'});
+    nextState = bookmarks.NodeState.updateNodes(nextState, action);
+
+    assertEquals('folder', nextState['4'].title);
+    assertEquals(undefined, nextState['4'].url);
+
+    // Cannot edit URL of a folder:
+    action = bookmarks.actions.editBookmark('4', {url: 'folder.com'});
+    nextState = bookmarks.NodeState.updateNodes(nextState, action);
+
+    assertEquals('folder', nextState['4'].title);
+    assertEquals(undefined, nextState['4'].url);
+  });
+
+  test('updates when a node is deleted', function() {
+    var action = bookmarks.actions.removeBookmark('3', '1', 1);
+    var nextState = bookmarks.NodeState.updateNodes(initialState, action);
+
+    assertDeepEquals(['2', '4'], nextState['1'].children);
+
+    // TODO(tsergeant): Deleted nodes should be removed from the nodes map
+    // entirely.
+  });
+});
diff --git a/chrome/test/data/webui/md_bookmarks/test_util.js b/chrome/test/data/webui/md_bookmarks/test_util.js
index 837841e..b024e266 100644
--- a/chrome/test/data/webui/md_bookmarks/test_util.js
+++ b/chrome/test/data/webui/md_bookmarks/test_util.js
@@ -12,6 +12,13 @@
 }
 
 /**
+ * Convert a tree of bookmark nodes into a normalized lookup table of nodes.
+ */
+function testTree(root) {
+  return bookmarks.util.normalizeNodes(root);
+}
+
+/**
  * Initialize a tree for UI testing. This performs the same initialization as
  * `setUpStore_` in <bookmarks-store>, but without the need for a store element
  * in the test.
diff --git a/chrome/tools/safe_browsing/DEPS b/chrome/tools/safe_browsing/DEPS
deleted file mode 100644
index 322a686..0000000
--- a/chrome/tools/safe_browsing/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
-  "+components/safe_browsing/csd.pb.h",
-]
diff --git a/chrome/tools/safe_browsing/sb_sigutil.cc b/chrome/tools/safe_browsing/sb_sigutil.cc
index c33ffa7..88ca8d6ac 100644
--- a/chrome/tools/safe_browsing/sb_sigutil.cc
+++ b/chrome/tools/safe_browsing/sb_sigutil.cc
@@ -16,7 +16,7 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
-#include "components/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 
 // Command-line switch for the executable to extract a signature from.
 static const char kExecutable[] = "executable";
diff --git a/chrome/utility/safe_browsing/mac/BUILD.gn b/chrome/utility/safe_browsing/mac/BUILD.gn
index 60d6979..90b9329 100644
--- a/chrome/utility/safe_browsing/mac/BUILD.gn
+++ b/chrome/utility/safe_browsing/mac/BUILD.gn
@@ -16,7 +16,7 @@
     ":dmg_common",
     "//base",
     "//chrome/common",
-    "//components/safe_browsing:csd_proto",
+    "//chrome/common/safe_browsing:proto",
   ]
 }
 
diff --git a/chrome/utility/safe_browsing/mac/DEPS b/chrome/utility/safe_browsing/mac/DEPS
deleted file mode 100644
index 322a686..0000000
--- a/chrome/utility/safe_browsing/mac/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
-  "+components/safe_browsing/csd.pb.h",
-]
diff --git a/chrome/utility/safe_browsing/mac/dmg_analyzer.cc b/chrome/utility/safe_browsing/mac/dmg_analyzer.cc
index a93d19c..94cb913 100644
--- a/chrome/utility/safe_browsing/mac/dmg_analyzer.cc
+++ b/chrome/utility/safe_browsing/mac/dmg_analyzer.cc
@@ -13,10 +13,10 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/common/safe_browsing/binary_feature_extractor.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
 #include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
 #include "chrome/utility/safe_browsing/mac/dmg_iterator.h"
 #include "chrome/utility/safe_browsing/mac/read_stream.h"
-#include "components/safe_browsing/csd.pb.h"
 #include "crypto/secure_hash.h"
 #include "crypto/sha2.h"
 
diff --git a/components/captive_portal/OWNERS b/components/captive_portal/OWNERS
index 53e68eb7..a122871 100644
--- a/components/captive_portal/OWNERS
+++ b/components/captive_portal/OWNERS
@@ -1 +1,3 @@
 mmenke@chromium.org
+
+# COMPONENT: Internals>Network
diff --git a/components/error_page/OWNERS b/components/error_page/OWNERS
index 2b70a4c..bebb2c4 100644
--- a/components/error_page/OWNERS
+++ b/components/error_page/OWNERS
@@ -3,4 +3,6 @@
 
 # If changing network error strings, please visit go/chrome-neterror-strings
 # (Google internal) or contact edwardjung@chromium.org and rachelis@chromium.org
-per-file error_page_strings.grdp=edwardjung@chromium.org
\ No newline at end of file
+per-file error_page_strings.grdp=edwardjung@chromium.org
+
+# COMPONENT: Internals>Network
diff --git a/components/neterror/OWNERS b/components/neterror/OWNERS
index 9f303e4e..0dd67b9f 100644
--- a/components/neterror/OWNERS
+++ b/components/neterror/OWNERS
@@ -1,4 +1,6 @@
 jar@chromium.org
 mmenke@chromium.org
 juliatuttle@chromium.org
-edwardjung@chromium.org
\ No newline at end of file
+edwardjung@chromium.org
+
+# COMPONENT: Internals>Network
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index aabaf2c..6b7fe2d 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -171,10 +171,6 @@
     sources -= [ "login_database_posix.cc" ]
   }
 
-  if (!is_ios) {
-    deps += [ "//components/safe_browsing/password_protection" ]
-  }
-
   # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
   configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
 }
diff --git a/components/password_manager/core/browser/DEPS b/components/password_manager/core/browser/DEPS
index 2b537df..fac484e01 100644
--- a/components/password_manager/core/browser/DEPS
+++ b/components/password_manager/core/browser/DEPS
@@ -2,7 +2,6 @@
   "+components/autofill/core/browser",
   "+components/keyed_service/core",
   "+components/pref_registry",
-  "+components/safe_browsing/password_protection",
   "+components/security_state",
   "+components/sync/base",
   "+components/sync/driver",
diff --git a/components/password_manager/core/browser/password_reuse_detection_manager.cc b/components/password_manager/core/browser/password_reuse_detection_manager.cc
index afd1a8e5..24a7c4f 100644
--- a/components/password_manager/core/browser/password_reuse_detection_manager.cc
+++ b/components/password_manager/core/browser/password_reuse_detection_manager.cc
@@ -9,9 +9,6 @@
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-#include "components/safe_browsing/password_protection/password_protection_service.h"
-#endif
 
 namespace password_manager {
 
@@ -27,13 +24,6 @@
 
 PasswordReuseDetectionManager::~PasswordReuseDetectionManager() {}
 
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-void PasswordReuseDetectionManager::SetPasswordProtectionService(
-    base::WeakPtr<safe_browsing::PasswordProtectionService> pp_service) {
-  password_protection_service_ = pp_service;
-}
-#endif
-
 void PasswordReuseDetectionManager::DidNavigateMainFrame(
     const GURL& main_frame_url) {
   main_frame_url_ = main_frame_url;
@@ -70,10 +60,6 @@
   metrics_util::LogPasswordReuse(
       password.size(), saved_passwords, number_matches,
       client_->GetPasswordManager()->IsPasswordFieldDetectedOnPage());
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-  if (password_protection_service_)
-    password_protection_service_->RecordPasswordReuse(main_frame_url_);
-#endif
 }
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_reuse_detection_manager.h b/components/password_manager/core/browser/password_reuse_detection_manager.h
index 52437fe..eaf9384 100644
--- a/components/password_manager/core/browser/password_reuse_detection_manager.h
+++ b/components/password_manager/core/browser/password_reuse_detection_manager.h
@@ -10,10 +10,6 @@
 #include "components/password_manager/core/browser/password_reuse_detector_consumer.h"
 #include "url/gurl.h"
 
-namespace safe_browsing {
-class PasswordProtectionService;
-}
-
 namespace password_manager {
 
 class PasswordManagerClient;
@@ -26,11 +22,6 @@
   explicit PasswordReuseDetectionManager(PasswordManagerClient* client);
   ~PasswordReuseDetectionManager() override;
 
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-  void SetPasswordProtectionService(
-      base::WeakPtr<safe_browsing::PasswordProtectionService> pp_service);
-#endif
-
   void DidNavigateMainFrame(const GURL& main_frame_url);
   void OnKeyPressed(const base::string16& text);
 
@@ -42,10 +33,6 @@
 
  private:
   PasswordManagerClient* client_;
-#if defined(SAFE_BROWSING_DB_LOCAL) || defined(SAFE_BROWSING_DB_REMOTE)
-  base::WeakPtr<safe_browsing::PasswordProtectionService>
-      password_protection_service_;
-#endif
   base::string16 input_characters_;
   GURL main_frame_url_;
 
diff --git a/components/safe_browsing/BUILD.gn b/components/safe_browsing/BUILD.gn
index ff80235e..ca81666 100644
--- a/components/safe_browsing/BUILD.gn
+++ b/components/safe_browsing/BUILD.gn
@@ -2,14 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//third_party/protobuf/proto_library.gni")
-
-proto_library("csd_proto") {
-  sources = [
-    "csd.proto",
-  ]
-}
-
 source_set("safe_browsing") {
   sources = [
     "base_blocking_page.cc",
diff --git a/components/safe_browsing/DEPS b/components/safe_browsing/DEPS
index 36b6e0f8..ac3d8bb7 100644
--- a/components/safe_browsing/DEPS
+++ b/components/safe_browsing/DEPS
@@ -11,5 +11,4 @@
   "+net/log",
   "+net/url_request",
   "+testing/gtest",
-  "+third_party/protobuf",
 ]
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 05443225..d1982fd4 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1165,14 +1165,14 @@
     "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_surface_impl.cc",
     "renderer_host/offscreen_canvas_surface_impl.h",
-    "renderer_host/offscreen_canvas_surface_manager.cc",
-    "renderer_host/offscreen_canvas_surface_manager.h",
     "renderer_host/overscroll_configuration.cc",
     "renderer_host/overscroll_controller.cc",
     "renderer_host/overscroll_controller.h",
@@ -1795,6 +1795,8 @@
       "android/tracing_controller_android.h",
       "android/web_contents_observer_proxy.cc",
       "android/web_contents_observer_proxy.h",
+      "frame_host/render_frame_host_android.cc",
+      "frame_host/render_frame_host_android.h",
       "media/capture/screen_capture_device_android.cc",
       "media/capture/screen_capture_device_android.h",
       "renderer_host/compositor_impl_android.cc",
diff --git a/content/browser/android/browser_jni_registrar.cc b/content/browser/android/browser_jni_registrar.cc
index 502643c..08bff79 100644
--- a/content/browser/android/browser_jni_registrar.cc
+++ b/content/browser/android/browser_jni_registrar.cc
@@ -24,6 +24,7 @@
 #include "content/browser/android/tracing_controller_android.h"
 #include "content/browser/android/web_contents_observer_proxy.h"
 #include "content/browser/frame_host/navigation_controller_android.h"
+#include "content/browser/frame_host/render_frame_host_android.h"
 #include "content/browser/media/session/audio_focus_delegate_android.h"
 #include "content/browser/media/session/media_session_android.h"
 #include "content/browser/memory/memory_monitor_android.h"
@@ -60,6 +61,7 @@
     {"NavigationControllerAndroid",
      content::NavigationControllerAndroid::Register},
     {"RegisterImeAdapter", content::RegisterImeAdapter},
+    {"RenderFrameHostAndroid", content::RenderFrameHostAndroid::Register},
     {"SpeechRecognizerImplAndroid",
      content::SpeechRecognizerImplAndroid::RegisterSpeechRecognizer},
     {"TracingControllerAndroid", content::RegisterTracingControllerAndroid},
diff --git a/content/browser/frame_host/render_frame_host_android.cc b/content/browser/frame_host/render_frame_host_android.cc
new file mode 100644
index 0000000..aa84f81
--- /dev/null
+++ b/content/browser/frame_host/render_frame_host_android.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/frame_host/render_frame_host_android.h"
+
+#include "base/android/jni_string.h"
+#include "base/logging.h"
+#include "content/browser/frame_host/render_frame_host_delegate.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "jni/RenderFrameHostImpl_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::JavaRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+
+// static
+bool RenderFrameHostAndroid::Register(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+RenderFrameHostAndroid::RenderFrameHostAndroid(
+    RenderFrameHostImpl* render_frame_host)
+    : render_frame_host_(render_frame_host) {}
+
+RenderFrameHostAndroid::~RenderFrameHostAndroid() {
+  ScopedJavaLocalRef<jobject> jobj = GetJavaObject();
+  if (!jobj.is_null()) {
+    Java_RenderFrameHostImpl_clearNativePtr(AttachCurrentThread(), jobj);
+    obj_.reset();
+  }
+}
+
+base::android::ScopedJavaLocalRef<jobject>
+RenderFrameHostAndroid::GetJavaObject() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  if (obj_.is_uninitialized()) {
+    ScopedJavaLocalRef<jobject> local_ref = Java_RenderFrameHostImpl_create(
+        env, reinterpret_cast<intptr_t>(this),
+        render_frame_host_->delegate()->GetJavaRenderFrameHostDelegate());
+    obj_ = JavaObjectWeakGlobalRef(env, local_ref);
+    return local_ref;
+  }
+  return obj_.get(env);
+}
+
+ScopedJavaLocalRef<jstring> RenderFrameHostAndroid::GetLastCommittedURL(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj) const {
+  return ConvertUTF8ToJavaString(
+      env, render_frame_host_->GetLastCommittedURL().spec());
+}
+
+}  // namespace content
diff --git a/content/browser/frame_host/render_frame_host_android.h b/content/browser/frame_host/render_frame_host_android.h
new file mode 100644
index 0000000..89377d3
--- /dev/null
+++ b/content/browser/frame_host/render_frame_host_android.h
@@ -0,0 +1,50 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_HOST_ANDROID_H_
+#define CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_HOST_ANDROID_H_
+
+#include <jni.h>
+
+#include <memory>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/supports_user_data.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class RenderFrameHostImpl;
+
+// Android wrapper around RenderFrameHost that provides safer passage from java
+// and back to native and provides java with a means of communicating with its
+// native counterpart.
+class RenderFrameHostAndroid : public base::SupportsUserData::Data {
+ public:
+  static bool Register(JNIEnv* env);
+
+  explicit RenderFrameHostAndroid(RenderFrameHostImpl* render_frame_host);
+  ~RenderFrameHostAndroid() override;
+
+  base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
+
+  // Methods called from Java
+  base::android::ScopedJavaLocalRef<jstring> GetLastCommittedURL(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>&) const;
+
+ private:
+  RenderFrameHostImpl* const render_frame_host_;
+  JavaObjectWeakGlobalRef obj_;
+
+  DISALLOW_COPY_AND_ASSIGN(RenderFrameHostAndroid);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_FRAME_HOST_RENDER_FRAME_HOST_ANDROID_H_
diff --git a/content/browser/frame_host/render_frame_host_delegate.cc b/content/browser/frame_host/render_frame_host_delegate.cc
index e260c8e..bab7c82d 100644
--- a/content/browser/frame_host/render_frame_host_delegate.cc
+++ b/content/browser/frame_host/render_frame_host_delegate.cc
@@ -101,4 +101,11 @@
   return false;
 }
 
+#if defined(OS_ANDROID)
+base::android::ScopedJavaLocalRef<jobject>
+RenderFrameHostDelegate::GetJavaRenderFrameHostDelegate() {
+  return nullptr;
+}
+#endif
+
 }  // namespace content
diff --git a/content/browser/frame_host/render_frame_host_delegate.h b/content/browser/frame_host/render_frame_host_delegate.h
index c863b00..148c5ed 100644
--- a/content/browser/frame_host/render_frame_host_delegate.h
+++ b/content/browser/frame_host/render_frame_host_delegate.h
@@ -26,6 +26,10 @@
 #include "ui/gfx/native_widget_types.h"
 #endif
 
+#if defined(OS_ANDROID)
+#include "base/android/scoped_java_ref.h"
+#endif
+
 class GURL;
 
 namespace IPC {
@@ -290,6 +294,11 @@
                                                  const url::Origin& origin,
                                                  const GURL& resource_url);
 
+#if defined(OS_ANDROID)
+  virtual base::android::ScopedJavaLocalRef<jobject>
+  GetJavaRenderFrameHostDelegate();
+#endif
+
  protected:
   virtual ~RenderFrameHostDelegate() {}
 };
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 64c719c..26fb404 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -112,8 +112,9 @@
 #include "url/gurl.h"
 
 #if defined(OS_ANDROID)
-#include "content/public/browser/android/java_interfaces.h"
+#include "content/browser/frame_host/render_frame_host_android.h"
 #include "content/browser/media/android/media_player_renderer.h"
+#include "content/public/browser/android/java_interfaces.h"
 #include "media/base/audio_renderer_sink.h"
 #include "media/base/video_renderer_sink.h"
 #include "media/mojo/services/mojo_renderer_service.h"  // nogncheck
@@ -135,6 +136,10 @@
 
 namespace {
 
+#if defined(OS_ANDROID)
+const void* const kRenderFrameHostAndroidKey = &kRenderFrameHostAndroidKey;
+#endif  // OS_ANDROID
+
 // The next value to use for the accessibility reset token.
 int g_next_accessibility_reset_token = 1;
 
@@ -3467,4 +3472,18 @@
       entry_id_for_data_nav, false);  // started_from_context_menu
 }
 
+#if defined(OS_ANDROID)
+base::android::ScopedJavaLocalRef<jobject>
+RenderFrameHostImpl::GetJavaRenderFrameHost() {
+  RenderFrameHostAndroid* render_frame_host_android =
+      static_cast<RenderFrameHostAndroid*>(
+          GetUserData(kRenderFrameHostAndroidKey));
+  if (!render_frame_host_android) {
+    render_frame_host_android = new RenderFrameHostAndroid(this);
+    SetUserData(kRenderFrameHostAndroidKey, render_frame_host_android);
+  }
+  return render_frame_host_android->GetJavaObject();
+}
+#endif
+
 }  // namespace content
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 74ee70b..0141d86e 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -20,6 +20,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string16.h"
+#include "base/supports_user_data.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "content/browser/accessibility/browser_accessibility_manager.h"
@@ -111,6 +112,7 @@
 
 class CONTENT_EXPORT RenderFrameHostImpl
     : public RenderFrameHost,
+      public base::SupportsUserData,
       NON_EXPORTED_BASE(public mojom::FrameHost),
       public BrowserAccessibilityDelegate,
       public SiteInstanceImpl::Observer,
@@ -615,6 +617,10 @@
     return has_focused_editable_element_;
   }
 
+#if defined(OS_ANDROID)
+  base::android::ScopedJavaLocalRef<jobject> GetJavaRenderFrameHost();
+#endif
+
  protected:
   friend class RenderFrameHostFactory;
 
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_manager.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
similarity index 67%
rename from content/browser/renderer_host/offscreen_canvas_surface_manager.cc
rename to content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
index 1eb9373f..ef4cbcd 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_manager.cc
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.cc
@@ -2,32 +2,36 @@
 // 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_manager.h"
+
 #include "base/lazy_instance.h"
 #include "cc/surfaces/surface_manager.h"
 #include "content/browser/compositor/surface_utils.h"
-#include "content/browser/renderer_host/offscreen_canvas_surface_manager.h"
 
 namespace content {
 
 namespace {
-base::LazyInstance<OffscreenCanvasSurfaceManager>::Leaky g_manager =
+base::LazyInstance<OffscreenCanvasCompositorFrameSinkManager>::Leaky g_manager =
     LAZY_INSTANCE_INITIALIZER;
 }
 
-OffscreenCanvasSurfaceManager::OffscreenCanvasSurfaceManager() {
+OffscreenCanvasCompositorFrameSinkManager::
+    OffscreenCanvasCompositorFrameSinkManager() {
   GetSurfaceManager()->AddObserver(this);
 }
 
-OffscreenCanvasSurfaceManager::~OffscreenCanvasSurfaceManager() {
+OffscreenCanvasCompositorFrameSinkManager::
+    ~OffscreenCanvasCompositorFrameSinkManager() {
   registered_surface_instances_.clear();
   GetSurfaceManager()->RemoveObserver(this);
 }
 
-OffscreenCanvasSurfaceManager* OffscreenCanvasSurfaceManager::GetInstance() {
+OffscreenCanvasCompositorFrameSinkManager*
+OffscreenCanvasCompositorFrameSinkManager::GetInstance() {
   return g_manager.Pointer();
 }
 
-void OffscreenCanvasSurfaceManager::RegisterFrameSinkToParent(
+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())
@@ -39,7 +43,7 @@
   }
 }
 
-void OffscreenCanvasSurfaceManager::UnregisterFrameSinkFromParent(
+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())
@@ -51,7 +55,7 @@
   }
 }
 
-void OffscreenCanvasSurfaceManager::OnSurfaceCreated(
+void OffscreenCanvasCompositorFrameSinkManager::OnSurfaceCreated(
     const cc::SurfaceInfo& surface_info) {
   auto surface_iter =
       registered_surface_instances_.find(surface_info.id().frame_sink_id());
@@ -61,21 +65,24 @@
   surfaceImpl->OnSurfaceCreated(surface_info);
 }
 
-void OffscreenCanvasSurfaceManager::RegisterOffscreenCanvasSurfaceInstance(
-    const cc::FrameSinkId& frame_sink_id,
-    OffscreenCanvasSurfaceImpl* surface_instance) {
+void OffscreenCanvasCompositorFrameSinkManager::
+    RegisterOffscreenCanvasSurfaceInstance(
+        const cc::FrameSinkId& frame_sink_id,
+        OffscreenCanvasSurfaceImpl* surface_instance) {
   DCHECK(surface_instance);
   DCHECK_EQ(registered_surface_instances_.count(frame_sink_id), 0u);
   registered_surface_instances_[frame_sink_id] = surface_instance;
 }
 
-void OffscreenCanvasSurfaceManager::UnregisterOffscreenCanvasSurfaceInstance(
-    const cc::FrameSinkId& frame_sink_id) {
+void OffscreenCanvasCompositorFrameSinkManager::
+    UnregisterOffscreenCanvasSurfaceInstance(
+        const cc::FrameSinkId& frame_sink_id) {
   DCHECK_EQ(registered_surface_instances_.count(frame_sink_id), 1u);
   registered_surface_instances_.erase(frame_sink_id);
 }
 
-OffscreenCanvasSurfaceImpl* OffscreenCanvasSurfaceManager::GetSurfaceInstance(
+OffscreenCanvasSurfaceImpl*
+OffscreenCanvasCompositorFrameSinkManager::GetSurfaceInstance(
     const cc::FrameSinkId& frame_sink_id) {
   auto search = registered_surface_instances_.find(frame_sink_id);
   if (search != registered_surface_instances_.end()) {
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_manager.h b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h
similarity index 71%
rename from content/browser/renderer_host/offscreen_canvas_surface_manager.h
rename to content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h
index 651dc8cb..7cd9ea8 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_manager.h
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.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 CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_MANAGER_H_
-#define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_MANAGER_H_
+#ifndef CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_MANAGER_H_
 
 #include "base/memory/weak_ptr.h"
 #include "cc/surfaces/surface_id.h"
@@ -12,13 +12,13 @@
 
 namespace content {
 
-class CONTENT_EXPORT OffscreenCanvasSurfaceManager
+class CONTENT_EXPORT OffscreenCanvasCompositorFrameSinkManager
     : public cc::SurfaceObserver {
  public:
-  OffscreenCanvasSurfaceManager();
-  virtual ~OffscreenCanvasSurfaceManager();
+  OffscreenCanvasCompositorFrameSinkManager();
+  virtual ~OffscreenCanvasCompositorFrameSinkManager();
 
-  static OffscreenCanvasSurfaceManager* GetInstance();
+  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
@@ -35,7 +35,7 @@
       const cc::FrameSinkId& frame_sink_id);
 
  private:
-  friend class OffscreenCanvasSurfaceManagerTest;
+  friend class OffscreenCanvasCompositorFrameSinkManagerTest;
 
   // cc::SurfaceObserver implementation.
   void OnSurfaceCreated(const cc::SurfaceInfo& surface_info) override;
@@ -47,9 +47,9 @@
                      OffscreenCanvasSurfaceImpl*,
                      cc::FrameSinkIdHash>
       registered_surface_instances_;
-  DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasSurfaceManager);
+  DISALLOW_COPY_AND_ASSIGN(OffscreenCanvasCompositorFrameSinkManager);
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_SURFACE_MANAGER_H_
+#endif  // CONTENT_BROWSER_RENDERER_HOST_OFFSCREEN_CANVAS_COMPOSITOR_FRAME_SINK_MANAGER_H_
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc
similarity index 77%
rename from content/browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc
rename to content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc
index 8df2c9a9..c0f1629 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc
+++ b/content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc
@@ -5,8 +5,8 @@
 #include "base/message_loop/message_loop.h"
 #include "cc/surfaces/local_surface_id_allocator.h"
 #include "content/browser/compositor/test/no_transport_image_transport_factory.h"
+#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
 #include "content/browser/renderer_host/offscreen_canvas_surface_impl.h"
-#include "content/browser/renderer_host/offscreen_canvas_surface_manager.h"
 #include "content/public/test/test_browser_thread.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -19,15 +19,15 @@
 
 namespace content {
 
-class OffscreenCanvasSurfaceManagerTest : public testing::Test {
+class OffscreenCanvasCompositorFrameSinkManagerTest : public testing::Test {
  public:
   int getNumSurfaceImplInstances() {
-    return OffscreenCanvasSurfaceManager::GetInstance()
+    return OffscreenCanvasCompositorFrameSinkManager::GetInstance()
         ->registered_surface_instances_.size();
   }
 
   void OnSurfaceCreated(const cc::SurfaceId& surface_id) {
-    OffscreenCanvasSurfaceManager::GetInstance()->OnSurfaceCreated(
+    OffscreenCanvasCompositorFrameSinkManager::GetInstance()->OnSurfaceCreated(
         cc::SurfaceInfo(surface_id, 1.0f, gfx::Size(10, 10)));
   }
 
@@ -40,7 +40,7 @@
   base::MessageLoopForUI message_loop_;
 };
 
-void OffscreenCanvasSurfaceManagerTest::SetUp() {
+void OffscreenCanvasCompositorFrameSinkManagerTest::SetUp() {
 #if !defined(OS_ANDROID)
   ImageTransportFactory::InitializeForUnitTests(
       std::unique_ptr<ImageTransportFactory>(
@@ -49,7 +49,7 @@
   ui_thread_.reset(new TestBrowserThread(BrowserThread::UI, &message_loop_));
 }
 
-void OffscreenCanvasSurfaceManagerTest::TearDown() {
+void OffscreenCanvasCompositorFrameSinkManagerTest::TearDown() {
 #if !defined(OS_ANDROID)
   ImageTransportFactory::Terminate();
 #endif
@@ -57,7 +57,7 @@
 
 // This test mimics the workflow of OffscreenCanvas.commit() on renderer
 // process.
-TEST_F(OffscreenCanvasSurfaceManagerTest,
+TEST_F(OffscreenCanvasCompositorFrameSinkManagerTest,
        SingleHTMLCanvasElementTransferToOffscreen) {
   cc::mojom::DisplayCompositorClientPtr client;
   cc::FrameSinkId frame_sink_id(3, 3);
@@ -69,8 +69,8 @@
       cc::FrameSinkId(), frame_sink_id, std::move(client)));
   EXPECT_EQ(1, this->getNumSurfaceImplInstances());
   EXPECT_EQ(surface_impl.get(),
-            OffscreenCanvasSurfaceManager::GetInstance()->GetSurfaceInstance(
-                frame_sink_id));
+            OffscreenCanvasCompositorFrameSinkManager::GetInstance()
+                ->GetSurfaceInstance(frame_sink_id));
 
   this->OnSurfaceCreated(
       cc::SurfaceId(frame_sink_id, current_local_surface_id));
@@ -80,7 +80,7 @@
   EXPECT_EQ(0, this->getNumSurfaceImplInstances());
 }
 
-TEST_F(OffscreenCanvasSurfaceManagerTest,
+TEST_F(OffscreenCanvasCompositorFrameSinkManagerTest,
        MultiHTMLCanvasElementTransferToOffscreen) {
   cc::mojom::DisplayCompositorClientPtr client_a;
   cc::FrameSinkId dummy_parent_frame_sink_id(0, 0);
@@ -96,11 +96,11 @@
 
   EXPECT_EQ(2, this->getNumSurfaceImplInstances());
   EXPECT_EQ(surface_impl_a.get(),
-            OffscreenCanvasSurfaceManager::GetInstance()->GetSurfaceInstance(
-                frame_sink_id_a));
+            OffscreenCanvasCompositorFrameSinkManager::GetInstance()
+                ->GetSurfaceInstance(frame_sink_id_a));
   EXPECT_EQ(surface_impl_b.get(),
-            OffscreenCanvasSurfaceManager::GetInstance()->GetSurfaceInstance(
-                frame_sink_id_b));
+            OffscreenCanvasCompositorFrameSinkManager::GetInstance()
+                ->GetSurfaceInstance(frame_sink_id_b));
 
   surface_impl_a = nullptr;
   EXPECT_EQ(1, this->getNumSurfaceImplInstances());
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
index 4bc06e0e..adef5ba 100644
--- 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
@@ -7,7 +7,7 @@
 #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_surface_manager.h"
+#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 
 namespace content {
@@ -31,8 +31,8 @@
       base::MakeUnique<OffscreenCanvasCompositorFrameSink>(
           this, frame_sink_id, std::move(request), std::move(client));
 
-  OffscreenCanvasSurfaceManager::GetInstance()->RegisterFrameSinkToParent(
-      frame_sink_id);
+  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
+      ->RegisterFrameSinkToParent(frame_sink_id);
 }
 
 cc::SurfaceManager*
@@ -50,8 +50,8 @@
 
 void OffscreenCanvasCompositorFrameSinkProviderImpl::
     OnCompositorFrameSinkClientDestroyed(const cc::FrameSinkId& frame_sink_id) {
-  OffscreenCanvasSurfaceManager::GetInstance()->UnregisterFrameSinkFromParent(
-      frame_sink_id);
+  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
+      ->UnregisterFrameSinkFromParent(frame_sink_id);
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
index 16f64aa..3795b60 100644
--- a/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
+++ b/content/browser/renderer_host/offscreen_canvas_surface_impl.cc
@@ -11,7 +11,7 @@
 #include "cc/surfaces/surface.h"
 #include "cc/surfaces/surface_manager.h"
 #include "content/browser/compositor/surface_utils.h"
-#include "content/browser/renderer_host/offscreen_canvas_surface_manager.h"
+#include "content/browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager.h"
 #include "content/public/browser/browser_thread.h"
 
 namespace content {
@@ -23,13 +23,13 @@
     : client_(std::move(client)),
       frame_sink_id_(frame_sink_id),
       parent_frame_sink_id_(parent_frame_sink_id) {
-  OffscreenCanvasSurfaceManager::GetInstance()
+  OffscreenCanvasCompositorFrameSinkManager::GetInstance()
       ->RegisterOffscreenCanvasSurfaceInstance(frame_sink_id_, this);
 }
 
 OffscreenCanvasSurfaceImpl::~OffscreenCanvasSurfaceImpl() {
   if (frame_sink_id_.is_valid()) {
-    OffscreenCanvasSurfaceManager::GetInstance()
+    OffscreenCanvasCompositorFrameSinkManager::GetInstance()
         ->UnregisterOffscreenCanvasSurfaceInstance(frame_sink_id_);
   }
 }
diff --git a/content/browser/web_contents/web_contents_android.cc b/content/browser/web_contents/web_contents_android.cc
index 03157ecf..20033db 100644
--- a/content/browser/web_contents/web_contents_android.cc
+++ b/content/browser/web_contents/web_contents_android.cc
@@ -294,6 +294,12 @@
   return window_android->GetJavaObject();
 }
 
+ScopedJavaLocalRef<jobject> WebContentsAndroid::GetMainFrame(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj) const {
+  return web_contents_->GetMainFrame()->GetJavaRenderFrameHost();
+}
+
 ScopedJavaLocalRef<jstring> WebContentsAndroid::GetTitle(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) const {
diff --git a/content/browser/web_contents/web_contents_android.h b/content/browser/web_contents/web_contents_android.h
index 8915045..3eb20b85 100644
--- a/content/browser/web_contents/web_contents_android.h
+++ b/content/browser/web_contents/web_contents_android.h
@@ -42,6 +42,9 @@
   base::android::ScopedJavaLocalRef<jobject> GetTopLevelNativeWindow(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
+  base::android::ScopedJavaLocalRef<jobject> GetMainFrame(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj) const;
   base::android::ScopedJavaLocalRef<jstring> GetTitle(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj) const;
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 237b342..569e113 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3586,6 +3586,13 @@
       web_contents, allowed_per_prefs, origin, resource_url);
 }
 
+#if defined(OS_ANDROID)
+base::android::ScopedJavaLocalRef<jobject>
+WebContentsImpl::GetJavaRenderFrameHostDelegate() {
+  return GetJavaWebContents();
+}
+#endif
+
 void WebContentsImpl::OnDidDisplayContentWithCertificateErrors(
     RenderFrameHostImpl* source,
     const GURL& url) {
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 29cf8be..d8c4d1f 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -516,6 +516,10 @@
                                          bool allowed_per_prefs,
                                          const url::Origin& origin,
                                          const GURL& resource_url) override;
+#if defined(OS_ANDROID)
+  base::android::ScopedJavaLocalRef<jobject> GetJavaRenderFrameHostDelegate()
+      override;
+#endif
 
   // RenderViewHostDelegate ----------------------------------------------------
   RenderViewHostDelegateView* GetDelegateView() override;
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index b510d54..c37f2dc 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -168,6 +168,8 @@
     "java/src/org/chromium/content/browser/crypto/ByteArrayGenerator.java",
     "java/src/org/chromium/content/browser/crypto/CipherFactory.java",
     "java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java",
+    "java/src/org/chromium/content/browser/framehost/RenderFrameHostDelegate.java",
+    "java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java",
     "java/src/org/chromium/content/browser/input/AnimationIntervalProvider.java",
     "java/src/org/chromium/content/browser/input/ChromiumBaseInputConnection.java",
     "java/src/org/chromium/content/browser/input/CursorAnchorInfoController.java",
@@ -228,6 +230,7 @@
     "java/src/org/chromium/content_public/browser/NavigationController.java",
     "java/src/org/chromium/content_public/browser/NavigationEntry.java",
     "java/src/org/chromium/content_public/browser/NavigationHistory.java",
+    "java/src/org/chromium/content_public/browser/RenderFrameHost.java",
     "java/src/org/chromium/content_public/browser/SmartClipCallback.java",
     "java/src/org/chromium/content_public/browser/WebContents.java",
     "java/src/org/chromium/content_public/browser/WebContentsObserver.java",
@@ -338,6 +341,7 @@
     "java/src/org/chromium/content/browser/TracingControllerAndroid.java",
     "java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java",
     "java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java",
+    "java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java",
     "java/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java",
     "java/src/org/chromium/content/browser/input/HandleViewResources.java",
     "java/src/org/chromium/content/browser/input/ImeAdapter.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostDelegate.java b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostDelegate.java
new file mode 100644
index 0000000..215527e
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostDelegate.java
@@ -0,0 +1,11 @@
+// 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.content.browser.framehost;
+
+/**
+ * The RenderFrameHost Java wrapper to allow communicating with the native RenderFrameHost object.
+ *
+ */
+public interface RenderFrameHostDelegate {}
diff --git a/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java
new file mode 100644
index 0000000..90b9740
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java
@@ -0,0 +1,49 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser.framehost;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.content_public.browser.RenderFrameHost;
+
+/**
+ * The RenderFrameHostImpl Java wrapper to allow communicating with the native RenderFrameHost
+ * object.
+ */
+@JNINamespace("content")
+public class RenderFrameHostImpl implements RenderFrameHost {
+    private long mNativeRenderFrameHostAndroid;
+    // mDelegate can be null.
+    final RenderFrameHostDelegate mDelegate;
+
+    private RenderFrameHostImpl(
+            long nativeRenderFrameHostAndroid, RenderFrameHostDelegate delegate) {
+        mNativeRenderFrameHostAndroid = nativeRenderFrameHostAndroid;
+        mDelegate = delegate;
+    }
+
+    @CalledByNative
+    private static RenderFrameHostImpl create(
+            long nativeRenderFrameHostAndroid, RenderFrameHostDelegate delegate) {
+        return new RenderFrameHostImpl(nativeRenderFrameHostAndroid, delegate);
+    }
+
+    @CalledByNative
+    private void clearNativePtr() {
+        mNativeRenderFrameHostAndroid = 0;
+    }
+
+    public RenderFrameHostDelegate getRenderFrameHostDelegate() {
+        return mDelegate;
+    }
+
+    @Override
+    public String getLastCommittedURL() {
+        if (mNativeRenderFrameHostAndroid == 0) return null;
+        return nativeGetLastCommittedURL(mNativeRenderFrameHostAndroid);
+    }
+
+    private native String nativeGetLastCommittedURL(long nativeRenderFrameHostAndroid);
+}
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
index b8737cf..1058494a 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
@@ -20,6 +20,8 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.content.browser.AppWebMessagePort;
 import org.chromium.content.browser.MediaSessionImpl;
+import org.chromium.content.browser.framehost.RenderFrameHostDelegate;
+import org.chromium.content.browser.framehost.RenderFrameHostImpl;
 import org.chromium.content_public.browser.AccessibilitySnapshotCallback;
 import org.chromium.content_public.browser.AccessibilitySnapshotNode;
 import org.chromium.content_public.browser.ContentBitmapCallback;
@@ -27,6 +29,7 @@
 import org.chromium.content_public.browser.JavaScriptCallback;
 import org.chromium.content_public.browser.MessagePort;
 import org.chromium.content_public.browser.NavigationController;
+import org.chromium.content_public.browser.RenderFrameHost;
 import org.chromium.content_public.browser.SmartClipCallback;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
@@ -43,9 +46,9 @@
  * object.
  */
 @JNINamespace("content")
-//TODO(tedchoc): Remove the package restriction once this class moves to a non-public content
+// TODO(tedchoc): Remove the package restriction once this class moves to a non-public content
 //               package whose visibility will be enforced via DEPS.
-/* package */ class WebContentsImpl implements WebContents {
+/* package */ class WebContentsImpl implements WebContents, RenderFrameHostDelegate {
     private static final String PARCEL_VERSION_KEY = "version";
     private static final String PARCEL_WEBCONTENTS_KEY = "webcontents";
     private static final String PARCEL_PROCESS_GUARD_KEY = "processguard";
@@ -92,8 +95,17 @@
                 }
             };
 
+    public static WebContents fromRenderFrameHost(RenderFrameHost rfh) {
+        RenderFrameHostDelegate delegate = ((RenderFrameHostImpl) rfh).getRenderFrameHostDelegate();
+        if (delegate == null || !(delegate instanceof WebContents)) {
+            return null;
+        }
+        return (WebContents) delegate;
+    }
+
     private long mNativeWebContentsAndroid;
     private NavigationController mNavigationController;
+    private RenderFrameHost mMainFrame;
 
     // Lazily created proxy observer for handling all Java-based WebContentsObservers.
     private WebContentsObserverProxy mObserverProxy;
@@ -172,6 +184,14 @@
     }
 
     @Override
+    public RenderFrameHost getMainFrame() {
+        if (mMainFrame == null) {
+            mMainFrame = nativeGetMainFrame(mNativeWebContentsAndroid);
+        }
+        return mMainFrame;
+    }
+
+    @Override
     public String getTitle() {
         return nativeGetTitle(mNativeWebContentsAndroid);
     }
@@ -562,6 +582,7 @@
     private static native WebContents nativeFromNativePtr(long webContentsAndroidPtr);
 
     private native WindowAndroid nativeGetTopLevelNativeWindow(long nativeWebContentsAndroid);
+    private native RenderFrameHost nativeGetMainFrame(long nativeWebContentsAndroid);
     private native String nativeGetTitle(long nativeWebContentsAndroid);
     private native String nativeGetVisibleURL(long nativeWebContentsAndroid);
     private native boolean nativeIsLoading(long nativeWebContentsAndroid);
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/RenderFrameHost.java b/content/public/android/java/src/org/chromium/content_public/browser/RenderFrameHost.java
new file mode 100644
index 0000000..5748da2
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content_public/browser/RenderFrameHost.java
@@ -0,0 +1,18 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content_public.browser;
+
+/**
+ * The RenderFrameHost Java wrapper to allow communicating with the native RenderFrameHost object.
+ *
+ */
+public interface RenderFrameHost {
+    /**
+     * Get the last committed URL of the frame.
+     *
+     * @return The last committed URL of the frame.
+     */
+    String getLastCommittedURL();
+}
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java
index ba25aaa..3bee2658 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java
@@ -57,6 +57,11 @@
     NavigationController getNavigationController();
 
     /**
+     * @return  The main frame associated with this WebContents.
+     */
+    RenderFrameHost getMainFrame();
+
+    /**
      * @return The title for the current visible page.
      */
     String getTitle();
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java
index fa52a8a7..110a099c 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java
@@ -11,6 +11,7 @@
 import android.support.test.filters.SmallTest;
 
 import org.chromium.base.ThreadUtils;
+import org.chromium.content_public.browser.RenderFrameHost;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_shell.Shell;
 import org.chromium.content_shell_apk.ContentShellActivity;
@@ -310,6 +311,35 @@
         }
     }
 
+    /**
+     * Check that the main frame associated with the WebContents is not null
+     * and corresponds with the test URL.
+     *
+     * @throws InterruptedException
+     */
+    @SmallTest
+    public void testWebContentsMainFrame() throws InterruptedException {
+        final ContentShellActivity activity = launchContentShellWithUrl(TEST_URL_1);
+        waitForActiveShellToBeDoneLoading();
+        final WebContents webContents = activity.getActiveWebContents();
+
+        ThreadUtils.postOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                RenderFrameHost frameHost = webContents.getMainFrame();
+
+                assertNotNull(frameHost);
+
+                assertEquals("RenderFrameHost has incorrect last committed URL", "about:blank",
+                        frameHost.getLastCommittedURL());
+
+                WebContents associatedWebContents = WebContentsImpl.fromRenderFrameHost(frameHost);
+                assertEquals("RenderFrameHost associated with different WebContents", webContents,
+                        associatedWebContents);
+            }
+        });
+    }
+
     private boolean isWebContentsDestroyed(final WebContents webContents) {
         return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
             @Override
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 5de4ffb9d..c1489049 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1239,7 +1239,7 @@
     "../browser/renderer_host/media/video_capture_controller_unittest.cc",
     "../browser/renderer_host/media/video_capture_manager_unittest.cc",
     "../browser/renderer_host/media/video_capture_unittest.cc",
-    "../browser/renderer_host/offscreen_canvas_surface_manager_unittest.cc",
+    "../browser/renderer_host/offscreen_canvas_compositor_frame_sink_manager_unittest.cc",
     "../browser/renderer_host/render_process_host_unittest.cc",
     "../browser/renderer_host/render_view_host_unittest.cc",
     "../browser/renderer_host/render_widget_host_unittest.cc",
diff --git a/content/test/gpu/page_sets/data/maps_004_expectations.json b/content/test/gpu/page_sets/data/maps_004_expectations.json
index 735c81d..d656fc6 100644
--- a/content/test/gpu/page_sets/data/maps_004_expectations.json
+++ b/content/test/gpu/page_sets/data/maps_004_expectations.json
@@ -23,7 +23,8 @@
         "scale_factor": 1.566
       },
       {
-        "device_type": "NVIDIA Shield",
+        "comment": "NVIDIA Shield",
+        "device_type": "sb_na_wf",
         "scale_factor": 1.226
       }
     ]
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
index f03e811..49af8d2 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
@@ -202,7 +202,6 @@
   if (status == 0  // android.bluetooth.BluetoothGatt.GATT_SUCCESS
       && !read_callback.is_null()) {
     base::android::JavaByteArrayToByteVector(env, value, &value_);
-    adapter_->NotifyGattCharacteristicValueChanged(this, value_);
     read_callback.Run(value_);
   } else if (!read_error_callback.is_null()) {
     read_error_callback.Run(
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
index b0f8d69..4b628c6 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
@@ -74,8 +74,8 @@
   // Called by the BluetoothRemoteGattServiceMac instance when the
   // characteristics value has been read.
   void DidUpdateValue(NSError* error);
-  // Updates value_ and notifies the adapter of the new value.
-  void UpdateValueAndNotify();
+  // Updates value_.
+  void UpdateValue();
   // Called by the BluetoothRemoteGattServiceMac instance when the
   // characteristics value has been written.
   void DidWriteValue(NSError* error);
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
index ae721181..2e2da4e6 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
@@ -257,10 +257,12 @@
       callbacks.second.Run(error_code);
       return;
     }
-    UpdateValueAndNotify();
+    UpdateValue();
     callbacks.first.Run(value_);
   } else if (IsNotifying()) {
-    UpdateValueAndNotify();
+    UpdateValue();
+    gatt_service_->GetMacAdapter()->NotifyGattCharacteristicValueChanged(
+        this, value_);
   } else {
     // In case of buggy device, nothing should be done if receiving extra
     // read confirmation.
@@ -269,12 +271,10 @@
   }
 }
 
-void BluetoothRemoteGattCharacteristicMac::UpdateValueAndNotify() {
+void BluetoothRemoteGattCharacteristicMac::UpdateValue() {
   NSData* nsdata_value = cb_characteristic_.get().value;
   const uint8_t* buffer = static_cast<const uint8_t*>(nsdata_value.bytes);
   value_.assign(buffer, buffer + nsdata_value.length);
-  gatt_service_->GetMacAdapter()->NotifyGattCharacteristicValueChanged(this,
-                                                                       value_);
 }
 
 void BluetoothRemoteGattCharacteristicMac::DidWriteValue(NSError* error) {
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
index e21e347f..0e779e7 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
@@ -526,14 +526,14 @@
     BluetoothRemoteGattCharacteristic::ValueCallback callback,
     const TestBluetoothAdapterObserver& callback_observer,
     const std::vector<uint8_t>& value) {
-  EXPECT_EQ(1, callback_observer.gatt_characteristic_value_changed_count());
+  EXPECT_EQ(0, callback_observer.gatt_characteristic_value_changed_count());
   callback.Run(value);
 }
 
-// Tests that ReadRemoteCharacteristic results in a
+// Tests that ReadRemoteCharacteristic doesn't result in a
 // GattCharacteristicValueChanged call.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       ReadRemoteCharacteristic_GattCharacteristicValueChanged) {
+       ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -552,12 +552,17 @@
   SimulateGattCharacteristicRead(characteristic1_, test_vector);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_EQ(1, observer.gatt_characteristic_value_changed_count());
-  EXPECT_EQ(characteristic1_->GetIdentifier(),
-            observer.last_gatt_characteristic_id());
-  EXPECT_EQ(characteristic1_->GetUUID(),
-            observer.last_gatt_characteristic_uuid());
-  EXPECT_EQ(test_vector, observer.last_changed_characteristic_value());
+  EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
+// TODO(https://crbug.com/699694): Remove this #if once the bug on Windows is
+// fixed.
+#if defined(OS_WIN)
+  EXPECT_FALSE(observer.last_gatt_characteristic_id().empty());
+  EXPECT_TRUE(observer.last_gatt_characteristic_uuid().IsValid());
+#else
+  EXPECT_TRUE(observer.last_gatt_characteristic_id().empty());
+  EXPECT_FALSE(observer.last_gatt_characteristic_uuid().IsValid());
+#endif  // defined(OS_WIN)
+  EXPECT_TRUE(observer.last_changed_characteristic_value().empty());
 }
 #endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc
index 3abe3bd..633be1c 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc
@@ -397,9 +397,6 @@
     for (ULONG i = 0; i < value->DataSize; i++)
       characteristic_value_.push_back(value->Data[i]);
 
-    parent_service_->GetWinAdapter()->NotifyGattCharacteristicValueChanged(
-        this, characteristic_value_);
-
     callbacks.first.Run(characteristic_value_);
   }
   characteristic_value_read_or_write_in_progress_ = false;
diff --git a/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc b/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc
index d8538b6e..30eed1b 100644
--- a/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc
+++ b/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc
@@ -1101,8 +1101,7 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, last_service_error_);
   EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
 
-  // Issue a read request. A successful read results in a
-  // CharacteristicValueChanged notification.
+  // Issue a read request.
   characteristic = service->GetCharacteristic(
       fake_bluetooth_gatt_characteristic_client_->GetBodySensorLocationPath()
           .value());
@@ -1119,7 +1118,7 @@
                  base::Unretained(this)));
   EXPECT_EQ(2, success_callback_count_);
   EXPECT_EQ(4, error_callback_count_);
-  EXPECT_EQ(1, observer.gatt_characteristic_value_changed_count());
+  EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
   EXPECT_TRUE(ValuesEqual(characteristic->GetValue(), last_read_value_));
 
   // Test long-running actions.
@@ -1143,7 +1142,7 @@
   // tne next one.
   EXPECT_EQ(2, success_callback_count_);
   EXPECT_EQ(4, error_callback_count_);
-  EXPECT_EQ(1, observer.gatt_characteristic_value_changed_count());
+  EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
 
   // Next read should error because IN_PROGRESS
   characteristic->ReadRemoteCharacteristic(
@@ -1157,7 +1156,7 @@
 
   // But previous call finished.
   EXPECT_EQ(3, success_callback_count_);
-  EXPECT_EQ(2, observer.gatt_characteristic_value_changed_count());
+  EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
   EXPECT_TRUE(ValuesEqual(characteristic->GetValue(), last_read_value_));
   fake_bluetooth_gatt_characteristic_client_->SetExtraProcessing(0);
 
@@ -1172,7 +1171,7 @@
   EXPECT_EQ(6, error_callback_count_);
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_AUTHORIZED,
             last_service_error_);
-  EXPECT_EQ(2, observer.gatt_characteristic_value_changed_count());
+  EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
   fake_bluetooth_gatt_characteristic_client_->SetAuthorized(true);
 
   // Test unauthenticated / needs login.
@@ -1186,7 +1185,7 @@
   EXPECT_EQ(7, error_callback_count_);
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_PAIRED,
             last_service_error_);
-  EXPECT_EQ(2, observer.gatt_characteristic_value_changed_count());
+  EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
   fake_bluetooth_gatt_characteristic_client_->SetAuthenticated(true);
 }
 
diff --git a/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc b/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc
index ce386da..f9a2e23 100644
--- a/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc
@@ -45,6 +45,7 @@
     : BluetoothGattCharacteristicBlueZ(object_path),
       has_notify_session_(false),
       service_(service),
+      num_of_characteristic_value_read_in_progress_(0),
       weak_ptr_factory_(this) {
   VLOG(1) << "Creating remote GATT characteristic with identifier: "
           << GetIdentifier() << ", UUID: " << GetUUID().canonical_value();
@@ -184,11 +185,15 @@
           << GetIdentifier() << ", UUID: " << GetUUID().canonical_value()
           << ".";
 
+  DCHECK_GE(num_of_characteristic_value_read_in_progress_, 0);
+  ++num_of_characteristic_value_read_in_progress_;
+
   bluez::BluezDBusManager::Get()
       ->GetBluetoothGattCharacteristicClient()
-      ->ReadValue(object_path(), callback,
-                  base::Bind(&BluetoothRemoteGattCharacteristicBlueZ::OnError,
-                             weak_ptr_factory_.GetWeakPtr(), error_callback));
+      ->ReadValue(
+          object_path(), callback,
+          base::Bind(&BluetoothRemoteGattCharacteristicBlueZ::OnReadError,
+                     weak_ptr_factory_.GetWeakPtr(), error_callback));
 }
 
 void BluetoothRemoteGattCharacteristicBlueZ::WriteRemoteCharacteristic(
@@ -201,9 +206,10 @@
 
   bluez::BluezDBusManager::Get()
       ->GetBluetoothGattCharacteristicClient()
-      ->WriteValue(object_path(), value, callback,
-                   base::Bind(&BluetoothRemoteGattCharacteristicBlueZ::OnError,
-                              weak_ptr_factory_.GetWeakPtr(), error_callback));
+      ->WriteValue(
+          object_path(), value, callback,
+          base::Bind(&BluetoothRemoteGattCharacteristicBlueZ::OnWriteError,
+                     weak_ptr_factory_.GetWeakPtr(), error_callback));
 }
 
 void BluetoothRemoteGattCharacteristicBlueZ::SubscribeToNotifications(
@@ -353,7 +359,19 @@
   OnStopNotifySuccess(callback);
 }
 
-void BluetoothRemoteGattCharacteristicBlueZ::OnError(
+void BluetoothRemoteGattCharacteristicBlueZ::OnReadError(
+    const ErrorCallback& error_callback,
+    const std::string& error_name,
+    const std::string& error_message) {
+  VLOG(1) << "Operation failed: " << error_name
+          << ", message: " << error_message;
+  --num_of_characteristic_value_read_in_progress_;
+  DCHECK_GE(num_of_characteristic_value_read_in_progress_, 0);
+  error_callback.Run(
+      BluetoothGattServiceBlueZ::DBusErrorToServiceError(error_name));
+}
+
+void BluetoothRemoteGattCharacteristicBlueZ::OnWriteError(
     const ErrorCallback& error_callback,
     const std::string& error_name,
     const std::string& error_message) {
diff --git a/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h b/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h
index 253c00ef..01b61f2 100644
--- a/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h
+++ b/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h
@@ -107,11 +107,17 @@
                          const std::string& error_name,
                          const std::string& error_message);
 
-  // Called by dbus:: on unsuccessful completion of a request to read or write
+  // Called by dbus:: on unsuccessful completion of a request to read
   // the characteristic value.
-  void OnError(const ErrorCallback& error_callback,
-               const std::string& error_name,
-               const std::string& error_message);
+  void OnReadError(const ErrorCallback& error_callback,
+                   const std::string& error_name,
+                   const std::string& error_message);
+
+  // Called by dbus:: on unsuccessful completion of a request to write
+  // the characteristic value.
+  void OnWriteError(const ErrorCallback& error_callback,
+                    const std::string& error_name,
+                    const std::string& error_message);
 
   // True, if there exists a Bluez notify session.
   bool has_notify_session_;
@@ -130,6 +136,9 @@
   // The GATT service this GATT characteristic belongs to.
   BluetoothRemoteGattServiceBlueZ* service_;
 
+  // Number of gatt read requests in progress.
+  int num_of_characteristic_value_read_in_progress_;
+
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
   base::WeakPtrFactory<BluetoothRemoteGattCharacteristicBlueZ>
diff --git a/device/bluetooth/bluez/bluetooth_remote_gatt_service_bluez.cc b/device/bluetooth/bluez/bluetooth_remote_gatt_service_bluez.cc
index 50786999..a11fcd87 100644
--- a/device/bluetooth/bluez/bluetooth_remote_gatt_service_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_remote_gatt_service_bluez.cc
@@ -243,9 +243,15 @@
 
   if (property_name == properties->flags.name())
     NotifyServiceChanged();
-  else if (property_name == properties->value.name())
-    GetAdapter()->NotifyGattCharacteristicValueChanged(
-        iter->second, properties->value.value());
+  else if (property_name == properties->value.name()) {
+    DCHECK_GE(iter->second->num_of_characteristic_value_read_in_progress_, 0);
+    if (iter->second->num_of_characteristic_value_read_in_progress_ > 0) {
+      --iter->second->num_of_characteristic_value_read_in_progress_;
+    } else {
+      GetAdapter()->NotifyGattCharacteristicValueChanged(
+          iter->second, properties->value.value());
+    }
+  }
 }
 
 }  // namespace bluez
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index c9b46c9..622258f0 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -60,7 +60,6 @@
 
       deps += [
         "//device/gamepad",
-        "//gpu/ipc/common:interfaces",
         "//third_party/WebKit/public:blink_headers",
       ]
       ldflags = [ "-landroid" ]
@@ -100,7 +99,6 @@
   ]
 
   public_deps = [
-    "//gpu/ipc/common:interfaces",
     "//mojo/common:common_custom_types",
   ]
 
diff --git a/device/vr/DEPS b/device/vr/DEPS
index c0b9fe7..d8c8cee 100644
--- a/device/vr/DEPS
+++ b/device/vr/DEPS
@@ -1,5 +1,4 @@
 include_rules = [
-  "+gpu/command_buffer/common/mailbox_holder.h",
   "+jni",
   "+third_party/WebKit/public/platform/WebGamepads.h",
   "+third_party/WebKit/public/platform/modules/vr/vr_service.mojom.h",
diff --git a/device/vr/android/gvr/gvr_delegate.h b/device/vr/android/gvr/gvr_delegate.h
index 8684f59c..ed31de33 100644
--- a/device/vr/android/gvr/gvr_delegate.h
+++ b/device/vr/android/gvr/gvr_delegate.h
@@ -17,12 +17,10 @@
 class DEVICE_VR_EXPORT GvrDelegate {
  public:
   virtual void SetWebVRSecureOrigin(bool secure_origin) = 0;
-  virtual void SubmitWebVRFrame(int16_t frame_index,
-                                const gpu::MailboxHolder& mailbox) = 0;
+  virtual void SubmitWebVRFrame() = 0;
   virtual void UpdateWebVRTextureBounds(int16_t frame_index,
                                         const gvr::Rectf& left_bounds,
-                                        const gvr::Rectf& right_bounds,
-                                        const gvr::Sizei& source_size) = 0;
+                                        const gvr::Rectf& right_bounds) = 0;
   virtual void OnVRVsyncProviderRequest(
       mojom::VRVSyncProviderRequest request) = 0;
   virtual void UpdateVSyncInterval(int64_t timebase_nanos,
@@ -46,7 +44,6 @@
   virtual void SetDeviceProvider(GvrDeviceProvider* device_provider) = 0;
   virtual void ClearDeviceProvider() = 0;
   virtual void RequestWebVRPresent(
-      mojom::VRSubmitFrameClientPtr submit_client,
       const base::Callback<void(bool)>& callback) = 0;
   virtual void ExitWebVRPresent() = 0;
   virtual GvrDelegate* GetDelegate() = 0;
diff --git a/device/vr/android/gvr/gvr_device.cc b/device/vr/android/gvr/gvr_device.cc
index c0b8e5a..f0a86e2 100644
--- a/device/vr/android/gvr/gvr_device.cc
+++ b/device/vr/android/gvr/gvr_device.cc
@@ -40,9 +40,8 @@
     delegate->ResetPose();
 }
 
-void GvrDevice::RequestPresent(mojom::VRSubmitFrameClientPtr submit_client,
-                               const base::Callback<void(bool)>& callback) {
-  gvr_provider_->RequestPresent(std::move(submit_client), callback);
+void GvrDevice::RequestPresent(const base::Callback<void(bool)>& callback) {
+  gvr_provider_->RequestPresent(callback);
 }
 
 void GvrDevice::SetSecureOrigin(bool secure_origin) {
@@ -57,19 +56,15 @@
   OnExitPresent();
 }
 
-void GvrDevice::SubmitFrame(int16_t frame_index,
-                            const gpu::MailboxHolder& mailbox) {
+void GvrDevice::SubmitFrame(mojom::VRPosePtr pose) {
   GvrDelegate* delegate = GetGvrDelegate();
-  if (delegate) {
-    delegate->SubmitWebVRFrame(frame_index, mailbox);
-  }
+  if (delegate)
+    delegate->SubmitWebVRFrame();
 }
 
 void GvrDevice::UpdateLayerBounds(int16_t frame_index,
                                   mojom::VRLayerBoundsPtr left_bounds,
-                                  mojom::VRLayerBoundsPtr right_bounds,
-                                  int16_t source_width,
-                                  int16_t source_height) {
+                                  mojom::VRLayerBoundsPtr right_bounds) {
   GvrDelegate* delegate = GetGvrDelegate();
   if (!delegate)
     return;
@@ -86,9 +81,8 @@
   right_gvr_bounds.right = right_bounds->left + right_bounds->width;
   right_gvr_bounds.bottom = 1.0f - (right_bounds->top + right_bounds->height);
 
-  gvr::Sizei source_size = {source_width, source_height};
   delegate->UpdateWebVRTextureBounds(frame_index, left_gvr_bounds,
-                                     right_gvr_bounds, source_size);
+                                     right_gvr_bounds);
 }
 
 void GvrDevice::GetVRVSyncProvider(mojom::VRVSyncProviderRequest request) {
@@ -109,7 +103,7 @@
 }
 
 GvrDelegate* GvrDevice::GetGvrDelegate() {
-  GvrDelegateProvider* delegate_provider = gvr_provider_->GetDelegateProvider();
+  GvrDelegateProvider* delegate_provider = GvrDelegateProvider::GetInstance();
   if (delegate_provider)
     return delegate_provider->GetDelegate();
   return nullptr;
diff --git a/device/vr/android/gvr/gvr_device.h b/device/vr/android/gvr/gvr_device.h
index 4478e1f5..55fb95f3 100644
--- a/device/vr/android/gvr/gvr_device.h
+++ b/device/vr/android/gvr/gvr_device.h
@@ -23,18 +23,14 @@
       const base::Callback<void(mojom::VRDisplayInfoPtr)>& callback) override;
   void ResetPose() override;
 
-  void RequestPresent(mojom::VRSubmitFrameClientPtr submit_client,
-                      const base::Callback<void(bool)>& callback) override;
+  void RequestPresent(const base::Callback<void(bool)>& callback) override;
   void SetSecureOrigin(bool secure_origin) override;
   void ExitPresent() override;
 
-  void SubmitFrame(int16_t frame_index,
-                   const gpu::MailboxHolder& mailbox) override;
+  void SubmitFrame(mojom::VRPosePtr pose) override;
   void UpdateLayerBounds(int16_t frame_index,
                          mojom::VRLayerBoundsPtr left_bounds,
-                         mojom::VRLayerBoundsPtr right_bounds,
-                         int16_t source_width,
-                         int16_t source_height) override;
+                         mojom::VRLayerBoundsPtr right_bounds) override;
   void GetVRVSyncProvider(mojom::VRVSyncProviderRequest request) override;
   void OnDelegateChanged();
 
diff --git a/device/vr/android/gvr/gvr_device_provider.cc b/device/vr/android/gvr/gvr_device_provider.cc
index 9b5be90..4aff6ce 100644
--- a/device/vr/android/gvr/gvr_device_provider.cc
+++ b/device/vr/android/gvr/gvr_device_provider.cc
@@ -33,14 +33,9 @@
 }
 
 void GvrDeviceProvider::GetDevices(std::vector<VRDevice*>* devices) {
-  devices->push_back(vr_device_.get());
-}
-
-device::GvrDelegateProvider* GvrDeviceProvider::GetDelegateProvider() {
-  device::GvrDelegateProvider* provider =
-      device::GvrDelegateProvider::GetInstance();
-  Initialize(provider);
-  return provider;
+  Initialize();
+  if (initialized_)
+    devices->push_back(vr_device_.get());
 }
 
 void GvrDeviceProvider::Initialize() {
@@ -48,36 +43,41 @@
   // GvrDeviceProvider so we don't have to call this function multiple times.
   // Ideally the DelegateProvider would always be available, and GetInstance()
   // would create it.
-  Initialize(device::GvrDelegateProvider::GetInstance());
-}
-
-void GvrDeviceProvider::Initialize(device::GvrDelegateProvider* provider) {
-  if (!provider)
+  if (initialized_)
     return;
-  provider->SetDeviceProvider(this);
+  device::GvrDelegateProvider* delegate_provider =
+      device::GvrDelegateProvider::GetInstance();
+  if (!delegate_provider)
+    return;
+  delegate_provider->SetDeviceProvider(this);
+  initialized_ = true;
 }
 
 void GvrDeviceProvider::RequestPresent(
-    mojom::VRSubmitFrameClientPtr submit_client,
     const base::Callback<void(bool)>& callback) {
-  device::GvrDelegateProvider* delegate_provider = GetDelegateProvider();
+  Initialize();
+  device::GvrDelegateProvider* delegate_provider =
+      device::GvrDelegateProvider::GetInstance();
   if (!delegate_provider)
     return callback.Run(false);
 
   // RequestWebVRPresent is async as we may trigger a DON flow that pauses
   // Chrome.
-  delegate_provider->RequestWebVRPresent(std::move(submit_client), callback);
+  delegate_provider->RequestWebVRPresent(callback);
 }
 
 // VR presentation exit requested by the API.
 void GvrDeviceProvider::ExitPresent() {
-  device::GvrDelegateProvider* delegate_provider = GetDelegateProvider();
+  Initialize();
+  GvrDelegateProvider* delegate_provider = GvrDelegateProvider::GetInstance();
   if (delegate_provider)
     delegate_provider->ExitWebVRPresent();
 }
 
 void GvrDeviceProvider::SetListeningForActivate(bool listening) {
-  device::GvrDelegateProvider* delegate_provider = GetDelegateProvider();
+  Initialize();
+  device::GvrDelegateProvider* delegate_provider =
+      device::GvrDelegateProvider::GetInstance();
   if (!delegate_provider)
     return;
 
diff --git a/device/vr/android/gvr/gvr_device_provider.h b/device/vr/android/gvr/gvr_device_provider.h
index d215e603..5793021 100644
--- a/device/vr/android/gvr/gvr_device_provider.h
+++ b/device/vr/android/gvr/gvr_device_provider.h
@@ -11,11 +11,9 @@
 #include "base/macros.h"
 #include "device/vr/vr_device_provider.h"
 #include "device/vr/vr_export.h"
-#include "device/vr/vr_service.mojom.h"
 
 namespace device {
 
-class GvrDelegateProvider;
 class GvrDevice;
 
 class DEVICE_VR_EXPORT GvrDeviceProvider : public VRDeviceProvider {
@@ -29,17 +27,14 @@
   void SetListeningForActivate(bool listening) override;
 
   // Called from GvrDevice.
-  void RequestPresent(mojom::VRSubmitFrameClientPtr submit_client,
-                      const base::Callback<void(bool)>& callback);
+  void RequestPresent(const base::Callback<void(bool)>& callback);
   void ExitPresent();
 
-  device::GvrDelegateProvider* GetDelegateProvider();
-
   GvrDevice* Device() { return vr_device_.get(); }
 
  private:
-  void Initialize(device::GvrDelegateProvider* provider);
   std::unique_ptr<GvrDevice> vr_device_;
+  bool initialized_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(GvrDeviceProvider);
 };
diff --git a/device/vr/test/fake_vr_device.cc b/device/vr/test/fake_vr_device.cc
index 9595e5ff..c6999050 100644
--- a/device/vr/test/fake_vr_device.cc
+++ b/device/vr/test/fake_vr_device.cc
@@ -62,8 +62,7 @@
 
 void FakeVRDevice::ResetPose() {}
 
-void FakeVRDevice::RequestPresent(mojom::VRSubmitFrameClientPtr submit_client,
-                                  const base::Callback<void(bool)>& callback) {
+void FakeVRDevice::RequestPresent(const base::Callback<void(bool)>& callback) {
   callback.Run(true);
 }
 
@@ -73,14 +72,11 @@
   OnExitPresent();
 }
 
-void FakeVRDevice::SubmitFrame(int16_t frame_index,
-                               const gpu::MailboxHolder& mailbox) {}
+void FakeVRDevice::SubmitFrame(mojom::VRPosePtr pose) {}
 
 void FakeVRDevice::UpdateLayerBounds(int16_t frame_index,
                                      mojom::VRLayerBoundsPtr leftBounds,
-                                     mojom::VRLayerBoundsPtr rightBounds,
-                                     int16_t source_width,
-                                     int16_t source_height) {}
+                                     mojom::VRLayerBoundsPtr rightBounds) {}
 
 void FakeVRDevice::GetVRVSyncProvider(mojom::VRVSyncProviderRequest request) {}
 
diff --git a/device/vr/test/fake_vr_device.h b/device/vr/test/fake_vr_device.h
index 16ddcdf9..178610ea 100644
--- a/device/vr/test/fake_vr_device.h
+++ b/device/vr/test/fake_vr_device.h
@@ -27,17 +27,13 @@
       const base::Callback<void(mojom::VRDisplayInfoPtr)>& callback) override;
   void ResetPose() override;
 
-  void RequestPresent(mojom::VRSubmitFrameClientPtr submit_client,
-                      const base::Callback<void(bool)>& callback) override;
+  void RequestPresent(const base::Callback<void(bool)>& callback) override;
   void SetSecureOrigin(bool secure_origin) override;
   void ExitPresent() override;
-  void SubmitFrame(int16_t frame_index,
-                   const gpu::MailboxHolder& mailbox) override;
+  void SubmitFrame(mojom::VRPosePtr pose) override;
   void UpdateLayerBounds(int16_t frame_index,
                          mojom::VRLayerBoundsPtr leftBounds,
-                         mojom::VRLayerBoundsPtr rightBounds,
-                         int16_t source_width,
-                         int16_t source_height) override;
+                         mojom::VRLayerBoundsPtr rightBounds) override;
   void GetVRVSyncProvider(mojom::VRVSyncProviderRequest request) override;
 
  private:
diff --git a/device/vr/vr_device.cc b/device/vr/vr_device.cc
index 00e9425..03a9685 100644
--- a/device/vr/vr_device.cc
+++ b/device/vr/vr_device.cc
@@ -19,6 +19,12 @@
 
 VRDevice::~VRDevice() {}
 
+void VRDevice::RequestPresent(const base::Callback<void(bool)>& callback) {
+  callback.Run(true);
+}
+
+void VRDevice::SetSecureOrigin(bool secure_origin) {}
+
 void VRDevice::AddDisplay(VRDisplayImpl* display) {
   displays_.insert(display);
 }
diff --git a/device/vr/vr_device.h b/device/vr/vr_device.h
index ae7fefc6..be836ee 100644
--- a/device/vr/vr_device.h
+++ b/device/vr/vr_device.h
@@ -27,17 +27,13 @@
       const base::Callback<void(mojom::VRDisplayInfoPtr)>& callback) = 0;
   virtual void ResetPose() = 0;
 
-  virtual void RequestPresent(mojom::VRSubmitFrameClientPtr submit_client,
-                              const base::Callback<void(bool)>& callback) = 0;
+  virtual void RequestPresent(const base::Callback<void(bool)>& callback) = 0;
   virtual void SetSecureOrigin(bool secure_origin) = 0;
   virtual void ExitPresent() = 0;
-  virtual void SubmitFrame(int16_t frame_index,
-                           const gpu::MailboxHolder& mailbox) = 0;
+  virtual void SubmitFrame(mojom::VRPosePtr pose) = 0;
   virtual void UpdateLayerBounds(int16_t frame_index,
                                  mojom::VRLayerBoundsPtr left_bounds,
-                                 mojom::VRLayerBoundsPtr right_bounds,
-                                 int16_t source_width,
-                                 int16_t source_height) = 0;
+                                 mojom::VRLayerBoundsPtr right_bounds) = 0;
   virtual void GetVRVSyncProvider(mojom::VRVSyncProviderRequest request) = 0;
 
   virtual void AddDisplay(VRDisplayImpl* display);
diff --git a/device/vr/vr_display_impl.cc b/device/vr/vr_display_impl.cc
index 9a3bf63..37f8ec9 100644
--- a/device/vr/vr_display_impl.cc
+++ b/device/vr/vr_display_impl.cc
@@ -41,17 +41,15 @@
 }
 
 void VRDisplayImpl::RequestPresent(bool secure_origin,
-                                   mojom::VRSubmitFrameClientPtr submit_client,
                                    const RequestPresentCallback& callback) {
   if (!device_->IsAccessAllowed(this)) {
     callback.Run(false);
     return;
   }
 
-  device_->RequestPresent(
-      std::move(submit_client),
-      base::Bind(&VRDisplayImpl::RequestPresentResult,
-                 weak_ptr_factory_.GetWeakPtr(), callback, secure_origin));
+  device_->RequestPresent(base::Bind(&VRDisplayImpl::RequestPresentResult,
+                                     weak_ptr_factory_.GetWeakPtr(), callback,
+                                     secure_origin));
 }
 
 void VRDisplayImpl::RequestPresentResult(const RequestPresentCallback& callback,
@@ -69,24 +67,20 @@
     device_->ExitPresent();
 }
 
-void VRDisplayImpl::SubmitFrame(int16_t frame_index,
-                                const gpu::MailboxHolder& mailbox) {
+void VRDisplayImpl::SubmitFrame(mojom::VRPosePtr pose) {
   if (!device_->CheckPresentingDisplay(this))
     return;
-  device_->SubmitFrame(frame_index, mailbox);
+  device_->SubmitFrame(std::move(pose));
 }
 
 void VRDisplayImpl::UpdateLayerBounds(int16_t frame_index,
                                       mojom::VRLayerBoundsPtr left_bounds,
-                                      mojom::VRLayerBoundsPtr right_bounds,
-                                      int16_t source_width,
-                                      int16_t source_height) {
+                                      mojom::VRLayerBoundsPtr right_bounds) {
   if (!device_->IsAccessAllowed(this))
     return;
 
   device_->UpdateLayerBounds(frame_index, std::move(left_bounds),
-                             std::move(right_bounds), source_width,
-                             source_height);
+                             std::move(right_bounds));
 }
 
 void VRDisplayImpl::GetVRVSyncProvider(mojom::VRVSyncProviderRequest request) {
diff --git a/device/vr/vr_display_impl.h b/device/vr/vr_display_impl.h
index bf0b7ab1..7116d52 100644
--- a/device/vr/vr_display_impl.h
+++ b/device/vr/vr_display_impl.h
@@ -32,17 +32,13 @@
   void ResetPose() override;
 
   void RequestPresent(bool secure_origin,
-                      mojom::VRSubmitFrameClientPtr submit_client,
                       const RequestPresentCallback& callback) override;
   void ExitPresent() override;
-  void SubmitFrame(int16_t frame_index,
-                   const gpu::MailboxHolder& mailbox) override;
+  void SubmitFrame(mojom::VRPosePtr pose) override;
 
   void UpdateLayerBounds(int16_t frame_index,
                          mojom::VRLayerBoundsPtr left_bounds,
-                         mojom::VRLayerBoundsPtr right_bounds,
-                         int16_t source_width,
-                         int16_t source_height) override;
+                         mojom::VRLayerBoundsPtr right_bounds) override;
   void GetVRVSyncProvider(mojom::VRVSyncProviderRequest request) override;
 
   void RequestPresentResult(const RequestPresentCallback& callback,
diff --git a/device/vr/vr_display_impl_unittest.cc b/device/vr/vr_display_impl_unittest.cc
index 05cad2f..f53fde62 100644
--- a/device/vr/vr_display_impl_unittest.cc
+++ b/device/vr/vr_display_impl_unittest.cc
@@ -47,14 +47,9 @@
   }
 
   void RequestPresent(VRDisplayImpl* display_impl) {
-    // TODO(klausw,mthiesse): set up a VRSubmitFrameClient here? Currently,
-    // the FakeVRDisplay doesn't access the submit client, so a nullptr
-    // is ok.
-    device::mojom::VRSubmitFrameClientPtr submit_client = nullptr;
     display_impl->RequestPresent(
-        true, std::move(submit_client),
-        base::Bind(&VRDisplayImplTest::onPresentComplete,
-                   base::Unretained(this)));
+        true, base::Bind(&VRDisplayImplTest::onPresentComplete,
+                         base::Unretained(this)));
   }
 
   void ExitPresent(VRDisplayImpl* display_impl) { display_impl->ExitPresent(); }
diff --git a/device/vr/vr_service.mojom b/device/vr/vr_service.mojom
index f85a63b..a77d4a9b 100644
--- a/device/vr/vr_service.mojom
+++ b/device/vr/vr_service.mojom
@@ -5,8 +5,6 @@
 module device.mojom;
 
 import "mojo/common/time.mojom";
-import "gpu/ipc/common/mailbox_holder.mojom";
-import "gpu/ipc/common/sync_token.mojom";
 
 // A field of view, given by 4 degrees describing the view from a center point.
 struct VRFieldOfView {
@@ -19,7 +17,6 @@
 // A display's position, orientation, velocity, and acceleration state at the
 // given timestamp.
 struct VRPose {
-  double timestamp;
   array<float, 4>? orientation;
   array<float, 3>? position;
   array<float, 3>? angularVelocity;
@@ -87,25 +84,19 @@
                      VRDisplayInfo displayInfo);
 };
 
-interface VRSubmitFrameClient {
-  OnSubmitFrameTransferred();
-  OnSubmitFrameRendered();
-};
-
 interface VRDisplay {
   ResetPose();
 
-  RequestPresent(bool secureOrigin, VRSubmitFrameClient client) => (bool success);
+  RequestPresent(bool secureOrigin) => (bool success);
   ExitPresent();
-  SubmitFrame(int16 frameId, gpu.mojom.MailboxHolder mailboxHolder);
+  SubmitFrame(VRPose? pose);
   UpdateLayerBounds(int16 frameId, VRLayerBounds leftBounds,
-                    VRLayerBounds rightBounds, int16 sourceWidth,
-                    int16 sourceHeight);
+                    VRLayerBounds rightBounds);
   GetVRVSyncProvider(VRVSyncProvider& request);
 };
 
 interface VRVSyncProvider {
-  enum Status { SUCCESS, CLOSING };
+  enum Status { SUCCESS, RETRY };
 
   // The frameId maps a VSync to a frame arriving from the compositor. IDs will
   // be reused after the frame arrives from the compositor. Negative IDs imply
diff --git a/gpu/config/gpu_driver_bug_list_json.cc b/gpu/config/gpu_driver_bug_list_json.cc
index b41b77c..1caf764 100644
--- a/gpu/config/gpu_driver_bug_list_json.cc
+++ b/gpu/config/gpu_driver_bug_list_json.cc
@@ -19,7 +19,7 @@
 {
   "name": "gpu driver bug list",
   // Please update the version number whenever you change this file.
-  "version": "9.33",
+  "version": "9.34",
   "entries": [
     {
       "id": 1,
@@ -1899,7 +1899,11 @@
       "description": "glTexStorage* are buggy when base mipmap level is not 0",
       "cr_bugs": [640506],
       "os": {
-        "type": "macosx"
+        "type": "macosx",
+        "version": {
+          "op": "<",
+          "value": "10.12.4"
+        }
       },
       "features": [
         "reset_base_mipmap_level_before_texstorage"
diff --git a/gpu/ipc/common/mailbox_holder.typemap b/gpu/ipc/common/mailbox_holder.typemap
index 06d9d72..4644f321 100644
--- a/gpu/ipc/common/mailbox_holder.typemap
+++ b/gpu/ipc/common/mailbox_holder.typemap
@@ -8,8 +8,4 @@
 deps = [
   "//gpu/ipc/common:struct_traits",
 ]
-public_deps = [
-  "//gpu/command_buffer/common",
-  "//mojo/public/cpp/bindings",
-]
 type_mappings = [ "gpu.mojom.MailboxHolder=::gpu::MailboxHolder" ]
diff --git a/gpu/ipc/common/mailbox_holder_for_blink.typemap b/gpu/ipc/common/mailbox_holder_for_blink.typemap
deleted file mode 100644
index 976595e7..0000000
--- a/gpu/ipc/common/mailbox_holder_for_blink.typemap
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//gpu/ipc/common/mailbox_holder.mojom"
-public_headers = [ "//gpu/command_buffer/common/mailbox_holder.h" ]
-traits_headers = [ "//gpu/ipc/common/mailbox_holder_struct_traits.h" ]
-public_deps = [
-  "//gpu/command_buffer/common",
-  "//gpu/ipc/common:interfaces",
-  "//mojo/public/cpp/bindings",
-]
-type_mappings = [ "gpu.mojom.MailboxHolder=::gpu::MailboxHolder" ]
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index bc66c58..e00eb6a 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -300,14 +300,6 @@
     "//url",
   ]
 
-  if (is_win) {
-    deps += [
-      "//build/win:default_exe_manifest",
-      "//content:sandbox_helper_win",
-      "//sandbox",
-    ]
-  }
-
   if (!is_mac) {
     deps += [ "//ui/aura" ]
   }
@@ -461,6 +453,14 @@
     "//headless:headless_shell_lib",
   ]
 
+  if (is_win) {
+    deps += [
+      "//build/win:default_exe_manifest",
+      "//content:sandbox_helper_win",
+      "//sandbox",
+    ]
+  }
+
   configs += [ ":headless_implementation" ]
 }
 
diff --git a/headless/lib/browser/DEPS b/headless/lib/browser/DEPS
index 0df32e1..ef4dfad 100644
--- a/headless/lib/browser/DEPS
+++ b/headless/lib/browser/DEPS
@@ -2,4 +2,5 @@
   "+components/security_state",
   "+storage/browser/quota",
   "+ui/aura",
+  "+sandbox/win/src",
 ]
diff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc
index 71b79a0..37a9c928 100644
--- a/headless/lib/browser/headless_browser_impl.cc
+++ b/headless/lib/browser/headless_browser_impl.cc
@@ -25,6 +25,11 @@
 #include "ui/events/devices/device_data_manager.h"
 #include "ui/gfx/geometry/size.h"
 
+#if defined(OS_WIN)
+#include "content/public/app/sandbox_helper_win.h"
+#include "sandbox/win/src/sandbox_types.h"
+#endif
+
 namespace headless {
 namespace {
 
@@ -32,8 +37,14 @@
     HeadlessBrowser::Options options,
     const base::Callback<void(HeadlessBrowser*)>& on_browser_start_callback) {
   content::ContentMainParams params(nullptr);
+#if defined(OS_WIN)
+  sandbox::SandboxInterfaceInfo sandbox_info = {0};
+  content::InitializeSandboxInfo(&sandbox_info);
+  params.sandbox_info = &sandbox_info;
+#elif !defined(OS_ANDROID)
   params.argc = options.argc;
   params.argv = options.argv;
+#endif
 
   // TODO(skyostil): Implement custom message pumps.
   DCHECK(!options.message_pump);
diff --git a/headless/lib/headless_content_main_delegate.cc b/headless/lib/headless_content_main_delegate.cc
index 72df5a2..739e95f 100644
--- a/headless/lib/headless_content_main_delegate.cc
+++ b/headless/lib/headless_content_main_delegate.cc
@@ -6,6 +6,7 @@
 
 #include "base/base_switches.h"
 #include "base/command_line.h"
+#include "base/environment.h"
 #include "base/files/file_util.h"
 #include "base/lazy_instance.h"
 #include "base/path_service.h"
@@ -39,6 +40,8 @@
 
 base::LazyInstance<HeadlessCrashReporterClient>::Leaky g_headless_crash_client =
     LAZY_INSTANCE_INITIALIZER;
+
+const char kLogFileName[] = "CHROME_LOG_FILE";
 }  // namespace
 
 HeadlessContentMainDelegate::HeadlessContentMainDelegate(
@@ -127,6 +130,12 @@
     log_path = log_filename;
   }
 
+  std::string filename;
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+  if (env->GetVar(kLogFileName, &filename) && !filename.empty()) {
+    log_path = base::FilePath::FromUTF8Unsafe(filename);
+  }
+
   const std::string process_type =
       command_line.GetSwitchValueASCII(switches::kProcessType);
 
@@ -211,7 +220,9 @@
   // Unconditionally try to turn on crash reporting since we do not have access
   // to the latest browser options at this point when testing. Breakpad will
   // bail out gracefully if the browser process hasn't enabled crash reporting.
+#if defined(HEADLESS_USE_BREAKPAD)
   breakpad::InitCrashReporter(process_type);
+#endif
 }
 #endif
 
diff --git a/ios/BUILD.gn b/ios/BUILD.gn
index b080ddbf..7ba2d563 100644
--- a/ios/BUILD.gn
+++ b/ios/BUILD.gn
@@ -57,6 +57,7 @@
       "//ios/testing:all_tests",
       "//ios/web:all_tests",
       "//ios/web/shell/test:all_tests",
+      "//ios/web_view/shell/test:all_tests",
     ]
   }
 }
diff --git a/ios/web/shell/test/BUILD.gn b/ios/web/shell/test/BUILD.gn
index 47e138e2..0d7ae77 100644
--- a/ios/web/shell/test/BUILD.gn
+++ b/ios/web/shell/test/BUILD.gn
@@ -77,12 +77,12 @@
     "app/web_view_interaction_test_util.mm",
     "earl_grey/shell_actions.h",
     "earl_grey/shell_actions.mm",
-    "earl_grey/shell_base_test_case.h",
-    "earl_grey/shell_base_test_case.mm",
     "earl_grey/shell_earl_grey.h",
     "earl_grey/shell_earl_grey.mm",
     "earl_grey/shell_matchers.h",
     "earl_grey/shell_matchers.mm",
+    "earl_grey/web_shell_test_case.h",
+    "earl_grey/web_shell_test_case.mm",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
diff --git a/ios/web/shell/test/context_menu_egtest.mm b/ios/web/shell/test/context_menu_egtest.mm
index 46f665e..a99f195 100644
--- a/ios/web/shell/test/context_menu_egtest.mm
+++ b/ios/web/shell/test/context_menu_egtest.mm
@@ -16,10 +16,10 @@
 #import "ios/web/public/test/web_view_interaction_test_util.h"
 #import "ios/web/shell/test/app/web_shell_test_util.h"
 #include "ios/web/shell/test/app/web_view_interaction_test_util.h"
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
 #import "ios/web/shell/test/earl_grey/shell_actions.h"
 #import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
 #import "ios/web/shell/test/earl_grey/shell_matchers.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -30,7 +30,7 @@
 using web::WebViewContainingText;
 
 // Context menu test cases for the web shell.
-@interface ContextMenuTestCase : ShellBaseTestCase
+@interface ContextMenuTestCase : WebShellTestCase
 @end
 
 @implementation ContextMenuTestCase
diff --git a/ios/web/shell/test/earl_grey/shell_base_test_case.h b/ios/web/shell/test/earl_grey/shell_base_test_case.h
deleted file mode 100644
index 32578c9..0000000
--- a/ios/web/shell/test/earl_grey/shell_base_test_case.h
+++ /dev/null
@@ -1,15 +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 IOS_WEB_SHELL_TEST_EARL_GREY_SHELL_BASE_TEST_CASE_H_
-#define IOS_WEB_SHELL_TEST_EARL_GREY_SHELL_BASE_TEST_CASE_H_
-
-#import <XCTest/XCTest.h>
-
-// Base class for all web shell Earl Grey tests.
-@interface ShellBaseTestCase : XCTestCase
-
-@end
-
-#endif  // IOS_WEB_SHELL_TEST_EARL_GREY_SHELL_BASE_TEST_CASE_H_
diff --git a/ios/web/shell/test/earl_grey/web_shell_test_case.h b/ios/web/shell/test/earl_grey/web_shell_test_case.h
new file mode 100644
index 0000000..66f9321
--- /dev/null
+++ b/ios/web/shell/test/earl_grey/web_shell_test_case.h
@@ -0,0 +1,15 @@
+// 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 IOS_WEB_SHELL_TEST_EARL_GREY_WEB_SHELL_TEST_CASE_H_
+#define IOS_WEB_SHELL_TEST_EARL_GREY_WEB_SHELL_TEST_CASE_H_
+
+#import <XCTest/XCTest.h>
+
+// Base class for all web shell Earl Grey tests.
+@interface WebShellTestCase : XCTestCase
+
+@end
+
+#endif  // IOS_WEB_SHELL_TEST_EARL_GREY_WEB_SHELL_TEST_CASE_H_
diff --git a/ios/web/shell/test/earl_grey/shell_base_test_case.mm b/ios/web/shell/test/earl_grey/web_shell_test_case.mm
similarity index 94%
rename from ios/web/shell/test/earl_grey/shell_base_test_case.mm
rename to ios/web/shell/test/earl_grey/web_shell_test_case.mm
index c3d47dc..0c64573 100644
--- a/ios/web/shell/test/earl_grey/shell_base_test_case.mm
+++ b/ios/web/shell/test/earl_grey/web_shell_test_case.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 
 #import <EarlGrey/EarlGrey.h>
 
@@ -16,7 +16,7 @@
 using web::test::HttpServer;
 using web::WebViewContainingText;
 
-@implementation ShellBaseTestCase
+@implementation WebShellTestCase
 
 // Overrides |testInvocations| to skip all tests if a system alert view is
 // shown, since this isn't a case a user would encounter (i.e. they would
diff --git a/ios/web/shell/test/meta_tags_egtest.mm b/ios/web/shell/test/meta_tags_egtest.mm
index 84aa8d2d..a1f92005 100644
--- a/ios/web/shell/test/meta_tags_egtest.mm
+++ b/ios/web/shell/test/meta_tags_egtest.mm
@@ -8,9 +8,9 @@
 #import "base/test/ios/wait_util.h"
 #import "ios/web/public/test/http_server.h"
 #include "ios/web/public/test/http_server_util.h"
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
 #import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
 #import "ios/web/shell/test/earl_grey/shell_matchers.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -38,7 +38,7 @@
 using web::WebViewContainingText;
 
 // META tag test cases for the web shell.
-@interface MetaTagsTestCase : ShellBaseTestCase
+@interface MetaTagsTestCase : WebShellTestCase
 @end
 
 @implementation MetaTagsTestCase
diff --git a/ios/web/shell/test/navigation_egtest.mm b/ios/web/shell/test/navigation_egtest.mm
index ba71680..1698e41 100644
--- a/ios/web/shell/test/navigation_egtest.mm
+++ b/ios/web/shell/test/navigation_egtest.mm
@@ -12,9 +12,9 @@
 #import "ios/web/public/test/http_server.h"
 #include "ios/web/public/test/http_server_util.h"
 #include "ios/web/shell/test/app/web_view_interaction_test_util.h"
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
 #import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
 #import "ios/web/shell/test/earl_grey/shell_matchers.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -22,7 +22,7 @@
 
 // Navigation test cases for the web shell. These are Earl Grey integration
 // tests, which are based on XCTest.
-@interface NavigationTestCase : ShellBaseTestCase
+@interface NavigationTestCase : WebShellTestCase
 @end
 
 @implementation NavigationTestCase
diff --git a/ios/web/shell/test/page_state_egtest.mm b/ios/web/shell/test/page_state_egtest.mm
index 7c3e1e0..03a37c78 100644
--- a/ios/web/shell/test/page_state_egtest.mm
+++ b/ios/web/shell/test/page_state_egtest.mm
@@ -11,9 +11,9 @@
 #include "ios/testing/earl_grey/disabled_test_macros.h"
 #import "ios/web/public/test/http_server.h"
 #include "ios/web/public/test/http_server_util.h"
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
 #import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
 #import "ios/web/shell/test/earl_grey/shell_matchers.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -59,7 +59,7 @@
 using web::test::HttpServer;
 
 // Page state test cases for the web shell.
-@interface PageStateTestCase : ShellBaseTestCase
+@interface PageStateTestCase : WebShellTestCase
 @end
 
 @implementation PageStateTestCase
diff --git a/ios/web/shell/test/pdf_egtest.mm b/ios/web/shell/test/pdf_egtest.mm
index 565618a..ae39644 100644
--- a/ios/web/shell/test/pdf_egtest.mm
+++ b/ios/web/shell/test/pdf_egtest.mm
@@ -10,8 +10,8 @@
 #import "ios/web/public/test/http_server.h"
 #include "ios/web/public/test/http_server_util.h"
 #import "ios/web/shell/test/app/web_shell_test_util.h"
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
 #import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -48,7 +48,7 @@
 using web::test::HttpServer;
 
 // PDF test cases for the web shell.
-@interface PDFTestCase : ShellBaseTestCase
+@interface PDFTestCase : WebShellTestCase
 @end
 
 @implementation PDFTestCase
diff --git a/ios/web/shell/test/plugin_placeholder_egtest.mm b/ios/web/shell/test/plugin_placeholder_egtest.mm
index 1f4dbb9..5c8941c 100644
--- a/ios/web/shell/test/plugin_placeholder_egtest.mm
+++ b/ios/web/shell/test/plugin_placeholder_egtest.mm
@@ -11,9 +11,9 @@
 #import "base/test/ios/wait_util.h"
 #import "ios/web/public/test/http_server.h"
 #include "ios/web/public/test/http_server_util.h"
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
 #import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
 #import "ios/web/shell/test/earl_grey/shell_matchers.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 #include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -37,7 +37,7 @@
 
 // Plugin placeholder test cases for the web shell. These tests verify that web
 // page shows a placeholder for unsupported plugins.
-@interface PluginPlaceholderTestCase : ShellBaseTestCase
+@interface PluginPlaceholderTestCase : WebShellTestCase
 @end
 
 @implementation PluginPlaceholderTestCase
diff --git a/ios/web/shell/test/redirect_egtest.mm b/ios/web/shell/test/redirect_egtest.mm
index b9c0a66..b9df80e 100644
--- a/ios/web/shell/test/redirect_egtest.mm
+++ b/ios/web/shell/test/redirect_egtest.mm
@@ -8,9 +8,9 @@
 #include "ios/web/public/test/http_server_util.h"
 #import "ios/web/public/test/response_providers/html_response_provider.h"
 #import "ios/web/public/test/response_providers/html_response_provider_impl.h"
-#import "ios/web/shell/test/earl_grey/shell_base_test_case.h"
 #import "ios/web/shell/test/earl_grey/shell_earl_grey.h"
 #import "ios/web/shell/test/earl_grey/shell_matchers.h"
+#import "ios/web/shell/test/earl_grey/web_shell_test_case.h"
 #include "net/http/http_status_code.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -22,7 +22,7 @@
 using web::WebViewContainingText;
 
 // Redirect test cases for the web shell.
-@interface RedirectTestCase : ShellBaseTestCase
+@interface RedirectTestCase : WebShellTestCase
 @end
 
 @implementation RedirectTestCase
diff --git a/ios/web_view/shell/BUILD.gn b/ios/web_view/shell/BUILD.gn
index 0407dec7..389cc590 100644
--- a/ios/web_view/shell/BUILD.gn
+++ b/ios/web_view/shell/BUILD.gn
@@ -6,16 +6,20 @@
 import("//build/config/ios/rules.gni")
 
 ios_app_bundle("ios_web_view_shell") {
-  configs += [ "//build/config/compiler:enable_arc" ]
+  info_plist = "Info.plist"
+
   deps = [
-    "//base",
-    "//ios/web_view",
+    ":shell",
   ]
   bundle_deps = [
     ":resources",
     ":packed_resources",
   ]
-  info_plist = "Info.plist"
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
+source_set("shell") {
   sources = [
     "shell_app_delegate.h",
     "shell_app_delegate.m",
@@ -27,6 +31,12 @@
     "translate_controller.h",
     "translate_controller.m",
   ]
+
+  deps = [
+    "//base",
+    "//ios/web_view",
+  ]
+
   libs = [
     "CFNetwork.framework",
     "CoreFoundation.framework",
@@ -41,6 +51,8 @@
     "WebKit.framework",
     "resolv",
   ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
 }
 
 bundle_data("resources") {
diff --git a/ios/web_view/shell/shell_view_controller.h b/ios/web_view/shell/shell_view_controller.h
index 9c5ccc8..acf4b60 100644
--- a/ios/web_view/shell/shell_view_controller.h
+++ b/ios/web_view/shell/shell_view_controller.h
@@ -10,6 +10,12 @@
 #import "ios/web_view/public/cwv_navigation_delegate.h"
 #import "ios/web_view/public/cwv_ui_delegate.h"
 
+// Accessibility label added to the back button.
+extern NSString* const kWebViewShellBackButtonAccessibilityLabel;
+// Accessibility label added to the forward button.
+extern NSString* const kWebViewShellForwardButtonAccessibilityLabel;
+// Accessibility label added to the URL address text field.
+extern NSString* const kWebViewShellAddressFieldAccessibilityLabel;
 // Accessibility identifier added to the text field of JavaScript prompts.
 extern NSString* const
     kWebViewShellJavaScriptDialogTextFieldAccessibiltyIdentifier;
diff --git a/ios/web_view/shell/shell_view_controller.m b/ios/web_view/shell/shell_view_controller.m
index 7c2e33e..f29d4cb7 100644
--- a/ios/web_view/shell/shell_view_controller.m
+++ b/ios/web_view/shell/shell_view_controller.m
@@ -16,6 +16,9 @@
 #endif
 
 // Externed accessibility identifier.
+NSString* const kWebViewShellBackButtonAccessibilityLabel = @"Back";
+NSString* const kWebViewShellForwardButtonAccessibilityLabel = @"Forward";
+NSString* const kWebViewShellAddressFieldAccessibilityLabel = @"Address field";
 NSString* const kWebViewShellJavaScriptDialogTextFieldAccessibiltyIdentifier =
     @"WebViewShellJavaScriptDialogTextFieldAccessibiltyIdentifier";
 
@@ -88,6 +91,7 @@
   [_field setKeyboardType:UIKeyboardTypeWebSearch];
   [_field setAutocorrectionType:UITextAutocorrectionTypeNo];
   [_field setClearButtonMode:UITextFieldViewModeWhileEditing];
+  [_field setAccessibilityLabel:kWebViewShellAddressFieldAccessibilityLabel];
 
   // Set up the toolbar buttons.
   // Back.
@@ -101,6 +105,7 @@
   [back addTarget:self
                 action:@selector(back)
       forControlEvents:UIControlEventTouchUpInside];
+  [back setAccessibilityLabel:kWebViewShellBackButtonAccessibilityLabel];
 
   // Forward.
   UIButton* forward = [UIButton buttonWithType:UIButtonTypeCustom];
@@ -112,6 +117,7 @@
   [forward addTarget:self
                 action:@selector(forward)
       forControlEvents:UIControlEventTouchUpInside];
+  [forward setAccessibilityLabel:kWebViewShellForwardButtonAccessibilityLabel];
 
   // Stop.
   UIButton* stop = [UIButton buttonWithType:UIButtonTypeCustom];
diff --git a/ios/web_view/shell/test/BUILD.gn b/ios/web_view/shell/test/BUILD.gn
new file mode 100644
index 0000000..6c29a4c
--- /dev/null
+++ b/ios/web_view/shell/test/BUILD.gn
@@ -0,0 +1,54 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//ios/build/config.gni")
+import("//ios/third_party/earl_grey/ios_eg_test.gni")
+
+group("all_tests") {
+  testonly = true
+  deps = [
+    ":ios_web_view_shell_egtests",
+  ]
+}
+
+ios_eg_test("ios_web_view_shell_egtests") {
+  info_plist = "//ios/web_view/shell/Info.plist"
+  sources = [
+    "shell_egtest.mm",
+  ]
+
+  deps = [
+    ":earl_grey_test_support",
+
+    # All shared libraries must have the sanitizer deps to properly link in
+    # asan mode (this target will be empty in other cases).
+    "//build/config/sanitizers:deps",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  assert_no_deps = ios_assert_no_deps
+}
+
+source_set("earl_grey_test_support") {
+  testonly = true
+
+  deps = [
+    "//base",
+    "//ios/testing:ios_test_support",
+    "//ios/third_party/earl_grey",
+    "//ios/web_view/shell",
+  ]
+
+  public_deps = [
+    "//build/config/ios:xctest",
+  ]
+
+  sources = [
+    "earl_grey/web_view_shell_matchers.h",
+    "earl_grey/web_view_shell_matchers.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
diff --git a/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h
new file mode 100644
index 0000000..b0b7a4d0
--- /dev/null
+++ b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h
@@ -0,0 +1,28 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_WEB_VIEW_SHELL_TEST_EARL_GREY_WEB_VIEW_SHELL_MATCHERS_H_
+#define IOS_WEB_VIEW_SHELL_TEST_EARL_GREY_WEB_VIEW_SHELL_MATCHERS_H_
+
+#import <string>
+
+#import <EarlGrey/EarlGrey.h>
+
+namespace ios_web_view {
+
+// Matcher for web view shell address field text property equal to |text|.
+id<GREYMatcher> AddressFieldText(const std::string& text);
+
+// Matcher for back button in web view shell.
+id<GREYMatcher> BackButton();
+
+// Matcher for forward button in web view shell.
+id<GREYMatcher> ForwardButton();
+
+// Matcher for address field in web view shell.
+id<GREYMatcher> AddressField();
+
+}  // namespace ios_web_view
+
+#endif  // IOS_WEB_VIEW_SHELL_TEST_EARL_GREY_WEB_VIEW_SHELL_MATCHERS_H_
diff --git a/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.mm b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.mm
new file mode 100644
index 0000000..466bc22
--- /dev/null
+++ b/ios/web_view/shell/test/earl_grey/web_view_shell_matchers.mm
@@ -0,0 +1,61 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/web_view/shell/test/earl_grey/web_view_shell_matchers.h"
+
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+#import "ios/testing/wait_util.h"
+#import "ios/web_view/shell/shell_view_controller.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace ios_web_view {
+
+id<GREYMatcher> AddressFieldText(const std::string& text) {
+  MatchesBlock matches = ^BOOL(UIView* view) {
+    if (![view isKindOfClass:[UITextField class]]) {
+      return NO;
+    }
+    if (![[view accessibilityLabel]
+            isEqualToString:kWebViewShellAddressFieldAccessibilityLabel]) {
+      return NO;
+    }
+    UITextField* text_field = base::mac::ObjCCastStrict<UITextField>(view);
+    NSString* error_message = [NSString
+        stringWithFormat:
+            @"Address field text did not match. expected: %@, actual: %@",
+            base::SysUTF8ToNSString(text), text_field.text];
+    bool success = testing::WaitUntilConditionOrTimeout(
+        testing::kWaitForUIElementTimeout, ^{
+          return base::SysNSStringToUTF8(text_field.text) == text;
+        });
+    GREYAssert(success, error_message);
+    return YES;
+  };
+
+  DescribeToBlock describe = ^(id<GREYDescription> description) {
+    [description appendText:@"address field containing "];
+    [description appendText:base::SysUTF8ToNSString(text)];
+  };
+
+  return [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches
+                                              descriptionBlock:describe];
+}
+
+id<GREYMatcher> BackButton() {
+  return grey_accessibilityLabel(kWebViewShellBackButtonAccessibilityLabel);
+}
+
+id<GREYMatcher> ForwardButton() {
+  return grey_accessibilityLabel(kWebViewShellForwardButtonAccessibilityLabel);
+}
+
+id<GREYMatcher> AddressField() {
+  return grey_accessibilityLabel(kWebViewShellAddressFieldAccessibilityLabel);
+}
+
+}  // namespace ios_web_view
diff --git a/ios/web_view/shell/test/shell_egtest.mm b/ios/web_view/shell/test/shell_egtest.mm
new file mode 100644
index 0000000..0a0c5e2c
--- /dev/null
+++ b/ios/web_view/shell/test/shell_egtest.mm
@@ -0,0 +1,13 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <XCTest/XCTest.h>
+
+// Empty test case file for the web view shell.
+@interface ShellTestCase : XCTestCase
+@end
+
+@implementation ShellTestCase
+
+@end
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 4d9950f..ac79358 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -780,9 +780,16 @@
 }
 
 bool FFmpegDemuxerStream::HasAvailableCapacity() {
-  // Try to have two second's worth of encoded data per stream.
-  const base::TimeDelta kCapacity = base::TimeDelta::FromSeconds(2);
+  // TODO(scherkus): Remove this return and reenable time-based capacity
+  // after our data sources support canceling/concurrent reads, see
+  // http://crbug.com/165762 for details.
+#if 1
+  return !read_cb_.is_null();
+#else
+  // Try to have one second's worth of encoded data per stream.
+  const base::TimeDelta kCapacity = base::TimeDelta::FromSeconds(1);
   return buffer_queue_.IsEmpty() || buffer_queue_.Duration() < kCapacity;
+#endif
 }
 
 size_t FFmpegDemuxerStream::MemoryUsage() const {
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index a5d510d..3cfb72f 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -482,7 +482,7 @@
   audio->Read(NewReadCB(FROM_HERE, 27, 3000, true));
   base::RunLoop().Run();
 
-  EXPECT_EQ(166866, demuxer_->GetMemoryUsage());
+  EXPECT_EQ(22084, demuxer_->GetMemoryUsage());
 }
 
 TEST_F(FFmpegDemuxerTest, Read_Video) {
@@ -499,7 +499,7 @@
   video->Read(NewReadCB(FROM_HERE, 1057, 33000, false));
   base::RunLoop().Run();
 
-  EXPECT_EQ(148778, demuxer_->GetMemoryUsage());
+  EXPECT_EQ(323, demuxer_->GetMemoryUsage());
 }
 
 TEST_F(FFmpegDemuxerTest, Read_Text) {
diff --git a/net/nqe/network_quality_estimator.cc b/net/nqe/network_quality_estimator.cc
index 0d5fbe3..13e56e6 100644
--- a/net/nqe/network_quality_estimator.cc
+++ b/net/nqe/network_quality_estimator.cc
@@ -1470,8 +1470,10 @@
 
   const bool cached_estimate_available = network_quality_store_->GetById(
       current_network_id_, &cached_network_quality);
-  UMA_HISTOGRAM_BOOLEAN("NQE.CachedNetworkQualityAvailable",
-                        cached_estimate_available);
+  if (network_quality_store_->EligibleForCaching(current_network_id_)) {
+    UMA_HISTOGRAM_BOOLEAN("NQE.CachedNetworkQualityAvailable",
+                          cached_estimate_available);
+  }
 
   if (!cached_estimate_available)
     return false;
diff --git a/net/nqe/network_quality_estimator_unittest.cc b/net/nqe/network_quality_estimator_unittest.cc
index d5cc4f6..183b87c 100644
--- a/net/nqe/network_quality_estimator_unittest.cc
+++ b/net/nqe/network_quality_estimator_unittest.cc
@@ -316,7 +316,7 @@
   estimator.SimulateNetworkChange(
       NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI, std::string());
   histogram_tester.ExpectUniqueSample("NQE.CachedNetworkQualityAvailable",
-                                      false, 3);
+                                      false, 2);
   histogram_tester.ExpectTotalCount("NQE.PeakKbps.Unknown", 1);
   histogram_tester.ExpectTotalCount("NQE.FastestRTT.Unknown", 1);
 
@@ -347,7 +347,7 @@
   estimator.SimulateNetworkChange(
       NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN, "test");
   histogram_tester.ExpectBucketCount("NQE.CachedNetworkQualityAvailable", false,
-                                     3);
+                                     2);
   histogram_tester.ExpectBucketCount("NQE.CachedNetworkQualityAvailable", true,
                                      1);
 }
diff --git a/net/nqe/network_quality_store.cc b/net/nqe/network_quality_store.cc
index 43d2bca..0686bd45 100644
--- a/net/nqe/network_quality_store.cc
+++ b/net/nqe/network_quality_store.cc
@@ -35,15 +35,8 @@
   DCHECK_LE(cached_network_qualities_.size(),
             static_cast<size_t>(kMaximumNetworkQualityCacheSize));
 
-  // If the network name is unavailable, caching should not be performed. If
-  // |disable_offline_check_| is set to true, cache the network quality even if
-  // the network is set to offline.
-  if (network_id.type != NetworkChangeNotifier::CONNECTION_ETHERNET &&
-      network_id.id.empty() &&
-      (network_id.type != NetworkChangeNotifier::CONNECTION_NONE ||
-       !disable_offline_check_)) {
+  if (!EligibleForCaching(network_id))
     return;
-  }
 
   // Remove the entry from the map, if it is already present.
   cached_network_qualities_.erase(network_id);
@@ -104,6 +97,18 @@
   network_qualities_cache_observer_list_.RemoveObserver(observer);
 }
 
+bool NetworkQualityStore::EligibleForCaching(
+    const NetworkID& network_id) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // |disable_offline_check_| forces caching of the network quality even if
+  // the network is set to offline.
+  return network_id.type == NetworkChangeNotifier::CONNECTION_ETHERNET ||
+         !network_id.id.empty() ||
+         (network_id.type == NetworkChangeNotifier::CONNECTION_NONE &&
+          disable_offline_check_);
+}
+
 void NetworkQualityStore::DisableOfflineCheckForTesting(
     bool disable_offline_check) {
   DCHECK(thread_checker_.CalledOnValidThread());
diff --git a/net/nqe/network_quality_store.h b/net/nqe/network_quality_store.h
index 8970c35..6213da7 100644
--- a/net/nqe/network_quality_store.h
+++ b/net/nqe/network_quality_store.h
@@ -67,6 +67,9 @@
   void RemoveNetworkQualitiesCacheObserver(
       NetworkQualitiesCacheObserver* observer);
 
+  // Returns true if network quality for |network_id| can be cached.
+  bool EligibleForCaching(const NetworkID& network_id) const;
+
   // If |disable_offline_check| is set to true, the offline check is disabled
   // when storing the network quality.
   void DisableOfflineCheckForTesting(bool disable_offline_check);
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 75251273..3b49ea3 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -154,6 +154,9 @@
 crbug.com/667045 paint/invalidation/table/composited-cell-collapsed-border-add-anonymous.html [ Crash Timeout ]
 crbug.com/667045 virtual/disable-spinvalidation/paint/invalidation/table/composited-cell-collapsed-border-add-anonymous.html [ Crash Timeout ]
 
+crbug.com/699044 virtual/disable-spinvalidation/paint/invalidation/caret-subpixel.html [ NeedsRebaseline ]
+crbug.com/699044 virtual/disable-spinvalidation/paint/invalidation/caret-with-composited-scroll.html [ NeedsRebaseline ]
+
 # ====== Paint team owned tests to here ======
 
 # ====== LayoutNG-only failures from here ======
@@ -2473,10 +2476,5 @@
 crbug.com/698520 external/wpt/preload/fetch-destination.https.html [ Failure Pass ]
 crbug.com/698521 external/wpt/preload/preload-with-type.html [ Failure Pass ]
 
-# TODO(xiaochengh): The following tests should pass if IdleTimeSpellChecking is enabled.
-crbug.com/517298 editing/spelling/spellcheck_test.html [ Failure ]
-crbug.com/517298 editing/spelling/cold_mode_static_page.html [ Skip ]
-crbug.com/517298 editing/spelling/cold_mode_type_idle.html [ Skip ]
-
 # Sheriff failures 2017-03-06
 crbug.com/698872 [ Linux Debug ] external/wpt/service-workers/service-worker/fetch-event-redirect.https.html [ Crash ]
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-error.html
index 0e7af6c2..2910e06 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-error.html
@@ -18,7 +18,8 @@
         /* 0x0101 doesn't exist in this characteristic */
         characteristic.getDescriptor(0x0101),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-success.html
index e8feb89a..044ffee6 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-success.html
@@ -17,7 +17,8 @@
       promise = assert_promise_rejects_with_message(
         characteristic.getDescriptor(user_description.name),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-before.html
index 7794d0e..aa44683 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-before.html
@@ -23,7 +23,8 @@
         .then(() => assert_promise_rejects_with_message(
           measurement_interval.getDescriptor(user_description.name),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before getDescriptor. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-error.html
index 1c4f2648..3e0d9686 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-error.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             error_characteristic.getDescriptor(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-success.html
index e74c16d9..021a54f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-device-disconnects-during-success.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.getDescriptor(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-before.html
index c38dcad..f921b51 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-before.html
@@ -19,7 +19,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.getDescriptor(user_description.name),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-error.html
index d8e4589..8f5ed5b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-error.html
@@ -18,7 +18,8 @@
           let promise = assert_promise_rejects_with_message(
             error_characteristic.getDescriptor(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-success.html
index 0d557922..9be307c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptor/gen-gatt-op-disconnect-called-during-success.html
@@ -19,7 +19,8 @@
           let promise = assert_promise_rejects_with_message(
             measurement_interval.getDescriptor(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error-with-uuid.html
index 45a0fe1..a4076d03 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error-with-uuid.html
@@ -18,7 +18,8 @@
         /* 0x0101 doesn't exist in this characteristic */
         characteristic.getDescriptors(0x0101),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error.html
index 4bca004..fe5961e7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error.html
@@ -18,7 +18,8 @@
         /* 0x0101 doesn't exist in this characteristic */
         characteristic.getDescriptors(),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success-with-uuid.html
index 8c7652b7..6dd208d 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success-with-uuid.html
@@ -17,7 +17,8 @@
       promise = assert_promise_rejects_with_message(
         characteristic.getDescriptors(user_description.name),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success.html
index 839a2c5..41fa623 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success.html
@@ -17,7 +17,8 @@
       promise = assert_promise_rejects_with_message(
         characteristic.getDescriptors(),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before-with-uuid.html
index a0ed197..1b2bdc0a 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before-with-uuid.html
@@ -23,7 +23,8 @@
         .then(() => assert_promise_rejects_with_message(
           measurement_interval.getDescriptors(user_description.name),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before getDescriptors. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before.html
index 5cd5f0c..c687ca6 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-before.html
@@ -23,7 +23,8 @@
         .then(() => assert_promise_rejects_with_message(
           measurement_interval.getDescriptors(),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before getDescriptors. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error-with-uuid.html
index 1cb6c81f..aef4d6f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error-with-uuid.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             error_characteristic.getDescriptors(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error.html
index a197d32..e634f4b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-error.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             error_characteristic.getDescriptors(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success-with-uuid.html
index b256763..1d6698f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success-with-uuid.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.getDescriptors(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success.html
index a0bb7b7..7c0a80c7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-device-disconnects-during-success.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.getDescriptors(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before-with-uuid.html
index 47c84b2..10a8487 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before-with-uuid.html
@@ -19,7 +19,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.getDescriptors(user_description.name),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before.html
index 4b2118d2..99fd299 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-before.html
@@ -19,7 +19,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.getDescriptors(),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error-with-uuid.html
index a3374df..cf2e0509 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error-with-uuid.html
@@ -18,7 +18,8 @@
           let promise = assert_promise_rejects_with_message(
             error_characteristic.getDescriptors(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error.html
index cb9fd5c9..006f668 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-error.html
@@ -18,7 +18,8 @@
           let promise = assert_promise_rejects_with_message(
             error_characteristic.getDescriptors(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success-with-uuid.html
index 48a8f39f..fba944c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success-with-uuid.html
@@ -19,7 +19,8 @@
           let promise = assert_promise_rejects_with_message(
             measurement_interval.getDescriptors(user_description.name),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success.html
index b530eb99..3f50c9d 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/getDescriptors/gen-gatt-op-disconnect-called-during-success.html
@@ -19,7 +19,8 @@
           let promise = assert_promise_rejects_with_message(
             measurement_interval.getDescriptors(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-before.html
index 5b0f073..b0fc0993 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-before.html
@@ -23,7 +23,8 @@
         .then(() => assert_promise_rejects_with_message(
           measurement_interval.readValue(),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before readValue. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-error.html
index c5687cb..74aafad7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-error.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             error_characteristic.readValue(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-success.html
index 58d37be..f3e9157 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-success.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.readValue(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-error.html
index 21fa221..6d8b24a9 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-error.html
@@ -16,7 +16,8 @@
       let disconnected = eventPromise(characteristic.service.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         characteristic.readValue(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
         .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-success.html
index c2df5ca8..b38046b7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-success.html
@@ -16,7 +16,8 @@
       let disconnected = eventPromise(characteristic.service.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         characteristic.readValue(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
                          .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-before.html
index b5e6e29..74480cb 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-before.html
@@ -19,7 +19,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.readValue(),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-error.html
index d0e766623..9cf7a71 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-error.html
@@ -18,7 +18,8 @@
           let promise = assert_promise_rejects_with_message(
             error_characteristic.readValue(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-success.html
index 4ef0264..e3ce3bb 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-success.html
@@ -19,7 +19,8 @@
           let promise = assert_promise_rejects_with_message(
             measurement_interval.readValue(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-error.html
index bd4062c..cfa1c63 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-error.html
@@ -18,7 +18,8 @@
       promise = assert_promise_rejects_with_message(
         error_characteristic.readValue(),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-success.html
index 01d909276..0e43b22 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-success.html
@@ -18,7 +18,8 @@
       promise = assert_promise_rejects_with_message(
         measurement_interval.readValue(),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-error.html
index c35dbbc..d7b988a 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-error.html
@@ -15,7 +15,8 @@
     .then(characteristic => {
       let promise = assert_promise_rejects_with_message(
         characteristic.readValue(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-success.html
index 718d865..447d4e9 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-success.html
@@ -15,7 +15,8 @@
     .then(characteristic => {
       let promise = assert_promise_rejects_with_message(
         characteristic.readValue(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-before.html
index 92ee117..fa1541c07 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-before.html
@@ -23,7 +23,8 @@
         .then(() => assert_promise_rejects_with_message(
           measurement_interval.startNotifications(),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before startNotifications. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-error.html
index 90369b2..81af665f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-error.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             error_characteristic.startNotifications(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-success.html
index fa5042b6..5d40c688 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-success.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.startNotifications(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-error.html
index 67e0181..1c57102 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-error.html
@@ -16,7 +16,8 @@
       let disconnected = eventPromise(characteristic.service.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         characteristic.startNotifications(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
         .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-success.html
index 826d7368..68a6b7b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-success.html
@@ -16,7 +16,8 @@
       let disconnected = eventPromise(characteristic.service.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         characteristic.startNotifications(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
                          .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-before.html
index 3720d35..2ad7864 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-before.html
@@ -19,7 +19,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.startNotifications(),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-error.html
index a949cb3..1138fcc 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-error.html
@@ -18,7 +18,8 @@
           let promise = assert_promise_rejects_with_message(
             error_characteristic.startNotifications(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-success.html
index 16f4db8..8653a22 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-success.html
@@ -19,7 +19,8 @@
           let promise = assert_promise_rejects_with_message(
             measurement_interval.startNotifications(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-error.html
index 1742b77..401e84aa6 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-error.html
@@ -18,7 +18,8 @@
       promise = assert_promise_rejects_with_message(
         error_characteristic.startNotifications(),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-success.html
index 8e63831..734d1b56 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-success.html
@@ -18,7 +18,8 @@
       promise = assert_promise_rejects_with_message(
         measurement_interval.startNotifications(),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-error.html
index 889e301a..b6b06ce7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-error.html
@@ -15,7 +15,8 @@
     .then(characteristic => {
       let promise = assert_promise_rejects_with_message(
         characteristic.startNotifications(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-success.html
index 5486bd4..b77bab0 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-success.html
@@ -15,7 +15,8 @@
     .then(characteristic => {
       let promise = assert_promise_rejects_with_message(
         characteristic.startNotifications(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/device-reconnects-during-success.html
index 7bb1385e..74a00f6 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/device-reconnects-during-success.html
@@ -17,7 +17,8 @@
       let disconnected = eventPromise(characteristic.service.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         characteristic.stopNotifications(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
                          .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-before.html
index 2d9fcef..ab99cee 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-before.html
@@ -23,7 +23,8 @@
         .then(() => assert_promise_rejects_with_message(
           measurement_interval.stopNotifications(),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before stopNotifications. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-during-success.html
index 1dfd304..7c2d2a7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-during-success.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.stopNotifications(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-before.html
index bf00b65..027e96c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-before.html
@@ -19,7 +19,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.stopNotifications(),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-during-success.html
index aad35a2..84ec35e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-during-success.html
@@ -19,7 +19,8 @@
           let promise = assert_promise_rejects_with_message(
             measurement_interval.stopNotifications(),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-garbage-collection-ran-during-success.html
index 0a36b80..76607f8 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/gen-gatt-op-garbage-collection-ran-during-success.html
@@ -18,7 +18,8 @@
       promise = assert_promise_rejects_with_message(
         measurement_interval.stopNotifications(),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/reconnect-during-success.html
index 219b1469..41b4129a 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/stopNotifications/reconnect-during-success.html
@@ -16,7 +16,8 @@
     .then(characteristic => {
       let promise = assert_promise_rejects_with_message(
         characteristic.stopNotifications(),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-before.html
index f5063fc..fb12ba4af 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-before.html
@@ -23,7 +23,8 @@
         .then(() => assert_promise_rejects_with_message(
           measurement_interval.writeValue(val),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before writeValue. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-error.html
index f82eead5..203c9c9 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-error.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             error_characteristic.writeValue(val),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-success.html
index aabfbaf..dee8248 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-success.html
@@ -25,7 +25,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.writeValue(val),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-error.html
index 32057b4..2ffac61 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-error.html
@@ -16,7 +16,8 @@
       let disconnected = eventPromise(characteristic.service.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         characteristic.writeValue(val),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
         .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-success.html
index 33e4c10..6be55d9 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-success.html
@@ -16,7 +16,8 @@
       let disconnected = eventPromise(characteristic.service.device, 'gattserverdisconnected');
       let promise = assert_promise_rejects_with_message(
         characteristic.writeValue(val),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
                          .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-before.html
index 681e8fc..f05c89ee 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-before.html
@@ -19,7 +19,8 @@
           return assert_promise_rejects_with_message(
             measurement_interval.writeValue(val),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-error.html
index f1e5a2b..862f0df 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-error.html
@@ -18,7 +18,8 @@
           let promise = assert_promise_rejects_with_message(
             error_characteristic.writeValue(val),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-success.html
index bf1a48b..7bdf7e3 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-success.html
@@ -19,7 +19,8 @@
           let promise = assert_promise_rejects_with_message(
             measurement_interval.writeValue(val),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-error.html
index 184878da..a72ae34 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-error.html
@@ -18,7 +18,8 @@
       promise = assert_promise_rejects_with_message(
         error_characteristic.writeValue(val),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-success.html
index 8a4f378..5b03c25 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-success.html
@@ -18,7 +18,8 @@
       promise = assert_promise_rejects_with_message(
         measurement_interval.writeValue(val),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-error.html
index 3f706963..10bbbeb 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-error.html
@@ -15,7 +15,8 @@
     .then(characteristic => {
       let promise = assert_promise_rejects_with_message(
         characteristic.writeValue(val),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-success.html
index 3b7531b..726043f4 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-success.html
@@ -15,7 +15,8 @@
     .then(characteristic => {
       let promise = assert_promise_rejects_with_message(
         characteristic.writeValue(val),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-before.html
index bbe10ea4..4c852d4 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-before.html
@@ -27,7 +27,8 @@
                 () => assert_promise_rejects_with_message(
                     user_description.readValue(),
                     new DOMException(
-                        'GATT Server is disconnected. Cannot perform GATT operations.',
+                      'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                        '(Re)connect first with `device.gatt.connect`.',
                         'NetworkError')));
       });
 }, 'Device disconnects before readValue. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-error.html
index 92bf4cd4..d2b38f0 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-error.html
@@ -28,7 +28,8 @@
                   return assert_promise_rejects_with_message(
                       user_description.readValue(),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                 });
           });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-success.html
index 8b37a66..3f47ac9 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-success.html
@@ -27,7 +27,8 @@
                   return assert_promise_rejects_with_message(
                       user_description.readValue(),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                        '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                 });
           });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-error.html
index 295f48d1..ac1fa11 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-error.html
@@ -25,7 +25,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.readValue(),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             return disconnected
                 .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-success.html
index d0643a0..e4a977e6 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-success.html
@@ -25,7 +25,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.readValue(),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             return disconnected
                 .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-before.html
index 8a1ca77f..1808b9e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-before.html
@@ -23,7 +23,8 @@
               return assert_promise_rejects_with_message(
                   descriptor.readValue(),
                   new DOMException(
-                      'GATT Server is disconnected. Cannot perform GATT operations.',
+                    'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                      '(Re)connect first with `device.gatt.connect`.',
                       'NetworkError'));
             });
       });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-error.html
index 6e6a9cf0d..9851482 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-error.html
@@ -23,7 +23,8 @@
                   let promise = assert_promise_rejects_with_message(
                       descriptor.readValue(),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                   gattServer.disconnect();
                   return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-success.html
index c8af17d7..6933150 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-success.html
@@ -25,7 +25,8 @@
                   let promise = assert_promise_rejects_with_message(
                       descriptor.readValue(),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                   gattServer.disconnect();
                   return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-error.html
index dee5638..996c8d6 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-error.html
@@ -23,7 +23,8 @@
             promise = assert_promise_rejects_with_message(
                 error_descriptor.readValue(),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             // Disconnect called to clear attributeInstanceMap and allow the
             // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html
index d379600..3202d43c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html
@@ -24,7 +24,8 @@
             promise = assert_promise_rejects_with_message(
                 descriptor.readValue(),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             // Disconnect called to clear attributeInstanceMap and allow the
             // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html
index c72b5ddc..ba65197 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html
@@ -22,7 +22,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.readValue(),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             let gatt = descriptor.characteristic.service.device.gatt;
             gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-success.html
index f7966e5..0442811 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-success.html
@@ -22,7 +22,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.readValue(),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             let gatt = descriptor.characteristic.service.device.gatt;
             gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.html
index 2decfb8..5325c84 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.html
@@ -27,7 +27,8 @@
                 () => assert_promise_rejects_with_message(
                     user_description.writeValue(val),
                     new DOMException(
-                        'GATT Server is disconnected. Cannot perform GATT operations.',
+                      'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                        '(Re)connect first with `device.gatt.connect`.',
                         'NetworkError')));
       });
 }, 'Device disconnects before writeValue. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.html
index f1f787c6..de767f5 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.html
@@ -28,7 +28,8 @@
                   return assert_promise_rejects_with_message(
                       user_description.writeValue(val),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                 });
           });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.html
index 25a49665..9bc8f5fb 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.html
@@ -27,7 +27,8 @@
                   return assert_promise_rejects_with_message(
                       user_description.writeValue(val),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                        '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                 });
           });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.html
index 9933ca7c..80eeef4 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.html
@@ -25,7 +25,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.writeValue(val),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             return disconnected
                 .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.html
index 4251127c..b5a95a0 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.html
@@ -25,7 +25,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.writeValue(val),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             return disconnected
                 .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.html
index 31f741d..e428fa5 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.html
@@ -23,7 +23,8 @@
               return assert_promise_rejects_with_message(
                   descriptor.writeValue(val),
                   new DOMException(
-                      'GATT Server is disconnected. Cannot perform GATT operations.',
+                    'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                      '(Re)connect first with `device.gatt.connect`.',
                       'NetworkError'));
             });
       });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.html
index 5d5bc150..94ff73a 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.html
@@ -23,7 +23,8 @@
                   let promise = assert_promise_rejects_with_message(
                       descriptor.writeValue(val),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                   gattServer.disconnect();
                   return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.html
index 9e857bc..39bbe896 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.html
@@ -25,7 +25,8 @@
                   let promise = assert_promise_rejects_with_message(
                       descriptor.writeValue(val),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                   gattServer.disconnect();
                   return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.html
index d237ddcb..e8830f37 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.html
@@ -23,7 +23,8 @@
             promise = assert_promise_rejects_with_message(
                 error_descriptor.writeValue(val),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             // Disconnect called to clear attributeInstanceMap and allow the
             // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.html
index 2b525fc..95c93e2 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.html
@@ -24,7 +24,8 @@
             promise = assert_promise_rejects_with_message(
                 descriptor.writeValue(val),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             // Disconnect called to clear attributeInstanceMap and allow the
             // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.html
index d7db91f..3e24c35 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.html
@@ -22,7 +22,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.writeValue(val),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             let gatt = descriptor.characteristic.service.device.gatt;
             gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.html
index f55f7c5..957ef5e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.html
@@ -22,7 +22,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.writeValue(val),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             let gatt = descriptor.characteristic.service.device.gatt;
             gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-error.js
index e2417c60..b7e0ccf 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-error.js
@@ -15,7 +15,8 @@
           getDescriptors()|
           getDescriptors(0x0101)[UUID]]),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-success.js
index 6641316..f72df0a 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/descriptor-garbage-collection-ran-during-success.js
@@ -14,7 +14,8 @@
           getDescriptors()|
           getDescriptors(user_description.name)[UUID]]),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-before.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-before.js
index bd78b10..30278d2 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-before.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-before.js
@@ -24,7 +24,8 @@
             startNotifications()|
             stopNotifications()]),
           new DOMException(
-            'GATT Server is disconnected. Cannot perform GATT operations.',
+            'GATT Server is disconnected. Cannot perform GATT operations. ' +
+            '(Re)connect first with `device.gatt.connect`.',
             'NetworkError')));
     });
 }, 'Device disconnects before FUNCTION_NAME. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-error.js
index 77571980..77a0260 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-error.js
@@ -25,7 +25,8 @@
               writeValue(val)|
               startNotifications()]),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-success.js
index e185fcf..af43404 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-disconnects-during-success.js
@@ -26,7 +26,8 @@
               startNotifications()|
               stopNotifications()]),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-error.js
index 7466754..c597253 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-error.js
@@ -11,7 +11,8 @@
       let promise = assert_promise_rejects_with_message(
         characteristic.CALLS([
           readValue()| writeValue(val)| startNotifications()]),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
         .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-success.js
index d035bd3..8f27c2c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-device-reconnects-during-success.js
@@ -13,7 +13,8 @@
           readValue()|
           writeValue(val)|
           startNotifications()]),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       return disconnected.then(() => characteristic.service.device.gatt.connect())
                          .then(() => promise);
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-before.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-before.js
index 0c6a221..c1b39a1 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-before.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-before.js
@@ -20,7 +20,8 @@
               startNotifications()|
               stopNotifications()]),
             new DOMException(
-              'GATT Server is disconnected. Cannot perform GATT operations.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
         });
     });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-error.js
index d1b85cde..261021f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-error.js
@@ -18,7 +18,8 @@
               writeValue(val)|
               startNotifications()]),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-success.js
index 60afe38..620e19d 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-disconnect-called-during-success.js
@@ -20,7 +20,8 @@
               startNotifications()|
               stopNotifications()]),
             new DOMException(
-              'GATT Server disconnected while performing a GATT operation.',
+              'GATT Server is disconnected. Cannot perform GATT operations. ' +
+              '(Re)connect first with `device.gatt.connect`.',
               'NetworkError'));
           gattServer.disconnect();
           return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-error.js
index 7cb331d7..82c4a6b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-error.js
@@ -15,7 +15,8 @@
           writeValue(val)|
           startNotifications()]),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-success.js
index ad9b1ebb..57340d4 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-garbage-collection-ran-during-success.js
@@ -16,7 +16,8 @@
           startNotifications()|
           stopNotifications()]),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
       // Disconnect called to clear attributeInstanceMap and allow the
       // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-error.js
index 7002563f..fe48ddf 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-error.js
@@ -12,7 +12,8 @@
           readValue()|
           writeValue(val)|
           startNotifications()]),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-success.js
index a4e3b34..5885178 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/characteristic/gatt-op-reconnect-during-success.js
@@ -12,7 +12,8 @@
           readValue()|
           writeValue(val)|
           startNotifications()]),
-        new DOMException('GATT Server disconnected while performing a GATT operation.',
+        new DOMException('GATT Server is disconnected. Cannot perform GATT operations. ' +
+                         '(Re)connect first with `device.gatt.connect`.',
                          'NetworkError'));
       let gatt = characteristic.service.device.gatt;
       gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js
index 26c38f6..9502f94 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js
@@ -21,7 +21,8 @@
                 () => assert_promise_rejects_with_message(
                     user_description.CALLS([readValue()|writeValue(val)]),
                     new DOMException(
-                        'GATT Server is disconnected. Cannot perform GATT operations.',
+                      'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                        '(Re)connect first with `device.gatt.connect`.',
                         'NetworkError')));
       });
 }, 'Device disconnects before FUNCTION_NAME. Reject with NetworkError.');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js
index a5f1a72..a207141 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js
@@ -22,7 +22,8 @@
                   return assert_promise_rejects_with_message(
                       user_description.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                 });
           });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js
index b06f5146..0a800aa 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js
@@ -21,7 +21,8 @@
                   return assert_promise_rejects_with_message(
                       user_description.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                        '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                 });
           });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js
index 7a4f3b38..3cf6fe7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js
@@ -19,7 +19,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             return disconnected
                 .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js
index 1ffc823..2a000181 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js
@@ -19,7 +19,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             return disconnected
                 .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js
index 42c090c..2c1cb53 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js
@@ -17,7 +17,8 @@
               return assert_promise_rejects_with_message(
                   descriptor.CALLS([readValue()|writeValue(val)]),
                   new DOMException(
-                      'GATT Server is disconnected. Cannot perform GATT operations.',
+                    'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                      '(Re)connect first with `device.gatt.connect`.',
                       'NetworkError'));
             });
       });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js
index e8516733..be299a0f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js
@@ -17,7 +17,8 @@
                   let promise = assert_promise_rejects_with_message(
                       descriptor.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                   gattServer.disconnect();
                   return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js
index 62bb24b..c702a63 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js
@@ -19,7 +19,8 @@
                   let promise = assert_promise_rejects_with_message(
                       descriptor.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
-                          'GATT Server disconnected while performing a GATT operation.',
+                        'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                          '(Re)connect first with `device.gatt.connect`.',
                           'NetworkError'));
                   gattServer.disconnect();
                   return promise;
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js
index 250c016f..dc5155d5 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js
@@ -17,7 +17,8 @@
             promise = assert_promise_rejects_with_message(
                 error_descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             // Disconnect called to clear attributeInstanceMap and allow the
             // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js
index 8dad4b49..c231842 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js
@@ -18,7 +18,8 @@
             promise = assert_promise_rejects_with_message(
                 descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             // Disconnect called to clear attributeInstanceMap and allow the
             // object to get garbage collected.
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js
index 3bf5830..e7803f7 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js
@@ -16,7 +16,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             let gatt = descriptor.characteristic.service.device.gatt;
             gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js
index 54c0d89..4bb793e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js
@@ -16,7 +16,8 @@
             let promise = assert_promise_rejects_with_message(
                 descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
-                    'GATT Server disconnected while performing a GATT operation.',
+                  'GATT Server is disconnected. Cannot perform GATT operations. ' +
+                    '(Re)connect first with `device.gatt.connect`.',
                     'NetworkError'));
             let gatt = descriptor.characteristic.service.device.gatt;
             gatt.disconnect();
diff --git a/third_party/WebKit/LayoutTests/editing/inserting/5549929-2.html b/third_party/WebKit/LayoutTests/editing/inserting/5549929-2.html
index aa98ad0..28edda6 100644
--- a/third_party/WebKit/LayoutTests/editing/inserting/5549929-2.html
+++ b/third_party/WebKit/LayoutTests/editing/inserting/5549929-2.html
@@ -1,7 +1,7 @@
 <p>This tests to make sure that a br isn't inserted into a tab span during an InsertLineBreak operation.  You can test for its existence with the DOM inspector or you can look at the render tree.</p>
 <div id="div" contenteditable="true">
-<div><span class="Apple-tab-span" style="white-space:pre">	</span>foo</div>
-<div><span class="Apple-tab-span" style="white-space:pre">	</span>bar</div>
+<div><span style="white-space:pre">&#9;</span>foo</div>
+<div><span style="white-space:pre">&#9;</span>bar</div>
 </div>
 
 <script>
diff --git a/third_party/WebKit/LayoutTests/editing/inserting/insert-paragraph-after-tab-span-and-text.html b/third_party/WebKit/LayoutTests/editing/inserting/insert-paragraph-after-tab-span-and-text.html
deleted file mode 100644
index 457d8c5..0000000
--- a/third_party/WebKit/LayoutTests/editing/inserting/insert-paragraph-after-tab-span-and-text.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<body>
-<div id="test" contenteditable>
-<span class="Apple-tab-span" style="white-space:pre">    hello</span><br>
-<span class="Apple-style-span"><span class="Apple-tab-span" style="white-space: pre; ">    </span>world</span></div>
-<script src="../../resources/dump-as-markup.js"></script>
-<script>
-
-document.body.focus();
-var span = document.getElementsByClassName('Apple-tab-span')[0];
-window.getSelection().collapse(span, 1);
-document.execCommand('InsertParagraph', false, null);
-
-Markup.description('This test ensures WebKit inserts a paragraph separator properly at the end of a tab span.')
-Markup.dump('test');
-
-</script>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/editing/inserting/insert_tab.html b/third_party/WebKit/LayoutTests/editing/inserting/insert_tab.html
index 5aff7a3..09ab41e 100644
--- a/third_party/WebKit/LayoutTests/editing/inserting/insert_tab.html
+++ b/third_party/WebKit/LayoutTests/editing/inserting/insert_tab.html
@@ -13,7 +13,7 @@
     [
         '<div contenteditable>',
             '<span>',
-                '<span class="Apple-tab-span" style="white-space:pre">\t|</span>',
+                '<span style="white-space:pre">\t|</span>',
                 'foo',
              '</span>',
         '</div>'
@@ -31,7 +31,7 @@
         '<div contenteditable>',
             '<span>',
                 'foo',
-                '<span class="Apple-tab-span" style="white-space:pre">\t|</span>',
+                '<span style="white-space:pre">\t|</span>',
              '</span>',
         '</div>'
     ].join('')),
@@ -48,7 +48,7 @@
         '<div contenteditable>',
             '<span>',
                 'fo',
-                '<span class="Apple-tab-span" style="white-space:pre">\t|</span>',
+                '<span style="white-space:pre">\t|</span>',
                 'o',
              '</span>',
         '</div>'
@@ -69,7 +69,7 @@
         '<div contenteditable>',
             '<span>',
                 '<br>',
-                '<span class="Apple-tab-span" style="white-space:pre">\t|</span>',
+                '<span style="white-space:pre">\t|</span>',
                 'foo',
              '</span>',
         '</div>'
diff --git a/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1-expected.txt b/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1-expected.txt
index 90085db..371a9906 100644
--- a/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1-expected.txt
+++ b/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1-expected.txt
@@ -1,3 +1,3 @@
 This tests to see that tabs are put into tab spans when they are copied individually. The pasted tab should be inside of a tab span, not a style span. To run the test manually, paste and then inspect the editable region, and ensure that there is a tab span at the beginning of the editable div.
 
-<span class="Apple-tab-span" style="white-space: pre;">	</span><span class="Apple-tab-span" style="white-space:pre;">	</span>xxx
+<span style="white-space: pre;">	</span><span style="white-space:pre;">	</span>xxx
diff --git a/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1.html b/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1.html
index b7b3004f..87844a0 100644
--- a/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1.html
+++ b/third_party/WebKit/LayoutTests/editing/pasteboard/5761530-1.html
@@ -1,5 +1,5 @@
 <div id="description">This tests to see that tabs are put into tab spans when they are copied individually.  The pasted tab should be inside of a tab span, not a style span.  To run the test manually, paste and then inspect the editable region, and ensure that there is a tab span at the beginning of the editable div.</div>
-<div id="edit" contenteditable="true"><span class="Apple-tab-span" style="white-space:pre;">	</span>xxx</div>
+<div id="edit" contenteditable="true"><span style="white-space:pre;">	</span>xxx</div>
 <script>
 if (window.testRunner)
 	window.testRunner.dumpAsText();
diff --git a/third_party/WebKit/LayoutTests/external/wpt/editing/run/inserttext-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/editing/run/inserttext-expected.txt
index 107b148..1d4e1d92 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/editing/run/inserttext-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/editing/run/inserttext-expected.txt
@@ -20,7 +20,7 @@
 PASS [["inserttext",""]] "foo[bar]baz" queryCommandValue("inserttext") after 
 PASS [["inserttext","\t"]] "foo[]bar": execCommand("inserttext", false, "\t") return value 
 PASS [["inserttext","\t"]] "foo[]bar" checks for modifications to non-editable content 
-FAIL [["inserttext","\t"]] "foo[]bar" compare innerHTML assert_equals: Unexpected innerHTML (after normalizing inline style) expected "foo\tbar" but got "foo<span class=\"Apple-tab-span\" style=\"white-space:pre\">\t</span>bar"
+FAIL [["inserttext","\t"]] "foo[]bar" compare innerHTML assert_equals: Unexpected innerHTML (after normalizing inline style) expected "foo\tbar" but got "foo<span style=\"white-space:pre\">\t</span>bar"
 PASS [["inserttext","\t"]] "foo[]bar" queryCommandIndeterm("inserttext") before 
 PASS [["inserttext","\t"]] "foo[]bar" queryCommandState("inserttext") before 
 PASS [["inserttext","\t"]] "foo[]bar" queryCommandValue("inserttext") before 
@@ -1038,7 +1038,7 @@
 PASS [["inserttext","a"]] "http://a[]" queryCommandValue("inserttext") after 
 PASS [["inserttext","\t"]] "http://a[]": execCommand("inserttext", false, "\t") return value 
 PASS [["inserttext","\t"]] "http://a[]" checks for modifications to non-editable content 
-FAIL [["inserttext","\t"]] "http://a[]" compare innerHTML assert_equals: Unexpected innerHTML (after normalizing inline style) expected "<a href=\"http://a\">http://a</a>\t" but got "http://a<span class=\"Apple-tab-span\" style=\"white-space:pre\">\t</span>"
+FAIL [["inserttext","\t"]] "http://a[]" compare innerHTML assert_equals: Unexpected innerHTML (after normalizing inline style) expected "<a href=\"http://a\">http://a</a>\t" but got "http://a<span style=\"white-space:pre\">\t</span>"
 PASS [["inserttext","\t"]] "http://a[]" queryCommandIndeterm("inserttext") before 
 PASS [["inserttext","\t"]] "http://a[]" queryCommandState("inserttext") before 
 PASS [["inserttext","\t"]] "http://a[]" queryCommandValue("inserttext") before 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness-expected.txt
index d967c7f..21e0ffa 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 100 tests; 77 PASS, 23 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 99 tests; 76 PASS, 23 FAIL, 0 TIMEOUT, 0 NOTRUN.
 FAIL Window interface: attribute onvrdisplayconnect assert_own_property: The global object must have a property "onvrdisplayconnect" expected property "onvrdisplayconnect" missing
 FAIL Window interface: attribute onvrdisplaydisconnect assert_own_property: The global object must have a property "onvrdisplaydisconnect" expected property "onvrdisplaydisconnect" missing
 FAIL Window interface: attribute onvrdisplayactivate assert_own_property: The global object must have a property "onvrdisplayactivate" expected property "onvrdisplayactivate" missing
@@ -70,7 +70,6 @@
 PASS VRFrameData interface object name 
 FAIL VRFrameData interface: existence and properties of interface prototype object assert_equals: class string of VRFrameData.prototype expected "[object VRFrameDataPrototype]" but got "[object VRFrameData]"
 PASS VRFrameData interface: existence and properties of interface prototype object's "constructor" property 
-PASS VRFrameData interface: attribute timestamp 
 PASS VRFrameData interface: attribute leftProjectionMatrix 
 PASS VRFrameData interface: attribute leftViewMatrix 
 PASS VRFrameData interface: attribute rightProjectionMatrix 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness.html b/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness.html
index 958ca2d..77716df9 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webvr/idlharness.html
@@ -174,8 +174,6 @@
 
 [Constructor]
 interface VRFrameData {
-  readonly attribute DOMHighResTimeStamp timestamp;
-
   readonly attribute Float32Array leftProjectionMatrix;
   readonly attribute Float32Array leftViewMatrix;
 
diff --git a/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-change-and-input-events.js b/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-change-and-input-events.js
index ad704da..404031a6 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-change-and-input-events.js
+++ b/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-change-and-input-events.js
@@ -32,7 +32,8 @@
     debug('Click the upper button');
     // Move the cursor on the upper button.
     var spinButton = getElementByPseudoId(internals.oldestShadowRoot(testInput), "-webkit-inner-spin-button");
-    eventSender.mouseMoveTo(testInput.offsetLeft + spinButton.offsetLeft, testInput.offsetTop + testInput.offsetHeight / 4);
+    var rect = spinButton.getBoundingClientRect();
+    eventSender.mouseMoveTo(rect.left, rect.top + rect.height / 4);
     eventSender.mouseDown();
     debug('Triggers only input event on mouseDown');
     shouldBeEqualToString('testInput.value', expectedValue);
diff --git a/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-click-in-iframe.js b/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-click-in-iframe.js
index 2043a829..03e8be11 100644
--- a/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-click-in-iframe.js
+++ b/third_party/WebKit/LayoutTests/fast/forms/resources/common-spinbutton-click-in-iframe.js
@@ -31,9 +31,10 @@
     testInput.focus();
     var spinButton = getSpinButton(testInput);
     if (spinButton) {
+        var rect = spinButton.getBoundingClientRect();
         mouseMoveTo(
-            iframe.offsetLeft + spinButton.offsetLeft + spinButton.offsetWidth / 2,
-            iframe.offsetTop + spinButton.offsetTop + spinButton.offsetHeight / 4);
+            iframe.offsetLeft + rect.left + rect.width / 2,
+            iframe.offsetTop + rect.top + rect.height / 4);
     }
     mouseClick();
     shouldBeEqualToString('testInput.value', config['expectedValue']);
diff --git a/third_party/WebKit/LayoutTests/fast/forms/search/abspos-cancel-button-crash.html b/third_party/WebKit/LayoutTests/fast/forms/search/abspos-cancel-button-crash.html
new file mode 100644
index 0000000..f92dc5f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/search/abspos-cancel-button-crash.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+    #elm::-webkit-search-cancel-button { position: absolute; }
+</style>
+<input id="elm" type="search" value="How's Annie?">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+test(() => {
+    elm.focus();
+}, "No crash or assertion failure.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.cgi b/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.cgi
index 6236627..82df86f 100755
--- a/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.cgi
+++ b/third_party/WebKit/LayoutTests/http/tests/media/video-load-metadata-decode-error.cgi
@@ -30,7 +30,7 @@
 binmode FILE;
 my ($data, $n);
 my $total = 0;
-my $break = $filesize  * 3 / 4;
+my $break = $filesize / 4;
 my $string = "corrupt video";
 seek(FILE, 0, 0);
 
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/caret-change-paint-offset-keep-visual-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/caret-change-paint-offset-keep-visual-expected.html
new file mode 100644
index 0000000..e3df8dd2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/caret-change-paint-offset-keep-visual-expected.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<div style="height: 2px"></div>
+&nbsp;&nbsp;<input id="input" style="outline: none">
+<script>
+onload = function() {
+  input.focus();
+};
+</script>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/caret-change-paint-offset-keep-visual.html b/third_party/WebKit/LayoutTests/paint/invalidation/caret-change-paint-offset-keep-visual.html
new file mode 100644
index 0000000..1ab64af
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/caret-change-paint-offset-keep-visual.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<div id="container" style="position: relative; overflow: hidden; width: 200px; height: 100px">
+<div style="width: 400px; height: 2px"></div>
+&nbsp;&nbsp;<input style="outline: none; position: relative; top: 0; left: 0" id="input">
+</div>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<script>
+onload = function() {
+  input.focus();
+  runAfterLayoutAndPaint(function() {
+    container.scrollLeft = 56;
+    input.style.left = '56px';
+  }, true);
+};
+</script>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/move-caret-in-container-change-paint-offset-keep-visual-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/move-caret-in-container-change-paint-offset-keep-visual-expected.html
new file mode 100644
index 0000000..7303e452
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/move-caret-in-container-change-paint-offset-keep-visual-expected.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<div id="editable" contenteditable="true"
+    style="outline: none; position: absolute; top: 210px; left: 110px; width: 100px">ABCDE</div>
+<script>
+onload = function() {
+  editable.focus();
+  getSelection().collapse(editable.firstChild, 4);
+};
+</script>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/move-caret-in-container-change-paint-offset-keep-visual.html b/third_party/WebKit/LayoutTests/paint/invalidation/move-caret-in-container-change-paint-offset-keep-visual.html
new file mode 100644
index 0000000..a48dee19
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/move-caret-in-container-change-paint-offset-keep-visual.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<div id="fixed" style="position: fixed; width: 0; height: 0; top: 200px; left: 100px">
+  <div id="child" style="position: relative">
+    <!-- This is to isolate layout -->
+    <div style="width: 200px; height: 200px; overflow: hidden">
+      <div id="editable" contenteditable="true" style="outline: none; margin: 10px; width: 100px">ABCDE</div>
+    </div>
+  </div>
+</div>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<script src="resources/text-based-repaint.js"></script>
+<script>
+function repaintTest() {
+  fixed.style.top = '100px';
+  child.style.top = '100px';
+  getSelection().collapse(editable.firstChild, 4);
+}
+
+onload = function() {
+  editable.focus();
+  runAfterLayoutAndPaint(repaintTest, true);
+};
+</script>
diff --git a/third_party/WebKit/LayoutTests/platform/linux/editing/pasteboard/5761530-1-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/editing/pasteboard/5761530-1-expected.txt
deleted file mode 100644
index 619924e..0000000
--- a/third_party/WebKit/LayoutTests/platform/linux/editing/pasteboard/5761530-1-expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-This tests to see that tabs are put into tab spans when they are copied individually. The pasted tab should be inside of a tab span, not a style span. To run the test manually, paste and then inspect the editable region, and ensure that there is a tab span at the beginning of the editable div.
-
-<span class="Apple-tab-span" style="white-space: pre;">	</span><span class="Apple-tab-span" style="white-space:pre;"> </span>xxx
diff --git a/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js b/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js
index 9b6fae0..a73cbc6a3d 100644
--- a/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js
+++ b/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js
@@ -368,7 +368,8 @@
       return () => assert_promise_rejects_with_message(
         characteristic.writeValue(new Uint8Array([0])),
         new DOMException(
-          'GATT Server disconnected while performing a GATT operation.',
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
           'NetworkError'));
     });
 }
diff --git a/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html b/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html
index fc79de0..b3f10feb 100644
--- a/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html
+++ b/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html
@@ -24,9 +24,6 @@
         assert_true(display.getFrameData(fd2));
       }, "getFrameData successfully updated object");
       t.step( () => {
-        // Use assert_equals instead of assert_approx_equals since they should
-        // be completely identical
-        assert_equals(fd1.timestamp, fd2.timestamp);
         for (let i = 0; i < 16; i++) {
           assert_equals(fd1.leftProjectionMatrix[i],
               fd2.leftProjectionMatrix[i]);
diff --git a/third_party/WebKit/LayoutTests/vr/getFrameData_timestamp_updates.html b/third_party/WebKit/LayoutTests/vr/getFrameData_timestamp_updates.html
deleted file mode 100644
index e30d0ba..0000000
--- a/third_party/WebKit/LayoutTests/vr/getFrameData_timestamp_updates.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="../resources/mojo-helpers.js"></script>
-<script src="resources/fake-vr-displays.js"></script>
-<script src="resources/mock-vr-service.js"></script>
-<script src="resources/test-constants.js"></script>
-<script>
-let fakeDisplays = fakeVRDisplays();
-
-vr_test( (t, mock_service) => {
-  return navigator.getVRDisplays().then( (displays) => {
-    var display = displays[0];
-    var expected_pose = VALID_POSE;
-    var fd = new VRFrameData();
-    var old_fd = null;
-    mock_service.mockVRDisplays_[0].setPose(expected_pose);
-    var counter = 0;
-
-    function onFrame() {
-      display.requestAnimationFrame(onFrame);
-      t.step( () => {
-        assert_true(display.getFrameData(fd));
-      }, "getFrameData successfully updated object");
-      t.step( () => {
-        for (let field in expected_pose) {
-          assert_equals(fd.pose[field].length, expected_pose[field].length);
-          for (let i = 0; i < expected_pose[field].length; i++) {
-            assert_approx_equals(fd.pose[field][i], expected_pose[field][i],
-                FLOAT_EPSILON);
-          }
-        }
-      }, "pose matches expectation");
-      if (counter == 0) {
-        old_fd = fd;
-        t.step( () => {
-          // Timestamp should start at 0 for first frame
-          assert_approx_equals(fd.timestamp, 0.0, FLOAT_EPSILON);
-        }, "First framedata matches expectation");
-      } else {
-        // Only the timestamp should change since everything else is static
-        t.step( () => {
-          assert_approx_equals(fd.timestamp, 1000.0 / 60.0, FLOAT_EPSILON);
-          for (let i = 0; i < 16; i++) {
-            assert_approx_equals(fd.leftProjectionMatrix[i],
-                old_fd.leftProjectionMatrix[i], FLOAT_EPSILON);
-            assert_approx_equals(fd.leftViewMatrix[i],
-                old_fd.leftViewMatrix[i], FLOAT_EPSILON);
-            assert_approx_equals(fd.rightProjectionMatrix[i],
-                old_fd.rightProjectionMatrix[i], FLOAT_EPSILON);
-            assert_approx_equals(fd.rightViewMatrix[i],
-                old_fd.rightViewMatrix[i], FLOAT_EPSILON);
-          }
-        }, "Second framedata matches expectation");
-        t.done();
-      }
-      counter++;
-    }
-
-    display.requestAnimationFrame(onFrame);
-  }, (err) => {
-    t.step( () => {
-      assert_unreached("getVRDisplays rejected");
-    });
-  });
-}, [fakeDisplays["Pixel"]],
-"getFrameData only updates expected fields on rAF");
-
-</script>
diff --git a/third_party/WebKit/LayoutTests/vr/resources/mock-vr-service.js b/third_party/WebKit/LayoutTests/vr/resources/mock-vr-service.js
index d7760b3d..53806a0 100644
--- a/third_party/WebKit/LayoutTests/vr/resources/mock-vr-service.js
+++ b/third_party/WebKit/LayoutTests/vr/resources/mock-vr-service.js
@@ -67,7 +67,6 @@
     }
     getVSync() {
       if (this.pose_) {
-        this.pose_.timestamp = this.timeDelta_;
         this.pose_.poseIndex++;
       }
 
@@ -85,7 +84,6 @@
     }
     initPose() {
       this.pose_ = {
-        timestamp: 0,
         orientation: null,
         position: null,
         angularVelocity: null,
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index 20d67a0..8be874f 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -6939,7 +6939,6 @@
     getter pose
     getter rightProjectionMatrix
     getter rightViewMatrix
-    getter timestamp
     method constructor
 interface VRPose
     attribute @@toStringTag
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index fc9c4b2..9e54c16 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -6167,6 +6167,8 @@
   return 0;
 }
 
+// TODO(mustaq) |request| parameter maybe a misuse of HitTestRequest in
+// updateHoverActiveState() since the function doesn't bother with hit-testing.
 void Document::updateHoverActiveState(const HitTestRequest& request,
                                       Element* innerElement,
                                       Scrollbar* hitScrollbar) {
diff --git a/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.cpp b/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.cpp
index b5089bc6..f31f5fcf 100644
--- a/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.cpp
+++ b/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.cpp
@@ -117,8 +117,16 @@
 
 void CaretDisplayItemClient::updateStyleAndLayoutIfNeeded(
     const PositionWithAffinity& caretPosition) {
-  m_previousLayoutBlock = m_layoutBlock;
-  m_previousVisualRect = m_visualRect;
+  // This method may be called multiple times (e.g. in partial lifecycle
+  // updates) before a paint invalidation. We should save m_previousLayoutBlock
+  // and m_visualRectInPreviousLayoutBlock only if they have not been saved
+  // since the last paint invalidation to ensure the caret painted in the
+  // previous paint invalidated block will be invalidated. We don't care about
+  // intermediate changes of layoutBlock because they are not painted.
+  if (!m_previousLayoutBlock) {
+    m_previousLayoutBlock = m_layoutBlock;
+    m_visualRectInPreviousLayoutBlock = m_visualRect;
+  }
 
   LayoutBlock* newLayoutBlock = caretLayoutBlock(caretPosition.anchorNode());
   if (newLayoutBlock != m_layoutBlock) {
@@ -158,31 +166,26 @@
 
 void CaretDisplayItemClient::invalidatePaintIfNeeded(
     const LayoutBlock& block,
-    const PaintInvalidatorContext& context,
-    PaintInvalidationReason layoutBlockPaintInvalidationReason) {
+    const PaintInvalidatorContext& context) {
   if (block == m_layoutBlock) {
-    invalidatePaintInCurrentLayoutBlock(context,
-                                        layoutBlockPaintInvalidationReason);
+    invalidatePaintInCurrentLayoutBlock(context);
     return;
   }
 
-  if (block == m_previousLayoutBlock) {
-    invalidatePaintInPreviousLayoutBlock(context,
-                                         layoutBlockPaintInvalidationReason);
-  }
+  if (block == m_previousLayoutBlock)
+    invalidatePaintInPreviousLayoutBlock(context);
 }
 
 void CaretDisplayItemClient::invalidatePaintInPreviousLayoutBlock(
-    const PaintInvalidatorContext& context,
-    PaintInvalidationReason layoutBlockPaintInvalidationReason) {
+    const PaintInvalidatorContext& context) {
   DCHECK(m_previousLayoutBlock);
 
   ObjectPaintInvalidatorWithContext objectInvalidator(*m_previousLayoutBlock,
                                                       context);
   if (!isImmediateFullPaintInvalidationReason(
-          layoutBlockPaintInvalidationReason)) {
+          m_previousLayoutBlock->fullPaintInvalidationReason())) {
     objectInvalidator.invalidatePaintRectangleWithContext(
-        m_previousVisualRect, PaintInvalidationCaret);
+        m_visualRectInPreviousLayoutBlock, PaintInvalidationCaret);
   }
 
   context.paintingLayer->setNeedsRepaint();
@@ -191,8 +194,7 @@
 }
 
 void CaretDisplayItemClient::invalidatePaintInCurrentLayoutBlock(
-    const PaintInvalidatorContext& context,
-    PaintInvalidationReason layoutBlockPaintInvalidationReason) {
+    const PaintInvalidatorContext& context) {
   DCHECK(m_layoutBlock);
 
   LayoutRect newVisualRect;
@@ -210,14 +212,32 @@
     }
   }
 
-  if (!m_needsPaintInvalidation && newVisualRect == m_visualRect)
+  if (m_layoutBlock == m_previousLayoutBlock)
+    m_previousLayoutBlock = nullptr;
+
+  ObjectPaintInvalidatorWithContext objectInvalidator(*m_layoutBlock, context);
+  if (!m_needsPaintInvalidation && newVisualRect == m_visualRect) {
+    // The caret may change paint offset without changing visual rect, and we
+    // need to invalidate the display item client if the block is doing full
+    // paint invalidation.
+    if (isImmediateFullPaintInvalidationReason(
+            m_layoutBlock->fullPaintInvalidationReason()) ||
+        // For non-SPv2, ForcedSubtreeInvalidationChecking may hint change of
+        // paint offset. See ObjectPaintInvalidatorWithContext::
+        // invalidatePaintIfNeededWithComputedReason().
+        (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() &&
+         (context.forcedSubtreeInvalidationFlags &
+          PaintInvalidatorContext::ForcedSubtreeInvalidationChecking))) {
+      objectInvalidator.invalidateDisplayItemClient(*this,
+                                                    PaintInvalidationCaret);
+    }
     return;
+  }
 
   m_needsPaintInvalidation = false;
 
-  ObjectPaintInvalidatorWithContext objectInvalidator(*m_layoutBlock, context);
   if (!isImmediateFullPaintInvalidationReason(
-          layoutBlockPaintInvalidationReason)) {
+          m_layoutBlock->fullPaintInvalidationReason())) {
     objectInvalidator.fullyInvalidatePaint(PaintInvalidationCaret, m_visualRect,
                                            newVisualRect);
   }
diff --git a/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.h b/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.h
index 576381e..742a3c4 100644
--- a/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.h
+++ b/third_party/WebKit/Source/core/editing/CaretDisplayItemClient.h
@@ -74,10 +74,8 @@
   void updateStyleAndLayoutIfNeeded(const PositionWithAffinity& caretPosition);
 
   // Called during LayoutBlock paint invalidation.
-  void invalidatePaintIfNeeded(
-      const LayoutBlock&,
-      const PaintInvalidatorContext&,
-      PaintInvalidationReason layoutBlockPaintInvalidationReason);
+  void invalidatePaintIfNeeded(const LayoutBlock&,
+                               const PaintInvalidatorContext&);
 
   bool shouldPaintCaret(const LayoutBlock& block) const {
     return &block == m_layoutBlock;
@@ -91,32 +89,25 @@
   String debugName() const final;
 
  private:
-  void invalidatePaintInCurrentLayoutBlock(
-      const PaintInvalidatorContext&,
-      PaintInvalidationReason layoutBlockPaintInvalidationReason);
+  void invalidatePaintInCurrentLayoutBlock(const PaintInvalidatorContext&);
 
-  void invalidatePaintInPreviousLayoutBlock(
-      const PaintInvalidatorContext&,
-      PaintInvalidationReason layoutBlockPaintInvalidationReason);
+  void invalidatePaintInPreviousLayoutBlock(const PaintInvalidatorContext&);
 
   // These are updated by updateStyleAndLayoutIfNeeded().
   Color m_color;
   LayoutRect m_localRect;
   LayoutBlock* m_layoutBlock = nullptr;
 
-  // This is set to the previous value of m_layoutBlock during
-  // updateStyleAndLayoutIfNeeded() and can be used in invalidatePaintIfNeeded()
-  // only.
-  const LayoutBlock* m_previousLayoutBlock = nullptr;
-
   // Visual rect of the caret in m_layoutBlock. This is updated by
   // invalidatePaintIfNeeded().
   LayoutRect m_visualRect;
 
-  // This is set to the previous value of m_visualRect during
-  // updateStyleAndLayoutIfNeeded() and can be used in invalidatePaintIfNeeded()
-  // only.
-  LayoutRect m_previousVisualRect;
+  // These are set to the previous value of m_layoutBlock and m_visualRect
+  // during updateStyleAndLayoutIfNeeded() if they haven't been set since the
+  // last paint invalidation. They can only be used in invalidatePaintIfNeeded()
+  // to invalidate the caret in the previous layout block.
+  const LayoutBlock* m_previousLayoutBlock = nullptr;
+  LayoutRect m_visualRectInPreviousLayoutBlock;
 
   bool m_needsPaintInvalidation = false;
 };
diff --git a/third_party/WebKit/Source/core/editing/CaretDisplayItemClientTest.cpp b/third_party/WebKit/Source/core/editing/CaretDisplayItemClientTest.cpp
index 141f5a4..8cb6cd6b 100644
--- a/third_party/WebKit/Source/core/editing/CaretDisplayItemClientTest.cpp
+++ b/third_party/WebKit/Source/core/editing/CaretDisplayItemClientTest.cpp
@@ -50,6 +50,14 @@
     document().body()->appendChild(block);
     return block;
   }
+
+  void updateAllLifecyclePhases() {
+    // Partial lifecycle updates should not affect caret paint invalidation.
+    document().view()->updateLifecycleToLayoutClean();
+    document().view()->updateAllLifecyclePhases();
+    // Partial lifecycle updates should not affect caret paint invalidation.
+    document().view()->updateLifecycleToLayoutClean();
+  }
 };
 
 TEST_F(CaretDisplayItemClientTest, CaretPaintInvalidation) {
@@ -58,13 +66,13 @@
   document().page()->focusController().setFocused(true);
 
   Text* text = appendTextNode("Hello, World!");
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
   const auto* block = toLayoutBlock(document().body()->layoutObject());
 
   // Focus the body. Should invalidate the new caret.
   document().view()->setTracksPaintInvalidations(true);
   document().body()->focus();
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
   EXPECT_TRUE(block->shouldPaintCursorCaret());
 
   LayoutRect caretVisualRect = caretDisplayItemClient().visualRect();
@@ -91,7 +99,7 @@
   document().view()->setTracksPaintInvalidations(true);
   selection().setSelection(
       SelectionInDOMTree::Builder().collapse(Position(text, 5)).build());
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
   EXPECT_TRUE(block->shouldPaintCursorCaret());
 
   LayoutRect newCaretVisualRect = caretDisplayItemClient().visualRect();
@@ -121,7 +129,7 @@
   LayoutRect oldCaretVisualRect = newCaretVisualRect;
   document().view()->setTracksPaintInvalidations(true);
   selection().setSelection(SelectionInDOMTree());
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
   EXPECT_FALSE(block->shouldPaintCursorCaret());
   EXPECT_EQ(LayoutRect(), caretDisplayItemClient().visualRect());
 
@@ -146,13 +154,13 @@
   document().page()->focusController().setFocused(true);
   auto* blockElement1 = appendBlock("Block1");
   auto* blockElement2 = appendBlock("Block2");
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
   auto* block1 = toLayoutBlock(blockElement1->layoutObject());
   auto* block2 = toLayoutBlock(blockElement2->layoutObject());
 
   // Focus the body.
   document().body()->focus();
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
   LayoutRect caretVisualRect1 = caretDisplayItemClient().visualRect();
   EXPECT_EQ(1, caretVisualRect1.width());
   EXPECT_EQ(block1->visualRect().location(), caretVisualRect1.location());
@@ -164,7 +172,7 @@
   selection().setSelection(SelectionInDOMTree::Builder()
                                .collapse(Position(blockElement2, 0))
                                .build());
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
 
   LayoutRect caretVisualRect2 = caretDisplayItemClient().visualRect();
   EXPECT_EQ(1, caretVisualRect2.width());
@@ -192,7 +200,7 @@
   selection().setSelection(SelectionInDOMTree::Builder()
                                .collapse(Position(blockElement1, 0))
                                .build());
-  document().view()->updateAllLifecyclePhases();
+  updateAllLifecyclePhases();
 
   EXPECT_EQ(caretVisualRect1, caretDisplayItemClient().visualRect());
   EXPECT_TRUE(block1->shouldPaintCursorCaret());
diff --git a/third_party/WebKit/Source/core/editing/DragCaret.cpp b/third_party/WebKit/Source/core/editing/DragCaret.cpp
index e7aeb223..1b7564c60 100644
--- a/third_party/WebKit/Source/core/editing/DragCaret.cpp
+++ b/third_party/WebKit/Source/core/editing/DragCaret.cpp
@@ -55,10 +55,10 @@
                                                    : PositionWithAffinity());
 }
 
-void DragCaret::invalidatePaintIfNeeded(const LayoutBlock& block,
-                                        const PaintInvalidatorContext& context,
-                                        PaintInvalidationReason reason) {
-  m_displayItemClient->invalidatePaintIfNeeded(block, context, reason);
+void DragCaret::invalidatePaintIfNeeded(
+    const LayoutBlock& block,
+    const PaintInvalidatorContext& context) {
+  m_displayItemClient->invalidatePaintIfNeeded(block, context);
 }
 
 bool DragCaret::isContentRichlyEditable() const {
diff --git a/third_party/WebKit/Source/core/editing/DragCaret.h b/third_party/WebKit/Source/core/editing/DragCaret.h
index 75aa640..562b395 100644
--- a/third_party/WebKit/Source/core/editing/DragCaret.h
+++ b/third_party/WebKit/Source/core/editing/DragCaret.h
@@ -52,8 +52,7 @@
   void layoutBlockWillBeDestroyed(const LayoutBlock&);
   void updateStyleAndLayoutIfNeeded();
   void invalidatePaintIfNeeded(const LayoutBlock&,
-                               const PaintInvalidatorContext&,
-                               PaintInvalidationReason);
+                               const PaintInvalidatorContext&);
 
   bool shouldPaintCaret(const LayoutBlock&) const;
   void paintDragCaret(const LocalFrame*,
diff --git a/third_party/WebKit/Source/core/editing/EditingUtilities.cpp b/third_party/WebKit/Source/core/editing/EditingUtilities.cpp
index c4fd9c0..b0e8ece82 100644
--- a/third_party/WebKit/Source/core/editing/EditingUtilities.cpp
+++ b/third_party/WebKit/Source/core/editing/EditingUtilities.cpp
@@ -1572,11 +1572,12 @@
 }
 
 bool isTabHTMLSpanElement(const Node* node) {
-  if (!isHTMLSpanElement(node) ||
-      toHTMLSpanElement(node)->getAttribute(classAttr) != AppleTabSpanClass)
+  if (!isHTMLSpanElement(node) || !node->firstChild())
     return false;
-  UseCounter::count(node->document(), UseCounter::EditingAppleTabSpanClass);
-  return true;
+  if (node->firstChild()->isCharacterDataNode() &&
+      toCharacterData(node->firstChild())->data().contains('\t'))
+    return true;
+  return false;
 }
 
 bool isTabHTMLSpanElementTextNode(const Node* node) {
@@ -1594,7 +1595,6 @@
                                              Text* tabTextNode) {
   // Make the span to hold the tab.
   HTMLSpanElement* spanElement = HTMLSpanElement::create(document);
-  spanElement->setAttribute(classAttr, AppleTabSpanClass);
   spanElement->setAttribute(styleAttr, "white-space:pre");
 
   // Add tab text to that span.
diff --git a/third_party/WebKit/Source/core/editing/FrameCaret.cpp b/third_party/WebKit/Source/core/editing/FrameCaret.cpp
index 0024a45b..cba2d8cf 100644
--- a/third_party/WebKit/Source/core/editing/FrameCaret.cpp
+++ b/third_party/WebKit/Source/core/editing/FrameCaret.cpp
@@ -146,10 +146,10 @@
       shouldPaintCaret ? caretPosition() : PositionWithAffinity());
 }
 
-void FrameCaret::invalidatePaintIfNeeded(const LayoutBlock& block,
-                                         const PaintInvalidatorContext& context,
-                                         PaintInvalidationReason reason) {
-  m_displayItemClient->invalidatePaintIfNeeded(block, context, reason);
+void FrameCaret::invalidatePaintIfNeeded(
+    const LayoutBlock& block,
+    const PaintInvalidatorContext& context) {
+  m_displayItemClient->invalidatePaintIfNeeded(block, context);
 }
 
 bool FrameCaret::caretPositionIsValidForDocument(
diff --git a/third_party/WebKit/Source/core/editing/FrameCaret.h b/third_party/WebKit/Source/core/editing/FrameCaret.h
index 50639c1..2b42892 100644
--- a/third_party/WebKit/Source/core/editing/FrameCaret.h
+++ b/third_party/WebKit/Source/core/editing/FrameCaret.h
@@ -78,8 +78,7 @@
   void layoutBlockWillBeDestroyed(const LayoutBlock&);
   void updateStyleAndLayoutIfNeeded();
   void invalidatePaintIfNeeded(const LayoutBlock&,
-                               const PaintInvalidatorContext&,
-                               PaintInvalidationReason);
+                               const PaintInvalidatorContext&);
 
   bool shouldPaintCaret(const LayoutBlock&) const;
   void paintCaret(GraphicsContext&, const LayoutPoint&) const;
diff --git a/third_party/WebKit/Source/core/editing/FrameSelection.cpp b/third_party/WebKit/Source/core/editing/FrameSelection.cpp
index 03148bbd..4cc7856 100644
--- a/third_party/WebKit/Source/core/editing/FrameSelection.cpp
+++ b/third_party/WebKit/Source/core/editing/FrameSelection.cpp
@@ -452,9 +452,8 @@
 
 void FrameSelection::invalidatePaintIfNeeded(
     const LayoutBlock& block,
-    const PaintInvalidatorContext& context,
-    PaintInvalidationReason reason) {
-  m_frameCaret->invalidatePaintIfNeeded(block, context, reason);
+    const PaintInvalidatorContext& context) {
+  m_frameCaret->invalidatePaintIfNeeded(block, context);
 }
 
 bool FrameSelection::shouldPaintCaret(const LayoutBlock& block) const {
diff --git a/third_party/WebKit/Source/core/editing/FrameSelection.h b/third_party/WebKit/Source/core/editing/FrameSelection.h
index 9a7c7711..27227ac 100644
--- a/third_party/WebKit/Source/core/editing/FrameSelection.h
+++ b/third_party/WebKit/Source/core/editing/FrameSelection.h
@@ -199,8 +199,7 @@
   void layoutBlockWillBeDestroyed(const LayoutBlock&);
   void updateStyleAndLayoutIfNeeded();
   void invalidatePaintIfNeeded(const LayoutBlock&,
-                               const PaintInvalidatorContext&,
-                               PaintInvalidationReason);
+                               const PaintInvalidatorContext&);
 
   void paintCaret(GraphicsContext&, const LayoutPoint&);
 
diff --git a/third_party/WebKit/Source/core/editing/commands/ReplaceSelectionCommand.cpp b/third_party/WebKit/Source/core/editing/commands/ReplaceSelectionCommand.cpp
index a0a4b7a..a0dcc9c2 100644
--- a/third_party/WebKit/Source/core/editing/commands/ReplaceSelectionCommand.cpp
+++ b/third_party/WebKit/Source/core/editing/commands/ReplaceSelectionCommand.cpp
@@ -997,11 +997,6 @@
   // one of our internal classes.
   const HTMLElement* element = toHTMLElement(node);
   const AtomicString& classAttributeValue = element->getAttribute(classAttr);
-  if (classAttributeValue == AppleTabSpanClass) {
-    UseCounter::count(element->document(),
-                      UseCounter::EditingAppleTabSpanClass);
-    return true;
-  }
   if (classAttributeValue == AppleConvertedSpace) {
     UseCounter::count(element->document(),
                       UseCounter::EditingAppleConvertedSpace);
diff --git a/third_party/WebKit/Source/core/editing/serializers/HTMLInterchange.h b/third_party/WebKit/Source/core/editing/serializers/HTMLInterchange.h
index 5addf94..baaa58f 100644
--- a/third_party/WebKit/Source/core/editing/serializers/HTMLInterchange.h
+++ b/third_party/WebKit/Source/core/editing/serializers/HTMLInterchange.h
@@ -34,7 +34,6 @@
 
 #define AppleInterchangeNewline "Apple-interchange-newline"
 #define AppleConvertedSpace "Apple-converted-space"
-#define AppleTabSpanClass "Apple-tab-span"
 
 enum EAnnotateForInterchange {
   DoNotAnnotateForInterchange,
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/HotModeSpellCheckRequester.cpp b/third_party/WebKit/Source/core/editing/spellcheck/HotModeSpellCheckRequester.cpp
index 3b106ce..845cd1f 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/HotModeSpellCheckRequester.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/HotModeSpellCheckRequester.cpp
@@ -73,11 +73,13 @@
                                       .build();
   BackwardsCharacterIterator backwardIterator(fullRange.startPosition(),
                                               position, behavior);
-  backwardIterator.advance(kHotModeChunkSize / 2);
+  if (!backwardIterator.atEnd())
+    backwardIterator.advance(kHotModeChunkSize / 2);
   const Position& chunkStart = backwardIterator.endPosition();
   CharacterIterator forwardIterator(position, fullRange.endPosition(),
                                     behavior);
-  forwardIterator.advance(kHotModeChunkSize / 2);
+  if (!forwardIterator.atEnd())
+    forwardIterator.advance(kHotModeChunkSize / 2);
   const Position& chunkEnd = forwardIterator.endPosition();
   return expandRangeToSentenceBoundary(EphemeralRange(chunkStart, chunkEnd));
 }
diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp
index 5101980..85977cb6 100644
--- a/third_party/WebKit/Source/core/frame/FrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -1415,7 +1415,7 @@
   layoutSize.setWidth(layoutViewItem.viewWidth(IncludeScrollbars) / zoom);
   layoutSize.setHeight(layoutViewItem.viewHeight(IncludeScrollbars) / zoom);
 
-  BrowserControls& browserControls = m_frame->host()->browserControls();
+  BrowserControls& browserControls = m_frame->page()->browserControls();
   if (RuntimeEnabledFeatures::inertTopControlsEnabled() &&
       browserControls.permittedState() != WebBrowserControlsHidden) {
     // We use the layoutSize rather than frameRect to calculate viewport units
@@ -1646,7 +1646,7 @@
   }
 
   if (RuntimeEnabledFeatures::inertTopControlsEnabled() && layoutView() &&
-      m_frame->isMainFrame() && m_frame->host()->browserControls().height()) {
+      m_frame->isMainFrame() && m_frame->page()->browserControls().height()) {
     if (layoutView()->style()->hasFixedBackgroundImage()) {
       // In the case where we don't change layout size from top control resizes,
       // we wont perform a layout. If we have a fixed background image however,
diff --git a/third_party/WebKit/Source/core/frame/UseCounter.h b/third_party/WebKit/Source/core/frame/UseCounter.h
index c36bfab..7f4d5aa3 100644
--- a/third_party/WebKit/Source/core/frame/UseCounter.h
+++ b/third_party/WebKit/Source/core/frame/UseCounter.h
@@ -333,7 +333,6 @@
     EditingAppleConvertedSpace = 459,
     EditingApplePasteAsQuotation = 460,
     EditingAppleStyleSpanClass = 461,
-    EditingAppleTabSpanClass = 462,
     HTMLImportsAsyncAttribute = 463,
     XMLHttpRequestSynchronous = 465,
     CSSSelectorPseudoUnresolved = 466,
diff --git a/third_party/WebKit/Source/core/frame/VisualViewport.cpp b/third_party/WebKit/Source/core/frame/VisualViewport.cpp
index 5fe74c2..4e9194d 100644
--- a/third_party/WebKit/Source/core/frame/VisualViewport.cpp
+++ b/third_party/WebKit/Source/core/frame/VisualViewport.cpp
@@ -33,7 +33,6 @@
 #include <memory>
 #include "core/dom/DOMNodeIds.h"
 #include "core/dom/TaskRunnerHelper.h"
-#include "core/frame/FrameHost.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/LocalFrameClient.h"
@@ -254,7 +253,7 @@
   if (scale != m_scale) {
     m_scale = scale;
     valuesChanged = true;
-    frameHost().page().chromeClient().pageScaleFactorChanged();
+    page().chromeClient().pageScaleFactorChanged();
     enqueueResizeEvent();
   }
 
@@ -266,11 +265,10 @@
 
     // SVG runs with accelerated compositing disabled so no
     // ScrollingCoordinator.
-    if (ScrollingCoordinator* coordinator =
-            frameHost().page().scrollingCoordinator())
+    if (ScrollingCoordinator* coordinator = page().scrollingCoordinator())
       coordinator->scrollableAreaScrollLayerDidChange(this);
 
-    if (!frameHost().page().settings().getInertVisualViewport()) {
+    if (!page().settings().getInertVisualViewport()) {
       if (Document* document = mainFrame()->document())
         document->enqueueScrollEventForNode(document);
     }
@@ -295,9 +293,8 @@
 bool VisualViewport::magnifyScaleAroundAnchor(float magnifyDelta,
                                               const FloatPoint& anchor) {
   const float oldPageScale = scale();
-  const float newPageScale =
-      frameHost().page().chromeClient().clampPageScaleFactorToLimits(
-          magnifyDelta * oldPageScale);
+  const float newPageScale = page().chromeClient().clampPageScaleFactorToLimits(
+      magnifyDelta * oldPageScale);
   if (newPageScale == oldPageScale)
     return false;
   if (!mainFrame() || !mainFrame()->view())
@@ -336,7 +333,7 @@
   m_overlayScrollbarHorizontal = GraphicsLayer::create(this);
   m_overlayScrollbarVertical = GraphicsLayer::create(this);
 
-  ScrollingCoordinator* coordinator = frameHost().page().scrollingCoordinator();
+  ScrollingCoordinator* coordinator = page().scrollingCoordinator();
   DCHECK(coordinator);
   coordinator->setLayerIsContainerForFixedPositionLayers(
       m_innerViewportScrollLayer.get(), true);
@@ -344,7 +341,7 @@
   // Set masks to bounds so the compositor doesn't clobber a manually
   // set inner viewport container layer size.
   m_innerViewportContainerLayer->setMasksToBounds(
-      frameHost().page().settings().getMainFrameClipsContent());
+      page().settings().getMainFrameClipsContent());
   m_innerViewportContainerLayer->setSize(FloatSize(m_size));
 
   m_innerViewportScrollLayer->platformLayer()->setScrollClipLayer(
@@ -392,7 +389,7 @@
     return;
 
   if (visualViewportSuppliesScrollbars() &&
-      !frameHost().page().settings().getHideScrollbars()) {
+      !page().settings().getHideScrollbars()) {
     if (!m_overlayScrollbarHorizontal->parent())
       m_innerViewportContainerLayer->addChild(
           m_overlayScrollbarHorizontal.get());
@@ -429,8 +426,7 @@
   int scrollbarMargin = theme.scrollbarMargin();
 
   if (!webScrollbarLayer) {
-    ScrollingCoordinator* coordinator =
-        frameHost().page().scrollingCoordinator();
+    ScrollingCoordinator* coordinator = page().scrollingCoordinator();
     ASSERT(coordinator);
     ScrollbarOrientation webcoreOrientation =
         isHorizontal ? HorizontalScrollbar : VerticalScrollbar;
@@ -469,15 +465,15 @@
 }
 
 bool VisualViewport::visualViewportSuppliesScrollbars() const {
-  return frameHost().page().settings().getViewportEnabled();
+  return page().settings().getViewportEnabled();
 }
 
 bool VisualViewport::scrollAnimatorEnabled() const {
-  return frameHost().page().settings().getScrollAnimatorEnabled();
+  return page().settings().getScrollAnimatorEnabled();
 }
 
 HostWindow* VisualViewport::getHostWindow() const {
-  return &frameHost().page().chromeClient();
+  return &page().chromeClient();
 }
 
 bool VisualViewport::shouldUseIntegerScrollOffset() const {
@@ -528,11 +524,8 @@
   FloatSize frameViewSize(contentsSize());
 
   if (m_browserControlsAdjustment) {
-    float minScale = frameHost()
-                         .page()
-                         .pageScaleConstraintsSet()
-                         .finalConstraints()
-                         .minimumScale;
+    float minScale =
+        page().pageScaleConstraintsSet().finalConstraints().minimumScale;
     frameViewSize.expand(0, m_browserControlsAdjustment / minScale);
   }
 
@@ -653,9 +646,8 @@
                                    const IntRect&) const {}
 
 LocalFrame* VisualViewport::mainFrame() const {
-  return frameHost().page().mainFrame() &&
-                 frameHost().page().mainFrame()->isLocalFrame()
-             ? frameHost().page().deprecatedLocalMainFrame()
+  return page().mainFrame() && page().mainFrame()->isLocalFrame()
+             ? page().deprecatedLocalMainFrame()
              : 0;
 }
 
@@ -783,7 +775,7 @@
   //    the initial viewport width.
   // 2. The author has disabled viewport zoom.
   const PageScaleConstraints& constraints =
-      frameHost().page().pageScaleConstraintsSet().pageDefinedConstraints();
+      page().pageScaleConstraintsSet().pageDefinedConstraints();
 
   return mainFrame()->view()->layoutSize().width() == m_size.width() ||
          (constraints.minimumScale == constraints.maximumScale &&
@@ -791,15 +783,15 @@
 }
 
 CompositorAnimationHost* VisualViewport::compositorAnimationHost() const {
-  DCHECK(frameHost().page().mainFrame()->isLocalFrame());
-  ScrollingCoordinator* c = frameHost().page().scrollingCoordinator();
+  DCHECK(page().mainFrame()->isLocalFrame());
+  ScrollingCoordinator* c = page().scrollingCoordinator();
   return c ? c->compositorAnimationHost() : nullptr;
 }
 
 CompositorAnimationTimeline* VisualViewport::compositorAnimationTimeline()
     const {
-  DCHECK(frameHost().page().mainFrame()->isLocalFrame());
-  ScrollingCoordinator* c = frameHost().page().scrollingCoordinator();
+  DCHECK(page().mainFrame()->isLocalFrame());
+  ScrollingCoordinator* c = page().scrollingCoordinator();
   return c ? c->compositorAnimationTimeline() : nullptr;
 }
 
diff --git a/third_party/WebKit/Source/core/frame/VisualViewport.h b/third_party/WebKit/Source/core/frame/VisualViewport.h
index de0a59a..582e015 100644
--- a/third_party/WebKit/Source/core/frame/VisualViewport.h
+++ b/third_party/WebKit/Source/core/frame/VisualViewport.h
@@ -31,8 +31,10 @@
 #ifndef VisualViewport_h
 #define VisualViewport_h
 
+#include <memory>
 #include "core/CoreExport.h"
 #include "core/events/Event.h"
+#include "core/frame/FrameHost.h"
 #include "platform/geometry/FloatRect.h"
 #include "platform/geometry/FloatSize.h"
 #include "platform/geometry/IntSize.h"
@@ -40,7 +42,6 @@
 #include "platform/scroll/ScrollableArea.h"
 #include "public/platform/WebScrollbar.h"
 #include "public/platform/WebSize.h"
-#include <memory>
 
 namespace blink {
 class WebScrollbarLayer;
@@ -48,12 +49,12 @@
 
 namespace blink {
 
-class FrameHost;
 class GraphicsContext;
 class GraphicsLayer;
 class IntRect;
 class IntSize;
 class LocalFrame;
+class Page;
 
 // Represents the visual viewport the user is currently seeing the page through.
 // This class corresponds to the InnerViewport on the compositor. It is a
@@ -261,9 +262,9 @@
 
   LocalFrame* mainFrame() const;
 
-  FrameHost& frameHost() const {
-    ASSERT(m_frameHost);
-    return *m_frameHost;
+  Page& page() const {
+    DCHECK(m_frameHost);
+    return m_frameHost->page();
   }
 
   Member<FrameHost> m_frameHost;
diff --git a/third_party/WebKit/Source/core/input/EventHandler.cpp b/third_party/WebKit/Source/core/input/EventHandler.cpp
index 87a3a8de..6f022ada 100644
--- a/third_party/WebKit/Source/core/input/EventHandler.cpp
+++ b/third_party/WebKit/Source/core/input/EventHandler.cpp
@@ -816,7 +816,7 @@
 
   // Treat any mouse move events as readonly if the user is currently touching
   // the screen.
-  if (m_pointerEventManager->isAnyTouchActive())
+  if (m_pointerEventManager->isAnyTouchActive() && !forceLeave)
     hitType |= HitTestRequest::Active | HitTestRequest::ReadOnly;
   HitTestRequest request(hitType);
   MouseEventWithHitTestResults mev = MouseEventWithHitTestResults(
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlock.cpp b/third_party/WebKit/Source/core/layout/LayoutBlock.cpp
index 841db4b2..7aa6ac9 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBlock.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBlock.cpp
@@ -239,7 +239,17 @@
     textAutosizer->record(this);
 
   propagateStyleToAnonymousChildren();
+
+  // The LayoutView is always a container of fixed positioned descendants. In
+  // addition, SVG foreignObjects become such containers, so that descendants
+  // of a foreignObject cannot escape it. Similarly, text controls let authors
+  // select elements inside that are created by user agent shadow DOM, and we
+  // have (C++) code that assumes that the elements are indeed contained by the
+  // text control. So just make sure this is the case. Finally, computed style
+  // may turn us into a container of all things, e.g. if the element is
+  // transformed, or contain:paint is specified.
   setCanContainFixedPositionObjects(isLayoutView() || isSVGForeignObject() ||
+                                    isTextControl() ||
                                     newStyle.canContainFixedPositionObjects());
 
   // It's possible for our border/padding to change, but for the overall logical
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.cpp b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
index bbed5e06..07ed841b 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
@@ -1982,7 +1982,7 @@
   // If this box has a transform or contains paint, it acts as a fixed position
   // container for fixed descendants, and may itself also be fixed position. So
   // propagate 'fixed' up only if this box is fixed position.
-  if (style()->canContainFixedPositionObjects() && !isFixedPos)
+  if (canContainFixedPositionObjects() && !isFixedPos)
     mode &= ~IsFixed;
   else if (isFixedPos)
     mode |= IsFixed;
@@ -2001,7 +2001,7 @@
   // If this box has a transform or contains paint, it acts as a fixed position
   // container for fixed descendants, and may itself also be fixed position. So
   // propagate 'fixed' up only if this box is fixed position.
-  if (style()->canContainFixedPositionObjects() && !isFixedPos)
+  if (canContainFixedPositionObjects() && !isFixedPos)
     mode &= ~IsFixed;
   else if (isFixedPos)
     mode |= IsFixed;
diff --git a/third_party/WebKit/Source/core/layout/LayoutGeometryMap.cpp b/third_party/WebKit/Source/core/layout/LayoutGeometryMap.cpp
index 9c55011..be27311 100644
--- a/third_party/WebKit/Source/core/layout/LayoutGeometryMap.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutGeometryMap.cpp
@@ -196,10 +196,11 @@
        current = current->parent()) {
     const ComputedStyle& style = current->styleRef();
     if (style.position() == EPosition::kFixed ||
-        style.isFlippedBlocksWritingMode())
+        style.isFlippedBlocksWritingMode() ||
+        style.hasTransformRelatedProperty())
       return false;
 
-    if (current->style()->canContainFixedPositionObjects() ||
+    if (current->canContainFixedPositionObjects() ||
         current->isLayoutFlowThread() || current->isSVGRoot())
       return false;
 
diff --git a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp
index 3e727a9f..043fc9e1 100644
--- a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp
+++ b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp
@@ -1532,7 +1532,7 @@
   // the scroll layer is further up in the hierarchy, we need to avoid marking
   // the root layout view layer as a container.
   bool isContainer =
-      m_owningLayer.layoutObject().style()->canContainFixedPositionObjects() &&
+      m_owningLayer.layoutObject().canContainFixedPositionObjects() &&
       !m_owningLayer.isRootLayer();
   scrollingCoordinator->setLayerIsContainerForFixedPositionLayers(
       m_graphicsLayer.get(), isContainer);
diff --git a/third_party/WebKit/Source/core/page/scrolling/TopDocumentRootScrollerController.cpp b/third_party/WebKit/Source/core/page/scrolling/TopDocumentRootScrollerController.cpp
index 3734203..1082460 100644
--- a/third_party/WebKit/Source/core/page/scrolling/TopDocumentRootScrollerController.cpp
+++ b/third_party/WebKit/Source/core/page/scrolling/TopDocumentRootScrollerController.cpp
@@ -227,8 +227,8 @@
     RootFrameViewport& rootFrameViewport) {
   DCHECK(m_frameHost);
   m_viewportApplyScroll = ViewportScrollCallback::create(
-      &m_frameHost->browserControls(), &m_frameHost->overscrollController(),
-      rootFrameViewport);
+      &m_frameHost->page().browserControls(),
+      &m_frameHost->overscrollController(), rootFrameViewport);
 
   recomputeGlobalRootScroller();
 }
diff --git a/third_party/WebKit/Source/core/paint/BlockPaintInvalidator.cpp b/third_party/WebKit/Source/core/paint/BlockPaintInvalidator.cpp
index a7db1eb..17c818d5 100644
--- a/third_party/WebKit/Source/core/paint/BlockPaintInvalidator.cpp
+++ b/third_party/WebKit/Source/core/paint/BlockPaintInvalidator.cpp
@@ -25,11 +25,11 @@
   PaintInvalidationReason reason =
       BoxPaintInvalidator(m_block, context).invalidatePaintIfNeeded();
 
-  m_block.frame()->selection().invalidatePaintIfNeeded(m_block, context,
-                                                       reason);
-  m_block.frame()->page()->dragCaret().invalidatePaintIfNeeded(m_block, context,
-                                                               reason);
+  m_block.frame()->selection().invalidatePaintIfNeeded(m_block, context);
+  m_block.frame()->page()->dragCaret().invalidatePaintIfNeeded(m_block,
+                                                               context);
 
   return reason;
 }
-}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/timing/PerformanceResourceTiming.cpp b/third_party/WebKit/Source/core/timing/PerformanceResourceTiming.cpp
index 3353a97e..3b4eb06 100644
--- a/third_party/WebKit/Source/core/timing/PerformanceResourceTiming.cpp
+++ b/third_party/WebKit/Source/core/timing/PerformanceResourceTiming.cpp
@@ -136,10 +136,10 @@
 }
 
 DOMHighResTimeStamp PerformanceResourceTiming::fetchStart() const {
+  if (!m_timing)
+    return PerformanceEntry::startTime();
+
   if (m_lastRedirectEndTime) {
-    // FIXME: ASSERT(m_timing) should be in constructor once timeticks of
-    // AppCache is exposed from chrome network stack, crbug/251100
-    ASSERT(m_timing);
     return PerformanceBase::monotonicTimeToDOMHighResTimeStamp(
         m_timeOrigin, m_timing->requestTime());
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/GlassPane.js b/third_party/WebKit/Source/devtools/front_end/ui/GlassPane.js
index 82888888..29344488 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/GlassPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/GlassPane.js
@@ -234,6 +234,8 @@
         }
 
         positionX = Math.max(gutterSize, Math.min(anchorBox.x, containerWidth - width - gutterSize));
+        if (this._showArrow && positionX - arrowSize >= gutterSize)
+          positionX -= arrowSize;
         width = Math.min(width, containerWidth - positionX - gutterSize);
         if (2 * arrowSize >= width) {
           this._arrowElement.classList.add('arrow-none');
@@ -284,6 +286,8 @@
         }
 
         positionY = Math.max(gutterSize, Math.min(anchorBox.y, containerHeight - height - gutterSize));
+        if (this._showArrow && positionY - arrowSize >= gutterSize)
+          positionY -= arrowSize;
         height = Math.min(height, containerHeight - positionY - gutterSize);
         if (2 * arrowSize >= height) {
           this._arrowElement.classList.add('arrow-none');
diff --git a/third_party/WebKit/Source/modules/bluetooth/BluetoothError.cpp b/third_party/WebKit/Source/modules/bluetooth/BluetoothError.cpp
index 1b15a0118..78e59fc 100644
--- a/third_party/WebKit/Source/modules/bluetooth/BluetoothError.cpp
+++ b/third_party/WebKit/Source/modules/bluetooth/BluetoothError.cpp
@@ -28,6 +28,9 @@
     case BluetoothOperation::CharacteristicsRetrieval:
       operationString = "retrieve characteristics";
       break;
+    case BluetoothOperation::GATT:
+      operationString = "perform GATT operations";
+      break;
   }
 
   return DOMException::create(
@@ -120,10 +123,6 @@
                 "GATT operation already in progress.");
       MAP_ERROR(UNTRANSLATED_CONNECT_ERROR_CODE, NetworkError,
                 "Unknown ConnectErrorCode.");
-      MAP_ERROR(GATT_SERVER_NOT_CONNECTED, NetworkError,
-                "GATT Server is disconnected. Cannot perform GATT operations.");
-      MAP_ERROR(GATT_SERVER_DISCONNECTED, NetworkError,
-                "GATT Server disconnected while performing a GATT operation.");
 
       // NotFoundErrors:
       MAP_ERROR(WEB_BLUETOOTH_NOT_SUPPORTED, NotFoundError,
diff --git a/third_party/WebKit/Source/modules/bluetooth/BluetoothError.h b/third_party/WebKit/Source/modules/bluetooth/BluetoothError.h
index 9ebdd142..c6c5c5a 100644
--- a/third_party/WebKit/Source/modules/bluetooth/BluetoothError.h
+++ b/third_party/WebKit/Source/modules/bluetooth/BluetoothError.h
@@ -12,10 +12,11 @@
 namespace blink {
 
 // Used when generating DOMExceptions specific to each operation.
-// TODO(crbug.com/684445): Add DescriptorsRetrieval and GATTOperation.
+// TODO(crbug.com/684445): Add DescriptorsRetrieval.
 enum class BluetoothOperation {
   ServicesRetrieval,
   CharacteristicsRetrieval,
+  GATT,
 };
 
 // These error codes requires detailed error messages.
diff --git a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTCharacteristic.cpp b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTCharacteristic.cpp
index 94af3f75..44d4666 100644
--- a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTCharacteristic.cpp
+++ b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTCharacteristic.cpp
@@ -106,8 +106,8 @@
 
   // If the device is disconnected, reject.
   if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) {
-    resolver->reject(BluetoothError::createDOMException(
-        blink::mojom::WebBluetoothResult::GATT_SERVER_DISCONNECTED));
+    resolver->reject(
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
     return;
   }
 
@@ -127,8 +127,7 @@
   if (!getGatt()->connected()) {
     return ScriptPromise::rejectWithDOMException(
         scriptState,
-        BluetoothError::createDOMException(
-            blink::mojom::WebBluetoothResult::GATT_SERVER_NOT_CONNECTED));
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
   }
 
   if (!getGatt()->device()->isValidCharacteristic(
@@ -161,8 +160,8 @@
 
   // If the device is disconnected, reject.
   if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) {
-    resolver->reject(BluetoothError::createDOMException(
-        blink::mojom::WebBluetoothResult::GATT_SERVER_DISCONNECTED));
+    resolver->reject(
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
     return;
   }
 
@@ -180,8 +179,7 @@
   if (!getGatt()->connected()) {
     return ScriptPromise::rejectWithDOMException(
         scriptState,
-        BluetoothError::createDOMException(
-            blink::mojom::WebBluetoothResult::GATT_SERVER_NOT_CONNECTED));
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
   }
 
   if (!getGatt()->device()->isValidCharacteristic(
@@ -228,8 +226,8 @@
 
   // If the device is disconnected, reject.
   if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) {
-    resolver->reject(BluetoothError::createDOMException(
-        blink::mojom::WebBluetoothResult::GATT_SERVER_DISCONNECTED));
+    resolver->reject(
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
     return;
   }
 
@@ -245,8 +243,7 @@
   if (!getGatt()->connected()) {
     return ScriptPromise::rejectWithDOMException(
         scriptState,
-        BluetoothError::createDOMException(
-            blink::mojom::WebBluetoothResult::GATT_SERVER_NOT_CONNECTED));
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
   }
 
   if (!getGatt()->device()->isValidCharacteristic(
@@ -274,8 +271,7 @@
   if (!getGatt()->connected()) {
     return ScriptPromise::rejectWithDOMException(
         scriptState,
-        BluetoothError::createDOMException(
-            blink::mojom::WebBluetoothResult::GATT_SERVER_NOT_CONNECTED));
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
   }
 
   if (!getGatt()->device()->isValidCharacteristic(
@@ -340,8 +336,8 @@
   if (!getGatt()->connected()) {
     return ScriptPromise::rejectWithDOMException(
         scriptState,
-        BluetoothError::createDOMException(
-            blink::mojom::WebBluetoothResult::GATT_SERVER_NOT_CONNECTED));
+        // TODO(crbug.com/684445): Change to DescriptorsRetrieval.
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
   }
 
   if (!getGatt()->device()->isValidCharacteristic(
@@ -381,8 +377,9 @@
 
   // If the device is disconnected, reject.
   if (!m_service->device()->gatt()->RemoveFromActiveAlgorithms(resolver)) {
-    resolver->reject(BluetoothError::createDOMException(
-        blink::mojom::WebBluetoothResult::GATT_SERVER_DISCONNECTED));
+    // TODO(crbug.com/684445): Change to DescriptorsRetrieval.
+    resolver->reject(
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
     return;
   }
 
diff --git a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp
index ad14abb..d6a72b3 100644
--- a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp
+++ b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp
@@ -38,8 +38,8 @@
 
   // If the device is disconnected, reject.
   if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) {
-    resolver->reject(BluetoothError::createDOMException(
-        mojom::blink::WebBluetoothResult::GATT_SERVER_DISCONNECTED));
+    resolver->reject(
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
     return;
   }
 
@@ -59,8 +59,7 @@
   if (!getGatt()->connected()) {
     return ScriptPromise::rejectWithDOMException(
         scriptState,
-        BluetoothError::createDOMException(
-            mojom::blink::WebBluetoothResult::GATT_SERVER_NOT_CONNECTED));
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
   }
 
   if (!getGatt()->device()->isValidDescriptor(m_descriptor->instance_id)) {
@@ -91,8 +90,8 @@
   // If the resolver is not in the set of ActiveAlgorithms then the frame
   // disconnected so we reject.
   if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) {
-    resolver->reject(BluetoothError::createDOMException(
-        mojom::blink::WebBluetoothResult::GATT_SERVER_DISCONNECTED));
+    resolver->reject(
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
     return;
   }
 
@@ -110,8 +109,7 @@
   if (!getGatt()->connected()) {
     return ScriptPromise::rejectWithDOMException(
         scriptState,
-        BluetoothError::createDOMException(
-            mojom::blink::WebBluetoothResult::GATT_SERVER_NOT_CONNECTED));
+        BluetoothError::createNotConnectedException(BluetoothOperation::GATT));
   }
 
   if (!getGatt()->device()->isValidDescriptor(m_descriptor->instance_id)) {
diff --git a/third_party/WebKit/Source/modules/vr/DEPS b/third_party/WebKit/Source/modules/vr/DEPS
index fd6692e..c7d7eec 100644
--- a/third_party/WebKit/Source/modules/vr/DEPS
+++ b/third_party/WebKit/Source/modules/vr/DEPS
@@ -2,5 +2,4 @@
     "+mojo/public/cpp/bindings/binding.h",
     "+device/vr/vr_service.mojom-blink.h",
     "+gpu/command_buffer/client/gles2_interface.h",
-    "+gpu/command_buffer/common/mailbox_holder.h",
 ]
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
index 3da99b5..cc8f103 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
@@ -8,17 +8,13 @@
 #include "core/dom/DOMException.h"
 #include "core/dom/DocumentUserGestureToken.h"
 #include "core/dom/FrameRequestCallback.h"
+#include "core/dom/Fullscreen.h"
 #include "core/dom/ScriptedAnimationController.h"
 #include "core/dom/TaskRunnerHelper.h"
-#include "core/frame/FrameView.h"
-#include "core/frame/ImageBitmap.h"
 #include "core/frame/UseCounter.h"
 #include "core/inspector/ConsoleMessage.h"
-#include "core/layout/LayoutView.h"
-#include "core/layout/compositing/PaintLayerCompositor.h"
 #include "core/loader/DocumentLoader.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
-#include "gpu/command_buffer/common/mailbox_holder.h"
 #include "modules/EventTargetModules.h"
 #include "modules/vr/NavigatorVR.h"
 #include "modules/vr/VRController.h"
@@ -31,7 +27,6 @@
 #include "modules/webgl/WebGLRenderingContextBase.h"
 #include "platform/Histogram.h"
 #include "platform/UserGestureIndicator.h"
-#include "platform/instrumentation/tracing/TraceEvent.h"
 #include "public/platform/Platform.h"
 #include "wtf/AutoReset.h"
 #include "wtf/Time.h"
@@ -42,6 +37,10 @@
 
 namespace {
 
+// Magic numbers used to mark valid pose index values encoded in frame
+// data. Must match the magic numbers used in vr_shell.cc.
+static constexpr std::array<uint8_t, 2> kWebVrPosePixelMagicNumbers{{42, 142}};
+
 VREye stringToVREye(const String& whichEye) {
   if (whichEye == "left")
     return VREyeLeft;
@@ -60,8 +59,12 @@
       m_capabilities(new VRDisplayCapabilities()),
       m_eyeParametersLeft(new VREyeParameters()),
       m_eyeParametersRight(new VREyeParameters()),
+      m_fullscreenCheckTimer(
+          TaskRunnerHelper::get(TaskType::UnspecedTimer,
+                                navigatorVR->document()->frame()),
+          this,
+          &VRDisplay::onFullscreenCheck),
       m_display(std::move(display)),
-      m_submit_frame_client_binding(this),
       m_displayClientBinding(this, std::move(request)) {}
 
 VRDisplay::~VRDisplay() {}
@@ -236,6 +239,17 @@
     return promise;
   }
 
+  // TODO(mthiesse): Remove fullscreen requirement for presentation. See
+  // crbug.com/687369
+  Document* doc = this->document();
+  if (!doc || !Fullscreen::fullscreenEnabled(*doc)) {
+    DOMException* exception =
+        DOMException::create(InvalidStateError, "Fullscreen is not enabled.");
+    resolver->reject(exception);
+    ReportPresentationResult(PresentationResult::FullscreenNotEnabled);
+    return promise;
+  }
+
   // A valid number of layers must be provided in order to present.
   if (layers.size() == 0 || layers.size() > m_capabilities->maxLayers()) {
     forceExitPresent();
@@ -310,12 +324,9 @@
     }
 
     m_pendingPresentResolvers.append(resolver);
-    m_submit_frame_client_binding.Close();
-    m_display->RequestPresent(
-        secureContext,
-        m_submit_frame_client_binding.CreateInterfacePtrAndBind(),
-        convertToBaseCallback(
-            WTF::bind(&VRDisplay::onPresentComplete, wrapPersistent(this))));
+    m_display->RequestPresent(secureContext, convertToBaseCallback(WTF::bind(
+                                                 &VRDisplay::onPresentComplete,
+                                                 wrapPersistent(this))));
   } else {
     updateLayerBounds();
     resolver->resolve();
@@ -384,9 +395,50 @@
     return;
   } else {
     if (m_layer.source().isHTMLCanvasElement()) {
-      // TODO(klausw,crbug.com/698923): suppress compositor updates
-      // since they aren't needed, they do a fair amount of extra
-      // work.
+      HTMLCanvasElement* canvas = m_layer.source().getAsHTMLCanvasElement();
+      // TODO(klausw,crbug.com/655722): Need a proper VR compositor, but
+      // for the moment on mobile we'll just make the canvas fullscreen
+      // so that VrShell can pick it up through the standard (high
+      // latency) compositing path.      auto canvas =
+      // m_layer.source().getAsHTMLCanvasElement();
+      auto inlineStyle = canvas->inlineStyle();
+      if (inlineStyle) {
+        // THREE.js's VREffect sets explicit style.width/height on its rendering
+        // canvas based on the non-fullscreen window dimensions, and it keeps
+        // those unchanged when presenting. Unfortunately it appears that a
+        // fullscreened canvas just gets centered if it has explicitly set a
+        // size smaller than the fullscreen dimensions. Manually set size to
+        // 100% in this case and restore it when exiting fullscreen. This is a
+        // stopgap measure since THREE.js's usage appears legal according to the
+        // WebVR API spec. This will no longer be necessary once we can get rid
+        // of this fullscreen hack.
+        m_fullscreenOrigWidth = inlineStyle->getPropertyValue(CSSPropertyWidth);
+        if (!m_fullscreenOrigWidth.isNull()) {
+          canvas->setInlineStyleProperty(CSSPropertyWidth, "100%");
+        }
+        m_fullscreenOrigHeight =
+            inlineStyle->getPropertyValue(CSSPropertyHeight);
+        if (!m_fullscreenOrigHeight.isNull()) {
+          canvas->setInlineStyleProperty(CSSPropertyHeight, "100%");
+        }
+      } else {
+        m_fullscreenOrigWidth = String();
+        m_fullscreenOrigHeight = String();
+      }
+
+      if (doc) {
+        // Since the callback for requestPresent is asynchronous, we've lost our
+        // UserGestureToken, and need to create a new one to enter fullscreen.
+        gestureIndicator = WTF::wrapUnique(
+            new UserGestureIndicator(DocumentUserGestureToken::create(
+                doc, UserGestureToken::Status::PossiblyExistingGesture)));
+      }
+      Fullscreen::requestFullscreen(*canvas);
+
+      // Check to see if the canvas is still the current fullscreen
+      // element once every 2 seconds.
+      m_fullscreenCheckTimer.startRepeating(2.0, BLINK_FROM_HERE);
+      m_reenteredFullscreen = false;
     } else {
       DCHECK(m_layer.source().isOffscreenCanvas());
       // TODO(junov, crbug.com/695497): Implement OffscreenCanvas presentation
@@ -467,8 +519,7 @@
   }
 
   m_display->UpdateLayerBounds(m_vrFrameId, std::move(leftBounds),
-                               std::move(rightBounds), m_sourceWidth,
-                               m_sourceHeight);
+                               std::move(rightBounds));
 }
 
 HeapVector<VRLayer> VRDisplay::getLayers() {
@@ -484,7 +535,6 @@
 void VRDisplay::submitFrame() {
   if (!m_display)
     return;
-  TRACE_EVENT1("gpu", "submitFrame", "frame", m_vrFrameId);
 
   Document* doc = this->document();
   if (!m_isPresenting) {
@@ -513,110 +563,42 @@
 
   // No frame Id to write before submitting the frame.
   if (m_vrFrameId < 0) {
-    // TODO(klausw): There used to be a submitFrame here, but we can't
-    // submit without a frameId and associated pose data. Just drop it.
+    m_display->SubmitFrame(m_framePose.Clone());
     return;
   }
 
-  m_contextGL->Flush();
-  auto elem = m_layer.source();
+  // Write the frame number for the pose used into a bottom left pixel block.
+  // It is read by chrome/browser/android/vr_shell/vr_shell.cc to associate
+  // the correct corresponding pose for submission.
+  auto gl = m_contextGL;
 
-  // Check if the canvas got resized, if yes send a bounds update.
-  int currentWidth = m_renderingContext->drawingBufferWidth();
-  int currentHeight = m_renderingContext->drawingBufferHeight();
-  if ((currentWidth != m_sourceWidth || currentHeight != m_sourceHeight) &&
-      currentWidth != 0 && currentHeight != 0) {
-    m_sourceWidth = currentWidth;
-    m_sourceHeight = currentHeight;
-    updateLayerBounds();
-  }
+  // We must ensure that the WebGL app's GL state is preserved. We do this by
+  // calling low-level GL commands directly so that the rendering context's
+  // saved parameters don't get overwritten.
 
-  // There's two types of synchronization needed for submitting frames:
-  //
-  // - Before submitting, need to wait for the previous frame to be
-  //   pulled off the transfer surface to avoid lost frames. This
-  //   is currently a compile-time option, normally we always want
-  //   to defer this wait to increase parallelism.
-  //
-  // - After submitting, need to wait for the mailbox to be consumed,
-  //   and must remain inside the execution context while waiting.
-  //   The waitForPreviousTransferToFinish option defers this wait
-  //   until the next frame. That's more efficient, but seems to
-  //   cause wobbly framerates.
-  bool waitForPreviousTransferToFinish =
-      RuntimeEnabledFeatures::webVRExperimentalRenderingEnabled();
+  gl->Enable(GL_SCISSOR_TEST);
+  // Use a few pixels to ensure we get a clean color. The resolution for the
+  // WebGL buffer may not match the final rendered destination size, and
+  // texture filtering could interfere for single pixels. This isn't visible
+  // since the final rendering hides the edges via a vignette effect.
+  gl->Scissor(0, 0, 4, 4);
+  gl->ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+  // Careful with the arithmetic here. Float color 1.f is equivalent to int 255.
+  // Use the low byte of the index as the red component, and store an arbitrary
+  // magic number in green/blue. This number must match the reading code in
+  // vr_shell.cc. Avoid all-black/all-white.
+  gl->ClearColor((m_vrFrameId & 255) / 255.0f,
+                 kWebVrPosePixelMagicNumbers[0] / 255.0f,
+                 kWebVrPosePixelMagicNumbers[1] / 255.0f, 1.0f);
+  gl->Clear(GL_COLOR_BUFFER_BIT);
 
-  if (waitForPreviousTransferToFinish) {
-    TRACE_EVENT0("gpu", "VRDisplay::waitForPreviousTransferToFinish");
-    while (m_pendingSubmitFrame) {
-      if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) {
-        DLOG(ERROR) << "Failed to receive SubmitFrame response";
-        break;
-      }
-    }
-  }
+  // Set the GL state back to what was set by the WebVR application.
+  m_renderingContext->restoreScissorEnabled();
+  m_renderingContext->restoreScissorBox();
+  m_renderingContext->restoreColorMask();
+  m_renderingContext->restoreClearColor();
 
-  RefPtr<Image> imageRef = m_renderingContext->getImage(
-      PreferAcceleration, SnapshotReasonCreateImageBitmap);
-
-  // Hardware-accelerated rendering should always be texture backed.
-  // I hope nobody is trying to do WebVR with software rendering.
-  DCHECK(imageRef->isTextureBacked());
-
-  // The AcceleratedStaticBitmapImage must be kept alive until the
-  // mailbox is used via createAndConsumeTextureCHROMIUM, the mailbox
-  // itself does not keep it alive. We must keep a reference to the
-  // image until the mailbox was consumed.
-  StaticBitmapImage* staticImage =
-      static_cast<StaticBitmapImage*>(imageRef.get());
-  staticImage->ensureMailbox();
-
-  if (waitForPreviousTransferToFinish) {
-    // Save a reference to the image to keep it alive until next frame,
-    // where we'll wait for the transfer to finish before overwriting
-    // it.
-    m_previousImage = std::move(imageRef);
-  }
-
-  // Wait for the previous render to finish, to avoid losing frames in the
-  // Android Surface / GLConsumer pair. TODO(klausw): make this tunable?
-  // Other devices may have different preferences.
-  {
-    TRACE_EVENT0("gpu", "waitForPreviousRenderToFinish");
-    while (m_pendingPreviousFrameRender) {
-      if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) {
-        DLOG(ERROR) << "Failed to receive SubmitFrame response";
-        break;
-      }
-    }
-  }
-
-  m_pendingPreviousFrameRender = true;
-  m_pendingSubmitFrame = true;
-  m_display->SubmitFrame(
-      m_vrFrameId, gpu::MailboxHolder(staticImage->mailbox(),
-                                      staticImage->syncToken(), GL_TEXTURE_2D));
-
-  // If we're not deferring the wait for transferring the mailbox,
-  // we need to wait for it now to prevent the image going out of
-  // scope before its mailbox is retrieved.
-  if (!waitForPreviousTransferToFinish) {
-    TRACE_EVENT0("gpu", "waitForCurrentTransferToFinish");
-    while (m_pendingSubmitFrame) {
-      if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) {
-        DLOG(ERROR) << "Failed to receive SubmitFrame response";
-        break;
-      }
-    }
-  }
-}
-
-void VRDisplay::OnSubmitFrameTransferred() {
-  m_pendingSubmitFrame = false;
-}
-
-void VRDisplay::OnSubmitFrameRendered() {
-  m_pendingPreviousFrameRender = false;
+  m_display->SubmitFrame(m_framePose.Clone());
 }
 
 Document* VRDisplay::document() {
@@ -625,7 +607,7 @@
 
 void VRDisplay::OnPresentChange() {
   if (m_isPresenting && !m_isValidDeviceForPresenting) {
-    DVLOG(1) << __FUNCTION__ << ": device not valid, not sending event";
+    VLOG(1) << __FUNCTION__ << ": device not valid, not sending event";
     return;
   }
   m_navigatorVR->enqueueVREvent(VRDisplayEvent::create(
@@ -654,8 +636,19 @@
   if (m_isPresenting) {
     if (!m_capabilities->hasExternalDisplay()) {
       if (m_layer.source().isHTMLCanvasElement()) {
-        // TODO(klausw,crbug.com/698923): If compositor updates are
-        // suppressed, restore them here.
+        auto canvas = m_layer.source().getAsHTMLCanvasElement();
+        Fullscreen::fullyExitFullscreen(canvas->document());
+        m_fullscreenCheckTimer.stop();
+        if (!m_fullscreenOrigWidth.isNull()) {
+          canvas->setInlineStyleProperty(CSSPropertyWidth,
+                                         m_fullscreenOrigWidth);
+          m_fullscreenOrigWidth = String();
+        }
+        if (!m_fullscreenOrigHeight.isNull()) {
+          canvas->setInlineStyleProperty(CSSPropertyWidth,
+                                         m_fullscreenOrigHeight);
+          m_fullscreenOrigHeight = String();
+        }
       } else {
         // TODO(junov, crbug.com/695497): Implement for OffscreenCanvas
       }
@@ -668,8 +661,6 @@
 
   m_renderingContext = nullptr;
   m_contextGL = nullptr;
-  m_pendingSubmitFrame = false;
-  m_pendingPreviousFrameRender = false;
 }
 
 void VRDisplay::OnActivate(device::mojom::blink::VRDisplayEventReason reason) {
@@ -692,12 +683,18 @@
   switch (error) {
     case device::mojom::blink::VRVSyncProvider::Status::SUCCESS:
       break;
-    case device::mojom::blink::VRVSyncProvider::Status::CLOSING:
+    case device::mojom::blink::VRVSyncProvider::Status::RETRY:
+      m_vrVSyncProvider->GetVSync(convertToBaseCallback(
+          WTF::bind(&VRDisplay::OnVSync, wrapWeakPersistent(this))));
       return;
   }
   m_pendingVsync = false;
+  if (m_displayBlurred)
+    return;
+  if (!m_scriptedAnimationController)
+    return;
   Document* doc = this->document();
-  if (!doc || m_displayBlurred || !m_scriptedAnimationController)
+  if (!doc)
     return;
 
   WTF::TimeDelta timeDelta =
@@ -719,8 +716,6 @@
   if (!m_navigatorVR->isFocused() || m_vrVSyncProvider.is_bound())
     return;
   m_display->GetVRVSyncProvider(mojo::MakeRequest(&m_vrVSyncProvider));
-  m_vrVSyncProvider.set_connection_error_handler(convertToBaseCallback(
-      WTF::bind(&VRDisplay::OnVSyncConnectionError, wrapWeakPersistent(this))));
   if (m_pendingRaf && !m_displayBlurred) {
     m_pendingVsync = true;
     m_vrVSyncProvider->GetVSync(convertToBaseCallback(
@@ -728,9 +723,42 @@
   }
 }
 
-void VRDisplay::OnVSyncConnectionError() {
-  m_vrVSyncProvider.reset();
-  ConnectVSyncProvider();
+void VRDisplay::onFullscreenCheck(TimerBase*) {
+  DCHECK(m_layer.source().isHTMLCanvasElement());
+  if (!m_isPresenting) {
+    m_fullscreenCheckTimer.stop();
+    return;
+  }
+  // TODO: This is a temporary measure to track if fullscreen mode has been
+  // exited by the UA. If so we need to end VR presentation. Soon we won't
+  // depend on the Fullscreen API to fake VR presentation, so this will
+  // become unnessecary. Until that point, though, this seems preferable to
+  // adding a bunch of notification plumbing to Fullscreen.
+  if (!Fullscreen::isCurrentFullScreenElement(
+          *m_layer.source().getAsHTMLCanvasElement())) {
+    // TODO(mthiesse): Due to asynchronous resizing, we might get kicked out of
+    // fullscreen when changing display parameters upon entering WebVR. So one
+    // time only, we reenter fullscreen after having left it; otherwise we exit
+    // presentation.
+    if (m_reenteredFullscreen) {
+      m_isPresenting = false;
+      OnPresentChange();
+      m_fullscreenCheckTimer.stop();
+      if (m_display)
+        m_display->ExitPresent();
+      return;
+    }
+    m_reenteredFullscreen = true;
+    auto canvas = m_layer.source().getAsHTMLCanvasElement();
+    Document* doc = this->document();
+    std::unique_ptr<UserGestureIndicator> gestureIndicator;
+    if (doc) {
+      gestureIndicator = WTF::wrapUnique(
+          new UserGestureIndicator(DocumentUserGestureToken::create(
+              doc, UserGestureToken::Status::PossiblyExistingGesture)));
+    }
+    Fullscreen::requestFullscreen(*canvas);
+  }
 }
 
 ScriptedAnimationController& VRDisplay::ensureScriptedAnimationController(
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.h b/third_party/WebKit/Source/modules/vr/VRDisplay.h
index 3eaa741..af955c5 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.h
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.h
@@ -41,8 +41,7 @@
 class VRDisplay final : public EventTargetWithInlineData,
                         public ActiveScriptWrappable<VRDisplay>,
                         public ContextLifecycleObserver,
-                        public device::mojom::blink::VRDisplayClient,
-                        public device::mojom::blink::VRSubmitFrameClient {
+                        public device::mojom::blink::VRDisplayClient {
   DEFINE_WRAPPERTYPEINFO();
   USING_GARBAGE_COLLECTED_MIXIN(VRDisplay);
   USING_PRE_FINALIZER(VRDisplay, dispose);
@@ -117,6 +116,7 @@
   VRController* controller();
 
  private:
+  void onFullscreenCheck(TimerBase*);
   void onPresentComplete(bool);
 
   void onConnected();
@@ -126,10 +126,6 @@
 
   void OnPresentChange();
 
-  // VRSubmitFrameClient
-  void OnSubmitFrameTransferred();
-  void OnSubmitFrameRendered();
-
   // VRDisplayClient
   void OnChanged(device::mojom::blink::VRDisplayInfoPtr) override;
   void OnExitPresent() override;
@@ -143,7 +139,6 @@
                int16_t frameId,
                device::mojom::blink::VRVSyncProvider::Status);
   void ConnectVSyncProvider();
-  void OnVSyncConnectionError();
 
   ScriptedAnimationController& ensureScriptedAnimationController(Document*);
 
@@ -167,33 +162,24 @@
   double m_depthNear = 0.01;
   double m_depthFar = 10000.0;
 
-  // Current dimensions of the WebVR source canvas. May be different from
-  // the recommended renderWidth/Height if the client overrides dimensions.
-  int m_sourceWidth = 0;
-  int m_sourceHeight = 0;
-
   void dispose();
 
+  TaskRunnerTimer<VRDisplay> m_fullscreenCheckTimer;
+  String m_fullscreenOrigWidth;
+  String m_fullscreenOrigHeight;
   gpu::gles2::GLES2Interface* m_contextGL = nullptr;
   Member<WebGLRenderingContextBase> m_renderingContext;
 
-  // Used to keep the image alive until the next frame if using
-  // waitForPreviousTransferToFinish.
-  RefPtr<Image> m_previousImage;
-
   Member<ScriptedAnimationController> m_scriptedAnimationController;
   bool m_pendingRaf = false;
   bool m_pendingVsync = false;
   bool m_inAnimationFrame = false;
   bool m_displayBlurred = false;
+  bool m_reenteredFullscreen = false;
   double m_timebase = -1;
-  bool m_pendingPreviousFrameRender = false;
-  bool m_pendingSubmitFrame = false;
 
   device::mojom::blink::VRDisplayPtr m_display;
 
-  mojo::Binding<device::mojom::blink::VRSubmitFrameClient>
-      m_submit_frame_client_binding;
   mojo::Binding<device::mojom::blink::VRDisplayClient> m_displayClientBinding;
   device::mojom::blink::VRVSyncProviderPtr m_vrVSyncProvider;
 
diff --git a/third_party/WebKit/Source/modules/vr/VRFrameData.cpp b/third_party/WebKit/Source/modules/vr/VRFrameData.cpp
index 83627ca..ffce7da8 100644
--- a/third_party/WebKit/Source/modules/vr/VRFrameData.cpp
+++ b/third_party/WebKit/Source/modules/vr/VRFrameData.cpp
@@ -167,7 +167,7 @@
   return true;
 };
 
-VRFrameData::VRFrameData() : m_timestamp(0.0) {
+VRFrameData::VRFrameData() {
   m_leftProjectionMatrix = DOMFloat32Array::create(16);
   m_leftViewMatrix = DOMFloat32Array::create(16);
   m_rightProjectionMatrix = DOMFloat32Array::create(16);
@@ -180,8 +180,6 @@
                          VREyeParameters* rightEye,
                          float depthNear,
                          float depthFar) {
-  m_timestamp = pose->timestamp;
-
   // Build the projection matrices
   projectionFromFieldOfView(m_leftProjectionMatrix, leftEye->fieldOfView(),
                             depthNear, depthFar);
diff --git a/third_party/WebKit/Source/modules/vr/VRFrameData.h b/third_party/WebKit/Source/modules/vr/VRFrameData.h
index ef5e3ab..60acc1b 100644
--- a/third_party/WebKit/Source/modules/vr/VRFrameData.h
+++ b/third_party/WebKit/Source/modules/vr/VRFrameData.h
@@ -6,7 +6,6 @@
 #define VRFrameData_h
 
 #include "bindings/core/v8/ScriptWrappable.h"
-#include "core/dom/DOMHighResTimeStamp.h"
 #include "core/dom/DOMTypedArray.h"
 #include "device/vr/vr_service.mojom-blink.h"
 #include "platform/heap/Handle.h"
@@ -26,7 +25,6 @@
 
   VRFrameData();
 
-  DOMHighResTimeStamp timestamp() const { return m_timestamp; }
   DOMFloat32Array* leftProjectionMatrix() const {
     return m_leftProjectionMatrix;
   }
@@ -49,7 +47,6 @@
   DECLARE_VIRTUAL_TRACE()
 
  private:
-  DOMHighResTimeStamp m_timestamp;
   Member<DOMFloat32Array> m_leftProjectionMatrix;
   Member<DOMFloat32Array> m_leftViewMatrix;
   Member<DOMFloat32Array> m_rightProjectionMatrix;
diff --git a/third_party/WebKit/Source/modules/vr/VRFrameData.idl b/third_party/WebKit/Source/modules/vr/VRFrameData.idl
index b4fa005..a07fcb2 100644
--- a/third_party/WebKit/Source/modules/vr/VRFrameData.idl
+++ b/third_party/WebKit/Source/modules/vr/VRFrameData.idl
@@ -7,8 +7,6 @@
     OriginTrialEnabled=WebVR,
     Constructor,
 ] interface VRFrameData {
-    readonly attribute DOMHighResTimeStamp timestamp;
-
     readonly attribute Float32Array leftProjectionMatrix;
     readonly attribute Float32Array leftViewMatrix;
 
diff --git a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5 b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
index 1022cc5..83b46fe 100644
--- a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
+++ b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
@@ -448,6 +448,7 @@
     },
     {
       name: "IdleTimeSpellChecking",
+      status: "test",
     },
     {
       name: "ImageCapture",
diff --git a/third_party/WebKit/Source/web/WebPagePopupImpl.cpp b/third_party/WebKit/Source/web/WebPagePopupImpl.cpp
index 2751595e..c12f69ba 100644
--- a/third_party/WebKit/Source/web/WebPagePopupImpl.cpp
+++ b/third_party/WebKit/Source/web/WebPagePopupImpl.cpp
@@ -129,8 +129,7 @@
     if (LayoutTestSupport::isRunningLayoutTest())
       m_popup->m_webView->mainFrameImpl()->frameWidget()->scheduleAnimation();
 
-    if (m_popup->isAcceleratedCompositingActive()) {
-      DCHECK(m_popup->m_layerTreeView);
+    if (m_popup->m_layerTreeView) {
       m_popup->m_layerTreeView->setNeedsBeginFrame();
       return;
     }
@@ -325,6 +324,8 @@
   DCHECK_EQ(m_popupClient->ownerElement().document().existingAXObjectCache(),
             frame->document()->existingAXObjectCache());
 
+  initializeLayerTreeView();
+
   RefPtr<SharedBuffer> data = SharedBuffer::create();
   m_popupClient->writeDocument(data.get());
   frame->setPageZoomFactor(m_popupClient->zoomFactor());
@@ -370,7 +371,7 @@
   m_rootGraphicsLayer = layer;
   m_rootLayer = layer ? layer->platformLayer() : 0;
 
-  setIsAcceleratedCompositingActive(layer);
+  m_isAcceleratedCompositingActive = !!layer;
   if (m_layerTreeView) {
     if (m_rootLayer) {
       m_layerTreeView->setRootLayer(*m_rootLayer);
@@ -380,29 +381,16 @@
   }
 }
 
-void WebPagePopupImpl::setIsAcceleratedCompositingActive(bool enter) {
-  if (m_isAcceleratedCompositingActive == enter)
-    return;
-
-  if (!enter) {
-    m_isAcceleratedCompositingActive = false;
-  } else if (m_layerTreeView) {
-    m_isAcceleratedCompositingActive = true;
+void WebPagePopupImpl::initializeLayerTreeView() {
+  TRACE_EVENT0("blink", "WebPagePopupImpl::initializeLayerTreeView");
+  m_layerTreeView = m_widgetClient->initializeLayerTreeView();
+  if (m_layerTreeView) {
+    m_layerTreeView->setVisible(true);
+    m_animationHost = WTF::makeUnique<CompositorAnimationHost>(
+        m_layerTreeView->compositorAnimationHost());
+    m_page->layerTreeViewInitialized(*m_layerTreeView, nullptr);
   } else {
-    TRACE_EVENT0("blink",
-                 "WebPagePopupImpl::setIsAcceleratedCompositingActive(true)");
-
-    m_layerTreeView = m_widgetClient->initializeLayerTreeView();
-    if (m_layerTreeView) {
-      m_layerTreeView->setVisible(true);
-      m_isAcceleratedCompositingActive = true;
-      m_animationHost = WTF::makeUnique<CompositorAnimationHost>(
-          m_layerTreeView->compositorAnimationHost());
-      m_page->layerTreeViewInitialized(*m_layerTreeView, nullptr);
-    } else {
-      m_isAcceleratedCompositingActive = false;
-      m_animationHost = nullptr;
-    }
+    m_animationHost = nullptr;
   }
 }
 
@@ -418,7 +406,7 @@
   if (m_page && m_layerTreeView)
     m_page->willCloseLayerTreeView(*m_layerTreeView, nullptr);
 
-  setIsAcceleratedCompositingActive(false);
+  m_isAcceleratedCompositingActive = false;
   m_layerTreeView = nullptr;
   m_animationHost = nullptr;
 }
diff --git a/third_party/WebKit/Source/web/WebPagePopupImpl.h b/third_party/WebKit/Source/web/WebPagePopupImpl.h
index 1d89868..c648b66 100644
--- a/third_party/WebKit/Source/web/WebPagePopupImpl.h
+++ b/third_party/WebKit/Source/web/WebPagePopupImpl.h
@@ -105,8 +105,8 @@
   explicit WebPagePopupImpl(WebWidgetClient*);
   bool initializePage();
   void destroyPage();
+  void initializeLayerTreeView();
   void setRootGraphicsLayer(GraphicsLayer*);
-  void setIsAcceleratedCompositingActive(bool enter);
 
   WebRect windowRectInScreen() const;
 
diff --git a/third_party/WebKit/Source/web/WebViewImpl.cpp b/third_party/WebKit/Source/web/WebViewImpl.cpp
index f934dd0b..eb43d4e9 100644
--- a/third_party/WebKit/Source/web/WebViewImpl.cpp
+++ b/third_party/WebKit/Source/web/WebViewImpl.cpp
@@ -1859,7 +1859,7 @@
 }
 
 BrowserControls& WebViewImpl::browserControls() {
-  return page()->frameHost().browserControls();
+  return page()->browserControls();
 }
 
 void WebViewImpl::resizeViewWhileAnchored(float browserControlsHeight,
diff --git a/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp b/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp
index 459e94cb..d864322 100644
--- a/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp
+++ b/third_party/WebKit/Source/web/tests/RootScrollerTest.cpp
@@ -98,9 +98,9 @@
 
   WebViewImpl* webViewImpl() const { return m_helper.webView(); }
 
-  FrameHost& frameHost() const {
-    return m_helper.webView()->page()->frameHost();
-  }
+  Page& page() const { return *m_helper.webView()->page(); }
+
+  FrameHost& frameHost() const { return page().frameHost(); }
 
   LocalFrame* mainFrame() const {
     return webViewImpl()->mainFrameImpl()->frame();
@@ -116,9 +116,7 @@
     return frameHost().visualViewport();
   }
 
-  BrowserControls& browserControls() const {
-    return frameHost().browserControls();
-  }
+  BrowserControls& browserControls() const { return page().browserControls(); }
 
   Node* effectiveRootScroller(Document* doc) const {
     return &doc->rootScrollerController().effectiveRootScroller();
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
index 90fd961..739131fc 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
@@ -33,53 +33,9 @@
 
 _log = logging.getLogger(__name__)
 
-conversion_patterns = (
-    (re.compile(r"^diff --git \w/(.+) \w/(?P<FilePath>.+)"), lambda matched: "Index: " + matched.group('FilePath') + "\n"),
-    (re.compile(r"^new file.*"), lambda matched: "\n"),
-    (re.compile(r"^index (([0-9a-f]{7}\.\.[0-9a-f]{7})|([0-9a-f]{40}\.\.[0-9a-f]{40})) [0-9]{6}"),
-     lambda matched: ("=" * 67) + "\n"),
-    (re.compile(r"^--- \w/(?P<FilePath>.+)"), lambda matched: "--- " + matched.group('FilePath') + "\n"),
-    (re.compile(r"^\+\+\+ \w/(?P<FilePath>.+)"), lambda matched: "+++ " + matched.group('FilePath') + "\n"),
-)
+INDEX_PATTERN = re.compile(r'^diff --git \w/(.+) \w/(?P<FilePath>.+)')
+LINES_CHANGED_PATTERN = re.compile(r"^@@ -(?P<OldStartLine>\d+)(,\d+)? \+(?P<NewStartLine>\d+)(,\d+)? @@")
 
-index_pattern = re.compile(r"^Index: (?P<FilePath>.+)")
-lines_changed_pattern = re.compile(r"^@@ -(?P<OldStartLine>\d+)(,\d+)? \+(?P<NewStartLine>\d+)(,\d+)? @@")
-diff_git_pattern = re.compile(r"^diff --git \w/")
-
-
-def git_diff_to_svn_diff(line):
-    """Converts a git formatted diff line to a svn formatted line.
-
-    Args:
-      line: A string representing a line of the diff.
-    """
-    for pattern, conversion in conversion_patterns:
-        matched = pattern.match(line)
-        if matched:
-            return conversion(matched)
-    return line
-
-
-# This function exists so we can unittest get_diff_converter function
-def svn_diff_to_svn_diff(line):
-    return line
-
-
-def get_diff_converter(lines):
-    """Gets a converter function of diff lines.
-
-    Args:
-      lines: The lines of a diff file.
-             If this line is git formatted, we'll return a
-             converter from git to SVN.
-    """
-    for i, line in enumerate(lines[:-1]):
-        # Stop when we find the first patch
-        if line[:3] == "+++" and lines[i + 1] == "---":
-            break
-        if diff_git_pattern.match(line):
-            return git_diff_to_svn_diff
-    return svn_diff_to_svn_diff
 
 _INITIAL_STATE = 1
 _DECLARED_FILE_PATH = 2
@@ -140,12 +96,10 @@
         current_file = None
         old_diff_line = None
         new_diff_line = None
-        transform_line = get_diff_converter(diff_input)
         for line in diff_input:
-            line = line.rstrip("\n")
-            line = transform_line(line)
+            line = line.rstrip('\n')
 
-            file_declaration = index_pattern.match(line)
+            file_declaration = INDEX_PATTERN.match(line)
             if file_declaration:
                 filename = file_declaration.group('FilePath')
                 current_file = DiffFile(filename)
@@ -153,7 +107,7 @@
                 state = _DECLARED_FILE_PATH
                 continue
 
-            lines_changed = lines_changed_pattern.match(line)
+            lines_changed = LINES_CHANGED_PATTERN.match(line)
             if lines_changed:
                 if state != _DECLARED_FILE_PATH and state != _PROCESSING_CHUNK:
                     _log.error('Unexpected line change without file path declaration: %r', line)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py
index 6ea84b6d..8af9066d 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py
@@ -26,44 +26,47 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import cStringIO as StringIO
 import re
 import unittest
 
-from webkitpy.common.checkout import diff_parser
+from webkitpy.common.checkout.diff_parser import DiffParser
 from webkitpy.common.checkout.diff_test_data import DIFF_TEST_DATA
 
 
 class DiffParserTest(unittest.TestCase):
-    maxDiff = None
 
     def test_diff_parser(self, parser=None):
         if not parser:
-            parser = diff_parser.DiffParser(DIFF_TEST_DATA.splitlines())
+            parser = DiffParser(DIFF_TEST_DATA.splitlines())
         self.assertEqual(3, len(parser.files))
 
         self.assertIn('WebCore/style/StyleFlexibleBoxData.h', parser.files)
         diff = parser.files['WebCore/style/StyleFlexibleBoxData.h']
         self.assertEqual(7, len(diff.lines))
+
         # The first two unchanged lines.
         self.assertEqual((47, 47), diff.lines[0][0:2])
         self.assertEqual('', diff.lines[0][2])
         self.assertEqual((48, 48), diff.lines[1][0:2])
         self.assertEqual('    unsigned align : 3; // EBoxAlignment', diff.lines[1][2])
-        # The deleted line
+
+        # The deleted line.
         self.assertEqual((50, 0), diff.lines[3][0:2])
         self.assertEqual('    unsigned orient: 1; // EBoxOrient', diff.lines[3][2])
 
         # The first file looks OK. Let's check the next, more complicated file.
         self.assertIn('WebCore/style/StyleRareInheritedData.cpp', parser.files)
         diff = parser.files['WebCore/style/StyleRareInheritedData.cpp']
+
         # There are 3 chunks.
         self.assertEqual(7 + 7 + 9, len(diff.lines))
+
         # Around an added line.
         self.assertEqual((60, 61), diff.lines[9][0:2])
         self.assertEqual((0, 62), diff.lines[10][0:2])
         self.assertEqual((61, 63), diff.lines[11][0:2])
-        # Look through the last chunk, which contains both add's and delete's.
+
+        # Look through the last chunk, which contains both adds and deletes.
         self.assertEqual((81, 83), diff.lines[14][0:2])
         self.assertEqual((82, 84), diff.lines[15][0:2])
         self.assertEqual((83, 85), diff.lines[16][0:2])
@@ -79,99 +82,22 @@
         self.assertEqual(1, len(diff.lines))
         self.assertEqual((0, 1), diff.lines[0][0:2])
 
-    def test_diff_converter(self):
-        comment_lines = [
-            "Hey people,\n",
-            "\n",
-            "See my awesome patch below!\n",
-            "\n",
-            " - Cool Hacker\n",
-            "\n",
-        ]
+    def test_diff_parser_with_different_mnemonic_prefixes(self):
+        # This repeats test_diff_parser but with different versions
+        # of DIFF_TEST_DATA that use other prefixes instead of a/b.
+        prefixes = (
+            ('i', 'w'),  # git-diff (compares the (i)ndex and the (w)ork tree)
+            ('c', 'w'),  # git-diff HEAD (compares a (c)ommit and the (w)ork tree)
+            ('c', 'i'),  # git diff --cached (compares a (c)ommit and the (i)ndex)
+            ('o', 'w'),  # git-diff HEAD:file1 file2 (compares an (o)bject and a (w)ork tree entity)
+            ('1', '2'),  # git diff --no-index a b (compares two non-git things (1) and (2))
+        )
+        for a_replacement, b_replacement in prefixes:
+            patch = self._patch(a_replacement, b_replacement)
+            self.test_diff_parser(DiffParser(patch.splitlines()))
 
-        revision_lines = [
-            "Subversion Revision 289799\n",
-        ]
-
-        svn_diff_lines = [
-            "Index: Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
-            "===================================================================\n",
-            "--- Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
-            "+++ Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
-            "@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):\n",
-        ]
-        self.assertEqual(diff_parser.get_diff_converter(svn_diff_lines), diff_parser.svn_diff_to_svn_diff)
-        self.assertEqual(diff_parser.get_diff_converter(comment_lines + svn_diff_lines), diff_parser.svn_diff_to_svn_diff)
-        self.assertEqual(diff_parser.get_diff_converter(revision_lines + svn_diff_lines), diff_parser.svn_diff_to_svn_diff)
-
-        git_diff_lines = [
-            ("diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py "
-             "b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n"),
-            "index 3c5b45b..0197ead 100644\n",
-            "--- a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
-            "+++ b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py\n",
-            "@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):\n",
-        ]
-        self.assertEqual(diff_parser.get_diff_converter(git_diff_lines), diff_parser.git_diff_to_svn_diff)
-        self.assertEqual(diff_parser.get_diff_converter(comment_lines + git_diff_lines), diff_parser.git_diff_to_svn_diff)
-        self.assertEqual(diff_parser.get_diff_converter(revision_lines + git_diff_lines), diff_parser.git_diff_to_svn_diff)
-
-    def test_git_mnemonicprefix(self):
-        p = re.compile(r' ([a|b])/')
-
-        prefixes = [
-            {'a': 'i', 'b': 'w'},  # git-diff (compares the (i)ndex and the (w)ork tree)
-            {'a': 'c', 'b': 'w'},  # git-diff HEAD (compares a (c)ommit and the (w)ork tree)
-            {'a': 'c', 'b': 'i'},  # git diff --cached (compares a (c)ommit and the (i)ndex)
-            {'a': 'o', 'b': 'w'},  # git-diff HEAD:file1 file2 (compares an (o)bject and a (w)ork tree entity)
-            {'a': '1', 'b': '2'},  # git diff --no-index a b (compares two non-git things (1) and (2))
-        ]
-
-        for prefix in prefixes:
-            patch = p.sub(lambda x: " %s/" % prefix[x.group(1)], DIFF_TEST_DATA)
-            self.test_diff_parser(diff_parser.DiffParser(patch.splitlines()))
-
-    def test_git_diff_to_svn_diff(self):
-        output = """\
-Index: Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-===================================================================
---- Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-+++ Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):
- A
- B
- C
-+D
- E
- F
-"""
-
-        inputfmt = StringIO.StringIO("""\
-diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-index 2ed552c4555db72df16b212547f2c125ae301a04..72870482000c0dba64ce4300ed782c03ee79b74f 100644
---- a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-+++ b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):
- A
- B
- C
-+D
- E
- F
-""")
-        shortfmt = StringIO.StringIO("""\
-diff --git a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-index b48b162..f300960 100644
---- a/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-+++ b/Tools/Scripts/webkitpy/common/checkout/diff_parser.py
-@@ -59,6 +59,7 @@ def git_diff_to_svn_diff(line):
- A
- B
- C
-+D
- E
- F
-""")
-
-        self.assertMultiLineEqual(output, ''.join(diff_parser.git_diff_to_svn_diff(x) for x in shortfmt.readlines()))
-        self.assertMultiLineEqual(output, ''.join(diff_parser.git_diff_to_svn_diff(x) for x in inputfmt.readlines()))
+    @staticmethod
+    def _patch(a_replacement='a', b_replacement='b'):
+        """Returns a version of the example patch with mnemonic prefixes a/b changed."""
+        patch = re.sub(r' a/', ' %s/' % a_replacement, DIFF_TEST_DATA)
+        return re.sub(r' b/', ' %s/' % b_replacement, patch)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
index 02fe3e63..6593c970 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
@@ -87,6 +87,7 @@
         ('mac10.9', 'x86'),
         ('mac10.10', 'x86'),
         ('mac10.11', 'x86'),
+        ('mac10.12', 'x86'),
         ('win7', 'x86'),
         ('win10', 'x86'),
         ('trusty', 'x86_64'),
@@ -99,7 +100,7 @@
     )
 
     CONFIGURATION_SPECIFIER_MACROS = {
-        'mac': ['retina', 'mac10.9', 'mac10.10', 'mac10.11'],
+        'mac': ['retina', 'mac10.9', 'mac10.10', 'mac10.11', 'mac10.12'],
         'win': ['win7', 'win10'],
         'linux': ['trusty'],
         'android': ['icecreamsandwich'],
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader.py b/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader.py
index 54b13a6..a6a0795 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader.py
@@ -29,10 +29,8 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 import logging
-import re
 
 from webkitpy.common.checkout.diff_parser import DiffParser
-from webkitpy.common.system.filesystem import FileSystem
 
 
 _log = logging.getLogger(__name__)
@@ -49,26 +47,15 @@
         """
         self._text_file_reader = text_file_reader
 
-    def check(self, patch_string, fs=None):
-        """Check style in the given patch."""
-        fs = fs or FileSystem()
+    def check(self, patch_string):
+        """Checks style in the given patch."""
         patch_files = DiffParser(patch_string.splitlines()).files
 
-        # If the user uses git, checking subversion config file only once is enough.
-        # TODO(qyearsley): Simplify this since git is now the only supported SCM system.
-        call_only_once = True
-
         for path, diff_file in patch_files.iteritems():
             line_numbers = diff_file.added_or_modified_line_numbers()
             _log.debug('Found %s new or modified lines in: %s', len(line_numbers), path)
 
             if not line_numbers:
-                match = re.search(r"\s*png$", path)
-                if match and fs.exists(path):
-                    if call_only_once:
-                        self._text_file_reader.process_file(file_path=path, line_numbers=None)
-                        call_only_once = False
-                    continue
                 # Don't check files which contain only deleted lines
                 # as they can never add style errors. However, mark them as
                 # processed so that we count up number of such files.
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader_unittest.py
index e96e5b6..03871e33 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/style/patchreader_unittest.py
@@ -31,21 +31,16 @@
 
 import unittest
 
-from webkitpy.common.system.filesystem_mock import MockFileSystem
 from webkitpy.style.patchreader import PatchReader
 
 
 class PatchReaderTest(unittest.TestCase):
 
-    """Test the PatchReader class."""
-
     class MockTextFileReader(object):
 
         def __init__(self):
-            self.passed_to_process_file = []
-            """A list of (file_path, line_numbers) pairs."""
-            self.delete_only_file_count = 0
-            """A number of times count_delete_only_file() called"""
+            self.passed_to_process_file = []  # A list of (file_path, line_numbers) pairs.
+            self.delete_only_file_count = 0  # A number of times count_delete_only_file() called.
 
         def process_file(self, file_path, line_numbers):
             self.passed_to_process_file.append((file_path, line_numbers))
@@ -54,48 +49,41 @@
             self.delete_only_file_count += 1
 
     def setUp(self):
-        file_reader = self.MockTextFileReader()
-        self._file_reader = file_reader
-        self._patch_checker = PatchReader(file_reader)
-
-    def _call_check_patch(self, patch_string):
-        self._patch_checker.check(patch_string)
+        self._file_reader = self.MockTextFileReader()
 
     def _assert_checked(self, passed_to_process_file, delete_only_file_count):
-        self.assertEqual(self._file_reader.passed_to_process_file,
-                         passed_to_process_file)
-        self.assertEqual(self._file_reader.delete_only_file_count,
-                         delete_only_file_count)
+        self.assertEqual(self._file_reader.passed_to_process_file, passed_to_process_file)
+        self.assertEqual(self._file_reader.delete_only_file_count, delete_only_file_count)
 
     def test_check_patch(self):
-        # The modified line_numbers array for this patch is: [2].
-        self._call_check_patch("""diff --git a/__init__.py b/__init__.py
-index ef65bee..e3db70e 100644
---- a/__init__.py
-+++ b/__init__.py
-@@ -1,1 +1,2 @@
- # Required for Python to search this directory for module files
-+# New line
-""")
-        self._assert_checked([("__init__.py", [2])], 0)
+        PatchReader(self._file_reader).check(
+            'diff --git a/__init__.py b/__init__.py\n'
+            'index ef65bee..e3db70e 100644\n'
+            '--- a/__init__.py\n'
+            '+++ b/__init__.py\n'
+            '@@ -1,1 +1,2 @@\n'
+            ' # Required for Python to search this directory for module files\n'
+            '+# New line\n')
+        self._assert_checked(
+            passed_to_process_file=[('__init__.py', [2])],
+            delete_only_file_count=0)
 
     def test_check_patch_with_deletion(self):
-        self._call_check_patch("""Index: __init__.py
-===================================================================
---- __init__.py  (revision 3593)
-+++ __init__.py  (working copy)
-@@ -1 +0,0 @@
--foobar
-""")
-        # _mock_check_file should not be called for the deletion patch.
-        self._assert_checked([], 1)
+        PatchReader(self._file_reader).check(
+            'diff --git a/__init__.py b/__init.py\n'
+            'deleted file mode 100644\n'
+            'index ef65bee..0000000\n'
+            '--- a/__init__.py\n'
+            '+++ /dev/null\n'
+            '@@ -1 +0,0 @@\n'
+            '-foobar\n')
+        # The deleted file isn't be processed.
+        self._assert_checked(passed_to_process_file=[], delete_only_file_count=1)
 
     def test_check_patch_with_png_deletion(self):
-        fs = MockFileSystem()
-        diff_text = """Index: LayoutTests/platform/mac/foo-expected.png
-===================================================================
-Cannot display: file marked as a binary type.
-svn:mime-type = image/png
-"""
-        self._patch_checker.check(diff_text, fs)
-        self._assert_checked([], 1)
+        PatchReader(self._file_reader).check(
+            'diff --git a/foo-expected.png b/foo-expected.png\n'
+            'deleted file mode 100644\n'
+            'index ef65bee..0000000\n'
+            'Binary files a/foo-expected.png and /dev/null differ\n')
+        self._assert_checked(passed_to_process_file=[], delete_only_file_count=1)
diff --git a/third_party/WebKit/public/blink_typemaps.gni b/third_party/WebKit/public/blink_typemaps.gni
index 7d0461d4..0ae60711 100644
--- a/third_party/WebKit/public/blink_typemaps.gni
+++ b/third_party/WebKit/public/blink_typemaps.gni
@@ -11,6 +11,5 @@
   "//cc/ipc/surface_id.typemap",
   "//cc/ipc/surface_info.typemap",
   "//cc/ipc/surface_sequence.typemap",
-  "//gpu/ipc/common/mailbox_holder_for_blink.typemap",
   "//gpu/ipc/common/sync_token.typemap",
 ]
diff --git a/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom b/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom
index 166857533..4a25bce 100644
--- a/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom
+++ b/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom
@@ -70,8 +70,6 @@
   DEVICE_NO_LONGER_IN_RANGE,
   GATT_NOT_PAIRED,
   GATT_OPERATION_IN_PROGRESS,
-  GATT_SERVER_DISCONNECTED,
-  GATT_SERVER_NOT_CONNECTED,
   UNTRANSLATED_CONNECT_ERROR_CODE,
   // NotFoundError:
   NO_BLUETOOTH_ADAPTER,
diff --git a/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js b/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
index ad3c6b5..f9a2d9d3 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
@@ -237,11 +237,13 @@
   }
 
   var dialogShown = false;
+  var tokenObtained = false;
 
   this.widget_.ready()
       .then(
           /** @return {!Promise} */
           function() {
+            tokenObtained = true;
             return this.showDialog_(title);
           }.bind(this))
       .then(
@@ -264,14 +266,22 @@
           function(error) {
             console.error('Failed to start CWS widget: ' + error);
 
-            // If the widget dialog was not shown, consider the widget
-            // canceled.
             if (!dialogShown) {
               // Reset any widget state set in |this.widget_.ready()|. The
               // returned value is ignored because it doesn't influence the
               // value reported by dialog.
               this.widget_.finalizeAndGetResult();
-              onDialogClosed(SuggestAppsDialog.Result.CANCELLED, null);
+
+              var result = tokenObtained ?
+                  // Got access token but the widget dialog was not shown.
+                  // Consider the widget was cancelled.
+                  SuggestAppsDialog.Result.CANCELLED :
+                  // Access token was unavailable.
+                  // This can happen in the Guest mode. crbug.com/694419
+                  // Callback shows an alert notifying the file was not opened
+                  // because of the unsupported type.
+                  SuggestAppsDialog.Result.FAILED;
+              onDialogClosed(result, null);
               return;
             }